From 42dcefbf7e9109988676b67da00766ab72f97f6f Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 9 Nov 2023 21:35:02 +0100 Subject: [PATCH 001/277] Pin GitHub Actions runners to a specific version (#3547) --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/pr-commenter.yml | 2 +- .github/workflows/stale.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 8a8803bfb9e..5d1acd2ebe3 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -12,7 +12,7 @@ on: jobs: housekeeping: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 @@ -49,7 +49,7 @@ jobs: ./bin/template_status.py -v -p .problem-specifications canonical_sync: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: housekeeping strategy: matrix: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index dfd6c59c516..b547987dc2c 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -5,7 +5,7 @@ on: jobs: comment-on-new-issue: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 name: Comments for every NEW issue. steps: - name: Checkout diff --git a/.github/workflows/pr-commenter.yml b/.github/workflows/pr-commenter.yml index d8cc6939065..411f9f198d6 100644 --- a/.github/workflows/pr-commenter.yml +++ b/.github/workflows/pr-commenter.yml @@ -4,7 +4,7 @@ on: jobs: pr-comment: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: exercism/pr-commenter-action@v1.5.1 with: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 41156435d82..e43617427f0 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -6,7 +6,7 @@ on: jobs: stale: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/stale@v8 with: diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index b3668593b8d..9db386422de 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -8,7 +8,7 @@ on: jobs: test-runner: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Run test-runner From e3a189c5194a4b3c06422575a22fa45c02e2817d Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 14 Nov 2023 15:11:36 +0100 Subject: [PATCH 002/277] Pin GitHub Actions workflows to a specific version (#3548) --- .github/workflows/ci-workflow.yml | 8 ++++---- .github/workflows/issue-commenter.yml | 6 +++--- .github/workflows/pr-commenter.yml | 2 +- .github/workflows/stale.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 5d1acd2ebe3..1632f55e2e3 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,10 +14,10 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 with: python-version: 3.11.2 @@ -55,9 +55,9 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index b547987dc2c..cbe6388366d 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,16 +9,16 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Read issue-comment.md id: issue-comment - uses: juliangruber/read-file-action@v1 + uses: juliangruber/read-file-action@02bbba9876a8f870efd4ad64e3b9088d3fb94d4b with: path: .github/issue-comment.md - name: Base comment - uses: jd-0001/gh-action-comment-on-new-issue@v2.0.3 + uses: jd-0001/gh-action-comment-on-new-issue@c443e1151cc69b146fd6918cc983ec1bd27ab254 with: message: "${{ steps.issue-comment.outputs.content }}" ignore-label: ":anger: prickle" diff --git a/.github/workflows/pr-commenter.yml b/.github/workflows/pr-commenter.yml index 411f9f198d6..3b2592cda13 100644 --- a/.github/workflows/pr-commenter.yml +++ b/.github/workflows/pr-commenter.yml @@ -6,7 +6,7 @@ jobs: pr-comment: runs-on: ubuntu-22.04 steps: - - uses: exercism/pr-commenter-action@v1.5.1 + - uses: exercism/pr-commenter-action@085ef62d2a541a112c3ade1d24deea83665ea186 with: github-token: "${{ github.token }}" config-file: ".github/pr-commenter.yml" \ No newline at end of file diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index e43617427f0..86f881a21c7 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,7 +8,7 @@ jobs: stale: runs-on: ubuntu-22.04 steps: - - uses: actions/stale@v8 + - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 21 diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index 9db386422de..1228b8f104d 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Run test-runner run: docker-compose run test-runner From 1cd88ed899cc0416c422cf24be8b4e075f935864 Mon Sep 17 00:00:00 2001 From: Laran Evans Date: Thu, 23 Nov 2023 05:49:45 -0800 Subject: [PATCH 003/277] Update about.md (#3552) Spelling --- concepts/conditionals/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/conditionals/about.md b/concepts/conditionals/about.md index 3b3d5b1ba1d..8f0ae8fde08 100644 --- a/concepts/conditionals/about.md +++ b/concepts/conditionals/about.md @@ -55,7 +55,7 @@ else: >>> z is greater than x and y ``` -[Boolen operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing: +[Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing: ```python From 6a6eacf391c402c394e49d063ebae3d81b3800d6 Mon Sep 17 00:00:00 2001 From: JoPro0 <113715866+JoPro0@users.noreply.github.com> Date: Mon, 4 Dec 2023 21:36:45 +0100 Subject: [PATCH 004/277] added missing greater-than sign in about.md (#3557) --- concepts/lists/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/lists/about.md b/concepts/lists/about.md index 851c10e138b..657cd43ff6e 100644 --- a/concepts/lists/about.md +++ b/concepts/lists/about.md @@ -7,7 +7,7 @@ A [`list`][list] is a mutable collection of items in _sequence_. Lists support both [common][common sequence operations] and [mutable][mutable sequence operations] sequence operations such as `min()`/`max()`, `.index()`, `.append()` and `.reverse()`. - List elements can be iterated over using the `for item in ` construct. `for index, item in enumerate(` construct. `for index, item in enumerate()` can be used when both the element index and the element value are needed. Lists are implemented as [dynamic arrays][dynamic array] -- similar to Java's [`Arraylist`][arraylist] type, and are most often used to store groups of similar data (_strings, numbers, sets etc._) of unknown length (_the number of entries may arbitrarily expand or shrink_). From fea327ba0913dba6eb9196879e4611284ddd3115 Mon Sep 17 00:00:00 2001 From: colinleach Date: Tue, 5 Dec 2023 14:51:17 -0700 Subject: [PATCH 005/277] complex-numbers concept docs (#3554) * complex-numbers concept docs * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * fixed several links * some relatively small fixes, mainly typos and formatting * Moved sections around, in the search for a clearer flow of ideas. * added bit on `complex(string)` constructor. * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Update concepts/complex-numbers/about.md Co-authored-by: BethanyG * Edited About to serve as Introduction * Titled Introduction as Introduction --------- Co-authored-by: BethanyG --- concepts/complex-numbers/.meta/config.json | 4 +- concepts/complex-numbers/about.md | 260 ++++++++++++++++++++- concepts/complex-numbers/introduction.md | 93 +++++++- concepts/complex-numbers/links.json | 16 +- 4 files changed, 361 insertions(+), 12 deletions(-) diff --git a/concepts/complex-numbers/.meta/config.json b/concepts/complex-numbers/.meta/config.json index 9b9e8da5a9b..ca6ccc8811d 100644 --- a/concepts/complex-numbers/.meta/config.json +++ b/concepts/complex-numbers/.meta/config.json @@ -1,5 +1,5 @@ { - "blurb": "TODO: add blurb for this concept", - "authors": ["bethanyg", "cmccandless"], + "blurb": "Complex numbers are a fundamental data type in Python, along with int and float. Further support is added with the cmath module, which is part of the Python standard library.", + "authors": ["BethanyG", "colinleach"], "contributors": [] } diff --git a/concepts/complex-numbers/about.md b/concepts/complex-numbers/about.md index c628150d565..dfe067be4ee 100644 --- a/concepts/complex-numbers/about.md +++ b/concepts/complex-numbers/about.md @@ -1,2 +1,260 @@ -#TODO: Add about for this concept. +# About + +`Complex numbers` are not complicated. +They just need a less alarming name. + +They are so useful, especially in engineering and science, that Python includes [`complex`][complex] as a standard numeric type alongside integers ([`int`s][ints]) and floating-point numbers ([`float`s][floats]). + + +## Basics + +A `complex` value in Python is essentially a pair of floating-point numbers. +These are called the "real" and "imaginary" parts, for unfortunate historical reasons. +Again, it is best to focus on the underlying simplicity and not the strange names. + +There are two common ways to create complex numbers. + +1) The [`complex(real, imag)`][complex] constructor takes two `float` parameters: + +```python +>>> z1 = complex(1.5, 2.0) +>>> z1 +(1.5+2j) +``` + +The constructor can also parse string input. +This has the odd limitation that it fails if the string contains spaces. + +```python +>>> complex('4+2j') +(4+2j) + +>>> complex('4 + 2j') +Traceback (most recent call last): + File "", line 1, in +ValueError: complex() arg is a malformed string +``` + + +2) The complex number can be specified as ` + j` literal, or just `j` if the real part is zero: + + +```python +>>> z2 = 2.0 + 1.5j +>>> z2 +(2+1.5j) +``` +The end result is identical to using the `complex()` constructor. + + +There are two rules for that imaginary part of the complex number: + + +- It is designated with `j` (not `i` as you may see in math textbooks). + +- The `j` must immediately follow a number, to prevent Python seeing it as a variable name. If necessary, use `1j`. + +```python +>>> j +Traceback (most recent call last): + File "", line 1, in +NameError: name 'j' is not defined + +>>> 1j +1j + +>>> type(1j) + +``` + +Most engineers are happy with `j`. +Most scientists and mathematicians prefer the mathematical notation `i` for _imaginary_, but that notation conflicts with the use of `i` to mean _current_ in Electrical Engineering. +So in designing Python, the Electrical Engineers won. + + +To access the parts of a complex number individually: + +```python +>>> z2.real +2.0 +>>> z2.imag +1.5 +``` + +Either part can be zero and mathematicians may then talk of the number being "wholly real" or "wholly imaginary". +However, it is still a complex number in Python: + + +```python +>>> complex(0, 1) +1j +>>> type(complex(0, 1)) + + +>>> complex(1, 0) +(1+0j) +``` + +You may have heard that "`i` (or `j`) is the square root of -1". + +For now, all this means is that the imaginary part _by definition_ satisfies the equality +```python +1j * 1j == -1 # => True +``` + +This is a simple idea, but it leads to interesting consequences. + +## Arithmetic + +Most of the [`operators`][operators] used with floats and ints also work with complex numbers: + + +```python +>>> z1 = (1.5+2j) +>>> z2 = (2+1.5j) + + +>>> z1 + z2 # addition +(3.5+3.5j) + +>>> z1 - z2 # subtraction +(-0.5+0.5j) + +>>> z1 * z2 # multiplication +6.25j + +>>> z1 / z2 # division +(0.96+0.28j) + +>>> z1 ** 2 # exponentiation +(-1.75+6j) + +>>> 2 ** z1 # another exponentiation +(0.5188946835878313+2.7804223253571183j) + +>>> 1j ** 2 # j * j == -1 +(-1+0j) +``` + +Explaining the rules for complex number multiplication and division is out of scope for this concept (_and you are unlikely to have to perform those operations "by hand" very often_). + +Any [mathematical][math-complex] or [electrical engineering][engineering-complex] introduction to complex numbers will cover this, should you want to dig into the topic. + +Alternatively, Exercism has a `Complex Numbers` practice exercise where you can implement a complex number class with these operations from first principles. + + +Integer division is ___not___ possible on complex numbers, so the `//` and `%` operators and `divmod()` functions will fail for the complex number type. + + +There are two functions implemented for numeric types that are very useful when working with complex numbers: + +- `.conjugate()` simply flips the sign of the imaginary part of a complex number (_from + to - or vice-versa_). + - Because of the way complex multiplication works, this is more useful than you might think. +- `abs()` is guaranteed to return a real number with no imaginary part. + + +```python +>>> z1 +(1.5+2j) + +>>> z1.conjugate() # flip the z1.imag sign +(1.5-2j) + +>>> abs(z1) # sqrt(z1.real ** 2 + z1.imag ** 2) +2.5 +``` + +## The `cmath` module + +The Python standard library has a [`math`][math-module] module full of useful functionality for working with real numbers. + +It also has an equivalent [`cmath`][cmath] module for working with complex numbers. + + +We encourage you to read through the module and experiment, but the main categories are: + +- Conversion between Cartesian and polar coordinates, +- Exponential and log functions, +- Trigonometric functions, +- Hyperbolic functions, +- Classification functions, and +- Useful constants. + +Here is an example using some constants: + +```python +>>> import cmath + +>>> euler = cmath.exp(1j * cmath.pi) # Euler's equation + +>>> euler.real +-1.0 +>>> round(euler.imag, 15) # round to 15 decimal places +0.0 +``` + +So a simple expression with three of the most important constants in nature `e`, `i` (or `j`) and `pi` gives the result `-1`. +Some people believe this is the most beautiful result in all of mathematics. +It dates back to around 1740. + +----- + +## Optional section: a Complex Numbers FAQ + +This part can be skipped, unless you are interested. + +### Isn't this some strange new piece of pure mathematics? + +It was strange and new in the 16th century. + +500 years later, it is central to most of engineering and the physical sciences. + +### Why would anyone use these? + +It turns out that complex numbers are the simplest way to describe anything that rotates or anything with a wave-like property. +So they are used widely in electrical engineering, audio processing, physics, computer gaming, and navigation - to name only a few applications. + +You can see things rotate. +Complex numbers may not make the world go round, but they are great for explaining _what happens_ as a result of the world going round: look at any satellite image of a major storm. + + +Less obviously, sound is wave-like, light is wave-like, radio signals are wave-like, and even the economy of your home country is at least partly wave-like. + + +A lot of this wave processing can be done with trig functions (`sin()` and `cos()`) but that gets messy quite quickly. + +Complex exponentials are ___much___ easier to work with. + +### But I don't need complex numbers! + + +Only true if you are living in a cave and foraging for your food. + +If you are reading this on any sort of screen, you are utterly dependent on some useful 20th-Century advances made through the use of complex numbers. + + +1. __Semiconductor chips__. + - These make no sense in classical physics and can only be explained (and designed) by quantum mechanics (QM). + - In QM, everything is complex-valued by definition. (_its waveforms all the way down_) + +2. __The Fast Fourier Transform algorithm__. + - FFT is an application of complex numbers, and it is in _everything_ connected to sound transmission, audio processing, photos, and video. + + -MP3 and other audio formats use FFT for compression, ensuring more audio can fit within a smaller storage space. + - JPEG compression and MP4 video, among many other image and video formats also use FTT for compression. + + - FFT is also deployed in the digital filters that allow cellphone towers to separate your personal cell signal from everyone else's. + + +So, you are probably using technology that relies on complex number calculations thousands of times per second. + + +[complex]: https://docs.python.org/3/library/functions.html#complex +[cmath]: https://docs.python.org/3/library/cmath.html +[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex +[math-module]: https://docs.python.org/3/library/math.html +[math-complex]: https://www.nagwa.com/en/videos/143121736364/ +[engineering-complex]: https://www.khanacademy.org/science/electrical-engineering/ee-circuit-analysis-topic/ee-ac-analysis/v/ee-complex-numbers +[ints]: https://docs.python.org/3/library/functions.html#int +[floats]: https://docs.python.org/3/library/functions.html#float diff --git a/concepts/complex-numbers/introduction.md b/concepts/complex-numbers/introduction.md index fcde74642ca..a82f47cb6cb 100644 --- a/concepts/complex-numbers/introduction.md +++ b/concepts/complex-numbers/introduction.md @@ -1,2 +1,93 @@ -#TODO: Add introduction for this concept. +# Introduction +`Complex numbers` are not complicated. +They just need a less alarming name. + +They are so useful, especially in engineering and science (_everything from JPEG compression to quantum mechanics_), that Python includes [`complex`][complex] as a standard numeric type alongside integers ([`int`s][ints]) and floating-point numbers ([`float`s][floats]). + +A `complex` value in Python is essentially a pair of floating-point numbers: + +```python +>>> my_complex = 5.443+6.77j +(5.443+6.77j) +``` + +These are called the "real" and "imaginary" parts. +You may have heard that "`i` (or `j`) is the square root of -1". +For now, all this means is that the imaginary part _by definition_ satisfies the equality `1j * 1j == -1`. +This is a simple idea, but it leads to interesting mathematical consequences. + +In Python, the "imaginary" part is designated with `j` (_not `i` as you would see in math textbooks_), and +the `j` must immediately follow a number, to prevent Python seeing it as a variable name: + + +```python +>>> j +Traceback (most recent call last): + File "", line 1, in +NameError: name 'j' is not defined + +>>> 1j +1j + +>>> type(1j) + +``` + + +There are two common ways to create complex numbers. + +1) The [`complex(real, imag)`][complex] constructor takes two `float` parameters: + + ```python + >>> z1 = complex(1.5, 2.0) + >>> z1 + (1.5+2j) + ``` + + The constructor can also parse string input. + This has the odd limitation that it fails if the string contains spaces. + + ```python + >>> complex('4+2j') + (4+2j) + + >>> complex('4 + 2j') + Traceback (most recent call last): + File "", line 1, in + ValueError: complex() arg is a malformed string + ``` + + +2) The complex number can be specified as ` + j` literal, or just `j` if the real part is zero: + + + ```python + >>> z2 = 2.0 + 1.5j + >>> z2 + (2+1.5j) + ``` + The end result is identical to using the `complex()` constructor. + + +## Arithmetic + +Most of the [`operators`][operators] used with floats and ints also work with complex numbers. + +Integer division is _**not**_ possible on complex numbers, so the `//` and `%` operators and `divmod()` functions will fail for the complex number type. + +Explaining the rules for complex number multiplication and division is out of scope for this concept (_and you are unlikely to have to perform those operations "by hand" very often_). + +Any [mathematical][math-complex] or [electrical engineering][engineering-complex] introduction to complex numbers will cover these scenarios, should you want to dig into the topic. + +The Python standard library has a [`math`][math-module] module full of useful functionality for working with real numbers and the [`cmath`][cmath] module is its equivalent for working with complex numbers. + + +[cmath]: https://docs.python.org/3/library/cmath.html +[complex]: https://docs.python.org/3/library/functions.html#complex +[engineering-complex]: https://www.khanacademy.org/science/electrical-engineering/ee-circuit-analysis-topic/ee-ac-analysis/v/ee-complex-numbers +[floats]: https://docs.python.org/3/library/functions.html#float +[ints]: https://docs.python.org/3/library/functions.html#int +[math-complex]: https://www.nagwa.com/en/videos/143121736364/ +[math-module]: https://docs.python.org/3/library/math.html +[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex diff --git a/concepts/complex-numbers/links.json b/concepts/complex-numbers/links.json index eb5fb7c38a5..759ef1689ff 100644 --- a/concepts/complex-numbers/links.json +++ b/concepts/complex-numbers/links.json @@ -1,18 +1,18 @@ [ { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex/", + "description": "Operations on numeric types." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://docs.python.org/3/library/functions.html#complex/", + "description": "The complex class." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://docs.python.org/3/library/cmath.html/", + "description": "Module documentation for cmath." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://docs.python.org/3/library/math.html/", + "description": "Module documentation for math." } ] From 44b0c7f41ad7760d00c3e166bb1a9a81bcc882d4 Mon Sep 17 00:00:00 2001 From: colinleach Date: Tue, 5 Dec 2023 14:52:54 -0700 Subject: [PATCH 006/277] fractions concept (#3555) * fractions concept * added links, tried to clarify wording * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Update concepts/fractions/about.md Co-authored-by: BethanyG * Introduction from About Added some links and edited config.json. --------- Co-authored-by: BethanyG --- concepts/fractions/.meta/config.json | 5 ++ concepts/fractions/about.md | 122 +++++++++++++++++++++++++++ concepts/fractions/introduction.md | 85 +++++++++++++++++++ concepts/fractions/links.json | 18 ++++ config.json | 5 ++ 5 files changed, 235 insertions(+) create mode 100644 concepts/fractions/.meta/config.json create mode 100644 concepts/fractions/about.md create mode 100644 concepts/fractions/introduction.md create mode 100644 concepts/fractions/links.json diff --git a/concepts/fractions/.meta/config.json b/concepts/fractions/.meta/config.json new file mode 100644 index 00000000000..621a3766d84 --- /dev/null +++ b/concepts/fractions/.meta/config.json @@ -0,0 +1,5 @@ +{ + "blurb": "The fractions module enables working with rational numbers, which preserve exact values and avoid the rounding errors common with floats.", + "authors": ["BethanyG", "colinleach"], + "contributors": [] +} diff --git a/concepts/fractions/about.md b/concepts/fractions/about.md new file mode 100644 index 00000000000..d41124c39c4 --- /dev/null +++ b/concepts/fractions/about.md @@ -0,0 +1,122 @@ +# About + +The [`Fractions`][fractions] module allows us to create and work with [`rational numbers`][rational]: fractions with an integer numerator divided by an integer denominator. + +For example, we can store `2/3` as an exact fraction instead of the approximate `float` value `0.6666...` + +## Creating Fractions + + +Unlike `int`, `float`, and `complex` numbers, fractions do not have a literal form. +However, the fractions constructor is quite flexible. + +Most obviously, it can take take two integers. +Common factors are automatically removed, converting the fraction to its "lowest form": the smallest integers that accurately represent the fraction. + + +```python +>>> from fractions import Fraction + +>>> f1 = Fraction(2, 3) # 2/3 +>>> f1 +Fraction(2, 3) + +>>> f2 = Fraction(6, 9) +>>> f2 +Fraction(2, 3) # automatically simplified + +>>> f1 == f2 +True +``` + +The fractions constructor can also parse a string representation: + + +```python +>>> f3 = Fraction('2/3') +>>> f3 +Fraction(2, 3) +``` + +It can also work with `float` parameters, but this may run into problems with the approximate nature of representing the decimal value internally as binary. +For more on this representation issue, see the [0.30000000000000004][0.30000000000000004] website, and [Floating Point Arithmetic: Issues and Limitations ][fp-issues] in the Python documentation. + +For a more reliable result when using floats with fractions, there is the `.limit_denominator()` method. + + +[`.limit_denominator()`][limit_denominator] can take an integer parameter if you have specific requirements, but even the default (`max_denominator=1000000`) can work well and give an acceptable, simple approximation. + +```python +>>> Fraction(1.2) +Fraction(5404319552844595, 4503599627370496) + +>>> Fraction(1.2).limit_denominator() +Fraction(6, 5) +``` + +## Arithmetic with Fractions + + +The usual [`arithmetic operators`][operators] `+ - * / **` work with fractions, as with other numeric types. + +Integers and other `Fraction`s can be included and give a `Fraction` result. +Including a `float` in the expression results in `float` output, with a consequent (possible) loss in precision. + + +```python +>>> Fraction(2, 3) + Fraction(1, 4) # addition +Fraction(11, 12) + +>>> Fraction(2, 3) * Fraction(6, 5) # multiply fractions +Fraction(4, 5) + +>>> Fraction(2, 3) * 6 / 5 # fraction with integers +Fraction(4, 5) + +>>> Fraction(2, 3) * 1.2 # fraction with float -> float +0.7999999999999999 + +>>> Fraction(2, 3) ** 2 # exponentiation with integer +Fraction(4, 9) +``` + +## Conversions to and from Fractions + + +Fractions are great for preserving precision during intermediate calculations, but may not be what you want for the final output. + +It is possible to get the numerator and denominator individually or as a tuple ([`tuples`][tuple] will be discussed in a later Concept): + +```python +>>> Fraction(2, 3).numerator +2 +>>> Fraction(2, 3).denominator +3 +>>> Fraction(2, 3).as_integer_ratio() +(2, 3) +``` + +Various standard Python numeric functions also give the result you might expect from working with `int` and `float` types: + +```python +>>> round(Fraction(11, 3)) +4 + +>>> from math import floor, ceil +>>> floor(Fraction(11, 3)) +3 +>>> ceil(Fraction(11, 3)) +4 + +>>> float(Fraction(11, 3)) +3.6666666666666665 +``` + +[fractions]: https://docs.python.org/3/library/fractions.html +[0.30000000000000004]: https://0.30000000000000004.com/ +[fp-issues]: https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues +[tuple]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences + +[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex +[rational]: https://en.wikipedia.org/wiki/Rational_number +[limit_denominator]: https://docs.python.org/3/library/fractions.html diff --git a/concepts/fractions/introduction.md b/concepts/fractions/introduction.md new file mode 100644 index 00000000000..437ccbbeb07 --- /dev/null +++ b/concepts/fractions/introduction.md @@ -0,0 +1,85 @@ +# Introduction + +The [`Fractions`][fractions] module allows us to create and work with [`rational numbers`][rational]: fractions with an integer numerator divided by an integer denominator. +For example, we can store `2/3` as an exact fraction instead of the approximate `float` value `0.6666...`. + +Unlike `int`, `float`, and `complex` numbers, fractions do not have a literal form. +However, the fractions constructor is quite flexible. + +Most obviously, it can take take two integers as arguments. +Common factors are automatically removed, converting the fraction to its "lowest form": the smallest integers that accurately represent the fraction: + +```python +>>> from fractions import Fraction + +>>> f1 = Fraction(2, 3) # 2/3 +>>> f1 +Fraction(2, 3) + +>>> f2 = Fraction(6, 9) +>>> f2 +Fraction(2, 3) # automatically simplified + +>>> f1 == f2 +True +``` + +The fractions constructor can also parse a string representation: + +```python +>>> f3 = Fraction('2/3') +>>> f3 +Fraction(2, 3) +``` + +Fractions can also work with `float` parameters, but this may run into problems with the approximate nature of representing the decimal value internally as binary. +For more on this representation issue, see the [0.30000000000000004][0.30000000000000004] website, and [Floating Point Arithmetic: Issues and Limitations ][fp-issues] in the Python documentation. + +For a more reliable result when using floats with fractions, there is the `.limit_denominator()` method. + + +## Arithmetic with Fractions + +The usual [`arithmetic operators`][operators] `+ - * / **` will work with fractions, as with other numeric types. + +Integers and other `Fraction`s can be included in the equation and give a `Fraction` result. +Including a `float` in the expression results in `float` output, with a consequent (possible) loss in precision: + +```python +>>> Fraction(2, 3) + Fraction(1, 4) # addition +Fraction(11, 12) + +>>> Fraction(2, 3) * Fraction(6, 5) # multiply fractions +Fraction(4, 5) + +>>> Fraction(2, 3) * 6 / 5 # fraction with integers +Fraction(4, 5) + +>>> Fraction(2, 3) * 1.2 # fraction with float -> float +0.7999999999999999 + +>>> Fraction(2, 3) ** 2 # exponentiation with integer +Fraction(4, 9) +``` + +Various standard Python numeric functions also give the result you might expect from working with `int` and `float` types: + +```python +>>> round(Fraction(11, 3)) +4 + +>>> from math import floor, ceil +>>> floor(Fraction(11, 3)) +3 +>>> ceil(Fraction(11, 3)) +4 + +>>> float(Fraction(11, 3)) +3.6666666666666665 +``` + +[0.30000000000000004]: https://0.30000000000000004.com/ +[fp-issues]: https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues +[fractions]: https://docs.python.org/3/library/fractions.html +[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex +[rational]: https://en.wikipedia.org/wiki/Rational_number diff --git a/concepts/fractions/links.json b/concepts/fractions/links.json new file mode 100644 index 00000000000..78d349bcfc3 --- /dev/null +++ b/concepts/fractions/links.json @@ -0,0 +1,18 @@ +[ + { + "url": "https://docs.python.org/3/library/fractions.html/", + "description": "Documentation for the Fractions module." + }, + { + "url": "https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues", + "description": "Limitations of Floating Point Arithmetic." + }, + { + "url": "https://leancrew.com/all-this/2023/08/decimal-to-fraction/", + "description": "And now it's all this: Decimal to fraction." + }, + { + "url": "https://nrich.maths.org/2515", + "description": "History of Fractions." + } +] diff --git a/config.json b/config.json index 98891388c25..734fa7a98fb 100644 --- a/config.json +++ b/config.json @@ -2567,6 +2567,11 @@ "uuid": "565f7618-4552-4eb0-b829-d6bacd03deaf", "slug": "with-statement", "name": "With Statement" + }, + { + "uuid": "000e7768-38b9-4904-9ae2-9a4e448f366c", + "slug": "fractions", + "name": "Fractions" } ], "key_features": [ From cb75332df9619e50d4b8ad3131072a0470a56f9b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 5 Dec 2023 14:03:51 -0800 Subject: [PATCH 007/277] Update config.json to add Fractions Concept (#3559) --- config.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config.json b/config.json index 734fa7a98fb..26d29574b3c 100644 --- a/config.json +++ b/config.json @@ -2373,6 +2373,11 @@ "slug": "enums", "name": "Enums" }, + { + "uuid": "8ac7c6b5-5786-45dd-8ce3-5b139da06471", + "slug": "fractions", + "name": "Fractions" + }, { "uuid": "26b147e0-2cdc-4325-a6b4-6a2dd5bb69b1", "slug": "function-arguments", From a706592185a8044dbf69c312cd934873a2156abe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:05:42 -0800 Subject: [PATCH 008/277] Bump actions/setup-python from 4.7.1 to 4.8.0 (#3560) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.7.1 to 4.8.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236...b64ffcaf5b410884ad320a9cfac8866006a109aa) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 1632f55e2e3..e79f0b549ee 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Set up Python - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 + uses: actions/setup-python@b64ffcaf5b410884ad320a9cfac8866006a109aa with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 + - uses: actions/setup-python@b64ffcaf5b410884ad320a9cfac8866006a109aa with: python-version: ${{ matrix.python-version }} From 0374d1ca286486a1f77aa753e18ba66e297576cb Mon Sep 17 00:00:00 2001 From: colinleach Date: Wed, 6 Dec 2023 18:48:23 -0700 Subject: [PATCH 009/277] new `random` concept (#3556) * new `random` concept * changed unused variables to underscore * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md I have been avoiding $\LaTeX$ (very reluctantly, but Jeremy and Erik insist). I guess Unicode will save us here. Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Update concepts/random/about.md Co-authored-by: BethanyG * Added Introduction.md and Links * Small touchups and link fixes * More Typo Fixes --------- Co-authored-by: BethanyG --- concepts/random/.meta/config.json | 5 + concepts/random/about.md | 245 ++++++++++++++++++++++++++++++ concepts/random/introduction.md | 108 +++++++++++++ concepts/random/links.json | 14 ++ config.json | 5 + 5 files changed, 377 insertions(+) create mode 100644 concepts/random/.meta/config.json create mode 100644 concepts/random/about.md create mode 100644 concepts/random/introduction.md create mode 100644 concepts/random/links.json diff --git a/concepts/random/.meta/config.json b/concepts/random/.meta/config.json new file mode 100644 index 00000000000..7319e329bad --- /dev/null +++ b/concepts/random/.meta/config.json @@ -0,0 +1,5 @@ +{ + "blurb": "The random module contains functionality to generate random values for modelling, simulations and games. It should not be used for security or cryptographic applications.", + "authors": ["BethanyG", "colinleach"], + "contributors": [] +} diff --git a/concepts/random/about.md b/concepts/random/about.md new file mode 100644 index 00000000000..9ed984179d3 --- /dev/null +++ b/concepts/random/about.md @@ -0,0 +1,245 @@ +# About + +Many programs need (apparently) random values to simulate real-world events. + +Common, familiar examples include: +- A coin toss: a random value from `('H', 'T')`. +- The roll of a die: a random integer from 1 to 6. +- Shuffling a deck of cards: a random ordering of a card list. + +Generating truly random values with a computer is a [surprisingly difficult technical challenge][truly-random], so you may see these results referred to as "pseudorandom". + +In practice, a well-designed library like the [`random`][random] module in the Python standard library is fast, flexible, and gives results that are amply good enough for most applications in modelling, simulation and games. + +The rest of this page will list a few of the most common functions in `random`. +We encourage you to explore the full `random` documentation, as there are many more options than what we cover here. + + + +~~~~exercism/caution + +The `random` module should __NOT__ be used for security and cryptographic applications. + +Instead, Python provides the [`secrets`][secrets] module. +This is specially optimized for cryptographic security. +Some of the prior issues and reasons for creating the secrets module can be found in [PEP 506][PEP 506]. + +[secrets]: https://docs.python.org/3.11/library/secrets.html#module-secrets +[PEP 506]: https://peps.python.org/pep-0506/ +~~~~ + + + +## Importing + +Before you can utilize the tools in the `random` module, you must first import it: + +```python +>>> import random + +# Choose random integer from a range +>>> random.randrange(1000) +360 + +>>> random.randrange(-1, 500) +228 + +>>> random.randrange(-10, 11, 2) +-8 + +# Choose random integer between two values (inclusive) +>>> random.randint(5, 25) +22 + +``` + +To avoid typing the name of the module, you can import specific functions by name: + +```python +>>> from random import choice, choices + +# Using choice() to pick Heads or Tails 10 times +>>> tosses = [] +>>> for side in range(10): +>>> tosses.append(choice(['H', 'T'])) + +>>> print(tosses) +['H', 'H', 'H', 'H', 'H', 'H', 'H', 'T', 'T', 'H'] + + +# Using choices() to pick Heads or Tails 8 times +>>> picks = [] +>>> picks.extend(choices(['H', 'T'], k=8)) +>>> print(picks) +['T', 'H', 'H', 'T', 'H', 'H', 'T', 'T'] +``` + + +## Creating random integers + +The `randrange()` function has three forms, to select a random value from `range(start, stop, step)`: + 1. `randrange(stop)` gives an integer `n` such that `0 <= n < stop` + 2. `randrange(start, stop)` gives an integer `n` such that `start <= n < stop` + 3. `randrange(start, stop, step)` gives an integer `n` such that `start <= n < stop` and `n` is in the sequence `start, start + step, start + 2*step...` + +For the common case where `step == 1`, the `randint(a, b)` function may be more convenient and readable. +Possible results from `randint()` _include_ the upper bound, so `randint(a, b)` is the same as using `randrange(a, b+1)`: + +```python +>>> import random + +# Select one number at random from the range 0, 499 +>>> random.randrange(500) +219 + +# Select 10 numbers at random between 0 and 9 two steps apart. +>>> numbers = [] +>>> for integer in range(10): +>>> numbers.append(random.randrange(0, 10, 2)) +>>> print(numbers) +[2, 8, 4, 0, 4, 2, 6, 6, 8, 8] + +# roll a die +>>> random.randint(1, 6) +4 +``` + + + +## Working with sequences + +The functions in this section assume that you are starting from some [sequence][sequence-types], or other container. + + +This will typically be a `list`, or with some limitations a `tuple` or a `set` (_a `tuple` is immutable, and `set` is unordered_). + + + +### `choice()` and `choices()` + +The `choice()` function will return one entry chosen at random from a given sequence. +At its simplest, this might be a coin-flip: + +```python +# This will pick one of the two values in the list at random 5 separate times +>>> [random.choice(['H', 'T']) for _ in range(5)] +['T', 'H', 'H', 'T', 'H'] + +We could accomplish essentially the same thing using the `choices()` function, supplying a keyword argument with the list length: + + +```python +>>> random.choices(['H', 'T'], k=5) +['T', 'H', 'T', 'H', 'H'] +``` + + +In the examples above, we assumed a fair coin with equal probability of heads or tails, but weights can also be specified. +For example, if a bag contains 10 red balls and 15 green balls, and we would like to pull one out at random: + +```python +>>> random.choices(['red', 'green'], [10, 15]) +['red'] +``` + + + +### `sample()` + +The `choices()` example above assumes what statisticians call ["sampling with replacement"][sampling-with-replacement]. +Each pick or choice has **no effect** on the probability of future choices, and the distribution of potential choices remains the same from pick to pick. + + +In the example with red and green balls: after each choice, we _return_ the ball to the bag and shake well before the next pick. +This is in contrast to a situation where we pull out a red ball and _it stays out_. +Not returning the ball means there are now fewer red balls in the bag, and the next choice is now _less likely_ to be red. + +To simulate this "sampling without replacement", the random module provides the `sample()` function. +The syntax of `sample()` is similar to `choices()`, except it adds a `counts` keyword parameter: + + +```python +>>> random.sample(['red', 'green'], counts=[10, 15], k=10) +['green', 'green', 'green', 'green', 'green', 'red', 'red', 'red', 'red', 'green'] +``` + +Samples are returned in the order they were chosen. + + + +### `shuffle()` + +Both `choices()` and `sample()` return new lists when `k > 1`. +In contrast, `shuffle()` randomizes the order of a list _**in place**_, and the original ordering is lost: + +```python +>>> my_list = [1, 2, 3, 4, 5] +>>> random.shuffle(my_list) +>>> my_list +[4, 1, 5, 2, 3] +``` + + +## Working with Distributions + +Until now, we have concentrated on cases where all outcomes are equally likely. +For example, `random.randrange(100)` is equally likely to give any integer from 0 to 99. + +Many real-world situations are far less simple than this. +As a result, statisticians have created a wide variety of [`distributions`][probability-distribution] to describe "real world" results mathematically. + + + +### Uniform distributions + +For integers, `randrange()` and `randint()` are used when all probabilities are equal. +This is called a [`uniform`][uniform-distribution] distribution. + + +There are floating-point equivalents to `randrange()` and `randint()`. + +__`random()`__ gives a `float` value `x` such that `0.0 <= x < 1.0`. + +__`uniform(a, b)`__ gives `x` such that `a <= x <= b`. + +```python +>>> [round(random.random(), 3) for _ in range(5)] +[0.876, 0.084, 0.483, 0.22, 0.863] + +>>> [round(random.uniform(2, 5), 3) for _ in range(5)] +[2.798, 2.539, 3.779, 3.363, 4.33] +``` + + + +### Gaussian distribution + +Also called the "normal" distribution or the "bell-shaped" curve, this is a very common way to describe imprecision in measured values. + +For example, suppose the factory where you work has just bought 10,000 bolts which should be identical. +You want to set up the factory robot to handle them, so you weigh a sample of 100 and find that they have an average (or `mean`) weight of 4.731g. +This is extremely unlikely to mean that they all weigh exactly 4.731g. +Perhaps you find that values range from 4.627 to 4.794g but cluster around 4.731g. + +This is the [`Gaussian distribution`][gaussian-distribution], for which probabilities peak at the mean and tails off symmetrically on both sides (hence "bell-shaped"). +To simulate this in software, we need some way to specify the width of the curve (_typically, expensive bolts will cluster more tightly around the mean than cheap bolts!_). + +By convention, this is done with the [`standard deviation`][standard-deviation]: small values for a sharp, narrow curve, large for a low, broad curve. +Mathematicians love Greek letters, so we use `μ` ('mu') to represent the mean and `σ` ('sigma') to represent the standard deviation. +Thus, if you read that "95% of values are within 2σ of μ" or "the Higgs boson has been detected with 5-sigma confidence", such comments relate to the standard deviation. + +```python +>>> mu = 4.731 +>>> sigma = 0.316 +>>> [round(random.gauss(mu, sigma), 3) for _ in range(5)] +[4.72, 4.957, 4.64, 4.556, 4.968] +``` + +[gaussian-distribution]: https://simple.wikipedia.org/wiki/Normal_distribution +[probability-distribution]: https://simple.wikipedia.org/wiki/Probability_distribution +[random]: https://docs.python.org/3/library/random.html +[sampling-with-replacement]: https://www.youtube.com/watch?v=LnGFL_A6A6A +[sequence-types]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range +[standard-deviation]: https://simple.wikipedia.org/wiki/Standard_deviation +[truly-random]: https://www.malwarebytes.com/blog/news/2013/09/in-computers-are-random-numbers-really-random +[uniform-distribution]: https://www.investopedia.com/terms/u/uniform-distribution.asp#:~:text=In%20statistics%2C%20uniform%20distribution%20refers,a%20spade%20is%20equally%20likely. diff --git a/concepts/random/introduction.md b/concepts/random/introduction.md new file mode 100644 index 00000000000..6bf880be57f --- /dev/null +++ b/concepts/random/introduction.md @@ -0,0 +1,108 @@ +# Introduction + +Many programs need (apparently) random values to simulate real-world events. + +Common, familiar examples include: +- A coin toss: a random value from `('H', 'T')`. +- The roll of a die: a random integer from 1 to 6. +- Shuffling a deck of cards: a random ordering of a card list. +- The creation of trees and bushes in a 3-D graphics simulation. + +Generating _truly_ random values with a computer is a [surprisingly difficult technical challenge][truly-random], so you may see these results referred to as "pseudorandom". + +In practice, a well-designed library like the [`random`][random] module in the Python standard library is fast, flexible, and gives results that are amply good enough for most applications in modelling, simulation and games. + +For this brief introduction, we show the four most commonly used functions from the module. +We encourage you to explore the full [`random`][random] documentation, as there are many tools and options. + + +~~~~exercism/caution + +The `random` module should __NOT__ be used for security and cryptographic applications!! + +Instead, Python provides the [`secrets`][secrets] module. +This is specially optimized for cryptographic security. +Some of the prior issues and reasons for creating the secrets module can be found in [PEP 506][PEP 506]. + +[secrets]: https://docs.python.org/3.11/library/secrets.html#module-secrets +[PEP 506]: https://peps.python.org/pep-0506/ +~~~~ + + +Before you can utilize the tools in the `random` module, you must first import it: + +```python +>>> import random + +# Choose random integer from a range +>>> random.randrange(1000) +360 + +>>> random.randrange(-1, 500) +228 + +>>> random.randrange(-10, 11, 2) +-8 + +# Choose random integer between two values (inclusive) +>>> random.randint(5, 25) +22 + +``` + +To avoid typing the name of the module, you can import specific functions by name: + +```python +>>> from random import choice, choices + +# Using choice() to pick Heads or Tails 10 times +>>> tosses = [] +>>> for side in range(10): +>>> tosses.append(choice(['H', 'T'])) + +>>> print(tosses) +['H', 'H', 'H', 'H', 'H', 'H', 'H', 'T', 'T', 'H'] + + +# Using choices() to pick Heads or Tails 8 times +>>> picks = [] +>>> picks.extend(choices(['H', 'T'], k=8)) +>>> print(picks) +['T', 'H', 'H', 'T', 'H', 'H', 'T', 'T'] +``` + + + +## `randrange()` and `randint()` + +Shown in the first example above, the `randrange()` function has three forms: + +1. `randrange(stop)` gives an integer `n` such that `0 <= n < stop` +2. `randrange(start, stop)` gives an integer `n` such that `start <= n < stop` +3. `randrange(start, stop, step)` gives an integer `n` such that `start <= n < stop` + and `n` is in the sequence `start, start + step, start + 2*step...` + +For the most common case where `step == 1`, `randint(a, b)` may be more convenient and readable. +Possible results from `randint()` _include_ the upper bound, so `randint(a, b)` is the same as using `randrange(a, b+1)`. + + + +## `choice()` and `choices()` + +These two functions assume that you are starting from some [sequence][sequence-types], or other container. +This will typically be a `list`, or with some limitations a `tuple` or a `set` (_a `tuple` is immutable, and `set` is unordered_). + +The `choice()` function will return one entry chosen at random from a given sequence, and `choices()` will return `k` number of entries chosen at random from a given sequence. +In the examples shown above, we assumed a fair coin with equal probability of heads or tails, but weights can also be specified. + +For example, if a bag contains 10 red balls and 15 green balls, and we would like to pull one out at random: + + +```python +>>> random.choices(['red', 'green'], [10, 15]) +['red'] +``` + +[random]: https://docs.python.org/3/library/random.html +[sequence-types]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range +[truly-random]: https://www.malwarebytes.com/blog/news/2013/09/in-computers-are-random-numbers-really-random diff --git a/concepts/random/links.json b/concepts/random/links.json new file mode 100644 index 00000000000..22f60dbfb46 --- /dev/null +++ b/concepts/random/links.json @@ -0,0 +1,14 @@ +[ + { + "url": "https://docs.python.org/3/library/random.html/", + "description": "Official documentation for the random module." + }, + { + "url": "https://engineering.mit.edu/engage/ask-an-engineer/can-a-computer-generate-a-truly-random-number/", + "description": "MIT Engineering: Can a computer generate a truly random number?" + }, + { + "url": "https://www.malwarebytes.com/blog/news/2013/09/in-computers-are-random-numbers-really-random", + "description": "Are Random Numbers Really Random?" + } +] diff --git a/config.json b/config.json index 26d29574b3c..2d59e5b0dc6 100644 --- a/config.json +++ b/config.json @@ -2573,6 +2573,11 @@ "slug": "with-statement", "name": "With Statement" }, + { + "uuid": "af6cad74-50c2-48f4-a6ce-cfeb72548d00", + "slug": "random", + "name": "Random" + }, { "uuid": "000e7768-38b9-4904-9ae2-9a4e448f366c", "slug": "fractions", From d7fe4d0d58102766d77bef588e75a3b35b6a0802 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Dec 2023 17:49:04 -0800 Subject: [PATCH 010/277] Bump actions/setup-python from 4.8.0 to 5.0.0 (#3561) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.8.0 to 5.0.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/b64ffcaf5b410884ad320a9cfac8866006a109aa...0a5c61591373683505ea898e09a3ea4f39ef2b9c) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index e79f0b549ee..944c2f531f7 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Set up Python - uses: actions/setup-python@b64ffcaf5b410884ad320a9cfac8866006a109aa + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: actions/setup-python@b64ffcaf5b410884ad320a9cfac8866006a109aa + - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c with: python-version: ${{ matrix.python-version }} From 8ebf5575ad2a69a8799f7c4e3f5b535a1ce466fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Dec 2023 10:03:54 -0800 Subject: [PATCH 011/277] Bump actions/stale from 8.0.0 to 9.0.0 (#3562) Bumps [actions/stale](https://github.com/actions/stale) from 8.0.0 to 9.0.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/1160a2240286f5da8ec72b1c0816ce2481aabf84...28ca1036281a5e5922ead5184a1bbf96e5fc984e) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 86f881a21c7..f526390311f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,7 +8,7 @@ jobs: stale: runs-on: ubuntu-22.04 steps: - - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 + - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 21 From 0b7819512d37eb87149d3d9f3ce824c31cc6e8ad Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen <62411302+safwansamsudeen@users.noreply.github.com> Date: Mon, 18 Dec 2023 11:00:03 +0530 Subject: [PATCH 012/277] approach for nth prime (#3496) --- .../nth-prime/.approaches/config.json | 21 ++++++ .../.approaches/generator-fun/content.md | 51 +++++++++++++++ .../.approaches/generator-fun/snippet.txt | 6 ++ .../nth-prime/.approaches/introduction.md | 46 +++++++++++++ .../nth-prime/.approaches/tracking/content.md | 65 +++++++++++++++++++ .../.approaches/tracking/snippet.txt | 8 +++ 6 files changed, 197 insertions(+) create mode 100644 exercises/practice/nth-prime/.approaches/config.json create mode 100644 exercises/practice/nth-prime/.approaches/generator-fun/content.md create mode 100644 exercises/practice/nth-prime/.approaches/generator-fun/snippet.txt create mode 100644 exercises/practice/nth-prime/.approaches/introduction.md create mode 100644 exercises/practice/nth-prime/.approaches/tracking/content.md create mode 100644 exercises/practice/nth-prime/.approaches/tracking/snippet.txt diff --git a/exercises/practice/nth-prime/.approaches/config.json b/exercises/practice/nth-prime/.approaches/config.json new file mode 100644 index 00000000000..00bbaff5fcd --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/config.json @@ -0,0 +1,21 @@ +{ + "introduction": { + "authors": ["safwansamsudeen"] + }, + "approaches": [ + { + "uuid": "c97a3f8e-a97d-4e45-b44f-128bcffb2d3a", + "slug": "generator-fun", + "title": "Generator Fun", + "blurb": "Utilize Python library and generators", + "authors": ["safwansamsudeen"] + }, + { + "uuid": "e989fdd2-3f39-4195-9d7c-120a6d6376b6", + "slug": "tracking", + "title": "Tracking", + "blurb": "Track previous prime values and return the nth one", + "authors": ["safwansamsudeen"] + } + ] +} diff --git a/exercises/practice/nth-prime/.approaches/generator-fun/content.md b/exercises/practice/nth-prime/.approaches/generator-fun/content.md new file mode 100644 index 00000000000..39290335a04 --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/generator-fun/content.md @@ -0,0 +1,51 @@ +# Generator Fun +The key of this approach is to not store the elements you do not need. + +This is a code representation: +```python +from itertools import islice, count + +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + gen = islice(filter(lambda counter: all(counter % test != 0 for test in range(2, int(counter ** 0.5) + 1)), count(2)), number) + for _ in range(number - 1): next(gen) + return next(gen) +``` + +Let's dissect it! `itertools.count` is like `range` without un upper bound - calling it returns a generator, and `for ... in count_obj` will result in an infinite loop. + +Using a lambda expression, we `filter` out any numbers above two that are prime. +Doesn't this result in an infinite loop? +No - `filter` _also_ returns a generator object (which are [evaluated lazily][generator]), so while it's too will produce values infinitely if evaluated, it doesn't hang to program at the time of instantiation. + +`itertools.islice` takes in a generator object and an end count, returning a generator object which _only evalutes until that end count_. + +The next line exhausts all the values in the generator except the end, and we finally return the last element. + +We can utilize the `functools.cache` decorator for greater speeds at higher values of `number`, so we take it out. The added bonus is that a very long line of code is cleant up. + +```python +from itertools import islice, count +from functools import cache + +@cache +def is_prime(counter): + return all(counter % test != 0 for test in range(2, int(counter ** 0.5) + 1)) + +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + gen = islice(filter(is_prime, count(2)), number) + for _ in range(number - 1): next(gen) + return next(gen) +``` + +~~~~exercism/note +Note that this that not create a list anywhere, and thus is both memory and time efficient. +~~~~ + +Read more on `itertools` on the [Python docs][itertools]. + +[itertools]: https://docs.python.org/3/library/itertools.html +[generator]: https://www.programiz.com/python-programming/generator \ No newline at end of file diff --git a/exercises/practice/nth-prime/.approaches/generator-fun/snippet.txt b/exercises/practice/nth-prime/.approaches/generator-fun/snippet.txt new file mode 100644 index 00000000000..d073667a4c9 --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/generator-fun/snippet.txt @@ -0,0 +1,6 @@ +from itertools import islice, count + +def prime(number): + gen = islice(filter(lambda n: all(counter % test != 0 for test in range(2, int(counter ** 0.5) + 1)), count(2)), number) + for _ in range(number - 1): next(gen) + return next(gen) \ No newline at end of file diff --git a/exercises/practice/nth-prime/.approaches/introduction.md b/exercises/practice/nth-prime/.approaches/introduction.md new file mode 100644 index 00000000000..e20541778ea --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/introduction.md @@ -0,0 +1,46 @@ +# Introduction +Nth Prime in Python is a very interesting exercise that can be solved in multiple ways. + +## General guidance +Every approach has to a) validate the input, b) calculate the prime numbers until n - 1, and c) return the nth prime number. + +As the previous numbers are calculated multiple times, it's advisable to extract that piece of code as a function and use `functools.cache` for greater speeds at higher numbers. + +## Approach: Tracking +Using this approach, we manually track the primes/number of primes until the nth prime, after which we quit the loop and return the last number in the list/currently tracked prime. +There are many possible code implementations, such as this one: +```python +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + counter = 2 + primes = [2] + while len(primes) < number: + counter += 1 + if all(counter % test != 0 for test in primes): + primes.append(counter) + return primes[-1] +``` + +If you're interested in learning more about this approach (and discover a lesser known Python feature!), go [here][approach-tracking]. + +## Approach: Generator Fun +A far more idiomatic approach that utilizes Python's powerful standard library is to use `itertools` and generator expression related functions. + +```python +from itertools import islice, count + +def is_prime(n): + return not any(n % k == 0 for k in range(2, int(n ** 0.5) + 1)) + +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + gen = islice(filter(is_prime, count(2)), number) + for _ in range(number - 1): next(gen) + return next(gen) +``` +If you'd like to understand this approach better, [read more][approach-generator-fun]. + +[approach-tracking]: https://exercism.org/tracks/python/exercises/nth-prime/approaches/tracking +[approach-generator-fun]: https://exercism.org/tracks/python/exercises/nth-prime/approaches/generator-fun \ No newline at end of file diff --git a/exercises/practice/nth-prime/.approaches/tracking/content.md b/exercises/practice/nth-prime/.approaches/tracking/content.md new file mode 100644 index 00000000000..7e6398c92f7 --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/tracking/content.md @@ -0,0 +1,65 @@ +# Tracking +This approach includes building a list of all the previous primes until it reaches the nth number, after which it quits the loop and return the last number in the list. +```python +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + counter = 2 + primes = [2] + while len(primes) < number: + counter += 1 + if all(counter % test != 0 for test in range(2, counter)): + primes.append(counter) + return primes[-1] +``` +Efficiency can be improved slightly by reducing the factor check to the square root of the number... +```python +... +if all(counter % test != 0 for test in range(2, int(counter ** 0.5) + 1)): + ... +``` +... or even better, checking whether a new number is merely not divisible by any of the primes (in which case it's a prime itself): +```python +... +if all(counter % test != 0 for test in primes): + ... +``` +Instead of building the list, it's more memory efficient to just save the number of primes found until now, and return the currently stored number when the nth prime is found. +However, this elongates the code. +```python +def prime(number): + if number == 0: + raise ValueError('there is no zeroth prime') + counter = 2 + prime_count = 0 + while True: + isprime = True + for test in range(2, int(counter ** 0.5) + 1): + if counter % test == 0: + isprime = False + break + if isprime: + prime_count += 1 + if prime_count == number: + return counter + counter += 1 +``` + +~~~~exercism/advanced +Tip: you can use `for... else` to make your code more idiomatic here. +```python +... +for test in range(2, int(counter ** 0.5) + 1): + if counter % test == 0: + break +else: + prime_count += 1 +if prime_count == number: + return counter +``` +The else block is executed if the `for` loop completes normally - that is, without `break`ing. +Read more on [for/else][for-else] +~~~~ + + +[for-else]: https://book.pythontips.com/en/latest/for_-_else.html \ No newline at end of file diff --git a/exercises/practice/nth-prime/.approaches/tracking/snippet.txt b/exercises/practice/nth-prime/.approaches/tracking/snippet.txt new file mode 100644 index 00000000000..1e8b3ab8830 --- /dev/null +++ b/exercises/practice/nth-prime/.approaches/tracking/snippet.txt @@ -0,0 +1,8 @@ +def prime(number): + counter = 2 + primes = [2] + while len(primes) < number: + counter += 1 + if all(counter % test != 0 for test in range(2, counter)): + primes.append(counter) + return primes[-1] \ No newline at end of file From 38caf4b50b9368e00c50c0d3c1bab403513c0692 Mon Sep 17 00:00:00 2001 From: colinleach Date: Tue, 26 Dec 2023 17:46:34 -0700 Subject: [PATCH 013/277] The `secrets` concept (#3565) * The `secrets` concept * Edits and Link Additions A few edits, and some additional links. --------- Co-authored-by: BethanyG --- concepts/secrets/.meta/config.json | 5 +++ concepts/secrets/about.md | 51 ++++++++++++++++++++++++++++++ concepts/secrets/introduction.md | 17 ++++++++++ concepts/secrets/links.json | 18 +++++++++++ config.json | 5 +++ 5 files changed, 96 insertions(+) create mode 100644 concepts/secrets/.meta/config.json create mode 100644 concepts/secrets/about.md create mode 100644 concepts/secrets/introduction.md create mode 100644 concepts/secrets/links.json diff --git a/concepts/secrets/.meta/config.json b/concepts/secrets/.meta/config.json new file mode 100644 index 00000000000..152aa0eb3ba --- /dev/null +++ b/concepts/secrets/.meta/config.json @@ -0,0 +1,5 @@ +{ + "blurb": "The secrets module is a cryptographically-secure alternative to the random module, intended for security-critical uses.", + "authors": ["BethanyG", "colinleach"], + "contributors": [] +} diff --git a/concepts/secrets/about.md b/concepts/secrets/about.md new file mode 100644 index 00000000000..5987ab37e91 --- /dev/null +++ b/concepts/secrets/about.md @@ -0,0 +1,51 @@ +# About + +A previous concept discussed the [concept:python/random]() module, which produces [pseudo-random numbers][pseudo-random-numbers] and pseudo-random list orderings. + +The [`secrets`][secrets] module overlaps with `random` in some of its functionality, but the two modules are designed with very different priorities. + +- `random` is optimized for high performance in modelling and simulation, with "good enough" pseudo-random number generation. +- `secrets` is designed to be crytographically secure for applications such as password hashing, security token generation, and account authentication. + + +Further details on why the addition of the `secrets` module proved necessary are given in [PEP 506][PEP506]. + +The `secrets` is relatively small and straightforward, with methods for generating random integers, bits, bytes or tokens, or a random entry from a given sequence. + +To use `scerets`, you mush first `import` it: + + +```python +>>> import secrets + +#Returns n, where 0 <= n < 1000. +>>> secrets.randbelow(1000) +577 + +#32-bit integers. +>>> secrets.randbits(32) +3028709440 + +>>> bin(secrets.randbits(32)) +'0b11111000101100101111110011110100' + +#Pick at random from a sequence. +>>> secrets.choice(['my', 'secret', 'thing']) +'thing' + +#Generate a token made up of random hexadecimal digits. +>>> secrets.token_hex() +'f683d093ea9aa1f2607497c837cf11d7afaefa903c5805f94b64f068e2b9e621' + +#Generate a URL-safe token of random alphanumeric characters. +>>> secrets.token_urlsafe(16) +'gkSUKRdiPDHqmImPi2HMnw' +``` + + +If you are writing security-sensitive applications, you will certainly want to read the [full documentation][secrets], which gives further advice and examples. + + +[PEP506]: https://peps.python.org/pep-0506/ +[pseudo-random-numbers]: https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/random-vs-pseudorandom-number-generators +[secrets]: https://docs.python.org/3/library/secrets.html diff --git a/concepts/secrets/introduction.md b/concepts/secrets/introduction.md new file mode 100644 index 00000000000..a82538acda0 --- /dev/null +++ b/concepts/secrets/introduction.md @@ -0,0 +1,17 @@ +# Introduction + +A previous concept discussed the [concept:python/random]() module, which produces [pseudo-random numbers][pseudo-random-numbers] and pseudo-random list orderings. + +The [`secrets`][secrets] module overlaps with `random` in some of its functionality, but the two modules are designed with very different priorities. + +- `random` is optimized for high performance in modelling and simulation, with "good enough" pseudo-random number generation. +- `secrets` is designed to be crytographically secure for applications such as password hashing, security token generation, and account authentication. + + +Further details on why the addition of the `secrets` module proved necessary are given in [PEP 506][PEP506]. + +If you are writing security-sensitive applications, you will certainly want to read the [full documentation][secrets], which gives further advice and examples. + +[PEP506]: https://peps.python.org/pep-0506/[secrets]: https://docs.python.org/3/library/secrets.html +[pseudo-random-numbers]: https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/random-vs-pseudorandom-number-generators +[secrets]: https://docs.python.org/3/library/secrets.html diff --git a/concepts/secrets/links.json b/concepts/secrets/links.json new file mode 100644 index 00000000000..ccf44e6fe59 --- /dev/null +++ b/concepts/secrets/links.json @@ -0,0 +1,18 @@ +[ + { + "url": "https://docs.python.org/3/library/secrets.html/", + "description": "The secrets module." + }, + { + "url": "https://peps.python.org/pep-0506/", + "description": "PEP 506, giving reasons why the secrets module is necessary." + }, + { + "url": "https://en.wikipedia.org/wiki/Pseudorandomness", + "description": "Wikipedia: Pseudorandomness." + }, + { + "url": "https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/random-vs-pseudorandom-number-generators", + "description": "Khan Academy: Pseudorandom Number Generators." + } +] diff --git a/config.json b/config.json index 2d59e5b0dc6..6afd5046809 100644 --- a/config.json +++ b/config.json @@ -2582,6 +2582,11 @@ "uuid": "000e7768-38b9-4904-9ae2-9a4e448f366c", "slug": "fractions", "name": "Fractions" + }, + { + "uuid": "e1496136-8d58-4409-9a41-4b6ee4721c6b", + "slug": "secrets", + "name": "Secrets" } ], "key_features": [ From d2be549dc5d8d5dcc136f8b705730c36b6db2966 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 26 Dec 2023 22:59:17 -0800 Subject: [PATCH 014/277] Update introduction.md (#3572) Fixed link typos. --- concepts/secrets/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/secrets/introduction.md b/concepts/secrets/introduction.md index a82538acda0..04308ed0a2a 100644 --- a/concepts/secrets/introduction.md +++ b/concepts/secrets/introduction.md @@ -12,6 +12,6 @@ Further details on why the addition of the `secrets` module proved necessary are If you are writing security-sensitive applications, you will certainly want to read the [full documentation][secrets], which gives further advice and examples. -[PEP506]: https://peps.python.org/pep-0506/[secrets]: https://docs.python.org/3/library/secrets.html +[PEP506]: https://peps.python.org/pep-0506/ [pseudo-random-numbers]: https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/random-vs-pseudorandom-number-generators [secrets]: https://docs.python.org/3/library/secrets.html From 60b19a6bd429e6b1d6cf6ac12ea8a6714ae417e1 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:05:44 -0800 Subject: [PATCH 015/277] Synced practice exercise docs to problem specificatons. (#3573) --- .../perfect-numbers/.docs/instructions.md | 44 +++++++++++++------ .../phone-number/.docs/instructions.md | 6 ++- .../queen-attack/.docs/instructions.md | 20 ++++----- .../practice/rest-api/.docs/instructions.md | 2 +- 4 files changed, 43 insertions(+), 29 deletions(-) diff --git a/exercises/practice/perfect-numbers/.docs/instructions.md b/exercises/practice/perfect-numbers/.docs/instructions.md index 689a73c00de..b2bc82ca3e9 100644 --- a/exercises/practice/perfect-numbers/.docs/instructions.md +++ b/exercises/practice/perfect-numbers/.docs/instructions.md @@ -2,22 +2,38 @@ Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers. -The Greek mathematician [Nicomachus][nicomachus] devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of **perfect**, **abundant**, or **deficient** based on their [aliquot sum][aliquot-sum]. -The aliquot sum is defined as the sum of the factors of a number not including the number itself. +The Greek mathematician [Nicomachus][nicomachus] devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of [perfect](#perfect), [abundant](#abundant), or [deficient](#deficient) based on their [aliquot sum][aliquot-sum]. +The _aliquot sum_ is defined as the sum of the factors of a number not including the number itself. For example, the aliquot sum of `15` is `1 + 3 + 5 = 9`. -- **Perfect**: aliquot sum = number - - 6 is a perfect number because (1 + 2 + 3) = 6 - - 28 is a perfect number because (1 + 2 + 4 + 7 + 14) = 28 -- **Abundant**: aliquot sum > number - - 12 is an abundant number because (1 + 2 + 3 + 4 + 6) = 16 - - 24 is an abundant number because (1 + 2 + 3 + 4 + 6 + 8 + 12) = 36 -- **Deficient**: aliquot sum < number - - 8 is a deficient number because (1 + 2 + 4) = 7 - - Prime numbers are deficient - -Implement a way to determine whether a given number is **perfect**. -Depending on your language track, you may also need to implement a way to determine whether a given number is **abundant** or **deficient**. +## Perfect + +A number is perfect when it equals its aliquot sum. +For example: + +- `6` is a perfect number because `1 + 2 + 3 = 6` +- `28` is a perfect number because `1 + 2 + 4 + 7 + 14 = 28` + +## Abundant + +A number is abundant when it is less than its aliquot sum. +For example: + +- `12` is an abundant number because `1 + 2 + 3 + 4 + 6 = 16` +- `24` is an abundant number because `1 + 2 + 3 + 4 + 6 + 8 + 12 = 36` + +## Deficient + +A number is deficient when it is greater than its aliquot sum. +For example: + +- `8` is a deficient number because `1 + 2 + 4 = 7` +- Prime numbers are deficient + +## Task + +Implement a way to determine whether a given number is [perfect](#perfect). +Depending on your language track, you may also need to implement a way to determine whether a given number is [abundant](#abundant) or [deficient](#deficient). [nicomachus]: https://en.wikipedia.org/wiki/Nicomachus [aliquot-sum]: https://en.wikipedia.org/wiki/Aliquot_sum diff --git a/exercises/practice/phone-number/.docs/instructions.md b/exercises/practice/phone-number/.docs/instructions.md index 6b86bbac9f8..62ba48e96fd 100644 --- a/exercises/practice/phone-number/.docs/instructions.md +++ b/exercises/practice/phone-number/.docs/instructions.md @@ -11,12 +11,14 @@ The first three digits of the local number represent the _exchange code_, follow The format is usually represented as ```text -(NXX)-NXX-XXXX +NXX NXX-XXXX ``` where `N` is any digit from 2 through 9 and `X` is any digit from 0 through 9. -Your task is to clean up differently formatted telephone numbers by removing punctuation and the country code (1) if present. +Sometimes they also have the country code (represented as `1` or `+1`) prefixed. + +Your task is to clean up differently formatted telephone numbers by removing punctuation and the country code if present. For example, the inputs diff --git a/exercises/practice/queen-attack/.docs/instructions.md b/exercises/practice/queen-attack/.docs/instructions.md index ad7ea954796..97f22a0aee1 100644 --- a/exercises/practice/queen-attack/.docs/instructions.md +++ b/exercises/practice/queen-attack/.docs/instructions.md @@ -8,18 +8,14 @@ A chessboard can be represented by an 8 by 8 array. So if you are told the white queen is at `c5` (zero-indexed at column 2, row 3) and the black queen at `f2` (zero-indexed at column 5, row 6), then you know that the set-up is like so: -```text - a b c d e f g h -8 _ _ _ _ _ _ _ _ 8 -7 _ _ _ _ _ _ _ _ 7 -6 _ _ _ _ _ _ _ _ 6 -5 _ _ W _ _ _ _ _ 5 -4 _ _ _ _ _ _ _ _ 4 -3 _ _ _ _ _ _ _ _ 3 -2 _ _ _ _ _ B _ _ 2 -1 _ _ _ _ _ _ _ _ 1 - a b c d e f g h -``` +![A chess board with two queens. Arrows emanating from the queen at c5 indicate possible directions of capture along file, rank and diagonal.](https://assets.exercism.org/images/exercises/queen-attack/queen-capture.svg) You are also able to answer whether the queens can attack each other. In this case, that answer would be yes, they can, because both pieces share a diagonal. + +## Credit + +The chessboard image was made by [habere-et-dispertire][habere-et-dispertire] using LaTeX and the [chessboard package][chessboard-package] by Ulrike Fischer. + +[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire +[chessboard-package]: https://github.com/u-fischer/chessboard diff --git a/exercises/practice/rest-api/.docs/instructions.md b/exercises/practice/rest-api/.docs/instructions.md index cb57f6f43f7..af223ba4b4d 100644 --- a/exercises/practice/rest-api/.docs/instructions.md +++ b/exercises/practice/rest-api/.docs/instructions.md @@ -44,5 +44,5 @@ Your task is to implement a simple [RESTful API][restful-wikipedia] that receive [restful-wikipedia]: https://en.wikipedia.org/wiki/Representational_state_transfer [iou]: https://en.wikipedia.org/wiki/IOU [github-rest]: https://developer.github.com/v3/ -[reddit-rest]: https://www.reddit.com/dev/api/ +[reddit-rest]: https://web.archive.org/web/20231202231149/https://www.reddit.com/dev/api/ [restfulapi]: https://restfulapi.net/ From 0bb0a2fd810e36391ff87c5e944efa7ced81bbf0 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:06:00 -0800 Subject: [PATCH 016/277] Synced practice exercise metadata to problem specifications. (#3574) --- exercises/practice/hello-world/.meta/config.json | 2 +- exercises/practice/pop-count/.meta/config.json | 2 +- exercises/practice/pythagorean-triplet/.meta/config.json | 2 +- exercises/practice/spiral-matrix/.meta/config.json | 2 +- exercises/practice/transpose/.meta/config.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exercises/practice/hello-world/.meta/config.json b/exercises/practice/hello-world/.meta/config.json index fc3599fd070..16a87cf554b 100644 --- a/exercises/practice/hello-world/.meta/config.json +++ b/exercises/practice/hello-world/.meta/config.json @@ -27,7 +27,7 @@ ".meta/example.py" ] }, - "blurb": "The classical introductory exercise. Just say \"Hello, World!\".", + "blurb": "Exercism's classic introductory exercise. Just say \"Hello, World!\".", "source": "This is an exercise to introduce users to using Exercism", "source_url": "https://en.wikipedia.org/wiki/%22Hello,_world!%22_program" } diff --git a/exercises/practice/pop-count/.meta/config.json b/exercises/practice/pop-count/.meta/config.json index 36b1259acec..0c402acfad7 100644 --- a/exercises/practice/pop-count/.meta/config.json +++ b/exercises/practice/pop-count/.meta/config.json @@ -13,7 +13,7 @@ ".meta/example.py" ] }, - "blurb": "Help Eluid count the number of eggs in her chicken coop by counting the number of 1 bits in a binary representation.", + "blurb": "Help Eliud count the number of eggs in her chicken coop by counting the number of 1 bits in a binary representation.", "source": "Christian Willner, Eric Willigers", "source_url": "https://forum.exercism.org/t/new-exercise-suggestion-pop-count/7632/5" } diff --git a/exercises/practice/pythagorean-triplet/.meta/config.json b/exercises/practice/pythagorean-triplet/.meta/config.json index 03d35139ac6..a9bed96083f 100644 --- a/exercises/practice/pythagorean-triplet/.meta/config.json +++ b/exercises/practice/pythagorean-triplet/.meta/config.json @@ -31,7 +31,7 @@ ".meta/example.py" ] }, - "blurb": "There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the product a * b * c.", + "blurb": "There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the triplet.", "source": "Problem 9 at Project Euler", "source_url": "https://projecteuler.net/problem=9" } diff --git a/exercises/practice/spiral-matrix/.meta/config.json b/exercises/practice/spiral-matrix/.meta/config.json index f6dfd5a5051..b2e3dd9f238 100644 --- a/exercises/practice/spiral-matrix/.meta/config.json +++ b/exercises/practice/spiral-matrix/.meta/config.json @@ -22,5 +22,5 @@ }, "blurb": "Given the size, return a square matrix of numbers in spiral order.", "source": "Reddit r/dailyprogrammer challenge #320 [Easy] Spiral Ascension.", - "source_url": "https://www.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/" + "source_url": "https://web.archive.org/web/20230607064729/https://old.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/" } diff --git a/exercises/practice/transpose/.meta/config.json b/exercises/practice/transpose/.meta/config.json index 5eb2b08ef74..45df3151246 100644 --- a/exercises/practice/transpose/.meta/config.json +++ b/exercises/practice/transpose/.meta/config.json @@ -27,5 +27,5 @@ }, "blurb": "Take input text and output it transposed.", "source": "Reddit r/dailyprogrammer challenge #270 [Easy].", - "source_url": "https://www.reddit.com/r/dailyprogrammer/comments/4msu2x/challenge_270_easy_transpose_the_input_text" + "source_url": "https://web.archive.org/web/20230630051421/https://old.reddit.com/r/dailyprogrammer/comments/4msu2x/challenge_270_easy_transpose_the_input_text/" } From ef951b40b8da66f51d8021f1db58fb5ca0f3a7bf Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:06:28 -0800 Subject: [PATCH 017/277] Synced tests to cannonical data and regenerated testfile. (#3575) --- exercises/practice/anagram/.meta/tests.toml | 5 +++++ exercises/practice/anagram/anagram_test.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/exercises/practice/anagram/.meta/tests.toml b/exercises/practice/anagram/.meta/tests.toml index 8a3708bbf9f..4f43811d26a 100644 --- a/exercises/practice/anagram/.meta/tests.toml +++ b/exercises/practice/anagram/.meta/tests.toml @@ -46,6 +46,11 @@ description = "detects anagrams using case-insensitive possible matches" [7cc195ad-e3c7-44ee-9fd2-d3c344806a2c] description = "does not detect an anagram if the original word is repeated" +include = false + +[630abb71-a94e-4715-8395-179ec1df9f91] +description = "does not detect an anagram if the original word is repeated" +reimplements = "7cc195ad-e3c7-44ee-9fd2-d3c344806a2c" [9878a1c9-d6ea-4235-ae51-3ea2befd6842] description = "anagrams must use all letters exactly once" diff --git a/exercises/practice/anagram/anagram_test.py b/exercises/practice/anagram/anagram_test.py index 5b0e9c6c4d7..bed1d80fc93 100644 --- a/exercises/practice/anagram/anagram_test.py +++ b/exercises/practice/anagram/anagram_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/anagram/canonical-data.json -# File last updated on 2023-07-20 +# File last updated on 2023-12-27 import unittest @@ -61,7 +61,7 @@ def test_detects_anagrams_using_case_insensitive_possible_matches(self): self.assertCountEqual(find_anagrams("orchestra", candidates), expected) def test_does_not_detect_an_anagram_if_the_original_word_is_repeated(self): - candidates = ["go Go GO"] + candidates = ["goGoGO"] expected = [] self.assertCountEqual(find_anagrams("go", candidates), expected) From 66160b0b30474e5285dfc7ccae3c09046bd85182 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:06:50 -0800 Subject: [PATCH 018/277] Synced tests to problem specifications. (#3576) --- .../practice/custom-set/.meta/tests.toml | 92 ++++++++++--------- .../practice/custom-set/custom_set_test.py | 7 +- 2 files changed, 57 insertions(+), 42 deletions(-) diff --git a/exercises/practice/custom-set/.meta/tests.toml b/exercises/practice/custom-set/.meta/tests.toml index 6ba62315950..8c450e0baf1 100644 --- a/exercises/practice/custom-set/.meta/tests.toml +++ b/exercises/practice/custom-set/.meta/tests.toml @@ -1,117 +1,127 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [20c5f855-f83a-44a7-abdd-fe75c6cf022b] -description = "sets with no elements are empty" +description = "Returns true if the set contains no elements -> sets with no elements are empty" [d506485d-5706-40db-b7d8-5ceb5acf88d2] -description = "sets with elements are not empty" +description = "Returns true if the set contains no elements -> sets with elements are not empty" [759b9740-3417-44c3-8ca3-262b3c281043] -description = "nothing is contained in an empty set" +description = "Sets can report if they contain an element -> nothing is contained in an empty set" [f83cd2d1-2a85-41bc-b6be-80adbff4be49] -description = "when the element is in the set" +description = "Sets can report if they contain an element -> when the element is in the set" [93423fc0-44d0-4bc0-a2ac-376de8d7af34] -description = "when the element is not in the set" +description = "Sets can report if they contain an element -> when the element is not in the set" [c392923a-637b-4495-b28e-34742cd6157a] -description = "empty set is a subset of another empty set" +description = "A set is a subset if all of its elements are contained in the other set -> empty set is a subset of another empty set" [5635b113-be8c-4c6f-b9a9-23c485193917] -description = "empty set is a subset of non-empty set" +description = "A set is a subset if all of its elements are contained in the other set -> empty set is a subset of non-empty set" [832eda58-6d6e-44e2-92c2-be8cf0173cee] -description = "non-empty set is not a subset of empty set" +description = "A set is a subset if all of its elements are contained in the other set -> non-empty set is not a subset of empty set" [c830c578-8f97-4036-b082-89feda876131] -description = "set is a subset of set with exact same elements" +description = "A set is a subset if all of its elements are contained in the other set -> set is a subset of set with exact same elements" [476a4a1c-0fd1-430f-aa65-5b70cbc810c5] -description = "set is a subset of larger set with same elements" +description = "A set is a subset if all of its elements are contained in the other set -> set is a subset of larger set with same elements" [d2498999-3e46-48e4-9660-1e20c3329d3d] -description = "set is not a subset of set that does not contain its elements" +description = "A set is a subset if all of its elements are contained in the other set -> set is not a subset of set that does not contain its elements" [7d38155e-f472-4a7e-9ad8-5c1f8f95e4cc] -description = "the empty set is disjoint with itself" +description = "Sets are disjoint if they share no elements -> the empty set is disjoint with itself" [7a2b3938-64b6-4b32-901a-fe16891998a6] -description = "empty set is disjoint with non-empty set" +description = "Sets are disjoint if they share no elements -> empty set is disjoint with non-empty set" [589574a0-8b48-48ea-88b0-b652c5fe476f] -description = "non-empty set is disjoint with empty set" +description = "Sets are disjoint if they share no elements -> non-empty set is disjoint with empty set" [febeaf4f-f180-4499-91fa-59165955a523] -description = "sets are not disjoint if they share an element" +description = "Sets are disjoint if they share no elements -> sets are not disjoint if they share an element" [0de20d2f-c952-468a-88c8-5e056740f020] -description = "sets are disjoint if they share no elements" +description = "Sets are disjoint if they share no elements -> sets are disjoint if they share no elements" [4bd24adb-45da-4320-9ff6-38c044e9dff8] -description = "empty sets are equal" +description = "Sets with the same elements are equal -> empty sets are equal" [f65c0a0e-6632-4b2d-b82c-b7c6da2ec224] -description = "empty set is not equal to non-empty set" +description = "Sets with the same elements are equal -> empty set is not equal to non-empty set" [81e53307-7683-4b1e-a30c-7e49155fe3ca] -description = "non-empty set is not equal to empty set" +description = "Sets with the same elements are equal -> non-empty set is not equal to empty set" [d57c5d7c-a7f3-48cc-a162-6b488c0fbbd0] -description = "sets with the same elements are equal" +description = "Sets with the same elements are equal -> sets with the same elements are equal" [dd61bafc-6653-42cc-961a-ab071ee0ee85] -description = "sets with different elements are not equal" +description = "Sets with the same elements are equal -> sets with different elements are not equal" [06059caf-9bf4-425e-aaff-88966cb3ea14] -description = "set is not equal to larger set with same elements" +description = "Sets with the same elements are equal -> set is not equal to larger set with same elements" + +[d4a1142f-09aa-4df9-8b83-4437dcf7ec24] +description = "Sets with the same elements are equal -> set is equal to a set constructed from an array with duplicates" [8a677c3c-a658-4d39-bb88-5b5b1a9659f4] -description = "add to empty set" +description = "Unique elements can be added to a set -> add to empty set" [0903dd45-904d-4cf2-bddd-0905e1a8d125] -description = "add to non-empty set" +description = "Unique elements can be added to a set -> add to non-empty set" [b0eb7bb7-5e5d-4733-b582-af771476cb99] -description = "adding an existing element does not change the set" +description = "Unique elements can be added to a set -> adding an existing element does not change the set" [893d5333-33b8-4151-a3d4-8f273358208a] -description = "intersection of two empty sets is an empty set" +description = "Intersection returns a set of all shared elements -> intersection of two empty sets is an empty set" [d739940e-def2-41ab-a7bb-aaf60f7d782c] -description = "intersection of an empty set and non-empty set is an empty set" +description = "Intersection returns a set of all shared elements -> intersection of an empty set and non-empty set is an empty set" [3607d9d8-c895-4d6f-ac16-a14956e0a4b7] -description = "intersection of a non-empty set and an empty set is an empty set" +description = "Intersection returns a set of all shared elements -> intersection of a non-empty set and an empty set is an empty set" [b5120abf-5b5e-41ab-aede-4de2ad85c34e] -description = "intersection of two sets with no shared elements is an empty set" +description = "Intersection returns a set of all shared elements -> intersection of two sets with no shared elements is an empty set" [af21ca1b-fac9-499c-81c0-92a591653d49] -description = "intersection of two sets with shared elements is a set of the shared elements" +description = "Intersection returns a set of all shared elements -> intersection of two sets with shared elements is a set of the shared elements" [c5e6e2e4-50e9-4bc2-b89f-c518f015b57e] -description = "difference of two empty sets is an empty set" +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of two empty sets is an empty set" [2024cc92-5c26-44ed-aafd-e6ca27d6fcd2] -description = "difference of empty set and non-empty set is an empty set" +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of empty set and non-empty set is an empty set" [e79edee7-08aa-4c19-9382-f6820974b43e] -description = "difference of a non-empty set and an empty set is the non-empty set" +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of a non-empty set and an empty set is the non-empty set" [c5ac673e-d707-4db5-8d69-7082c3a5437e] -description = "difference of two non-empty sets is a set of elements that are only in the first set" +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of two non-empty sets is a set of elements that are only in the first set" [c45aed16-5494-455a-9033-5d4c93589dc6] -description = "union of empty sets is an empty set" +description = "Union returns a set of all elements in either set -> union of empty sets is an empty set" [9d258545-33c2-4fcb-a340-9f8aa69e7a41] -description = "union of an empty set and non-empty set is the non-empty set" +description = "Union returns a set of all elements in either set -> union of an empty set and non-empty set is the non-empty set" [3aade50c-80c7-4db8-853d-75bac5818b83] -description = "union of a non-empty set and empty set is the non-empty set" +description = "Union returns a set of all elements in either set -> union of a non-empty set and empty set is the non-empty set" [a00bb91f-c4b4-4844-8f77-c73e2e9df77c] -description = "union of non-empty sets contains all unique elements" +description = "Union returns a set of all elements in either set -> union of non-empty sets contains all unique elements" diff --git a/exercises/practice/custom-set/custom_set_test.py b/exercises/practice/custom-set/custom_set_test.py index 9ee2bd19679..edd76564131 100644 --- a/exercises/practice/custom-set/custom_set_test.py +++ b/exercises/practice/custom-set/custom_set_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/custom-set/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2023-12-27 import unittest @@ -115,6 +115,11 @@ def test_set_is_not_equal_to_larger_set_with_same_elements(self): set2 = CustomSet([1, 2, 3, 4]) self.assertNotEqual(set1, set2) + def test_set_is_equal_to_a_set_constructed_from_an_array_with_duplicates(self): + set1 = CustomSet([1]) + set2 = CustomSet([1, 1]) + self.assertEqual(set1, set2) + def test_add_to_empty_set(self): sut = CustomSet() expected = CustomSet([3]) From b4dbac454c8eab71f36d2e4849ec8d877cb840c9 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:07:16 -0800 Subject: [PATCH 019/277] Synced exercise tests to problem specifications. (#3577) --- .../practice/dnd-character/.meta/tests.toml | 50 ++++++++++++------- .../dnd-character/dnd_character_test.py | 7 ++- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/exercises/practice/dnd-character/.meta/tests.toml b/exercises/practice/dnd-character/.meta/tests.toml index 5d9d1aa34a5..719043b2533 100644 --- a/exercises/practice/dnd-character/.meta/tests.toml +++ b/exercises/practice/dnd-character/.meta/tests.toml @@ -1,54 +1,61 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [1e9ae1dc-35bd-43ba-aa08-e4b94c20fa37] -description = "ability modifier for score 3 is -4" +description = "ability modifier -> ability modifier for score 3 is -4" [cc9bb24e-56b8-4e9e-989d-a0d1a29ebb9c] -description = "ability modifier for score 4 is -3" +description = "ability modifier -> ability modifier for score 4 is -3" [5b519fcd-6946-41ee-91fe-34b4f9808326] -description = "ability modifier for score 5 is -3" +description = "ability modifier -> ability modifier for score 5 is -3" [dc2913bd-6d7a-402e-b1e2-6d568b1cbe21] -description = "ability modifier for score 6 is -2" +description = "ability modifier -> ability modifier for score 6 is -2" [099440f5-0d66-4b1a-8a10-8f3a03cc499f] -description = "ability modifier for score 7 is -2" +description = "ability modifier -> ability modifier for score 7 is -2" [cfda6e5c-3489-42f0-b22b-4acb47084df0] -description = "ability modifier for score 8 is -1" +description = "ability modifier -> ability modifier for score 8 is -1" [c70f0507-fa7e-4228-8463-858bfbba1754] -description = "ability modifier for score 9 is -1" +description = "ability modifier -> ability modifier for score 9 is -1" [6f4e6c88-1cd9-46a0-92b8-db4a99b372f7] -description = "ability modifier for score 10 is 0" +description = "ability modifier -> ability modifier for score 10 is 0" [e00d9e5c-63c8-413f-879d-cd9be9697097] -description = "ability modifier for score 11 is 0" +description = "ability modifier -> ability modifier for score 11 is 0" [eea06f3c-8de0-45e7-9d9d-b8cab4179715] -description = "ability modifier for score 12 is +1" +description = "ability modifier -> ability modifier for score 12 is +1" [9c51f6be-db72-4af7-92ac-b293a02c0dcd] -description = "ability modifier for score 13 is +1" +description = "ability modifier -> ability modifier for score 13 is +1" [94053a5d-53b6-4efc-b669-a8b5098f7762] -description = "ability modifier for score 14 is +2" +description = "ability modifier -> ability modifier for score 14 is +2" [8c33e7ca-3f9f-4820-8ab3-65f2c9e2f0e2] -description = "ability modifier for score 15 is +2" +description = "ability modifier -> ability modifier for score 15 is +2" [c3ec871e-1791-44d0-b3cc-77e5fb4cd33d] -description = "ability modifier for score 16 is +3" +description = "ability modifier -> ability modifier for score 16 is +3" [3d053cee-2888-4616-b9fd-602a3b1efff4] -description = "ability modifier for score 17 is +3" +description = "ability modifier -> ability modifier for score 17 is +3" [bafd997a-e852-4e56-9f65-14b60261faee] -description = "ability modifier for score 18 is +4" +description = "ability modifier -> ability modifier for score 18 is +4" [4f28f19c-2e47-4453-a46a-c0d365259c14] description = "random ability is within range" @@ -58,3 +65,8 @@ description = "random character is valid" [2ca77b9b-c099-46c3-a02c-0d0f68ffa0fe] description = "each ability is only calculated once" +include = false + +[dca2b2ec-f729-4551-84b9-078876bb4808] +description = "each ability is only calculated once" +reimplements = "2ca77b9b-c099-46c3-a02c-0d0f68ffa0fe" diff --git a/exercises/practice/dnd-character/dnd_character_test.py b/exercises/practice/dnd-character/dnd_character_test.py index e506fdb1171..0e4709863b2 100644 --- a/exercises/practice/dnd-character/dnd_character_test.py +++ b/exercises/practice/dnd-character/dnd_character_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/dnd-character/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2023-12-27 import unittest @@ -76,3 +76,8 @@ def test_random_character_is_valid(self): def test_each_ability_is_only_calculated_once(self): Char = Character() self.assertIs(Char.strength == Char.strength, True) + self.assertIs(Char.dexterity == Char.dexterity, True) + self.assertIs(Char.constitution == Char.constitution, True) + self.assertIs(Char.intelligence == Char.intelligence, True) + self.assertIs(Char.wisdom == Char.wisdom, True) + self.assertIs(Char.charisma == Char.charisma, True) From c9c67a864fa5fbc0dbc6e711d7ee7b0e41bdc5f8 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:07:37 -0800 Subject: [PATCH 020/277] Synced exercise tests to problem specifications. (#3578) --- exercises/practice/knapsack/.meta/tests.toml | 18 +++++++++++++++--- exercises/practice/knapsack/knapsack_test.py | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/exercises/practice/knapsack/.meta/tests.toml b/exercises/practice/knapsack/.meta/tests.toml index 5a7805b017a..8e013ef1995 100644 --- a/exercises/practice/knapsack/.meta/tests.toml +++ b/exercises/practice/knapsack/.meta/tests.toml @@ -1,9 +1,21 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [a4d7d2f0-ad8a-460c-86f3-88ba709d41a7] description = "no items" +include = false + +[3993a824-c20e-493d-b3c9-ee8a7753ee59] +description = "no items" +reimplements = "a4d7d2f0-ad8a-460c-86f3-88ba709d41a7" [1d39e98c-6249-4a8b-912f-87cb12e506b0] description = "one item, too heavy" diff --git a/exercises/practice/knapsack/knapsack_test.py b/exercises/practice/knapsack/knapsack_test.py index 011f7ba8324..946d0d51303 100644 --- a/exercises/practice/knapsack/knapsack_test.py +++ b/exercises/practice/knapsack/knapsack_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/knapsack/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2023-12-27 import unittest From ca3f65e0adac14bf6a1ba6c0c60c950ece8dce7c Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:07:59 -0800 Subject: [PATCH 021/277] Synced exercise tests to problem specifications. (#3579) --- exercises/practice/ledger/.meta/tests.toml | 5 ++++ exercises/practice/ledger/ledger_test.py | 34 +++++++++++----------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/exercises/practice/ledger/.meta/tests.toml b/exercises/practice/ledger/.meta/tests.toml index e71dfbfcafd..4ea45ceb12c 100644 --- a/exercises/practice/ledger/.meta/tests.toml +++ b/exercises/practice/ledger/.meta/tests.toml @@ -20,6 +20,7 @@ description = "credit and debit" [502c4106-0371-4e7c-a7d8-9ce33f16ccb1] description = "multiple entries on same date ordered by description" +include = false [29dd3659-6c2d-4380-94a8-6d96086e28e1] description = "final order tie breaker is change" @@ -41,3 +42,7 @@ description = "Dutch negative number with 3 digits before decimal point" [29670d1c-56be-492a-9c5e-427e4b766309] description = "American negative number with 3 digits before decimal point" + +[9c70709f-cbbd-4b3b-b367-81d7c6101de4] +description = "multiple entries on same date ordered by description" +reimplements = "502c4106-0371-4e7c-a7d8-9ce33f16ccb1" diff --git a/exercises/practice/ledger/ledger_test.py b/exercises/practice/ledger/ledger_test.py index cc37167146d..1fc6671cf86 100644 --- a/exercises/practice/ledger/ledger_test.py +++ b/exercises/practice/ledger/ledger_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/ledger/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2023-12-27 import unittest @@ -54,22 +54,6 @@ def test_credit_and_debit(self): ) self.assertEqual(format_entries(currency, locale, entries), expected) - def test_multiple_entries_on_same_date_ordered_by_description(self): - currency = "USD" - locale = "en_US" - entries = [ - create_entry("2015-01-02", "Get present", 1000), - create_entry("2015-01-01", "Buy present", -1000), - ] - expected = "\n".join( - [ - "Date | Description | Change ", - "01/01/2015 | Buy present | ($10.00)", - "01/02/2015 | Get present | $10.00 ", - ] - ) - self.assertEqual(format_entries(currency, locale, entries), expected) - def test_final_order_tie_breaker_is_change(self): currency = "USD" locale = "en_US" @@ -171,3 +155,19 @@ def test_american_negative_number_with_3_digits_before_decimal_point(self): ] ) self.assertEqual(format_entries(currency, locale, entries), expected) + + def test_multiple_entries_on_same_date_ordered_by_description(self): + currency = "USD" + locale = "en_US" + entries = [ + create_entry("2015-01-01", "Get present", 1000), + create_entry("2015-01-01", "Buy present", -1000), + ] + expected = "\n".join( + [ + "Date | Description | Change ", + "01/01/2015 | Buy present | ($10.00)", + "01/01/2015 | Get present | $10.00 ", + ] + ) + self.assertEqual(format_entries(currency, locale, entries), expected) From dfcb006ebad0dc76daef9b8b11c6e2b2bc6b7234 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 05:08:18 -0800 Subject: [PATCH 022/277] Synced exercise tests to problem specificatoins. (#3580) --- exercises/practice/poker/.meta/tests.toml | 16 ++++++++++++++++ exercises/practice/poker/poker_test.py | 16 +++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/exercises/practice/poker/.meta/tests.toml b/exercises/practice/poker/.meta/tests.toml index 76ac892d933..2e654ef63b2 100644 --- a/exercises/practice/poker/.meta/tests.toml +++ b/exercises/practice/poker/.meta/tests.toml @@ -21,12 +21,18 @@ description = "a tie has multiple winners" [61ed83a9-cfaa-40a5-942a-51f52f0a8725] description = "multiple hands with the same high cards, tie compares next highest ranked, down to last card" +[da01becd-f5b0-4342-b7f3-1318191d0580] +description = "winning high card hand also has the lowest card" + [f7175a89-34ff-44de-b3d7-f6fd97d1fca4] description = "one pair beats high card" [e114fd41-a301-4111-a9e7-5a7f72a76561] description = "highest pair wins" +[b3acd3a7-f9fa-4647-85ab-e0a9e07d1365] +description = "both hands have the same pair, high card wins" + [935bb4dc-a622-4400-97fa-86e7d06b1f76] description = "two pairs beats one pair" @@ -53,6 +59,11 @@ description = "both hands have three of a kind, tie goes to highest ranked tripl [eb856cc2-481c-4b0d-9835-4d75d07a5d9d] description = "with multiple decks, two players can have same three of a kind, ties go to highest remaining cards" +include = false + +[26a4a7d4-34a2-4f18-90b4-4a8dd35d2bb1] +description = "with multiple decks, two players can have same three of a kind, ties go to highest remaining cards" +reimplements = "eb856cc2-481c-4b0d-9835-4d75d07a5d9d" [a858c5d9-2f28-48e7-9980-b7fa04060a60] description = "a straight beats three of a kind" @@ -77,6 +88,11 @@ description = "flush beats a straight" [4d90261d-251c-49bd-a468-896bf10133de] description = "both hands have a flush, tie goes to high card, down to the last one if necessary" +include = false + +[e04137c5-c19a-4dfc-97a1-9dfe9baaa2ff] +description = "both hands have a flush, tie goes to high card, down to the last one if necessary" +reimplements = "4d90261d-251c-49bd-a468-896bf10133de" [3a19361d-8974-455c-82e5-f7152f5dba7c] description = "full house beats a flush" diff --git a/exercises/practice/poker/poker_test.py b/exercises/practice/poker/poker_test.py index c80aad6f059..b9a252012dc 100644 --- a/exercises/practice/poker/poker_test.py +++ b/exercises/practice/poker/poker_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/poker/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2023-12-27 import unittest @@ -39,6 +39,11 @@ def test_multiple_hands_with_the_same_high_cards_tie_compares_next_highest_ranke best_hands(["3S 5H 6S 8D 7H", "2S 5D 6D 8C 7S"]), ["3S 5H 6S 8D 7H"] ) + def test_winning_high_card_hand_also_has_the_lowest_card(self): + self.assertEqual( + best_hands(["2S 5H 6S 8D 7H", "3S 4D 6D 8C 7S"]), ["2S 5H 6S 8D 7H"] + ) + def test_one_pair_beats_high_card(self): self.assertEqual( best_hands(["4S 5H 6C 8D KH", "2S 4H 6S 4D JH"]), ["2S 4H 6S 4D JH"] @@ -49,6 +54,11 @@ def test_highest_pair_wins(self): best_hands(["4S 2H 6S 2D JH", "2S 4H 6C 4D JD"]), ["2S 4H 6C 4D JD"] ) + def test_both_hands_have_the_same_pair_high_card_wins(self): + self.assertEqual( + best_hands(["4H 4S AH JC 3D", "4C 4D AS 5D 6C"]), ["4H 4S AH JC 3D"] + ) + def test_two_pairs_beats_one_pair(self): self.assertEqual( best_hands(["2S 8H 6S 8D JH", "4S 5H 4C 8C 5C"]), ["4S 5H 4C 8C 5C"] @@ -99,7 +109,7 @@ def test_with_multiple_decks_two_players_can_have_same_three_of_a_kind_ties_go_t self, ): self.assertEqual( - best_hands(["4S AH AS 7C AD", "4S AH AS 8C AD"]), ["4S AH AS 8C AD"] + best_hands(["5S AH AS 7C AD", "4S AH AS 8C AD"]), ["4S AH AS 8C AD"] ) def test_a_straight_beats_three_of_a_kind(self): @@ -143,7 +153,7 @@ def test_both_hands_have_a_flush_tie_goes_to_high_card_down_to_the_last_one_if_n self, ): self.assertEqual( - best_hands(["4H 7H 8H 9H 6H", "2S 4S 5S 6S 7S"]), ["4H 7H 8H 9H 6H"] + best_hands(["2H 7H 8H 9H 6H", "3S 5S 6S 7S 8S"]), ["2H 7H 8H 9H 6H"] ) def test_full_house_beats_a_flush(self): From 185fbdfac9d6961c355aaeb428ee92f1c3cc25f3 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 27 Dec 2023 06:02:10 -0800 Subject: [PATCH 023/277] Random grammar, spelling and content edits. (#3581) [no important files changed] --- CONTRIBUTING.md | 8 +- README.md | 6 +- bin/generate_tests.py | 2 +- concepts/binary-octal-hexadecimal/about.md | 7 +- .../binary-octal-hexadecimal/introduction.md | 2 +- concepts/classes/links.json | 2 +- concepts/conditionals/introduction.md | 8 +- concepts/function-arguments/about.md | 2 +- concepts/function-arguments/introduction.md | 2 +- concepts/functions/about.md | 6 +- concepts/functools/about.md | 21 +- concepts/functools/introduction.md | 2 +- docs/TRACEBACKS.md | 4 +- .../concept/ellens-alien-game/classes_test.py | 2 +- .../locomotive_engineer.py | 4 +- reference/concept-exercise-mapping.md | 4 +- reference/track_exercises_overview.md | 242 +++++++++--------- 17 files changed, 161 insertions(+), 163 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0bac23e0af3..bc278b027e5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -170,7 +170,7 @@ Our documents use [Markdown][markdown-language], with certain [alterations][exer - Favor `f-strings` for dynamic failure messages. Please make your error messages as relevant and human-readable as possible. - We relate test cases to **task number** via a custom [PyTest Marker][pytestmark]. - These take the form of `@pytest.mark.task(taskno=)`. See [Guido's Gorgeous Lasagna][guidos-gorgeous-lasagna-testfile] for an example. -- We prefer **test data files** when test inputs/ouputs are verbose. +- We prefer **test data files** when test inputs/outputs are verbose. - These should be named with `_data` or `_test_data` at the end of the filename, and saved alongside the test case file. - See the [Cater-Waiter][cater-waiter] exercise directory for an example of this setup. - **Test data files** need to be added under an `editor` key within [`config.json "files"`][exercise-config-json]. @@ -270,9 +270,9 @@ Although the majority of test cases are written using `unittest.TestCase`, - [ ] `.meta/config.json` (**required**) - [ ] `.meta/example.py` (**required**) - [ ] `.meta/design.md` (_optional_) - - [ ] `.meta/template.j2` (_template for generating tests from cannonical data_) - - [ ] `.meta/tests.toml` (_tests configuration from cannonical data_) - - [ ] `_test.py` (_**auto-generated from cannonical data**_) + - [ ] `.meta/template.j2` (_template for generating tests from canonical data_) + - [ ] `.meta/tests.toml` (_tests configuration from canonical data_) + - [ ] `_test.py` (_**auto-generated from canonical data**_) - [ ] `.py` (**required**) diff --git a/README.md b/README.md index 1c82f33b9d0..99357745f09 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,9 @@ Hi.  👋🏽  👋  **We are happy you are here.**  🎉&nb **`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website]. This repo holds all the instructions, tests, code, & support files for Python _exercises_ currently under development or implemented & available for students. -🌟   Track exercises support Python `3.7` - `3.11.2`. +🌟   Track exercises support Python `3.7` - `3.11.5`. Exceptions to this support are noted where they occur. -🌟   Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.11.2`. +🌟   Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.11.5`. Exercises are grouped into **concept** exercises which teach the [Python syllabus][python-syllabus], and **practice** exercises, which are unlocked by progressing in the syllabus tree  🌴 . Concept exercises are constrained to a small set of language or syntax features. @@ -67,7 +67,7 @@ _Thoughtful suggestions will likely result faster & more enthusiastic responses ## Python Software and Documentation -**Copyright © 2001-2022 Python Software Foundation. All rights reserved.** +**Copyright © 2001-2023 Python Software Foundation. All rights reserved.** Python software and documentation are licensed under the [PSF License Agreement][psf-license]. diff --git a/bin/generate_tests.py b/bin/generate_tests.py index 6c9b1c38b98..a3089eb8b4b 100755 --- a/bin/generate_tests.py +++ b/bin/generate_tests.py @@ -138,7 +138,7 @@ def parse_datetime(string: str, strip_module: bool = False) -> datetime: ]| # OR o(?:[0-8]{1,3}) # an octal value | # OR - x(?:[0-9A-Fa-f]{2}) # a hexidecimal value + x(?:[0-9A-Fa-f]{2}) # a hexadecimal value | # OR N # a unicode char name composed of \{ # an opening brace diff --git a/concepts/binary-octal-hexadecimal/about.md b/concepts/binary-octal-hexadecimal/about.md index 667f3eb6cd7..a7fca3714e3 100644 --- a/concepts/binary-octal-hexadecimal/about.md +++ b/concepts/binary-octal-hexadecimal/about.md @@ -190,8 +190,8 @@ As with binary and octal, Python will automatically convert hexadecimal literals 291 ``` -As with binary and octal - hexidecimal literals **are ints**, and you can perform all integer operations. -Prefixing a non-hexidecimal number with `0x` will raise a `SyntaxError`. +As with binary and octal - hexadecimal literals **are ints**, and you can perform all integer operations. +Prefixing a non-hexadecimal number with `0x` will raise a `SyntaxError`. ### Converting to and from Hexadecimal Representation @@ -216,9 +216,6 @@ As with binary and octal, giving the wrong base will raise a `ValueError`. [binary]: https://en.wikipedia.org/wiki/Binary_number [bit_count]: https://docs.python.org/3/library/stdtypes.html#int.bit_count [bit_length]: https://docs.python.org/3/library/stdtypes.html#int.bit_length -[bit_count]: https://docs.python.org/3/library/stdtypes.html#int.bit_count -[bit_length]: https://docs.python.org/3/library/stdtypes.html#int.bit_length [hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal -[methods-int]: https://docs.python.org/3/library/stdtypes.html#additional-methods-on-integer-types [numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system [octal]: https://en.wikipedia.org/wiki/Octal diff --git a/concepts/binary-octal-hexadecimal/introduction.md b/concepts/binary-octal-hexadecimal/introduction.md index 820aac33ee7..a06ac922faf 100644 --- a/concepts/binary-octal-hexadecimal/introduction.md +++ b/concepts/binary-octal-hexadecimal/introduction.md @@ -5,7 +5,7 @@ Binary is base 2, octal is base 8, and hexadecimal is base 16. Normal integers are base 10 in python. Binary, octal, and hexadecimal literals are all considered `int` subtypes and Python automatically converts between them. This means that they can only represent zero, positive, and negative numbers that do not have a fractional or decimal part. -Binary, octal, and hexidecimal numbers support all integer operations. +Binary, octal, and hexadecimal numbers support all integer operations. However, division (_as with ints_) will return a `float`. [numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system diff --git a/concepts/classes/links.json b/concepts/classes/links.json index 5687b92a3d1..8cc9ba5926e 100644 --- a/concepts/classes/links.json +++ b/concepts/classes/links.json @@ -17,7 +17,7 @@ }, { "url": "https://dbader.org/blog/6-things-youre-missing-out-on-by-never-using-classes-in-your-python-code", - "description": "6 Things Youre Missing out on by never using classes in your Python code." + "description": "6 Things You are Missing out on by never using classes in your Python code." }, { "url": "http://python-history.blogspot.com/2010/06/inside-story-on-new-style-classes.html", diff --git a/concepts/conditionals/introduction.md b/concepts/conditionals/introduction.md index 29e3e635975..77bf9608ed8 100644 --- a/concepts/conditionals/introduction.md +++ b/concepts/conditionals/introduction.md @@ -56,7 +56,7 @@ else: >>> z is greater than x and y ``` -[Boolen operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing: +[Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing: ```python @@ -77,8 +77,8 @@ else: '13' ``` -[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement -[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools -[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing [boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not [comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons +[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools +[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement +[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing diff --git a/concepts/function-arguments/about.md b/concepts/function-arguments/about.md index f4a9c2b3f26..0f2ab5dddda 100644 --- a/concepts/function-arguments/about.md +++ b/concepts/function-arguments/about.md @@ -4,7 +4,7 @@ For the basics on function arguments, please see the [function concept][function ## Parameter Names -Paramater names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers. +Parameter names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers. Parameter names should not contain spaces or punctuation. ## Positional Arguments diff --git a/concepts/function-arguments/introduction.md b/concepts/function-arguments/introduction.md index 171675ce3c4..07b885f332e 100644 --- a/concepts/function-arguments/introduction.md +++ b/concepts/function-arguments/introduction.md @@ -4,7 +4,7 @@ For the basics on function arguments, please see the [function concept][function ## Parameter Names -Paramater names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers. +Parameter names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers. Parameter names should not contain spaces or punctuation. ## Positional Arguments diff --git a/concepts/functions/about.md b/concepts/functions/about.md index 9d8fddfa956..f3630af763c 100644 --- a/concepts/functions/about.md +++ b/concepts/functions/about.md @@ -2,11 +2,11 @@ A [`function`][function] is a block of organized, reusable code that is used to perform a specific task. `Functions` provide better modularity for your application and a high degree of code reuse. -Python, like other programming languages, has [_built-in functions_][build-in functions] ([`print`][print], [`map`][map], and so on) that are readily available. +Python, like other programming languages, has [_built-in functions_][built-in functions] ([`print`][print], [`map`][map], and so on) that are readily available. You can also define your own functions. Those are called [`user-defined functions`][user defined functions]. Functions can run something as simple as _printing a message to the console_ or they can be quite complex. -To execute the code inside a function, you need to call the function, which is done by using the function name followed by parenthesese [`()`]. +To execute the code inside a function, you need to call the function, which is done by using the function name followed by parentheses [`()`]. Data, known as [`arguments`][arguments], can be passed to the function by placing them inside the parenthesese. A function definition may include zero or more [`parameters`][parameters]. Parameters define what argument(s) the function accepts. @@ -376,7 +376,7 @@ The full list of function attributes can be found at [Python DataModel][attribut [LEGB Rule]: https://realpython.com/python-scope-legb-rule/ [arguments]: https://www.w3schools.com/python/gloss_python_function_arguments.asp [attributes]: https://docs.python.org/3/reference/datamodel.html#index-33 -[build-in functions]: https://docs.python.org/3/library/functions.html +[built-in functions]: https://docs.python.org/3/library/functions.html [def]: https://www.geeksforgeeks.org/python-def-keyword/ [dict]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries [first class objects]: https://en.wikipedia.org/wiki/First-class_object diff --git a/concepts/functools/about.md b/concepts/functools/about.md index 32748a45c23..e5afb577d39 100644 --- a/concepts/functools/about.md +++ b/concepts/functools/about.md @@ -12,7 +12,7 @@ The functools module is for higher-order functions: functions that act on or ret Since a dictionary is used to cache results, the positional and keyword arguments to the function must be hashable. -Here ```maxsize = 128``` means that it is going to memoize lastest 128 function calls at max. +Here ```maxsize = 128``` means that it is going to memoize latest 128 function calls at max. The lru_cache works the same way but it can cache at max maxsize calls and if type = True, then the function arguments of different types will be cached separately i.e. 5 and 5.0 will be cached differently. @@ -23,8 +23,8 @@ The lru_cache works the same way but it can cache at max maxsize calls and if ty ```python >>> @cache - def factorial(n): - return n * factorial(n-1) if n else 1 +>>> def factorial(n): +>>> return n * factorial(n-1) if n else 1 >>> factorial(10) # no previously cached result, makes 11 recursive calls 3628800 @@ -37,9 +37,10 @@ The lru_cache works the same way but it can cache at max maxsize calls and if ty # Some types such as str and int may be cached separately even when typed is false. -@lru_cache(maxsize = 128) - def factorial(n): - return n * factorial(n-1) if n else 1 +>>> @lru_cache(maxsize = 128) +>>> def factorial(n): +>>> return n * factorial(n-1) if n else 1 + >>> factorial(10) 3628800 @@ -50,7 +51,7 @@ CacheInfo(hits=0, misses=11, maxsize=128, currsize=11) ## Generic functions -***[Generic functions](https://pymotw.com/3/functools/#generic-functions)*** are those which preform the operation based on the argument given to them. In statically typed languages it can be done by function overloading. +***[Generic functions](https://pymotw.com/3/functools/#generic-functions)*** are those which perform the operation based on the argument given to them. In statically typed languages it can be done by function overloading. In python functools provides the `singledispatch()` decorator to register a set of generic functions for automatic switching based on the type of the first argument to a function. @@ -193,11 +194,11 @@ True The ```pow_2.func``` is same as the ```pow``` function. -Here ```pow_2.args``` return an empty tuple becuse we does not pass any positional argument to out partial object call. +Here ```pow_2.args``` returns an empty tuple because we do not pass any positional argument to our partial object call. -```pow_2.keywords``` return a dictionary of keywords argument which will be supplied when the partial object is called. +```pow_2.keywords``` returns a dictionary of keywords argument which will be supplied when the partial object is called. -Here ```two_pow.args``` return an ```(2,)``` tuple because we passed 2 as an argument while creating the pratial object, which fixed the value of ```base``` argument as ```2```. +Here ```two_pow.args``` returns a ```(2,)``` tuple because we passed 2 as an argument while creating the partial object, which fixed the value of ```base``` argument as ```2```. ### ```partialmethod``` diff --git a/concepts/functools/introduction.md b/concepts/functools/introduction.md index 15e83e3e61a..c91aedc81bd 100644 --- a/concepts/functools/introduction.md +++ b/concepts/functools/introduction.md @@ -12,7 +12,7 @@ The functools module is for higher-order functions: functions that act on or ret Since a dictionary is used to cache results, the positional and keyword arguments to the function must be hashable. -Here ```maxsize = 128``` means that it is going to memoize lastest 128 function calls at max. +Here ```maxsize = 128``` means that it is going to memoize latest 128 function calls at max. ### ```@functools.cache(user_function)``` diff --git a/docs/TRACEBACKS.md b/docs/TRACEBACKS.md index 5914429db9d..b7a4b010b91 100644 --- a/docs/TRACEBACKS.md +++ b/docs/TRACEBACKS.md @@ -415,7 +415,7 @@ def halve_and_quadruple(num): print((num / 2) * 4) return (num / 2) * 4 -What the `print` calls revealed is that we used `/` when we should have used `//`, the [floor divison operator][floor divison operator]. +What the `print` calls revealed is that we used `/` when we should have used `//`, the [floor division operator][floor division operator]. ## Logging @@ -487,7 +487,7 @@ AssertionError: divisor must not be 0 ``` -If we start reading the Traceback at the bottom (as we should) we quickly see the problem is that `0` should not be passsed as the `divisor`. +If we start reading the Traceback at the bottom (as we should) we quickly see the problem is that `0` should not be passed as the `divisor`. `assert` can also be used to test that a value is of the expected type: diff --git a/exercises/concept/ellens-alien-game/classes_test.py b/exercises/concept/ellens-alien-game/classes_test.py index 58b78d7096b..3d2b986be4d 100644 --- a/exercises/concept/ellens-alien-game/classes_test.py +++ b/exercises/concept/ellens-alien-game/classes_test.py @@ -23,7 +23,7 @@ class ClassesTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_alien_has_correct_initial_coordinates(self): - """Test thst the Alien class gets correctly initialised.""" + """Test that the Alien class gets correctly initialised.""" alien = Alien(2, -1) error_message = (f'Created a new Alien by calling Alien(2, -1). ' diff --git a/exercises/concept/locomotive-engineer/locomotive_engineer.py b/exercises/concept/locomotive-engineer/locomotive_engineer.py index cea48cc028c..180329208cb 100644 --- a/exercises/concept/locomotive-engineer/locomotive_engineer.py +++ b/exercises/concept/locomotive-engineer/locomotive_engineer.py @@ -13,8 +13,8 @@ def get_list_of_wagons(): def fix_list_of_wagons(each_wagons_id, missing_wagons): """Fix the list of wagons. - :parm each_wagons_id: list - the list of wagons. - :parm missing_wagons: list - the list of missing wagons. + :param each_wagons_id: list - the list of wagons. + :param missing_wagons: list - the list of missing wagons. :return: list - list of wagons. """ pass diff --git a/reference/concept-exercise-mapping.md b/reference/concept-exercise-mapping.md index 1bda69ad12e..1b3e8a1a45f 100644 --- a/reference/concept-exercise-mapping.md +++ b/reference/concept-exercise-mapping.md @@ -398,14 +398,14 @@ _Concepts needed for a deeper understanding/fluency_ ## Specialized -_(These are probably outside scope of an Exercism Concept exercise, but might make good longer/practice exercises that recieve mentoring)_ +_(These are probably outside scope of an Exercism Concept exercise, but might make good longer/practice exercises that receive mentoring)_
Advanced/Specialized Concepts
-- [ ] Asynchronous operatons +- [ ] Asynchronous operations - [ ] [`async`][keyword-async] - [ ] [`await`][keyword-await] diff --git a/reference/track_exercises_overview.md b/reference/track_exercises_overview.md index 761f2c47644..6d09504071b 100644 --- a/reference/track_exercises_overview.md +++ b/reference/track_exercises_overview.md @@ -14,125 +14,125 @@ | Exercise | Difficulty | Solutions | Prereqs | Practices | Hints? | Approaches? | Mentor Notes | Appends? | Jinja? | |-------------------------------------------------------------------------------------------------------------------------------------------- |:----------: |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |---------------------------------------------------------------------------------------------------------- |---------------------------------------------------------------------------------------------------------- |-------------------------------------------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------ |---------------------------------------------------------------------------------------------------------------------------- |----------------------------------------------------------------------------------------------------------------- | | [**Hello World**](https://github.com/exercism/python/blob/main/exercises/practice/hello-world/.docs/instructions.md) | 1 | NA | NONE | NONE | NA | NA | NA | NA | NA | -| [Acronym](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/acronym/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L337) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L330) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/acronym/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.meta/template.j2) | -| [Affine Cipher](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/affine-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1174) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1173) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.meta/template.j2) | -| [All Your Base](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/all-your-base/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1394) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1393) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.meta/template.j2) | -| [Allergies](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/allergies/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L701) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L700) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/allergies/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.meta/template.j2) | -| [Alphametics](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/alphametics/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1935) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1934) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.meta/template.j2) | -| [Anagram](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/anagram/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L577) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L576) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.meta/template.j2) | -| [Armstrong Numbers](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/armstrong-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L512) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L511) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/armstrong-numbers/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.meta/template.j2) | -| [Atbash Cipher](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/atbash-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1102) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1101) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.meta/template.j2) | -| [Bank Account](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bank-account/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2207) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2206) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.meta/template.j2) | -| [Binary Search Tree](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search-tree/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1157) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1156) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.meta/template.j2) | -| [Binary Search](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/binary-search/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1192) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1191) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/binary-search/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.meta/template.j2) | -| [Bob](https://github.com/exercism/python/blob/main/exercises/practice/bob/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bob/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bob/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L715) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L714) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bob/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/bob/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bob/.meta/template.j2) | -| [Book Store](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/book-store/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L445) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L437) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.meta/template.j2) | +| [Acronym](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/acronym/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L337) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L330) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/acronym/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/acronym/.meta/template.j2) | +| [Affine Cipher](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/affine-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1174) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1173) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/affine-cipher/.meta/template.j2) | +| [All Your Base](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/all-your-base/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1394) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1393) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/all-your-base/.meta/template.j2) | +| [Allergies](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/allergies/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L701) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L700) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/allergies/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/allergies/.meta/template.j2) | +| [Alphametics](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/alphametics/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1935) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1934) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/alphametics/.meta/template.j2) | +| [Anagram](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/anagram/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L577) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L576) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/anagram/.meta/template.j2) | +| [Armstrong Numbers](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/armstrong-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L512) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L511) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/armstrong-numbers/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/armstrong-numbers/.meta/template.j2) | +| [Atbash Cipher](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/atbash-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1102) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1101) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/atbash-cipher/.meta/template.j2) | +| [Bank Account](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/bank-account/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2207) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2206) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bank-account/.meta/template.j2) | +| [Binary Search Tree](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/binary-search-tree/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1157) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1156) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/binary-search-tree/.meta/template.j2) | +| [Binary Search](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/binary-search/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1192) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1191) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/binary-search/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/binary-search/.meta/template.j2) | +| [Bob](https://github.com/exercism/python/blob/main/exercises/practice/bob/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bob/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/bob/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L715) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L714) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bob/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/bob/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bob/.meta/template.j2) | +| [Book Store](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/book-store/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L445) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L437) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/book-store/.meta/template.j2) | | [Bottle Song](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/bottle-song/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json/#LC1012) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#LC1012) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bottle-song/.meta/template.j2) | -| [Bowling](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/bowling/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1553) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1552) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.meta/template.j2) | -| [Change](https://github.com/exercism/python/blob/main/exercises/practice/change/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/change/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/change/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1412) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1411) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/change/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/change/.meta/template.j2) | -| [Circular Buffer](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/circular-buffer/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1475) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1469) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.meta/template.j2) | -| [Clock](https://github.com/exercism/python/blob/main/exercises/practice/clock/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/clock/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/clock/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L394) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L389) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/clock/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/clock/.meta/template.j2) | -| [Collatz Conjecture](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/collatz-conjecture/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L593) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L592) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.approaches/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.meta/template.j2) | -| [Complex Numbers](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/complex-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L799) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L792) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.meta/template.j2) | -| [Connect](https://github.com/exercism/python/blob/main/exercises/practice/connect/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/connect/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/connect/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L960) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L959) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/connect/.meta/template.j2) | -| [Crypto Square](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/crypto-square/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1263) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1262) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.meta/template.j2) | -| [Darts](https://github.com/exercism/python/blob/main/exercises/practice/darts/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/darts/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/darts/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2199) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2198) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/darts/.docs/hints.md) | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/darts/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/darts/.meta/template.j2) | -| [Diamond](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/diamond/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1696) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1695) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.meta/template.j2) | -| [Difference Of Squares](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/difference-of-squares/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L601) | NONE | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.meta/template.j2) | -| [Dnd Character](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dnd-character/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2078) | NONE | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/dnd-character/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.meta/template.j2) | -| [Dominoes](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dominoes/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1767) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1755) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.meta/template.j2) | -| [Dot Dsl](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/dot-dsl/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1434) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1427) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.docs/instructions.append.md) | | -| [Etl](https://github.com/exercism/python/blob/main/exercises/practice/etl/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/etl/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/etl/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1118) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1117) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/etl/.meta/template.j2) | -| [Flatten Array](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/flatten-array/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1126) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1125) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.meta/template.j2) | -| [Food Chain](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/food-chain/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1737) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1731) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.meta/template.j2) | -| [Forth](https://github.com/exercism/python/blob/main/exercises/practice/forth/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/forth/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/forth/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1571) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1570) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/forth/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/forth/.meta/template.j2) | -| [Gigasecond](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/gigasecond/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L450) | NONE | [✔](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/hints.md) | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/gigasecond/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.meta/template.j2) | -| [Go Counting](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/go-counting/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L868) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L867) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.meta/template.j2) | -| [Grade School](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grade-school/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L364) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L363) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/grade-school/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.meta/template.j2) | -| [Grains](https://github.com/exercism/python/blob/main/exercises/practice/grains/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grains/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grains/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L678) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L677) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grains/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/grains/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grains/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grains/.meta/template.j2) | -| [Grep](https://github.com/exercism/python/blob/main/exercises/practice/grep/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grep/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/grep/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1536) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1528) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grep/.meta/template.j2) | -| [Hamming](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hamming/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L259) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L254) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/hamming/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.meta/template.j2) | -| [Hangman](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/hangman/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2221) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2220) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.docs/instructions.append.md) | | -| [High Scores](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/high-scores/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L226) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L225) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/high-scores/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.meta/template.j2) | -| [House](https://github.com/exercism/python/blob/main/exercises/practice/house/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/house/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/house/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1279) | NONE | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/house/.meta/template.j2) | -| [Isbn Verifier](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isbn-verifier/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L609) | NONE | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/isbn-verifier/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.meta/template.j2) | -| [Isogram](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/isogram/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L273) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L272) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/isogram/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.meta/template.j2) | -| [Isogram](https://github.com/exercism/python/blob/main/exercises/practice/killer-sudoku-helper/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/killer-sudoku-helper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/killer-sudodu-helper/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1385) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1384) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/killer-sudoku-helper/.meta/template.j2) | -| [Kindergarten Garden](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/kindergarten-garden/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L350) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L344) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/kindergarten-garden/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.meta/template.j2) | -| [Knapsack](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/knapsack/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1453) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1452) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.meta/template.j2) | -| [Largest Series Product](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/largest-series-product/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L945) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L939) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.meta/template.j2) | -| [Leap](https://github.com/exercism/python/blob/main/exercises/practice/leap/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/leap/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/leap/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2103) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2102) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/leap/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/leap/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/leap/.meta/template.j2) | -| [Ledger](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ledger/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1590) | | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.meta/template.j2) | -| [Linked List](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/linked-list/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1379) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1371) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.meta/template.j2) | -| [List Ops](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/list-ops/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1294) | NONE | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.meta/template.j2) | -| [Luhn](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/luhn/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L372) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L371) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/luhn/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.meta/template.j2) | -| [Markdown](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/markdown/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1418) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1417) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/markdown/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.meta/template.j2) | -| [Matching Brackets](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matching-brackets/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L728) | NONE | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matching-brackets/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.meta/template.j2) | -| [Matrix](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/matrix/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matrix/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.meta/template.j2) | -| [Meetup](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/meetup/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L818) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L812) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.meta/template.j2) | -| [Minesweeper](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/minesweeper/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L981) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L980) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.meta/template.j2) | -| [Nth Prime](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/nth-prime/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1814) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.meta/template.j2) | -| [Ocr Numbers](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/ocr-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L997) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.meta/template.j2) | -| [Paasio](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/paasio/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1917) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1906) | | | | | | -| [Palindrome Products](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/palindrome-products/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L626) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L625) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/palindrome-products/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.meta/template.j2) | -| [Pangram](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pangram/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L463) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L462) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/pangram/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.meta/template.j2) | -| [Pascals Triangle](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pascals-triangle/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1300) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1299) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/hints.md) | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.meta/template.j2) | -| [Perfect Numbers](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/perfect-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L527) | NONE | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/perfect-numbers/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.meta/template.j2) | -| [Phone Number](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/phone-number/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L547) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L542) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/phone-numbers/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.meta/template.j2) | -| [Pig Latin](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pig-latin/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1832) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1831) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.meta/template.j2) | -| [Poker](https://github.com/exercism/python/blob/main/exercises/practice/poker/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/poker/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/poker/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1016) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1015) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/poker/.meta/template.j2) | -| [Pov](https://github.com/exercism/python/blob/main/exercises/practice/pov/.docs/instructions.md) | 9 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pov/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pov/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1677) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1676) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pov/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pov/.meta/template.j2) | -| [Prime Factors](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/prime-factors/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L686) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L685) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.meta/template.j2) | -| [Protein Translation](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/protein-translation/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L496) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L495) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.meta/template.j2) | -| [Proverb](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/proverb/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#LC661) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L661) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.meta/template.j2) | -| [Pythagorean Triplet](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/pythagorean-triplet/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L745) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L744) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.meta/template.j2) | -| [Queen Attack](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/queen-attack/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1302) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.meta/template.j2) | -| [Rail Fence Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rail-fence-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1849) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1848) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.meta/template.j2) | -| [Raindrops](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/raindrops/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L210) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L209) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/raindrops/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.meta/template.j2) | -| [Rational Numbers](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rational-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2240) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2239) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.meta/template.j2) | -| [React](https://github.com/exercism/python/blob/main/exercises/practice/react/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/react/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/react/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L890) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L884) | | | | | | -| [Rectangles](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rectangles/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1032) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1031) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.meta/template.j2) | -| [Resistor Color Trio](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color-trio/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#LC631) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L631) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.meta/template.j2) | -| [Resistor Color Duo](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color-duo/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2119) | NONE | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.meta/template.j2) | -| [Resistor Color](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/resistor-color/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2111) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2110) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.meta/template.j2) | -| [Rest Api](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.docs/instructions.md) | 8 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rest-api/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1795) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1787) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.meta/template.j2) | -| [Reverse String](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/reverse-string/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2133) | NONE | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/reverse-string/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.meta/template.j2) | -| [Rna Transcription](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rna-transcription/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2149) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2148) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/rna-transcription/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/template.j2) | -| [Robot Name](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-name/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L479) | NONE | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/robot-name/) | | | -| [Robot Simulator](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/robot-simulator/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1324) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1315) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.meta/template.j2) | -| [Roman Numerals](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/roman-numerals/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1340) | NONE | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.meta/template.j2) | -| [Rotational Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/rotational-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1209) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1208) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.meta/template.j2) | -| [Run Length Encoding](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/run-length-encoding/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1493) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1492) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.meta/template.j2) | -| [Saddle Points](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/saddle-points/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L649) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L643) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/saddle-points/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.meta/template.j2) | -| [Satellite](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/satellite/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1640) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1634) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.meta/template.j2) | -| [Say](https://github.com/exercism/python/blob/main/exercises/practice/say/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/say/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/say/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1052) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1051) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/say/.meta/template.j2) | -| [Scale Generator](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scale-generator/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1084) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1083) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.meta/template.j2) | -| [Scrabble Score](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/scrabble-score/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L316) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L315) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/scrabble-score/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.meta/template.j2) | -| [Secret Handshake](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/secret-handshake/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L924) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L923) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.meta/template.j2) | -| [Series](https://github.com/exercism/python/blob/main/exercises/practice/series/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/series/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/series/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L562) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L561) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/series/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/series/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/series/.meta/template.j2) | -| [Sgf Parsing](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sgf-parsing/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2261) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2255) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.meta/template.j2) | -| [Sieve](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sieve/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L836) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L835) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.meta/template.j2) | -| [Simple Cipher](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L761) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.meta/template.j2) | -| [Simple Linked List](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/simple-linked-list/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1357) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1356) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/hints.md) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/instructions.append.md) | | -| [Space Age](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/space-age/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2164) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2163) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/space-age/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.meta/template.j2) | -| [Spiral Matrix](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/spiral-matrix/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.meta/template.j2) | -| [Spiral Matrix](https://github.com/exercism/python/blob/main/exercises/practice/square-root/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/square-root/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/square-root/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L609) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L608) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/square-root/.meta/template.j2) | -| [Sublist](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sublist/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1141) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1140) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.meta/template.j2) | -| [Sum Of Multiples](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/sum-of-multiples/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L778) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L777) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/sum-of-multiples/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.meta/template.j2) | -| [Tournament](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tournament/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L421) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L409) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.meta/template.j2) | -| [Transpose](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/transpose/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1511) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1510) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.meta/template.j2) | -| [Tree Building](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/tree-building/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L852) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L851) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.docs/instructions.append.md) | | -| [Triangle](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/triangle/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L664) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L663) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/triangle/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.meta/template.j2) | -| [Twelve Days](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/twelve-days/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L281) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L280) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/hints.md) | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/twelve-days/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.meta/template.j2) | -| [Two Bucket](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-bucket/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1244) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1243) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.meta/template.j2) | -| [Two Fer](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/two-fer/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L202) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L201) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/two-fer/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.meta/template.j2) | -| [Variable Length Quantity](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/variable-length-quantity/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1226) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1225) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.meta/template.j2) | -| [Word Count](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-count/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L302) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L296) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.docs/hints.md) | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/word-count/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.meta/template.j2) | -| [Word Search](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/word-search/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1616) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1606) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.meta/template.j2) | -| [Wordy](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/wordy/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1069) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1068) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.approaches/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.meta/template.j2) | -| [Yacht](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/yacht/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2180) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2179) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.meta/template.j2) | -| [Zebra Puzzle](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zebra-puzzle/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1866) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1865) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.meta/template.j2) | -| [Zipper](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.meta/example.py)┋[most⭐](https://exercism.io/tracks/python/exercises/zipper/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1659) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1658) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.meta/template.j2) | +| [Bowling](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/bowling/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1553) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1552) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/bowling/.meta/template.j2) | +| [Change](https://github.com/exercism/python/blob/main/exercises/practice/change/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/change/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/change/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1412) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1411) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/change/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/change/.meta/template.j2) | +| [Circular Buffer](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/circular-buffer/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1475) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1469) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/circular-buffer/.meta/template.j2) | +| [Clock](https://github.com/exercism/python/blob/main/exercises/practice/clock/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/clock/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/clock/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L394) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L389) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/clock/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/clock/.meta/template.j2) | +| [Collatz Conjecture](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/collatz-conjecture/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L593) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L592) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.approaches/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/collatz-conjecture/.meta/template.j2) | +| [Complex Numbers](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/complex-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L799) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L792) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/complex-numbers/.meta/template.j2) | +| [Connect](https://github.com/exercism/python/blob/main/exercises/practice/connect/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/connect/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/connect/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L960) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L959) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/connect/.meta/template.j2) | +| [Crypto Square](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/crypto-square/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1263) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1262) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/crypto-square/.meta/template.j2) | +| [Darts](https://github.com/exercism/python/blob/main/exercises/practice/darts/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/darts/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/darts/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2199) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2198) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/darts/.docs/hints.md) | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/darts/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/darts/.meta/template.j2) | +| [Diamond](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/diamond/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1696) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1695) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/diamond/.meta/template.j2) | +| [Difference Of Squares](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/difference-of-squares/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L601) | NONE | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/difference-of-squares/.meta/template.j2) | +| [Dnd Character](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/dnd-character/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2078) | NONE | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/dnd-character/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/dnd-character/.meta/template.j2) | +| [Dominoes](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/dominoes/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1767) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1755) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/dominoes/.meta/template.j2) | +| [Dot Dsl](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/dot-dsl/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1434) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1427) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/dot-dsl/.docs/instructions.append.md) | | +| [Etl](https://github.com/exercism/python/blob/main/exercises/practice/etl/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/etl/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/etl/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1118) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1117) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/etl/.meta/template.j2) | +| [Flatten Array](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/flatten-array/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1126) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1125) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/flatten-array/.meta/template.j2) | +| [Food Chain](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/food-chain/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1737) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1731) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/food-chain/.meta/template.j2) | +| [Forth](https://github.com/exercism/python/blob/main/exercises/practice/forth/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/forth/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/forth/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1571) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1570) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/forth/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/forth/.meta/template.j2) | +| [Gigasecond](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/gigasecond/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L450) | NONE | [✔](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/hints.md) | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/gigasecond/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/gigasecond/.meta/template.j2) | +| [Go Counting](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/go-counting/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L868) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L867) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/go-counting/.meta/template.j2) | +| [Grade School](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/grade-school/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L364) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L363) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/grade-school/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grade-school/.meta/template.j2) | +| [Grains](https://github.com/exercism/python/blob/main/exercises/practice/grains/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grains/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/grains/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L678) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L677) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grains/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/grains/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grains/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grains/.meta/template.j2) | +| [Grep](https://github.com/exercism/python/blob/main/exercises/practice/grep/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/grep/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/grep/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1536) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1528) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/grep/.meta/template.j2) | +| [Hamming](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/hamming/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L259) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L254) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/hamming/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hamming/.meta/template.j2) | +| [Hangman](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/hangman/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2221) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2220) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/hangman/.docs/instructions.append.md) | | +| [High Scores](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/high-scores/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L226) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L225) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/high-scores/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/high-scores/.meta/template.j2) | +| [House](https://github.com/exercism/python/blob/main/exercises/practice/house/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/house/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/house/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1279) | NONE | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/house/.meta/template.j2) | +| [Isbn Verifier](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/isbn-verifier/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L609) | NONE | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/isbn-verifier/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/isbn-verifier/.meta/template.j2) | +| [Isogram](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/isogram/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L273) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L272) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/isogram/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/isogram/.meta/template.j2) | +| [Isogram](https://github.com/exercism/python/blob/main/exercises/practice/killer-sudoku-helper/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/killer-sudoku-helper/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/killer-sudodu-helper/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1385) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1384) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/killer-sudoku-helper/.meta/template.j2) | +| [Kindergarten Garden](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/kindergarten-garden/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L350) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L344) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/kindergarten-garden/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/kindergarten-garden/.meta/template.j2) | +| [Knapsack](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/knapsack/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1453) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1452) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/knapsack/.meta/template.j2) | +| [Largest Series Product](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/largest-series-product/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L945) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L939) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/largest-series-product/.meta/template.j2) | +| [Leap](https://github.com/exercism/python/blob/main/exercises/practice/leap/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/leap/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/leap/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2103) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2102) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/leap/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/leap/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/leap/.meta/template.j2) | +| [Ledger](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/ledger/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1590) | | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/ledger/.meta/template.j2) | +| [Linked List](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/linked-list/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1379) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1371) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/linked-list/.meta/template.j2) | +| [List Ops](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/list-ops/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1294) | NONE | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/list-ops/.meta/template.j2) | +| [Luhn](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/luhn/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L372) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L371) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/luhn/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/luhn/.meta/template.j2) | +| [Markdown](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/markdown/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1418) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1417) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/markdown/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/markdown/.meta/template.j2) | +| [Matching Brackets](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/matching-brackets/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L728) | NONE | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matching-brackets/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.meta/template.j2) | +| [Matrix](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/matrix/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/matrix/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/matrix/.meta/template.j2) | +| [Meetup](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/meetup/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L818) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L812) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/meetup/.meta/template.j2) | +| [Minesweeper](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/minesweeper/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L981) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L980) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/minesweeper/.meta/template.j2) | +| [Nth Prime](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/nth-prime/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1814) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/nth-prime/.meta/template.j2) | +| [Ocr Numbers](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/ocr-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L997) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/ocr-numbers/.meta/template.j2) | +| [Paasio](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/paasio/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/paasio/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1917) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1906) | | | | | | +| [Palindrome Products](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/palindrome-products/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L626) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L625) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/palindrome-products/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/palindrome-products/.meta/template.j2) | +| [Pangram](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/pangram/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L463) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L462) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/pangram/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pangram/.meta/template.j2) | +| [Pascals Triangle](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/pascals-triangle/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1300) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L1299) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/hints.md) | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pascals-triangle/.meta/template.j2) | +| [Perfect Numbers](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/perfect-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L527) | NONE | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/perfect-numbers/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/perfect-numbers/.meta/template.j2) | +| [Phone Number](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/phone-number/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L547) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L542) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/phone-numbers/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/phone-number/.meta/template.j2) | +| [Pig Latin](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/pig-latin/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1832) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1831) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pig-latin/.meta/template.j2) | +| [Poker](https://github.com/exercism/python/blob/main/exercises/practice/poker/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/poker/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/poker/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1016) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1015) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/poker/.meta/template.j2) | +| [Pov](https://github.com/exercism/python/blob/main/exercises/practice/pov/.docs/instructions.md) | 9 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pov/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/pov/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1677) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1676) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pov/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pov/.meta/template.j2) | +| [Prime Factors](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/prime-factors/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L686) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L685) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/prime-factors/.meta/template.j2) | +| [Protein Translation](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/protein-translation/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L496) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L495) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/protein-translation/.meta/template.j2) | +| [Proverb](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/proverb/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#LC661) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L661) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/proverb/.meta/template.j2) | +| [Pythagorean Triplet](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/pythagorean-triplet/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L745) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L744) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/pythagorean-triplet/.meta/template.j2) | +| [Queen Attack](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/queen-attack/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1302) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/queen-attack/.meta/template.j2) | +| [Rail Fence Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/rail-fence-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1849) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1848) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rail-fence-cipher/.meta/template.j2) | +| [Raindrops](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/raindrops/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L210) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L209) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/raindrops/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.meta/template.j2) | +| [Rational Numbers](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/rational-numbers/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2240) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2239) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rational-numbers/.meta/template.j2) | +| [React](https://github.com/exercism/python/blob/main/exercises/practice/react/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/react/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/react/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L890) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L884) | | | | | | +| [Rectangles](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/rectangles/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1032) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1031) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rectangles/.meta/template.j2) | +| [Resistor Color Trio](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/resistor-color-trio/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#LC631) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L631) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-trio/.meta/template.j2) | +| [Resistor Color Duo](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/resistor-color-duo/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2119) | NONE | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color-duo/.meta/template.j2) | +| [Resistor Color](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/resistor-color/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2111) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2110) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/resistor-color/.meta/template.j2) | +| [Rest Api](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.docs/instructions.md) | 8 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/rest-api/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1795) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1787) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rest-api/.meta/template.j2) | +| [Reverse String](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/reverse-string/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2133) | NONE | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/reverse-string/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/reverse-string/.meta/template.j2) | +| [Rna Transcription](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/rna-transcription/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2149) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2148) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/rna-transcription/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/template.j2) | +| [Robot Name](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/robot-name/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L479) | NONE | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/robot-name/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/robot-name/) | | | +| [Robot Simulator](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/robot-simulator/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1324) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1315) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/robot-simulator/.meta/template.j2) | +| [Roman Numerals](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/roman-numerals/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1340) | NONE | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/roman-numerals/.meta/template.j2) | +| [Rotational Cipher](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/rotational-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1209) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1208) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/rotational-cipher/.meta/template.j2) | +| [Run Length Encoding](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/run-length-encoding/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1493) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1492) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/run-length-encoding/.meta/template.j2) | +| [Saddle Points](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/saddle-points/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L649) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L643) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/saddle-points/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/saddle-points/.meta/template.j2) | +| [Satellite](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/satellite/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1640) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1634) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/satellite/.meta/template.j2) | +| [Say](https://github.com/exercism/python/blob/main/exercises/practice/say/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/say/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/say/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1052) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1051) | | ✔ | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/say/.meta/template.j2) | +| [Scale Generator](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/scale-generator/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1084) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1083) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/scale-generator/.meta/template.j2) | +| [Scrabble Score](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/scrabble-score/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L316) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L315) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.approaches/) | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/scrabble-score/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/scrabble-score/.meta/template.j2) | +| [Secret Handshake](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/secret-handshake/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L924) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L923) | | ✔ | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/secret-handshake/.meta/template.j2) | +| [Series](https://github.com/exercism/python/blob/main/exercises/practice/series/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/series/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/series/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L562) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L561) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/series/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/series/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/series/.meta/template.j2) | +| [Sgf Parsing](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.docs/instructions.md) | 7 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/sgf-parsing/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2261) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2255) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sgf-parsing/.meta/template.j2) | +| [Sieve](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/sieve/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L836) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L835) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sieve/.meta/template.j2) | +| [Simple Cipher](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/simple-cipher/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L761) | NONE | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-cipher/.meta/template.j2) | +| [Simple Linked List](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/simple-linked-list/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1357) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1356) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/hints.md) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/simple-linked-list/.docs/instructions.append.md) | | +| [Space Age](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/space-age/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2164) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2163) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/space-age/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/space-age/.meta/template.j2) | +| [Spiral Matrix](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/spiral-matrix/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1714) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1713) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/spiral-matrix/.meta/template.j2) | +| [Spiral Matrix](https://github.com/exercism/python/blob/main/exercises/practice/square-root/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/square-root/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/square-root/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L609) | [⚙⚙](https://github.com/exercism/python/blob/main/config.json#L608) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/square-root/.meta/template.j2) | +| [Sublist](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/sublist/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1141) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1140) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sublist/.meta/template.j2) | +| [Sum Of Multiples](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/sum-of-multiples/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L778) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L777) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/sum-of-multiples/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/sum-of-multiples/.meta/template.j2) | +| [Tournament](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/tournament/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L421) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L409) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/tournament/.meta/template.j2) | +| [Transpose](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/transpose/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1511) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1510) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/transpose/.meta/template.j2) | +| [Tree Building](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.docs/instructions.md) | 3 | [example](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/tree-building/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L852) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L851) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/tree-building/.docs/instructions.append.md) | | +| [Triangle](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/triangle/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L664) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L663) | | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/triangle/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/triangle/.meta/template.j2) | +| [Twelve Days](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/twelve-days/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L281) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L280) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/hints.md) | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/twelve-days/) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/twelve-days/.meta/template.j2) | +| [Two Bucket](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/two-bucket/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1244) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1243) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/two-bucket/.meta/template.j2) | +| [Two Fer](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/two-fer/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L202) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L201) | | | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/two-fer/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/two-fer/.meta/template.j2) | +| [Variable Length Quantity](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.docs/instructions.md) | 4 | [example](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/variable-length-quantity/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1226) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1225) | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/variable-length-quantity/.meta/template.j2) | +| [Word Count](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/word-count/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L302) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L296) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.docs/hints.md) | ✔ | [✔](https://github.com/exercism/website-copy/tree/main/tracks/python/exercises/word-count/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/word-count/.meta/template.j2) | +| [Word Search](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/word-search/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1616) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1606) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/word-search/.meta/template.j2) | +| [Wordy](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.docs/instructions.md) | 1 | [example](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/wordy/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1069) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1068) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.approaches/) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.docs/instructions.append.md) | [✔](https://github.com/exercism/python/blob/main/exercises/practice/wordy/.meta/template.j2) | +| [Yacht](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.docs/instructions.md) | 2 | [example](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/yacht/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2180) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L2179) | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.approaches/) | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/yacht/.meta/template.j2) | +| [Zebra Puzzle](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.docs/instructions.md) | 5 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/zebra-puzzle/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1866) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1865) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/zebra-puzzle/.meta/template.j2) | +| [Zipper](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.docs/instructions.md) | 6 | [example](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.meta/example.py)┋[most⭐](https://exercism.org/tracks/python/exercises/zipper/solutions?passed_head_tests=true) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1659) | [⚙⚙](https://github.com/exercism/python/blob/64396fd483c6c6770c1313b71cb4d972e5ab9819/config.json#L1658) | | | | | [✔](https://github.com/exercism/python/blob/main/exercises/practice/zipper/.meta/template.j2) |
@@ -217,8 +217,8 @@ | | [complex-numbers](https://github.com/exercism/python/blob/main/concepts/complex-numbers) | | ~ | [#2208](https://github.com/exercism/v3/issues/2208) | TBD | | | [conditionals](https://github.com/exercism/python/blob/main/concepts/conditionals) | | [Meltdown Mitigation ](https://github.com/exercism/python/tree/main/exercises/concept/meltdown-mitigation) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/meltdown-mitigation/.meta) | Full | | | [comparisons](https://github.com/exercism/python/blob/main/concepts/comparisons) | | [Black Jack](https://github.com/exercism/python/tree/main/exercises/concept/black-jack) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/black-jack/.meta) | Full | -| | [strings](https://github.com/exercism/python/blob/main/concepts/strings) | | [Litte Sister's Vocab](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab/.meta) | Full | -| | [string-methods](https://github.com/exercism/python/blob/main/concepts/string-methods) | | [Litte Sister's Essay](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay/.meta) | Full | +| | [strings](https://github.com/exercism/python/blob/main/concepts/strings) | | [Little Sister's Vocab](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-vocab/.meta) | Full | +| | [string-methods](https://github.com/exercism/python/blob/main/concepts/string-methods) | | [Little Sister's Essay](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/little-sisters-essay/.meta) | Full | | | [string-formatting](https://github.com/exercism/python/blob/main/concepts/string-formatting) | | TBD (_rewrite_) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/pretty-leaflet/.meta) | Full | | | [lists](https://github.com/exercism/python/blob/main/concepts/lists) | | [Card Games](https://github.com/exercism/python/tree/main/exercises/concept/card-games) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/card-games/.meta) | Full | | | [list-methods](https://github.com/exercism/python/blob/main/concepts/list-methods) | | [Chaitanas Colossal Coaster](https://github.com/exercism/python/tree/main/exercises/concept/chaitanas-colossal-coaster) | [`.meta`folder](https://github.com/exercism/python/tree/main/exercises/concept/chaitanas-colossal-coaster/.meta) | Full | @@ -359,7 +359,7 @@ type-annotation(("TBD-F
Type Annotation
(type hints)
")):::TB type-aliases((TBD-F
Type Aliases)):::TBD-F unicode-data((TBD-F
unicode data)):::TBD-F unpacking-and-multiple-assignment((Locomotive Engineer
Unpacking &
Multi-assignment
)):::Beta -user-defined-errors((TBD
User Definied Errors)):::TBD +user-defined-errors((TBD
User Defined Errors)):::TBD walrus-operator((TBD-F
Walrus Operator)):::TBD-F with(("TBD
with
(Context Managers)
")):::TBD unittest(("TBD-F
unittest
(testing)
")):::TBD-F From 7e5a83d333424c615b9a4886fc11ce23d4d1c1e0 Mon Sep 17 00:00:00 2001 From: colinleach Date: Wed, 27 Dec 2023 16:02:33 -0700 Subject: [PATCH 024/277] The `bitwise-operators` concept (#3564) * bitwise-operators concept * small fizes to about.md * Proposed Edits and Links First draft of edits and links for Bitwise operations. * Typos and Review Changes --------- Co-authored-by: BethanyG --- concepts/bitwise-operators/.meta/config.json | 4 +- concepts/bitwise-operators/about.md | 197 ++++++++++++++++++- concepts/bitwise-operators/introduction.md | 21 +- concepts/bitwise-operators/links.json | 20 +- 4 files changed, 230 insertions(+), 12 deletions(-) diff --git a/concepts/bitwise-operators/.meta/config.json b/concepts/bitwise-operators/.meta/config.json index 9b9e8da5a9b..7767ff5d740 100644 --- a/concepts/bitwise-operators/.meta/config.json +++ b/concepts/bitwise-operators/.meta/config.json @@ -1,5 +1,5 @@ { - "blurb": "TODO: add blurb for this concept", - "authors": ["bethanyg", "cmccandless"], + "blurb": "Python supports bitwise operations such as left/right shift, and, or, xor, and not.", + "authors": ["BethanyG", "colinleach"], "contributors": [] } diff --git a/concepts/bitwise-operators/about.md b/concepts/bitwise-operators/about.md index c628150d565..a4ddb509c11 100644 --- a/concepts/bitwise-operators/about.md +++ b/concepts/bitwise-operators/about.md @@ -1,2 +1,197 @@ -#TODO: Add about for this concept. +# About +Down at the hardware level, transistors can only be on or off: two states that we traditionally represent with `1` and `0`. +These are the [`binary digits`][binary-digits], abbreviated as [`bits`][bits]. +Awareness of `bits` and `binary` is particularly important for systems programmers working in low-level languages. + +However, for most of the history of computing the programming priority has been to find increasingly sophisticated ways to _abstract away_ this binary reality. +In Python (and many other [high-level programming languages][high-level-language]), we work with `int`, `float`, `string` and other defined _types_, up to and including audio and video formats. +We let the Python internals take care of (eventually) translating everything to bits. + +Nevertheless, using [bitwise-operators][python-bitwise-operators] and [bitwise operations][python-bitwise-operations] can sometimes have significant advantages in speed and memory efficiency, even in a high-level language like Python. + + +## Entering and Displaying Binary Numbers + +Unsurprisingly, Python interacts with the user using decimal numbers, but a programmer can override this default. +In fact, Python will readily accept an `int` in `binary`, `hexadecimal`, or `octal` format, and will happily perform mathematical operations between them. +For more details, you can review the [concept:python/binary-octal-hexadecimal]() concept. + +Binary numbers are entered with a `0b` prefix, just as `0x` can be used for hexadecimal (_hex numbers are a concise way to represent groups of 4 bits_), and `oct` can be used for octal numbers. + +There are multiple ways to convert integers to binary strings, varying in whether they include the `0b` prefix and whether they support left-padding with zeros: + + +```python +# Binary entry. +>>> 0b10111 +23 + +# Converting an int display to binary string, with prefix. +>>> bin(23) +'0b10111' + +>>> number = 23 + +# Binary without prefix, padded to 8 digits. +>>> format(number, '08b') +'00010111' + +# Same format, but using an f-string. +>>> f"{number} in decimal is {number:08b} in binary and {number:x} in hex" +'23 in decimal is 00010111 in binary and 17 in hex' +``` + + +## [`Bitwise Logic`][python-bitwise-operations] + +In the [concept:python/bools]() concept, we discussed the _logical operators_ `and`, `or` and `not` used with Boolean (_`True` and `False`_) values. +The same logic rules apply when working with bits. + +However, the bitwise equivalents of the logical operators `&` (_and_), `|` (_or_), `~` (_not_), and `^` (_[XOR][xor]_), are applied to each _bit_ in a binary representation, treating `1` as `True` ("on") and `0` as `False` ("off"). +An example with the bitwise `&` might make this clearer: + + +```python +>>> x = 0b01100110 +>>> y = 0b00101010 + +>>> format(x & y, '08b') +'00100010' +``` + +Only positions with a `1` in _**both**_ the input numbers are set to `1` in the output. + +Bitwise `&` is commonly used as a way to isolate single bits in a compacted set of `True`/`False` values, such as user-configurable settings in an app. +This enables the value of individual bits to control program logic: + + +```python +>>> number = 0b0110 +>>> number & 0b0001 > 0 +False + +>>> number & 0b0010 > 0 +True +``` + + +For a bitwise `|` (or), a `1` is set in the output if there is a `1` in _**either**_ of the inputs: + + +```python +>>> x = 0b01100110 +>>> y = 0b00101010 + +>>> format(x | y, '08b') +'01101110' +``` + + +With the `^` operator for bitwise e**x**clusive **or** (xor), a `1` is set if it appears in _**either**_ of the inputs _**but not both**_ inputs. +This symbol might seem familiar from the [concept:python/sets]() concept, where it is used for `set` _symmetric difference_, which is the same as [xor applied to sets][symmetric-difference]. +If xor `^` seems strange, be aware that this is by far the [most common operation in cryptography][xor-cipher]. + + +```python +>>> x = 0b01100110 +>>> y = 0b00101010 + +>>> format(x ^ y, '08b') +'01001100' +``` + + +Finally, there is the `~` operator (_the [tilde][tilde] character_), which is a bitwise `not` that takes a single input and _**inverts all the bits**_, which might not be the result you were expecting! +Each `1` in the representation changes to `0`, and vice versa. +See the section below for details. + + +## Negative Numbers and Binary Representation + +In decimal representation, we distinguish positive and negative numbers by using a `+` or `-` sign to the left of the digits. +Using these symbols at a binary level proved inefficient for digital computing and raised the problem that `+0` is not the same as `-0`. + +Rather than using `-` and `+`, all modern computers use a [`twos-complement`][twos-complement] representation for negative numbers, right down to the silicon chip level. +This means that all bits are inverted and a number is _**interpreted as negative**_ if the left-most bit (also termed the "most significant bit", or MSB) is a `1`. +Positive numbers have an MSB of `0`. +This representation has the advantage of only having one version of zero, so that the programmer doesn't have to manage `-0` and `+0`. + +This way of representing negative and positive numbers adds a complication for Python: there are no finite-integer concepts like `int32` or `int64` internally in the core langauge. +In 'modern' Python, `int`s are of unlimited size (_limited only by hardware capacity_), and a negative or bit-inverted number has a (_theoretically_) infinite number of `1`'s to the left, just as a positive number has unlimited `0`'s. + +This makes it difficult to give a useful example of `bitwise not`: + +```python +>>> x = 0b01100110 +>>> format(x, '08b') +'01100110' + +# This is a negative binary (not twos-complement display). +>>> format(~x, '08b') +'-1100111' + + # Decimal representation. +>>> x +102 + +# Using the Bitwise not, with an unintuitive result. +>>> ~x +-103 +``` + +This is **not** the `0b10011001` we would see in languages with fixed-size integers. + +The `~` operator only works as expected with _**unsigned**_ byte or integer types, or with fixed-sized integer types. +These numeric types are supported in third-party packages such as [`NumPy`][numpy], [`pandas`][pandas], and [`sympy`][sympy] but not in core Python. + +In practice, Python programmers quite often use the shift operators described below and `& | ^` with positive numbers only. +Bitwise operations with negative numbers are much less common. +One technique is to add [`2**32 (or 1 << 32)`][unsigned-int-python] to a negative value to make an `int` unsigned, but this gets difficult to manage. +Another strategy is to work with the [`ctypes`][ctypes-module] module, and use c-style integer types, but this is equally unwieldy. + + +## [`Shift operators`][bitwise-shift-operators] + +The left-shift operator `x << y` simply moves all the bits in `x` by `y` places to the left, filling the new gaps with zeros. +Note that this is arithmetically identical to multiplying a number by `2**y`. + +The right-shift operator `x >> y` does the opposite. +This is arithmetically identical to integer division `x // 2**y`. + +Keep in mind the previous section on negative numbers and their pitfalls when shifting. + + +```python +>>> x = 8 +>>> format(x, '08b') +'00001000' + +# A left bit shift. +>>> x << 2 +32 + +>>> format(x << 2, '08b') +'00100000' + +# A right bit shift. +>>> format(x >> 2, '08b') +'00000010' +``` + +[binary-digits]: https://www.khanacademy.org/computing/computers-and-internet/xcae6f4a7ff015e7d:digital-information/xcae6f4a7ff015e7d:binary-numbers/v/the-binary-number-system +[bits]: https://en.wikipedia.org/wiki/Bit +[bitwise-shift-operators]: https://docs.python.org/3/reference/expressions.html#shifting-operations +[ctypes-module]: https://docs.python.org/3/library/ctypes.html#module-ctypes +[high-level-language]: https://en.wikipedia.org/wiki/High-level_programming_language +[numpy]: https://numpy.org/doc/stable/user/basics.types.html +[pandas]: https://pandas.pydata.org/docs/reference/arrays.html#nullable-integer +[python-bitwise-operations]: https://docs.python.org/3/reference/expressions.html#binary-bitwise-operations +[python-bitwise-operators]: https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations +[symmetric-difference]: https://math.stackexchange.com/questions/84184/relation-between-xor-and-symmetric-difference#:~:text=It%20is%20the%20same%20thing,they%20are%20indeed%20the%20same. +[sympy]: https://docs.sympy.org/latest/modules/codegen.html#predefined-types +[tilde]: https://en.wikipedia.org/wiki/Tilde +[twos-complement]: https://en.wikipedia.org/wiki/Two%27s_complement#:~:text=Two's%20complement%20is%20the%20most,number%20is%20positive%20or%20negative. +[unsigned-int-python]: https://stackoverflow.com/a/20768199 +[xor-cipher]: https://en.wikipedia.org/wiki/XOR_cipher +[xor]: https://stackoverflow.com/a/2451393 diff --git a/concepts/bitwise-operators/introduction.md b/concepts/bitwise-operators/introduction.md index bbe12ffd5e9..88aba3a6a7b 100644 --- a/concepts/bitwise-operators/introduction.md +++ b/concepts/bitwise-operators/introduction.md @@ -1 +1,20 @@ -#TODO: Add introduction for this concept. +# Introduction + +Down at the hardware level, transistors can only be on or off: two states that we traditionally represent with `1` and `0`. +These are the [`binary digits`][binary-digits], abbreviated as [`bits`][bits]. +Awareness of `bits` and `binary` is particularly important for systems programmers working in low-level languages. + +However, for most of the history of computing the programming priority has been to find increasingly sophisticated ways to _abstract away_ this binary reality. + + +In Python (and many other [high-level programming languages][high-level-language]), we work with `int`, `float`, `string` and other defined _types_, up to and including audio and video formats. +We let the Python internals take care of (eventually) translating everything to bits. + + +Nevertheless, using [bitwise-operators][python-bitwise-operators] and [bitwise operations][python-bitwise-operations] can sometimes have significant advantages in speed and memory efficiency, even in a high-level language like Python. + +[high-level-language]: https://en.wikipedia.org/wiki/High-level_programming_language +[binary-digits]: https://www.khanacademy.org/computing/computers-and-internet/xcae6f4a7ff015e7d:digital-information/xcae6f4a7ff015e7d:binary-numbers/v/the-binary-number-system +[bits]: https://en.wikipedia.org/wiki/Bit +[python-bitwise-operations]: https://docs.python.org/3/reference/expressions.html#binary-bitwise-operations +[python-bitwise-operators]: https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations diff --git a/concepts/bitwise-operators/links.json b/concepts/bitwise-operators/links.json index eb5fb7c38a5..7c103c84630 100644 --- a/concepts/bitwise-operators/links.json +++ b/concepts/bitwise-operators/links.json @@ -1,18 +1,22 @@ [ { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://wiki.python.org/moin/BitwiseOperators/", + "description": "BitwiseOperators on the Python wiki." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://realpython.com/python-bitwise-operators", + "description": "Real Python: Bitwise Operators in Python." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://stackoverflow.com/a/20768199", + "description": "Stack Overflow: Convert a Python int to an unsigned int." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://www.khanacademy.org/computing/computer-science/cryptography/ciphers/a/xor-bitwise-operation", + "description": "Khan Academy: The Ultimate Shift Cipher." + }, + { + "url": "https://en.wikipedia.org/wiki/XOR_cipher", + "description": "The XOR Cipher" } ] From 4dd9e792e6ca3afc5d8308b67600a7bcbb8c7f82 Mon Sep 17 00:00:00 2001 From: colinleach Date: Fri, 12 Jan 2024 20:03:40 -0700 Subject: [PATCH 025/277] [Leap]: Add `calendar-isleap` approach and improved benchmarks (#3583) * [Leap]: Add `calendar-isleap` approach and improved benchmarks * added comments on `isleap()` implementation and operator precedence * Approach and Chart Edits Various language edits and touchups. Added chart reference and alt-textm as well as link to long description. Probably unnecessary for this particular article but wanted to establish an example of what to do when alt-texting a chart. See https://www.w3.org/WAI/tutorials/images/complex/ for more information and examples on how to alt-text complex images. * Fixed Awkward Chart Intro * Another try on alt-text * Trying Yet Again * Attempt to align figcaption * Giving up and going for description location in alt --------- Co-authored-by: BethanyG --- .../leap/.approaches/boolean-chain/content.md | 25 +++- .../.approaches/calendar-isleap/content.md | 38 ++++++ .../.approaches/calendar-isleap/snippet.txt | 4 + .../practice/leap/.approaches/config.json | 10 +- .../.approaches/datetime-addition/content.md | 9 +- .../practice/leap/.approaches/introduction.md | 62 ++++++--- exercises/practice/leap/.articles/config.json | 3 +- .../.articles/performance/code/Benchmark.py | 123 +++++++++--------- .../leap/.articles/performance/content.md | 58 ++++++--- 9 files changed, 222 insertions(+), 110 deletions(-) create mode 100644 exercises/practice/leap/.approaches/calendar-isleap/content.md create mode 100644 exercises/practice/leap/.approaches/calendar-isleap/snippet.txt diff --git a/exercises/practice/leap/.approaches/boolean-chain/content.md b/exercises/practice/leap/.approaches/boolean-chain/content.md index c909863fcaa..d4c78911e10 100644 --- a/exercises/practice/leap/.approaches/boolean-chain/content.md +++ b/exercises/practice/leap/.approaches/boolean-chain/content.md @@ -6,13 +6,15 @@ def leap_year(year): ``` +This might be considered the "most idiomatic" or "most Pythonic" solution, as it is exactly the same as the code implemented by the maintainers of the Python language for the [`calendar.isleap()`][isleap-source] method. + The first boolean expression uses the [modulo operator][modulo-operator] to check if the year is evenly divided by `4`. -- If the year is not evenly divisible by `4`, then the chain will "short circuit" due to the next operator being a [logical AND][logical-and] {`and`), and will return `False`. +- If the year is _not_ evenly divisible by `4`, then the chain will [short circuit][short-ciruiting] due to the next operator being a [logical AND][logical-and] {`and`), and will return `False`. - If the year _is_ evenly divisible by `4`, then the year is checked to _not_ be evenly divisible by `100`. -- If the year is not evenly divisible by `100`, then the expression is `True` and the chain will "short-circuit" to return `True`, -since the next operator is a [logical OR][logical-or] (`or`). +- If the year is not evenly divisible by `100`, then the expression is `True` and the interpreter will stop the evaluation to return `True`, since the next operator is a [logical OR][logical-or] (`or`). - If the year _is_ evenly divisible by `100`, then the expression is `False`, and the returned value from the chain will be if the year is evenly divisible by `400`. + | year | year % 4 == 0 | year % 100 != 0 | year % 400 == 0 | is leap year | | ---- | ------------- | --------------- | --------------- | ------------ | | 2020 | True | True | not evaluated | True | @@ -21,13 +23,24 @@ since the next operator is a [logical OR][logical-or] (`or`). | 1900 | True | False | False | False | -The chain of boolean expressions is efficient, as it proceeds from testing the most likely to least likely conditions. +The chain of boolean expressions is efficient, as it proceeds from testing the most to least likely conditions. It is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. + +## Operator precedence + +The implementation contains one set of parentheses, around the `or` clause: +- One set is enough, because the `%` operator is highest priority, then the `==` and `!=` relational operators. +- Those parentheses are required, because `and` is higher priority than `or`. +In Python, `a and b or c` is interpreted as `(a and b) or c`, which would give the wrong answer for this exercise. + +If in doubt, it is always permissible to add extra parentheses for clarity. + + ## Refactoring By using the [falsiness][falsiness] of `0`, the [`not` operator][not-operator] can be used instead of comparing equality to `0`. -For example +For example: ```python def leap_year(year): @@ -42,3 +55,5 @@ It can be thought of as the expression _not_ having a remainder. [logical-or]: https://realpython.com/python-or-operator/ [falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ [not-operator]: https://realpython.com/python-not-operator/ +[short-ciruiting]: https://mathspp.com/blog/pydonts/boolean-short-circuiting#short-circuiting-in-plain-english +[isleap-source]: https://github.com/python/cpython/blob/main/Lib/calendar.py#L141-L143 diff --git a/exercises/practice/leap/.approaches/calendar-isleap/content.md b/exercises/practice/leap/.approaches/calendar-isleap/content.md new file mode 100644 index 00000000000..0674eb56a44 --- /dev/null +++ b/exercises/practice/leap/.approaches/calendar-isleap/content.md @@ -0,0 +1,38 @@ +# The `calendar.isleap()` function + +```pythoon +from calendar import isleap + +def leap_year(year): + return isleap(year) +``` + +~~~~exercism/caution +This approach may be considered a "cheat" for this exercise, which is intended to practice Boolean operators and logic. +~~~~ + + +The Python standard library includes a [`calendar`][calendar] module for working with many aspects of dates in the [Gregorian calendar][gregorian-calendar]. + +One of the methods provided is [`isleap()`][isleap], which implements exactly the same functionality as this exercise. + +This is not a good way to practice the use of Booleans, as the exercise intends. +However, it may be convenient (_and better tested_) if you are working with calendar functions more broadly. + +## The library function + +This is the [implementation][implementation]: + +```python +def isleap(year): + """Return True for leap years, False for non-leap years.""" + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) +``` + +We can see that `calendar.isleap()` is just syntactic sugar for the `boolean-chain` approach. + + +[calendar]: https://docs.python.org/3/library/calendar.html +[gregorian-calendar]: https://en.wikipedia.org/wiki/Gregorian_calendar +[implementation]: https://github.com/python/cpython/blob/main/Lib/calendar.py +[isleap]: https://docs.python.org/3/library/calendar.html diff --git a/exercises/practice/leap/.approaches/calendar-isleap/snippet.txt b/exercises/practice/leap/.approaches/calendar-isleap/snippet.txt new file mode 100644 index 00000000000..220c74a2655 --- /dev/null +++ b/exercises/practice/leap/.approaches/calendar-isleap/snippet.txt @@ -0,0 +1,4 @@ +from calendar import isleap + +def leap_year(year): + return isleap(year) diff --git a/exercises/practice/leap/.approaches/config.json b/exercises/practice/leap/.approaches/config.json index 3b5d57f997a..515f702b762 100644 --- a/exercises/practice/leap/.approaches/config.json +++ b/exercises/practice/leap/.approaches/config.json @@ -1,7 +1,7 @@ { "introduction": { "authors": ["bobahop"], - "contributors": [] + "contributors": ["colinleach"] }, "approaches": [ { @@ -24,6 +24,14 @@ "title": "datetime addition", "blurb": "Use datetime addition.", "authors": ["bobahop"] + }, + { + "uuid": "d85be356-211a-4d2f-8af0-fa92e390b0b3", + "slug": "calendar-isleap", + "title": "calendar.isleap() function", + "blurb": "Use the calendar module.", + "authors": ["colinleach", + "BethanyG"] } ] } diff --git a/exercises/practice/leap/.approaches/datetime-addition/content.md b/exercises/practice/leap/.approaches/datetime-addition/content.md index 184869d131a..3c28ef480eb 100644 --- a/exercises/practice/leap/.approaches/datetime-addition/content.md +++ b/exercises/practice/leap/.approaches/datetime-addition/content.md @@ -11,11 +11,14 @@ def leap_year(year): ``` ~~~~exercism/caution -This approach may be considered a "cheat" for this exercise. +This approach may be considered a "cheat" for this exercise, which is intended to practice Boolean operators and logic. +It also adds a tremendous amount of overhead in both performance and memory, as it imports all of the `datetime` module and requires the instantiation of both a `datetime` object and a `datetime.timedelta` object. + +For more information, see this exercises performance article. ~~~~ -By adding a day to February 28th for the year, you can see if the new day is the 29th or the 1st. -If it is the 29th, then the function returns `True` for the year being a leap year. +By adding a day to February 28th for a given year, you can see if the new day falls on the 29th of February, or the 1st of March. +If it is February 29th, then the function returns `True` for the year being a leap year. - A new [datetime][datetime] object is created for February 28th of the year. - Then the [timedelta][timedelta] of one day is added to that `datetime`, diff --git a/exercises/practice/leap/.approaches/introduction.md b/exercises/practice/leap/.approaches/introduction.md index a5c8c829a56..4b136dd6b41 100644 --- a/exercises/practice/leap/.approaches/introduction.md +++ b/exercises/practice/leap/.approaches/introduction.md @@ -1,15 +1,16 @@ # Introduction -There are various idiomatic approaches to solve Leap. -You can use a chain of boolean expressions to test the conditions. -Or you can use a [ternary operator][ternary-operator]. +There are multiple idiomatic approaches to solving the Leap exercise. +You can use a chain of boolean expressions to test the conditions, a [ternary operator][ternary-operator], or built-in methods from the `datetime` or `calendar` modules. -## General guidance -The key to solving Leap is to know if the year is evenly divisible by `4`, `100` and `400`. +## General Guidance + +The key to efficiently solving Leap is to calculate if the year is evenly divisible by `4`, `100` and `400`. For determining that, you will use the [modulo operator][modulo-operator]. -## Approach: Chain of Boolean expressions + +## Approach: Chain of Boolean Expressions ```python def leap_year(year): @@ -17,9 +18,10 @@ def leap_year(year): ``` -For more information, check the [Boolean chain approach][approach-boolean-chain]. +For more information, see the [Boolean chain approach][approach-boolean-chain]. + -## Approach: Ternary operator of Boolean expressions +## Approach: Ternary Operator of Boolean Expressions ```python def leap_year(year): @@ -27,32 +29,50 @@ def leap_year(year): ``` -For more information, check the [Ternary operator approach][approach-ternary-operator]. +For more information, see the [Ternary operator approach][approach-ternary-operator]. -## Other approaches + +## Other Approaches Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows: -### Approach: datetime addition -Add a day to February 28th for the year and see if the new day is the 29th. For more information, see the [`datetime` addition approach][approach-datetime-addition]. +### Approach: `datetime` Addition + +Add a day to February 28th for the year and see if the new day is the 29th. +However, this approach may trade speed for convenience. +For more information, see the [`datetime` addition approach][approach-datetime-addition]. + + +### Approach: The `calendar` module + +It is possible to use `calendar.isleap(year)` from the standard library, which solves this exact problem. + +This is self-defeating in an Exercism practice exercise intended to explore ways to use booleans. +In a wider context, anyone testing for leap years may already be using `calendar` or related modules, and it is good to know what library functions are available. + ## Which approach to use? -- The chain of boolean expressions should be the most efficient, as it proceeds from the most likely to least likely conditions. +- The chain of boolean expressions should be the most efficient, as it proceeds from the most to least likely conditions and takes advantage of short-circuiting. It has a maximum of three checks. -It is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. +It is the fastest approach when testing a year that is not evenly divisible by `100` that is not a leap year. Since most years fit those conditions, it is overall the most efficient approach. -- The ternary operator has a maximum of only two checks, but it starts from a less likely condition. +It also happens to be the approach taken by the maintainers of the Python language in [implementing `calendar.isleap()`][calendar_isleap-code]. + + +- The ternary operator approach has a maximum of only two checks, but it starts from a less likely condition. The ternary operator was faster in benchmarking when the year was a leap year or was evenly divisible by `100`, -but those are the least likely conditions. -- Using `datetime` addition may be considered a "cheat" for the exercise, and it was slower than the other approaches in benchmarking. +but those are the _least likely_ conditions. +- Using `datetime` addition may be considered a "cheat" for the exercise, and it was slower by far than the other approaches in benchmarking. -For more information, check the [Performance article][article-performance]. +For more information, check out the [Performance article][article-performance]. -[modulo-operator]: https://realpython.com/python-modulo-operator/ -[ternary-operator]: https://www.pythontutorial.net/python-basics/python-ternary-operator/ [approach-boolean-chain]: https://exercism.org/tracks/python/exercises/leap/approaches/boolean-chain -[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/leap/approaches/ternary-operator [approach-datetime-addition]: https://exercism.org/tracks/python/exercises/leap/approaches/datetime-addition +[approach-ternary-operator]: https://exercism.org/tracks/python/exercises/leap/approaches/ternary-operator [article-performance]: https://exercism.org/tracks/python/exercises/leap/articles/performance +[calendar_isleap-code]: https://github.com/python/cpython/blob/3.9/Lib/calendar.py#L100-L102 +[modulo-operator]: https://realpython.com/python-modulo-operator/ +[ternary-operator]: https://www.pythontutorial.net/python-basics/python-ternary-operator/ + diff --git a/exercises/practice/leap/.articles/config.json b/exercises/practice/leap/.articles/config.json index acafe3b22d7..009c0ab0164 100644 --- a/exercises/practice/leap/.articles/config.json +++ b/exercises/practice/leap/.articles/config.json @@ -5,7 +5,8 @@ "slug": "performance", "title": "Performance deep dive", "blurb": "Deep dive to find out the most performant approach for determining a leap year.", - "authors": ["bobahop"] + "authors": ["bobahop", + "colinleach"] } ] } diff --git a/exercises/practice/leap/.articles/performance/code/Benchmark.py b/exercises/practice/leap/.articles/performance/code/Benchmark.py index 467a5952923..d2a204b3616 100644 --- a/exercises/practice/leap/.articles/performance/code/Benchmark.py +++ b/exercises/practice/leap/.articles/performance/code/Benchmark.py @@ -1,88 +1,91 @@ import timeit - +import pandas as pd +import numpy as np +import matplotlib as mpl +import matplotlib.pyplot as plt +import seaborn as sns +from matplotlib.colors import ListedColormap + +# Setting up the Data loops = 1_000_000 -val = timeit.timeit("""leap_year(1900)""", - """ -def leap_year(year): - return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) +row_headers = ["if-statements", "ternary", "datetime-add", "calendar-isleap"] +col_headers = ["1900", "2000", "2019", "2020"] -""", number=loops) / loops +# empty dataframe will be filled in one cell at a time later +df = pd.DataFrame(np.nan, index=row_headers, columns=col_headers) -print(f"if statements 1900: {val}") +setups = {} -val = timeit.timeit("""leap_year(2000)""", - """ +setups["if-statements"] = """ def leap_year(year): return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) +""" -""", number=loops) / loops - -print(f"if statements 2000: {val}") - - -val = timeit.timeit("""leap_year(2019)""", - """ -def leap_year(year): - return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) - -""", number=loops) / loops - -print(f"if statements 2019: {val}") - -val = timeit.timeit("""leap_year(2020)""", - """ +setups["ternary"] = """ def leap_year(year): - return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) - -""", number=loops) / loops + return not year % 400 if not year % 100 else not year % 4 +""" -print(f"if statements 2020: {val}") +setups["datetime-add"] = """ +from datetime import datetime, timedelta -val = timeit.timeit("""leap_year(1900)""", - """ def leap_year(year): - return not year % 400 if not year % 100 else not year % 4 - -""", number=loops) / loops + return (datetime(year, 2, 28) + timedelta(days=1)).day == 29 +""" -print(f"ternary 1900: {val}") +setups["calendar-isleap"] = """ +from calendar import isleap -val = timeit.timeit("""leap_year(2000)""", - """ def leap_year(year): - return not year % 400 if not year % 100 else not year % 4 + return isleap(year) +""" -""", number=loops) / loops -print(f"ternary 2000: {val}") +# Conducting ghe timings +for descriptor in row_headers: + val = timeit.timeit("""leap_year(1900)""", setups[descriptor], number=loops) / loops + year = '1900' + print(f"{descriptor} {year}: {val}") + df.loc[descriptor, year] = val -val = timeit.timeit("""leap_year(2019)""", - """ -def leap_year(year): - return not year % 400 if not year % 100 else not year % 4 + val = timeit.timeit("""leap_year(2000)""", setups[descriptor], number=loops) / loops + year = '2000' + print(f"{descriptor} {year}: {val}") + df.loc[descriptor, year] = val -""", number=loops) / loops + val = timeit.timeit("""leap_year(2019)""", setups[descriptor], number=loops) / loops + year = '2019' + print(f"{descriptor} {year}: {val}") + df.loc[descriptor, year] = val -print(f"ternary 2019: {val}") + val = timeit.timeit("""leap_year(2020)""", setups[descriptor], number=loops) / loops + year = '2020' + print(f"{descriptor} {year}: {val}") + df.loc[descriptor, year] = val -val = timeit.timeit("""leap_year(2020)""", - """ -def leap_year(year): - return not year % 400 if not year % 100 else not year % 4 -""", number=loops) / loops +# Settng up chart details and colors +mpl.rcParams['axes.labelsize'] = 18 +bar_colors = ["#AFAD6A", "#B1C9FD", "#CDC6FD", + "#FABD19", "#3B76F2", "#7467D1", + "#FA9A19", "#85832F", "#1A54CE","#4536B0"] -print(f"ternary 2020: {val}") +my_cmap = ListedColormap(sns.color_palette(bar_colors, as_cmap=True)) -val = timeit.timeit("""leap_year(2019)""", - """ -import datetime +# bar plot of actual run times +ax = df.plot.bar(figsize=(10, 7), + ylabel="time (s)", + fontsize=14, + width=0.8, + rot=0, + colormap=my_cmap) -def leap_year(year): - return (datetime.datetime(year, 2, 28) + - datetime.timedelta(days=1)).day == 29 +# Saving the graph for later use +plt.savefig('../timeit_bar_plot.svg') -""", number=loops) / loops -print(f"datetime add 2019: {val}") +# The next bit will be useful for `introduction.md` +# pd.options.display.float_format = '{:,.2e}'.format +print('\nDataframe in Markdown format:\n') +print(df.to_markdown(floatfmt=".1e")) diff --git a/exercises/practice/leap/.articles/performance/content.md b/exercises/practice/leap/.articles/performance/content.md index ce5ead1ca22..5edf207a9f0 100644 --- a/exercises/practice/leap/.articles/performance/content.md +++ b/exercises/practice/leap/.articles/performance/content.md @@ -1,39 +1,59 @@ # Performance -In this approach, we'll find out how to most efficiently calculate if a year is a leap year in Python. +In this article, we'll find out how to most efficiently calculate if a year is a leap year in Python. The [approaches page][approaches] lists two idiomatic approaches to this exercise: 1. [Using the boolean chain][approach-boolean-chain] 2. [Using the ternary operator][approach-ternary-operator] -For our performance investigation, we'll also include a third approach that [uses datetime addition][approach-datetime-addition]. +For our performance investigation, we will also include a two further approaches: +3. [datetime addition][approach-datetime-addition] +4. The [`calendar.isleap()`][approach-calendar-isleap] function from the calendar module in the standard library + ## Benchmarks -To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] library. - -``` -if statements 1900: 1.468243999988772e-07 -if statements 2000: 1.3710349999018945e-07 -if statements 2019: 8.861289999913425e-08 -if statements 2020: 1.21072500012815e-07 -ternary 1900: 1.091794999956619e-07 -ternary 2000: 1.0275900000124239e-07 -ternary 2019: 1.0278620000462979e-07 -ternary 2020: 1.0290379999787546e-07 -datetime add 2019: 6.689728000201284e-07 -``` - -- The boolean chain is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. +To benchmark the approaches, we wrote a [small benchmark application][benchmark-application] using the [`timeit`][timeit] module. +All methods are "fast", but the difference may be easier to see graphically. +**Note**: The y-axis values in the chart have a `1e-7` multiplier. + All run times are sub-microsecond. + + +

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

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

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

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

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

- + Grouped Bar Chart showing execution timings for 4 leap approaches using the years 1900, 200, 2019, and 202 as input data. Described under the heading Timings for approaches by input year. ### Timings for approaches by input year +
| | 1900 | 2000 | 2019 | 2020 | |:----------------|--------:|--------:|--------:|--------:| @@ -37,6 +32,7 @@ All methods are "fast", but the difference may be easier to see graphically. | datetime add | 2.8e-07 | 2.8e-07 | 2.7e-07 | 2.8e-07 | | calendar isleap | 6.5e-08 | 5.8e-08 | 3.8e-08 | 5.5e-08 | +
- The `if-statements` (_boolean chain_) is the fastest approach when testing a year that is not evenly divisible by `100` and is not a leap year. Since most years fit those conditions, it is overall the most efficient approach. From 5eed8cf9bb162bb61e01734fc19f347be785c2e5 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 12 Jan 2024 19:29:23 -0800 Subject: [PATCH 027/277] Update content.md with the correct link this time! (#3588) --- exercises/practice/leap/.articles/performance/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/leap/.articles/performance/content.md b/exercises/practice/leap/.articles/performance/content.md index 9901a1e0ff0..7e8d41dae52 100644 --- a/exercises/practice/leap/.articles/performance/content.md +++ b/exercises/practice/leap/.articles/performance/content.md @@ -19,7 +19,7 @@ All methods are "fast", but the difference may be easier to see graphically. **Note**: The y-axis values in the chart have a `1e-7` multiplier. All run times are sub-microsecond. - Grouped Bar Chart showing execution timings for 4 leap approaches using the years 1900, 200, 2019, and 202 as input data. Described under the heading Timings for approaches by input year. + Grouped Bar Chart showing execution timings for 4 leap approaches using the years 1900, 200, 2019, and 202 as input data. Described under the heading Timings for approaches by input year. ### Timings for approaches by input year From 3705a4964b41e3b9821aed9503954e424aabbefc Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 13 Jan 2024 12:45:11 -0800 Subject: [PATCH 028/277] Swapped HTML syntax for markdown for svg image. (#3590) --- exercises/practice/leap/.articles/performance/content.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/practice/leap/.articles/performance/content.md b/exercises/practice/leap/.articles/performance/content.md index 7e8d41dae52..c23e419a14e 100644 --- a/exercises/practice/leap/.articles/performance/content.md +++ b/exercises/practice/leap/.articles/performance/content.md @@ -19,7 +19,8 @@ All methods are "fast", but the difference may be easier to see graphically. **Note**: The y-axis values in the chart have a `1e-7` multiplier. All run times are sub-microsecond. - Grouped Bar Chart showing execution timings for 4 leap approaches using the years 1900, 200, 2019, and 202 as input data. Described under the heading Timings for approaches by input year. +!["Grouped Bar Chart showing execution timings for 4 leap approaches using the years 1900, 200, 2019, and 202 as input data. Described under the heading Timings for approaches by input year."](https://assets.exercism.org/images/tracks/python/leap/leap_timeit_bar_plot-light.svg) + ### Timings for approaches by input year From 63ab89fd1931468209bdc722c59d251556d1fb3f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 14 Jan 2024 15:20:08 -0800 Subject: [PATCH 029/277] Synced leap to problem specifications. (#3592) --- exercises/practice/leap/.docs/instructions.md | 21 +------------------ exercises/practice/leap/.docs/introduction.md | 16 ++++++++++++++ exercises/practice/leap/.meta/config.json | 2 +- 3 files changed, 18 insertions(+), 21 deletions(-) create mode 100644 exercises/practice/leap/.docs/introduction.md diff --git a/exercises/practice/leap/.docs/instructions.md b/exercises/practice/leap/.docs/instructions.md index a83826b2e0e..b14f8565d61 100644 --- a/exercises/practice/leap/.docs/instructions.md +++ b/exercises/practice/leap/.docs/instructions.md @@ -1,22 +1,3 @@ # Instructions -Given a year, report if it is a leap year. - -The tricky thing here is that a leap year in the Gregorian calendar occurs: - -```text -on every year that is evenly divisible by 4 - except every year that is evenly divisible by 100 - unless the year is also evenly divisible by 400 -``` - -For example, 1997 is not a leap year, but 1996 is. -1900 is not a leap year, but 2000 is. - -## Notes - -Though our exercise adopts some very simple rules, there is more to learn! - -For a delightful, four minute explanation of the whole leap year phenomenon, go watch [this youtube video][video]. - -[video]: https://www.youtube.com/watch?v=xX96xng7sAE +Your task is to determine whether a given year is a leap year. diff --git a/exercises/practice/leap/.docs/introduction.md b/exercises/practice/leap/.docs/introduction.md new file mode 100644 index 00000000000..1cb8b14ccba --- /dev/null +++ b/exercises/practice/leap/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +A leap year (in the Gregorian calendar) occurs: + +- In every year that is evenly divisible by 4 +- Unless the year is evenly divisible by 100, in which case it's only a leap year if the year is also evenly divisible by 400. + +Some examples: + +- 1997 was not a leap year as it's not divisible by 4. +- 1900 was not a leap year as it's not divisible by 400 +- 2000 was a leap year! + +~~~~exercism/note +For a delightful, four minute explanation of the whole phenomenon of leap years, check out [this youtube video](https://www.youtube.com/watch?v=xX96xng7sAE). +~~~~ diff --git a/exercises/practice/leap/.meta/config.json b/exercises/practice/leap/.meta/config.json index fd2bd240870..1c35f22be1f 100644 --- a/exercises/practice/leap/.meta/config.json +++ b/exercises/practice/leap/.meta/config.json @@ -32,7 +32,7 @@ ".meta/example.py" ] }, - "blurb": "Given a year, report if it is a leap year.", + "blurb": "Determine whether a given year is a leap year.", "source": "CodeRanch Cattle Drive, Assignment 3", "source_url": "https://coderanch.com/t/718816/Leap" } From 03229bcca3ca7577364593ae68c415087d720e85 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 14 Jan 2024 17:13:29 -0800 Subject: [PATCH 030/277] [Leap]: Updated Table in Performance Article with Newer Timings to Match Graph. (#3593) * Updated table with newer timings to match graph. * Apply suggestions from code review Further adjustment to the numbers. _sigh_ --- exercises/practice/leap/.articles/performance/content.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/practice/leap/.articles/performance/content.md b/exercises/practice/leap/.articles/performance/content.md index c23e419a14e..67d7b57b631 100644 --- a/exercises/practice/leap/.articles/performance/content.md +++ b/exercises/practice/leap/.articles/performance/content.md @@ -28,10 +28,10 @@ All methods are "fast", but the difference may be easier to see graphically. | | 1900 | 2000 | 2019 | 2020 | |:----------------|--------:|--------:|--------:|--------:| -| if statements | 5.1e-08 | 4.5e-08 | 2.5e-08 | 3.9e-08 | -| ternary | 3.3e-08 | 3.0e-08 | 3.0e-08 | 3.3e-08 | -| datetime add | 2.8e-07 | 2.8e-07 | 2.7e-07 | 2.8e-07 | -| calendar isleap | 6.5e-08 | 5.8e-08 | 3.8e-08 | 5.5e-08 | +| if-statements | 1.7e-07 | 1.6e-07 | 9.0e-08 | 1.3e-07 | +| ternary | 1.2e-07 | 1.0e-07 | 1.1e-07 | 1.1e-07 | +| datetime-add | 6.9e-07 | 6.7e-07 | 7.0e-07 | 6.7e-07 | +| calendar-isleap | 2.2e-07 | 2.2e-07 | 1.4e-07 | 1.7e-07 |
From f4a14bc5db5d7834cebac42569bd4ca644e6f68d Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 16 Jan 2024 19:31:51 +0100 Subject: [PATCH 031/277] Sync the `leap` exercise's docs with the latest data. (#3595) --- exercises/practice/leap/.docs/introduction.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/leap/.docs/introduction.md b/exercises/practice/leap/.docs/introduction.md index 1cb8b14ccba..4ffd2da594a 100644 --- a/exercises/practice/leap/.docs/introduction.md +++ b/exercises/practice/leap/.docs/introduction.md @@ -2,15 +2,15 @@ A leap year (in the Gregorian calendar) occurs: -- In every year that is evenly divisible by 4 +- In every year that is evenly divisible by 4. - Unless the year is evenly divisible by 100, in which case it's only a leap year if the year is also evenly divisible by 400. Some examples: - 1997 was not a leap year as it's not divisible by 4. -- 1900 was not a leap year as it's not divisible by 400 +- 1900 was not a leap year as it's not divisible by 400. - 2000 was a leap year! ~~~~exercism/note -For a delightful, four minute explanation of the whole phenomenon of leap years, check out [this youtube video](https://www.youtube.com/watch?v=xX96xng7sAE). +For a delightful, four-minute explanation of the whole phenomenon of leap years, check out [this YouTube video](https://www.youtube.com/watch?v=xX96xng7sAE). ~~~~ From 283716ec30129667151f010b45e01a8e9a4a2bac Mon Sep 17 00:00:00 2001 From: Knute Snortum Date: Tue, 16 Jan 2024 12:09:04 -0800 Subject: [PATCH 032/277] Fixes #3594, solutions are natural numbers (#3597) --- exercises/practice/square-root/.docs/instructions.append.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/square-root/.docs/instructions.append.md b/exercises/practice/square-root/.docs/instructions.append.md index 535038e466d..84b4cf8ee70 100644 --- a/exercises/practice/square-root/.docs/instructions.append.md +++ b/exercises/practice/square-root/.docs/instructions.append.md @@ -6,7 +6,7 @@ Python offers a wealth of mathematical functions in the form of the [math module][math-module] and built-ins such as [`pow()`][pow] and [`sum()`][sum]. However, we'd like you to consider the challenge of solving this exercise without those built-ins or modules. -While there is a mathematical formula that will find the square root of _any_ number, we have gone the route of only testing [natural numbers][nautral-number] (positive integers). +While there is a mathematical formula that will find the square root of _any_ number, we have gone the route of having only [natural numbers][nautral-number] (positive integers) as solutions. [math-module]: https://docs.python.org/3/library/math.html From bd8209708ad360323f5f1e0d2ea2309677cc29ae Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen <62411302+safwansamsudeen@users.noreply.github.com> Date: Wed, 17 Jan 2024 01:40:15 +0530 Subject: [PATCH 033/277] fix typo (#3584) --- .../.approaches/nested-for-loop-optimized/content.md | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md b/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md index 2bc2718391e..da24f2c2343 100644 --- a/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md +++ b/exercises/practice/palindrome-products/.approaches/nested-for-loop-optimized/content.md @@ -72,6 +72,7 @@ y = [99, 98] ... x = [90, 89, 88 ...] y = [99, 98,97,96,95,94,93,92,91,90] +``` Here we can see that the highest value for this "run" is 90 \* 99 = 8910. Meaning that running beyond this point won't give us any values higher than 9009. From 2a276fb5e3ca4251b8a88cd47ff0edfe50fc6699 Mon Sep 17 00:00:00 2001 From: Luis F M Alves <45541532+lfmalves@users.noreply.github.com> Date: Wed, 17 Jan 2024 13:59:49 -0300 Subject: [PATCH 034/277] Update introduction.md (typo) (#3596) Small typo on the intro to the exercise "meltdown mitigation" --- exercises/concept/meltdown-mitigation/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/meltdown-mitigation/.docs/introduction.md b/exercises/concept/meltdown-mitigation/.docs/introduction.md index fe9fff4d5d7..0eb3d88ccde 100644 --- a/exercises/concept/meltdown-mitigation/.docs/introduction.md +++ b/exercises/concept/meltdown-mitigation/.docs/introduction.md @@ -48,7 +48,7 @@ if x > y: elif y > z: print("y is greater than x and z") else: - print("z is great than x and y") + print("z is greater than x and y") ... >>> z is great than x and y ``` From e1879a3f60ef34933d75498fb7f45243b6670017 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 18 Jan 2024 20:13:55 +0100 Subject: [PATCH 035/277] reverse-string: sync (#3598) * Sync the `reverse-string` exercise's docs with the latest data. * Sync the `reverse-string` exercise's metadata with the latest data. --- .../practice/reverse-string/.docs/instructions.md | 10 ++++++---- .../practice/reverse-string/.docs/introduction.md | 5 +++++ exercises/practice/reverse-string/.meta/config.json | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 exercises/practice/reverse-string/.docs/introduction.md diff --git a/exercises/practice/reverse-string/.docs/instructions.md b/exercises/practice/reverse-string/.docs/instructions.md index 039ee33ae58..0ff4198e46e 100644 --- a/exercises/practice/reverse-string/.docs/instructions.md +++ b/exercises/practice/reverse-string/.docs/instructions.md @@ -1,7 +1,9 @@ # Instructions -Reverse a string +Your task is to reverse a given string. -For example: -input: "cool" -output: "looc" +Some examples: + +- Turn `"stressed"` into `"desserts"`. +- Turn `"strops"` into `"sports"`. +- Turn `"racecar"` into `"racecar"`. diff --git a/exercises/practice/reverse-string/.docs/introduction.md b/exercises/practice/reverse-string/.docs/introduction.md new file mode 100644 index 00000000000..02233e0755e --- /dev/null +++ b/exercises/practice/reverse-string/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +Reversing strings (reading them from right to left, rather than from left to right) is a surprisingly common task in programming. + +For example, in bioinformatics, reversing the sequence of DNA or RNA strings is often important for various analyses, such as finding complementary strands or identifying palindromic sequences that have biological significance. diff --git a/exercises/practice/reverse-string/.meta/config.json b/exercises/practice/reverse-string/.meta/config.json index 1df068a9580..bbc16e8661b 100644 --- a/exercises/practice/reverse-string/.meta/config.json +++ b/exercises/practice/reverse-string/.meta/config.json @@ -22,7 +22,7 @@ ".meta/example.py" ] }, - "blurb": "Reverse a string.", + "blurb": "Reverse a given string.", "source": "Introductory challenge to reverse an input string", "source_url": "https://medium.freecodecamp.org/how-to-reverse-a-string-in-javascript-in-3-different-ways-75e4763c68cb" } From 6f1fa934160cd501d8c8aa34a44ea37366de8679 Mon Sep 17 00:00:00 2001 From: colinleach Date: Fri, 19 Jan 2024 19:34:30 -0700 Subject: [PATCH 036/277] [Pythagorean Triplet]: Approaches Draft (#3582) * [Pythagorean Triplet]: Approaches Draft * [Leap]: Add `isleap` approach and improved benchmarks * Delete exercises/practice/leap/.articles/performance/timeit_bar_plot.svg Sorry, I committed this to the wrong branch. Embarrassingly amateurish! * Suggestions, Edits, etc. Suggestions and edits. Because we need to PR to a different repo, the `.svg` images have been removed, and the text adjusted. --------- Co-authored-by: BethanyG --- .../.approaches/config.json | 33 +++ .../.approaches/cubic/content.md | 24 +++ .../.approaches/cubic/snippet.txt | 8 + .../.approaches/introduction.md | 192 ++++++++++++++++++ .../.approaches/linear/content.md | 59 ++++++ .../.approaches/linear/snippet.txt | 8 + .../.approaches/quadratic/content.md | 47 +++++ .../.approaches/quadratic/snippet.txt | 8 + .../pythagorean-triplet/.articles/config.json | 12 ++ .../.articles/performance/code/Benchmark.py | 124 +++++++++++ .../performance/code/create_plots.py | 42 ++++ .../performance/code/fit_gradients.py | 38 ++++ .../performance/code/run_times.feather | Bin 0 -> 5426 bytes .../performance/code/transposed_logs.feather | Bin 0 -> 4010 bytes .../.articles/performance/content.md | 102 ++++++++++ .../.articles/performance/snippet.md | 8 + 16 files changed, 705 insertions(+) create mode 100644 exercises/practice/pythagorean-triplet/.approaches/config.json create mode 100644 exercises/practice/pythagorean-triplet/.approaches/cubic/content.md create mode 100644 exercises/practice/pythagorean-triplet/.approaches/cubic/snippet.txt create mode 100644 exercises/practice/pythagorean-triplet/.approaches/introduction.md create mode 100644 exercises/practice/pythagorean-triplet/.approaches/linear/content.md create mode 100644 exercises/practice/pythagorean-triplet/.approaches/linear/snippet.txt create mode 100644 exercises/practice/pythagorean-triplet/.approaches/quadratic/content.md create mode 100644 exercises/practice/pythagorean-triplet/.approaches/quadratic/snippet.txt create mode 100644 exercises/practice/pythagorean-triplet/.articles/config.json create mode 100644 exercises/practice/pythagorean-triplet/.articles/performance/code/Benchmark.py create mode 100644 exercises/practice/pythagorean-triplet/.articles/performance/code/create_plots.py create mode 100644 exercises/practice/pythagorean-triplet/.articles/performance/code/fit_gradients.py create mode 100644 exercises/practice/pythagorean-triplet/.articles/performance/code/run_times.feather create mode 100644 exercises/practice/pythagorean-triplet/.articles/performance/code/transposed_logs.feather create mode 100644 exercises/practice/pythagorean-triplet/.articles/performance/content.md create mode 100644 exercises/practice/pythagorean-triplet/.articles/performance/snippet.md diff --git a/exercises/practice/pythagorean-triplet/.approaches/config.json b/exercises/practice/pythagorean-triplet/.approaches/config.json new file mode 100644 index 00000000000..b9514f490aa --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/config.json @@ -0,0 +1,33 @@ +{ + "introduction": { + "authors": ["colinleach", + "BethanyG"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "4d4b4e6c-a026-4ed3-8e07-6b9edfabe713", + "slug": "cubic", + "title": "Cubic", + "blurb": "Cubic-time approach with loops nested 3 deep.", + "authors": ["colinleach", + "BethanyG"] + }, + { + "uuid": "385c0ace-117f-4480-8dfc-0632d8893e60", + "slug": "quadratic", + "title": "Quadratic", + "blurb": "Quadratic-time approaches with doubly-nested loops.", + "authors": ["colinleach", + "BethanyG"] + }, + { + "uuid": "1addb672-6064-4a07-acad-4a08f92d9e43", + "slug": "linear", + "title": "Linear Loop", + "blurb": "Linear-time approaches with no nesting of loops.", + "authors": ["colinleach", + "BethanyG"] + } + ] +} diff --git a/exercises/practice/pythagorean-triplet/.approaches/cubic/content.md b/exercises/practice/pythagorean-triplet/.approaches/cubic/content.md new file mode 100644 index 00000000000..9611745c2ae --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/cubic/content.md @@ -0,0 +1,24 @@ +# Cubic-time triply-nested loops + +```python +def triplets_with_sum(n): + triplets = [] + for a in range(1, n + 1): + for b in range(a + 1, n + 1): + for c in range(b + 1, n + 1): + if a**2 + b**2 == c**2 and a + b + c == n: + triplets.append([a, b, c]) + return triplets +``` + +The strategy in this case is to scan through all three variables and test them in the innermost loop. +But the innermost loop will test all variables _every time_ the enclosing loop iterates. +And the enclosing loop up will iterate through all of its range _every time_ the outermost loop iterates. + +So the 'work' this code has to do for each additional value in `range(1, n+1)` is `n**3`. + +This gives code that is simple, clear, ...and so slow as to be useless for all but the smallest values of `n`. + +We could tighten up the bounds on loop variables: for example, `a` is the smallest integer of a triplet that sums to `n`, so inevitably `a < n //3`. + +However, this is not nearly enough to rescue an inappropriate algorithm. diff --git a/exercises/practice/pythagorean-triplet/.approaches/cubic/snippet.txt b/exercises/practice/pythagorean-triplet/.approaches/cubic/snippet.txt new file mode 100644 index 00000000000..eb2b55c687d --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/cubic/snippet.txt @@ -0,0 +1,8 @@ +def triplets_with_sum(n): + triplets = [] + for a in range(1, n + 1): + for b in range(a + 1, n + 1): + for c in range(b + 1, n + 1): + if a**2 + b**2 == c**2 and a + b + c == n: + triplets.append([a, b, c]) + return triplets \ No newline at end of file diff --git a/exercises/practice/pythagorean-triplet/.approaches/introduction.md b/exercises/practice/pythagorean-triplet/.approaches/introduction.md new file mode 100644 index 00000000000..ab79ef1afde --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/introduction.md @@ -0,0 +1,192 @@ +# Introduction + +The main challenge in solving the Pythagorean Triplet exercise is to come up with a 'fast enough' algorithm. +The problem space can easily become very large, and 'naive' or more 'brute force' solutions may time out on the test runner. + +There are three reasonably common variations to this problem +1. A [cubic time][approaches-cubic] solution, which uses highly nested loops and is non-performant. +2. A [quadratic time][approaches-quadratic] solution, which uses one nested loop, and is reasonably easy to figure out. +3. A [linear time][approaches-linear] solution, requiring some deeper understanding of the mathematics of finding trplets. + + +If those terms are unclear to you, you might like to read about [time complexity][time-complexity], and how it is described by [asymptotic notation][asymptotic-notation]. + +The basic idea is to study how algorithms scale (_in CPU/GPU run time, memory usage, or other resource_) as the input parameters grow toward infinity. +In this document we will focus on run time, which is critical for this exercise. + + +## General guidance + +The goal of `Pythagorean Triplets` is to find combinations of three numbers `[a, b, c]` satisfying a set of conditions: + +1. `a < b < c` +2. `a**2 + b**2 == c**2` (_otherwise known as the [Pythagorean theorem][Pythagorean-theorem]_) +3. `a + b + c == n`, where `n` is supplied as a parameter to the function. + +For a given `n`, the solution should include all valid triplets as a list of lists. + + +## Approach: Cubic time solution with 3-deep nested loops + +```python +def triplets_with_sum(n): + triplets = [] + for a in range(1, n + 1): + for b in range(a + 1, n + 1): + for c in range(b + 1, n + 1): + if a**2 + b**2 == c**2 and a + b + c == n: + triplets.append([a, b, c]) + return triplets +``` + +This is the most 'naive' or 'brute force' approach, scanning through all possible integers `<= n` that satisfy `a < b < c`. +This might be the first thing you think of when sketching out the algorithm on paper following the exercise instructions. +It is useful to see the steps of the solution and to look at the size of the problem space. + +***Don't implement this in code!*** + +While it is a valid solution and it indeed works for small values of `n`, it becomes impossibly slow as `n` grows larger. +For any truly large values of `n`, this code might take over all the available processing power on your local computer and never complete. +For Exercism's online environment, the test suite will time out and fail. + + +## Approach: Quadratic time solution with 2-deep nested loops + +```python +def triplets_with_sum(n): + triplets = [] + for a in range(1, n + 1): + for b in range(a + 1, n + 1): + c = n - a - b + if a ** 2 + b ** 2 == c ** 2: + triplets.append([a, b, c]) + return triplets +``` + +Given the constraint that `a + b + c == n`, we can eliminate the innermost loop from the cubic approach and calculate `c` directly. +This gives a substantial speed advantage, allowing the tests to run to completion in a reasonable time, _locally_. + +However, the Exercism online test runner will still time out with this solution. + +Examining the code, it is clear that the upper bounds on the `loop` variables are far too generous, and too much work is bing done. + + +The solution below tightens the bounds and pre-calculates `c * c` in the outer `loop`. +This gives about a 4-fold speedup, but still times out on the online test runner: + + +```python +def triplets_with_sum(n): + result = [] + for c in range(5, n - 1): + c_sq = c * c + for a in range(3, (n - c + 1) // 2): + b = n - a - c + if a < b < c and a * a + b * b == c_sq: + result.append([a, b, c]) + return result +``` + +If a quadratic-time algorithm was the best available option, there are other ways to squeeze out small performance gains. + +For bigger problems outside Exercism, there are third-party packages such as [`numpy`][numpy] or [`numba`][numba] which replace Python +loops (_versatile but relatively slow_) with routines written in C/C++, perhaps with use of the GPU. +The runtime is still proportional to `n**2`, but the proportionality constant (_which would be measured in C/C++ as opposed to Python_) may be much smaller. + +Fortunately for the present discussion, mathematicians have been studying Pythagorean Triplets for centuries: see [Wikipedia][wiki-pythag], [Wolfram MathWorld][wolfram-pythag], or many other sources. + +So mathematically there are much faster algorithms, at the expense of reduced readability. + + +## Linear time solutions + +```python +from math import sqrt + +def triplets_with_sum(n): + N = float(n) + triplets = [] + for c in range(int(N / 2) - 1, int((sqrt(2) - 1) * N), -1): + D = sqrt(c ** 2 - N ** 2 + 2 * N * c) + if D == int(D): + triplets.append([int((N - c - D) / 2), int((N - c + D) / 2), c]) + return triplets +``` + +_All clear?_ 😉 + +After some thoughtful mathematical analysis, there is now only a single loop! + +Run time is now much faster, especially for large `n`, but a reasonable person could find it quite difficult to understand what the code is doing. + +If you do things like this out in the 'real world' ***please*** document your code carefully. +It might also be helpful to choose variable names that are more descriptive to help readers understand all of the values and operations. +In a few weeks, the bare code will puzzle your future self. +People reading it for the first time are likely to struggle even more than you will. + +The code above uses fairly 'generic' programming syntax. +Another submission used the same basic algorithm but in a more 'Pythonic' way: + + +```python +def triplets_with_sum(n): + def calculate_medium(small): + return (n ** 2 - 2 * n * small) / (2 * (n - small)) + + two_sides = ((int(medium), small) for small in range(3, n // 3) + if small < (medium := calculate_medium(small)) + and medium.is_integer()) + + return [[small, medium, (medium ** 2 + small ** 2) ** 0.5] + for medium, small in two_sides] +``` + +Although it is important to note that this solution could have chosen a better name for the `n` parameter, and clearer formatting for the `generator-expression` and the `list-comprehension`: + +```python +def triplets_with_sum(number): + def calculate_medium(small): + + # We have two numbers, but need the third. + return (number ** 2 - 2 * number * small) / (2 * (number - small)) + + two_sides = ( + (int(medium), small) for + small in range(3, number // 3) if + + #Calls calculate_medium and assigns return value to variable medium + small < (medium := calculate_medium(small)) and + medium.is_integer() + ) + + return [ + [small, medium, (medium ** 2 + small ** 2) ** 0.5] + for medium, small in two_sides + ] +``` + + +## Which approach to use? + +If we could be sure that the code only had to handle small values of `n`, a quadratic method would have the advantage of clarity. + +However, the test suite goes up to 30_000, and the online test runner quickly times out. +We therefor need to accept some less readable code and use a linear-time implementation. + +Full details of run-time benchmarking are given in the [Performance article][article-performance]. + +Overall, the results confirm the expectation that the linear-time methods are _very much_ faster. +More surprisingly, the first example of the linear implementation (_with an explicit loop_) proved slightly faster than the more 'Pythonic' code. +This is likely due to the overhead of creating and tracking the iterator for the `generator-expression`, calculating the 'expensive' `calculate_medium()` function call within that generator, and the additional 'expensive' conversions to `int()`. + +[Pythagorean-theorem]: https://en.wikipedia.org/wiki/Pythagorean_theorem +[approaches-cubic]: https://exercism.org/tracks/python/exercises/pythagorean-triplet/approaches/cubic +[approaches-linear]: https://exercism.org/tracks/python/exercises/pythagorean-triplet/approaches/linear +[approaches-quadratic]: https://exercism.org/tracks/python/exercises/pythagorean-triplet/approaches/quadratic +[article-performance]:https://exercism.org/tracks/python/exercises/pythagorean-triplet/articles/performance +[asymptotic-notation]: https://www.khanacademy.org/computing/computer-science/algorithms/asymptotic-notation/a/asymptotic-notation +[numba]: https://numba.pydata.org/ +[numpy]: https://numpy.org/ +[time-complexity]: https://yourbasic.org/algorithms/time-complexity-explained/ +[wiki-pythag]: https://en.wikipedia.org/wiki/Pythagorean_triple +[wolfram-pythag]: https://mathworld.wolfram.com/PythagoreanTriple.html diff --git a/exercises/practice/pythagorean-triplet/.approaches/linear/content.md b/exercises/practice/pythagorean-triplet/.approaches/linear/content.md new file mode 100644 index 00000000000..93dfa8448cd --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/linear/content.md @@ -0,0 +1,59 @@ +# Linear-time algorithms with no nested loops + + +The key point with this approach is that we only loop over the variable `c` in a specific range. +Some mathematical analysis (_essentially, [solving simultaneous equations][simultaneous-equasions]_) then allows us to find valid values of `a` and `b`. + +Other than that, the code syntax below is fairly mainstream across programming languages. +A related approach instead loops over `a`. + +```python +from math import sqrt + +def triplets_with_sum(n): + N = float(n) + triplets = [] + for c in range(int(N / 2) - 1, int((sqrt(2) - 1) * N), -1): + D = sqrt(c ** 2 - N ** 2 + 2 * N * c) + if D == int(D): + triplets.append([int((N - c - D) / 2), int((N - c + D) / 2), c]) + return triplets +``` + + +This second code example has no explicit `for` loop (_in Python syntax_), but the `generator-expression` and the `list-comprehension` both translate to `FOR_ITER` at the bytecode level. + So this solution is essentially the same as the first, written in a more 'Pythonic' syntax -- but that syntax does incur a small overhead in performance. + The performance hit is likely due to the extra instructions in the bytecode used to manage the `generator-expression` (_pausing the loop, resuming the loop, yielding results_) and then calling or unpacking the generator in the `list comprehension`. + However, you would have to carefully profile the code to really determine the slowdown. + + With all that said, using a `generator` or `generator-expression` with or without a `list-comprehension` might be a better strategy if your code needs to process a very large number of triplets, as it avoids storing all the results in memory until they need to be returned. + Using a `generator` or `generator-expression` by itself can also nicely set up a scenario where results are "streamed" or emitted 'on demand' for another part of the program or application. + + For more details on what these two solutions look like at the byte code level, take a look at Pythons [`dis`][dis] module. + + +```python +def triplets_with_sum(n): + def calculate_medium(small): + return (n ** 2 - 2 * n * small) / (2 * (n - small)) + + two_sides = ((int(medium), small) for small in range(3, n // 3) + if small < (medium := calculate_medium(small)) + and medium.is_integer()) + + return [[small, medium, (medium ** 2 + small ** 2) ** 0.5] + for medium, small in two_sides] +``` + + +Some implementation details to notice: +- Nested functions, with the inner function able to reference variables such as `n` passed into the outer function. +- The generator expression creates `two_sides` as a lazily-evaluated iterator (_smaller memory footprint_) +- The [`walrus operator`][walrus-operator] `:=` is new in Python 3.8. +- The `is_integer()` method replaces `if D == int(D)`. +- Using `** 0.5` to calculate the square roots avoids a `math` import. + + +[dis]: https://docs.python.org/3/library/dis.html +[simultaneous-equasions]: https://thirdspacelearning.com/gcse-maths/algebra/simultaneous-equations/ +[walrus-operator]: https://mathspp.com/blog/pydonts/assignment-expressions-and-the-walrus-operator diff --git a/exercises/practice/pythagorean-triplet/.approaches/linear/snippet.txt b/exercises/practice/pythagorean-triplet/.approaches/linear/snippet.txt new file mode 100644 index 00000000000..a8f26825c3c --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/linear/snippet.txt @@ -0,0 +1,8 @@ +def triplets_with_sum(n): + def calculate_medium(small): + return (n ** 2 - 2 * n * small) / (2 * (n - small)) + two_sides = ((int(medium), small) for small in range(3, n // 3) + if small < (medium := calculate_medium(small)) + and medium.is_integer()) + return [[small, medium, (medium ** 2 + small ** 2) ** 0.5] + for medium, small in two_sides] diff --git a/exercises/practice/pythagorean-triplet/.approaches/quadratic/content.md b/exercises/practice/pythagorean-triplet/.approaches/quadratic/content.md new file mode 100644 index 00000000000..15f2055f9ac --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/quadratic/content.md @@ -0,0 +1,47 @@ +# Quadratic-time doubly-nested loops + +```python +def triplets_with_sum(n): + triplets = [] + for a in range(1, n + 1): + for b in range(a + 1, n + 1): + c = n - a - b + if a ** 2 + b ** 2 == c ** 2: + triplets.append([a, b, c]) + return triplets +``` + +Because `a + b + c == n`, we only loop over `a` and `b`. +The third variable `c` is then predictable. + +The above code loops over the full range of both variables. +This means the 'work' this code has to do for each additional value in `range(1, n+1)` is `n**2`. +We know enough about the problems to tighten this up a bit. + +For example: +- The smallest pythagorean is (famously) `[3, 4, 5]`, so `a >= 3` +- `a + b == n - c` and `a <= b`, so `a <= (n - c) // 2` + +We can also avoid, to some extent, repeating the same multiplication many times. +This gets us to the code below: + + +```python +def triplets_with_sum(n): + result = [] + for c in range(5, n - 1): + c_sq = c * c + for a in range(3, (n - c + 1) // 2): + b = n - a - c + if a < b < c and a * a + b * b == c_sq: + result.append([a, b, c]) + return result +``` + +We could have done a bit better. +Though not stated in the problem description, `a + b > c`, otherwise they could not form a triangle. + +This means that `c <= n // 2`, reducing the iterations in the outer loop. + +However, these optimizations only reduce the run time by a small factor. +They do almost nothing to make the algorithm scale to large `n`. diff --git a/exercises/practice/pythagorean-triplet/.approaches/quadratic/snippet.txt b/exercises/practice/pythagorean-triplet/.approaches/quadratic/snippet.txt new file mode 100644 index 00000000000..fcac4bee78e --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.approaches/quadratic/snippet.txt @@ -0,0 +1,8 @@ +def triplets_with_sum(n): + triplets = [] + for a in range(1, n + 1): + for b in range(a + 1, n + 1): + c = n - a - b + if a ** 2 + b ** 2 == c ** 2: + triplets.append([a, b, c]) + return triplets \ No newline at end of file diff --git a/exercises/practice/pythagorean-triplet/.articles/config.json b/exercises/practice/pythagorean-triplet/.articles/config.json new file mode 100644 index 00000000000..07a0789875e --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.articles/config.json @@ -0,0 +1,12 @@ +{ + "articles": [ + { + "uuid": "b6ae73d5-6ee9-472d-bb48-d8eac8a097cf", + "slug": "performance", + "title": "Performance", + "blurb": "Results and analysis of timing tests for the various approaches.", + "authors": ["colinleach", + "BethanyG"] + } + ] +} diff --git a/exercises/practice/pythagorean-triplet/.articles/performance/code/Benchmark.py b/exercises/practice/pythagorean-triplet/.articles/performance/code/Benchmark.py new file mode 100644 index 00000000000..a00e3fc68dd --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.articles/performance/code/Benchmark.py @@ -0,0 +1,124 @@ +import timeit +import pandas as pd +import numpy as np + + +n_values = (12, 30, 100, 300, 1_000, 3_000, 10_000, 30_000, 100_000) +col_headers = [str(n) for n in n_values] +row_headers = ["cubic", "quad_loose", "quad_tight", "linear_loop", "linear_comp"] + +# empty dataframe will be filled in one cell at a time later +df = pd.DataFrame(np.nan, index=row_headers, columns=col_headers) + +# create a dictionary with all the solution codes + +code = { + 'cubic': """ +def triplets_with_sum(number): + triplets = [] + for a in range(1, number + 1): + for b in range(a + 1, number + 1): + for c in range(b + 1, number + 1): + if a**2 + b**2 == c**2 and a + b + c == number: + triplets.append([a, b, c]) + return triplets +""", + + 'quad_loose': """ +def triplets_with_sum(number): + triplets = [] + for a in range(1, number + 1): + for b in range(a + 1, number + 1): + c = number - a - b + if a ** 2 + b ** 2 == c ** 2: + triplets.append([a, b, c]) + return triplets +""", + + 'quad_tight': """ +def triplets_with_sum(number): + result = [] + for c in range(5, number - 1): + c_sq = c * c + for a in range(3, (number - c + 1) // 2): + b = number - a - c + if a < b < c and a * a + b * b == c_sq: + result.append([a, b, c]) + return result +""", + + 'linear_loop': """ +from math import sqrt + +def triplets_with_sum(number): + N = float(number) + triplets = [] + for c in range(int(N / 2) - 1, int((sqrt(2) - 1) * N), -1): + D = sqrt(c ** 2 - N ** 2 + 2 * N * c) + if D == int(D): + triplets.append([int((N - c - D) / 2), int((N - c + D) / 2), c]) + return triplets +""", + + 'linear_comp': """ +def triplets_with_sum(number): + def calculate_medium(small): + return (number ** 2 - 2 * number * small) / (2 * (number - small)) + + two_sides = ((int(medium), small) for small in range(3, number // 3) + if small < (medium := calculate_medium(small)) + and medium.is_integer()) + + return [[small, medium, (medium ** 2 + small ** 2) ** 0.5] + for medium, small in two_sides] +""" +} + +# Workaround for needing to do fewer runs with slow code + +run_params = { + 'cubic': (5, n_values[:5]), + 'quad_loose': (0, n_values[:-1]), + 'quad_tight': (0, n_values[:-1]), + 'linear_loop': (1000, n_values), + 'linear_comp': (1000, n_values) +} + +# Run the timing tests - SLOW! + +for descriptor in row_headers: + loops = run_params[descriptor][0] + for n in run_params[descriptor][1]: + # ugly hack for the quadratic runs + if descriptor.startswith('quad'): + loops = 10 if n <= 10_000 else 3 + + # including a string comprehension in the timed part of the run would + # normally be a bad idea. + # For the slow runs, the overhead is insignificant in this exercise. + function_call = f"triplets_with_sum({n})" + val = timeit.timeit(function_call, code[descriptor], number=loops) / loops + + print(f"{descriptor}, n = {n:6n}: {val:.2e}") + df.loc[descriptor, str(n)] = val + +# Save the data to avoid constantly regenerating it + +df.to_feather('run_times.feather') +print("\nDataframe saved to './run_times.feather'") + +# The next bit will be useful for `introduction.md` +pd.options.display.float_format = '{:,.2e}'.format +print('\nDataframe in Markdown format:\n') +print(df.to_markdown(floatfmt=".1e")) + + +# To plot and fit the slopes, the df needs to be log10-transformed and transposed + +pd.options.display.float_format = '{:,.2g}'.format +log_n_values = np.log10(n_values) +df[df == 0.0] = np.nan +transposed = np.log10(df).T +transposed = transposed.set_axis(log_n_values, axis=0) +transposed.to_feather('transposed_logs.feather') +print("\nDataframe saved to './transposed_logs.feather'") diff --git a/exercises/practice/pythagorean-triplet/.articles/performance/code/create_plots.py b/exercises/practice/pythagorean-triplet/.articles/performance/code/create_plots.py new file mode 100644 index 00000000000..59fa8896e10 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.articles/performance/code/create_plots.py @@ -0,0 +1,42 @@ +import matplotlib as mpl +import matplotlib.pyplot as plt +import pandas as pd + + +# These dataframes are slow to create, so they should be saved in Feather format + +try: + df = pd.read_feather('./run_times.feather') +except FileNotFoundError: + print("File './run_times.feather' not found!") + print("Please run './Benchmark.py' to create it.") + exit(1) + +try: + transposed = pd.read_feather('./transposed_logs.feather') +except FileNotFoundError: + print("File './transposed_logs.feather' not found!") + print("Please run './Benchmark.py' to create it.") + exit(1) + +# Ready to start creating plots + +mpl.rcParams['axes.labelsize'] = 18 + +# bar plot of actual run times +ax = df.plot.bar(figsize=(10, 7), + logy=True, + ylabel="time (s)", + fontsize=14, + width=0.8, + rot=0) +plt.savefig('../timeit_bar_plot.svg') + +# log-log plot of times vs n, to see slopes +transposed.plot(figsize=(8, 6), + marker='.', + markersize=10, + ylabel="$log_{10}(time)$ (s)", + xlabel="$log_{10}(n)$", + fontsize=14) +plt.savefig('../slopes.svg') diff --git a/exercises/practice/pythagorean-triplet/.articles/performance/code/fit_gradients.py b/exercises/practice/pythagorean-triplet/.articles/performance/code/fit_gradients.py new file mode 100644 index 00000000000..22ace93f819 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.articles/performance/code/fit_gradients.py @@ -0,0 +1,38 @@ +import pandas as pd +import numpy as np +from numpy.linalg import lstsq + + +# These dataframes are slow to create, so they should be saved in Feather format + +try: + transposed = pd.read_feather('./transposed_logs.feather') +except FileNotFoundError: + print("File './transposed_logs.feather' not found!") + print("Please run './Benchmark.py' to create it.") + exit(1) + +n_values = (12, 30, 100, 300, 1_000, 3_000, 10_000, 30_000, 100_000) +log_n_values = np.log10(n_values) +row_headers = ["cubic", "quad_loose", "quad_tight", "linear_loop", "linear_comp"] + + +# Do a least-squares fit to get the slopes, working around missing values +# Apparently, it does need to be this complicated + +def find_slope(name): + log_times = transposed[name] + missing = np.isnan(log_times) + log_times = log_times[~missing] + valid_entries = len(log_times) + A = np.vstack([log_n_values[:valid_entries], np.ones(valid_entries)]).T + m, _ = lstsq(A, log_times, rcond=None)[0] + return m + + +# Print the slope results +slopes = [(name, find_slope(name)) for name in row_headers] +print('\nSlopes of log-log plots:') +for name, slope in slopes: + print(f'{name:>14} : {slope:.2f}') + diff --git a/exercises/practice/pythagorean-triplet/.articles/performance/code/run_times.feather b/exercises/practice/pythagorean-triplet/.articles/performance/code/run_times.feather new file mode 100644 index 0000000000000000000000000000000000000000..6e93c9c3705b8846b936ce53a60118d5d2c7a26a GIT binary patch literal 5426 zcmeHLVQ5=b6uxcKHk4RXXr-NHb`O;aZI-mF%rf4~(1}y0tZs9-ZrN+{+NHj{#5Ac5 zAubk#{u?p}YjwrxkI7b8Q$a-bV?VTRE|oH!PMrz^#W}H?18u=s&$;j3G<|ugINu+k z7w)_7ob#RY-FtKIx$kCEXXn$;dI(v7@_r>DPJ*S1RFeu)PVDdorNoxgJ3zEC{cGj; z4rZvRx5E={cKWWzs(|;1tOewLLiPYYDFn5EGU)S%)p$tj7do1D3qGH&QM2HQALi0qcriT!S1LW zO<>-MB;;r`{334o0VUcW3~Q+Mw7T3bj{tdrv*#IGT^`qZAqBY>^Yw?2kcXjmL3Lwt zhB^byn@}TAy-+c4$NpMa0Ao4k{IyI64XJ@zkdvw1M*YThzcTczr+Rom!CbHZHiV2( zOewik1Pez2q3Z_PPZ-t)XAtzKATDBQTxfr1Sa%UDjtk>LLj;N`C0)y4F)m`CV%Xt4 zY%S<99+onw5SI)v+{7|Ch)Kp6?ott4DUC`SbPIlhvbI}M`-VgNi zw88_hh{ri6q?DfDyTFKj#^QiqMnAzFG$JXp5sc%+fw1BLjT0i{xsB)dAgmL_U<{md zUhr2({yMHOX(guf8f;wV_O!vyWo|!XuydK)>BmMs2G=(fZuc@7*Cm!+2Ky0%eZpYB zZm`>N!lCf^k1?6Y-^b*39Y&5YnTa`h9aEfT_RSmsE$-R;`9_mfG8;}V-apBFc^LUI zKVY#5TkH3Ehd@k^j*N_C?AOE|C{v$b5K(S8mk}qA&K~bkj*3_B_t?i1$3*I9;*a|8 z?prHOXR}$`+&qo*>)wmvSD*gz%+0d5#S=F!Z2fujWKn;h9eA!WB?0ez?H6tdWM4M_|GK2?pW;w{~96v2Ck6$y6<0-+V_62W%pY@h_77Rw&CQ#KXrdT?`-}+Os~cX zC^9dE%pI7{UVh;8-7`2pJMY;zDW#`6KD4wcl&-lntuM^JhFL(SZNe-`Q8b& z0qVU_`S#1bFuV(FH;6yp9rV8tmjgaE9PU@D$V4pIyFaD|HARlzMb1R@H@LKfBH(}p z()I$jcm_HkV`4UTfP{Gzv$YqVI3~=S_#2&tyA>9mFY@!SZoy|2^V>Gzud)BWlvIKMa1|4RP`vykWi literal 0 HcmV?d00001 diff --git a/exercises/practice/pythagorean-triplet/.articles/performance/code/transposed_logs.feather b/exercises/practice/pythagorean-triplet/.articles/performance/code/transposed_logs.feather new file mode 100644 index 0000000000000000000000000000000000000000..aacb2e1c55fe3425a528b60980a3bd4c3ab15631 GIT binary patch literal 4010 zcmeHKZ%kWN6u+YdI;dU89|1?SdoFQq$ObMr8A>r|SO%HEjM;E`Jla>*<@LQ3+NjCjprrFFKb6M07A#pUQ!E^4r4_YW$V%P^0 zZgSs!=bYa?=ib|MfA6%Su5S11MTE>k8%QR^POzmB8%ZI_!~$>7R+;DYRuIixpKr!@ zFhfVZ1)gZLuy@^83cULz)h)FUa>qnShu~G+a=Yl#l#owt5lS5mf+*@bMQ)W9ajPf_ z`wJX`j?hgq19k|i>f%yyx(bAB=Ls}NkFCa()!#4TiAycrzrqMMcwE*mmM$#@il7;Gnnp06VV)f#+zdtMm0$Lj$ ze5)L2@oFk+i;4@k78VJRm`3;*V{u_o;WnWYt~{^@Ar2sh8!(=M9)#|Mj(lMOt^w9C zA=hl+IvB`W=!>`jbSVsO@DdBkkgYlSJ#KZ9eOW=_M*#%>H zPRKEizxE(JyR3v<=6E~F0&Ywpsf;b{oKy94h}{p+Fk##RSj6L55|YH8;A-|=IM*yD zA@Amb9hut7yb_a{ps!vX&QMm+*cmhHn0uf_4Ek8j5c?lXc@-u`BitDb3F6MqTNlNJWps5H_m!@9 zXVaTw7pI4n40^6He{Af6mEQSzWA!9Srair*{rTh3k%sEWXY2*zBVotT`9SlHa!-9_ zS55Q%a@{>&#D9qY{h3pH)9>cc3ERorH#cX~q|Z*aP7K?r@cw>hWNBF^%tRH zfA$xL8m2}*-1pJTk^0X^wp?2`_)YC^@$na&EAH>ai*2@CEswgsq?>yG&ZV}j%Irf| za_DH)_2)1AnoUo2dY|4jl}USsYfW{w4C-oZk$;G;p&#rzU42-v(t~duyL$e;RLb_5 z!2j)F`jZYJkCqNp_{=}%&;v&=AO7jtN9ldf8{tpNvZ&x_vq>*y(6dJd``+1Sr|s5H zcXW4K>5L~(=)Yv4yX2C#sxosz|1Ut$sierq<45Z&C(9#$_W!bTa2kLAJ|~^|YHGu` zCg&QGX|a>cbUXROWOp|H;|}-TTkB+tC0Tsb>p0XQw_4$bBZF8$((kmHl!1ODbi5CE z2hJkdK1pNB Date: Mon, 22 Jan 2024 12:19:33 -0800 Subject: [PATCH 037/277] [Currency Exchange]: Update stub & exemplar with Module Docstring (#3600) * [Currency Exchange]: Update stub with Module Docstring Sub file was missing a docstring and tripping a "missing module docstring" warning in the Analyzer. * [Currency Exchange]: Update exemplar.py Added same docstring to exemplar file that was added to the stub file. * Update exchange.py removed numeric stack doc * Update exemplar.py Removed numeric stack doc. [no important files changed] --- exercises/concept/currency-exchange/.meta/exemplar.py | 7 +++++++ exercises/concept/currency-exchange/exchange.py | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/exercises/concept/currency-exchange/.meta/exemplar.py b/exercises/concept/currency-exchange/.meta/exemplar.py index e452818f240..1cd9b2262ee 100644 --- a/exercises/concept/currency-exchange/.meta/exemplar.py +++ b/exercises/concept/currency-exchange/.meta/exemplar.py @@ -1,3 +1,10 @@ +"""Functions for calculating steps in exchaning currency. + +Python numbers documentation: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex + +Overview of exchanging currency when travelling: https://www.compareremit.com/money-transfer-tips/guide-to-exchanging-currency-for-overseas-travel/ +""" + def exchange_money(budget, exchange_rate): """ diff --git a/exercises/concept/currency-exchange/exchange.py b/exercises/concept/currency-exchange/exchange.py index e05c7bcc54d..c7088f61835 100644 --- a/exercises/concept/currency-exchange/exchange.py +++ b/exercises/concept/currency-exchange/exchange.py @@ -1,3 +1,12 @@ +"""Functions for calculating steps in exchaning currency. + +Python numbers documentation: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex + +Overview of exchanging currency when travelling: https://www.compareremit.com/money-transfer-tips/guide-to-exchanging-currency-for-overseas-travel/ +""" + + + def exchange_money(budget, exchange_rate): """ From 7fad60db57a39194ff06129dbae80b96d1cacaab Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 22 Jan 2024 14:55:52 -0800 Subject: [PATCH 038/277] [Python Docs]: Update Python Version in INSTALLATION.md (#3601) Changed version from Python `3.11.2` to `3.11.5` --- docs/INSTALLATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 04f9c899349..7be6910710d 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -18,7 +18,7 @@ Some quick links into the documentation by operating system: We recommend reviewing some of the methods outlined in the Real Python article [Installing Python][installing-python] or the articles by Brett Cannon linked above. -Exercism tests and tooling currently support `3.7` - `3.11.2` (_tests_) and [`Python 3.11.2`][311-new-features] (_tooling_). +Exercism tests and tooling currently support `3.7` - `3.11.5` (_tests_) and [`Python 3.11.5`][311-new-features] (_tooling_). Exceptions to this support are noted where they occur. Most of the exercises will work with `Python 3.6+`, or even earlier versions. But we don't guarantee support for versions not listed under [Active Python Releases][active-python-releases]. From ac97b5e01e77f3714b1020f6c9357c72cbef3145 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 24 Jan 2024 13:28:14 -0800 Subject: [PATCH 039/277] [Parallel Letter Frequency]: Mark as Foregone in Config.json and Remove Exercise Directory (#3603) * Marked parallel-letter-frequency as foregone. * Removed string-formattig prerequisites and set dict-methods live to students. * Added parallel-letter-frequency back into depricated array. * Removed parallel-letter-frequency exercise directory. --- config.json | 15 +---- .../.docs/instructions.md | 7 --- .../.meta/config.json | 25 -------- .../.meta/example.py | 51 --------------- .../.meta/tests.toml | 62 ------------------- .../parallel_letter_frequency.py | 2 - .../parallel_letter_frequency_test.py | 61 ------------------ 7 files changed, 2 insertions(+), 221 deletions(-) delete mode 100644 exercises/practice/parallel-letter-frequency/.docs/instructions.md delete mode 100644 exercises/practice/parallel-letter-frequency/.meta/config.json delete mode 100644 exercises/practice/parallel-letter-frequency/.meta/example.py delete mode 100644 exercises/practice/parallel-letter-frequency/.meta/tests.toml delete mode 100644 exercises/practice/parallel-letter-frequency/parallel_letter_frequency.py delete mode 100644 exercises/practice/parallel-letter-frequency/parallel_letter_frequency_test.py diff --git a/config.json b/config.json index 6afd5046809..754ca9f3fdc 100644 --- a/config.json +++ b/config.json @@ -150,7 +150,7 @@ "uuid": "5ac0c40c-4038-47b8-945b-8480e4a3f44c", "concepts": ["dict-methods"], "prerequisites": ["dicts"], - "status": "wip" + "status": "beta" }, { "slug": "locomotive-engineer", @@ -206,7 +206,6 @@ "comprehensions", "loops", "sequences", - "string-formatting", "string-methods", "tuples" ], @@ -921,7 +920,6 @@ "loops", "numbers", "strings", - "string-formatting", "string-methods" ], "difficulty": 2 @@ -2190,15 +2188,6 @@ "difficulty": 3, "status": "deprecated" }, - { - "slug": "parallel-letter-frequency", - "name": "Parallel Letter Frequency", - "uuid": "da03fca4-4606-48d8-9137-6e40396f7759", - "practices": [], - "prerequisites": [], - "difficulty": 3, - "status": "deprecated" - }, { "slug": "point-mutations", "name": "Point Mutations", @@ -2245,7 +2234,7 @@ "status": "deprecated" } ], - "foregone": ["lens-person"] + "foregone": ["lens-person", "parallel-letter-frequency"] }, "concepts": [ { diff --git a/exercises/practice/parallel-letter-frequency/.docs/instructions.md b/exercises/practice/parallel-letter-frequency/.docs/instructions.md deleted file mode 100644 index 85abcf86a42..00000000000 --- a/exercises/practice/parallel-letter-frequency/.docs/instructions.md +++ /dev/null @@ -1,7 +0,0 @@ -# Instructions - -Count the frequency of letters in texts using parallel computation. - -Parallelism is about doing things in parallel that can also be done sequentially. -A common example is counting the frequency of letters. -Create a function that returns the total frequency of each letter in a list of texts and that employs parallelism. diff --git a/exercises/practice/parallel-letter-frequency/.meta/config.json b/exercises/practice/parallel-letter-frequency/.meta/config.json deleted file mode 100644 index 3945e3f2324..00000000000 --- a/exercises/practice/parallel-letter-frequency/.meta/config.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "blurb": "Count the frequency of letters in texts using parallel computation.", - "authors": [ - "forgeRW" - ], - "contributors": [ - "behrtam", - "cmccandless", - "Dog", - "kytrinyx", - "N-Parsons", - "tqa236" - ], - "files": { - "solution": [ - "parallel_letter_frequency.py" - ], - "test": [ - "parallel_letter_frequency_test.py" - ], - "example": [ - ".meta/example.py" - ] - } -} diff --git a/exercises/practice/parallel-letter-frequency/.meta/example.py b/exercises/practice/parallel-letter-frequency/.meta/example.py deleted file mode 100644 index 5a16fb31c17..00000000000 --- a/exercises/practice/parallel-letter-frequency/.meta/example.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -from collections import Counter -from threading import Lock, Thread -from time import sleep -from queue import Queue - - -TOTAL_WORKERS = 3 # Maximum number of threads chosen arbitrarily - -class LetterCounter: - - def __init__(self): - self.lock = Lock() - self.value = Counter() - - def add_counter(self, counter_to_add): - self.lock.acquire() - try: - self.value = self.value + counter_to_add - finally: - self.lock.release() - - -def count_letters(queue_of_texts, letter_to_frequency, worker_id): - while not queue_of_texts.empty(): - sleep(worker_id + 1) - line_input = queue_of_texts.get() - if line_input is not None: - letters_in_line = Counter(idx for idx in line_input.lower() if idx.isalpha()) - letter_to_frequency.add_counter(letters_in_line) - queue_of_texts.task_done() - if line_input is None: - break - - -def calculate(list_of_texts): - queue_of_texts = Queue() - for line in list_of_texts: - queue_of_texts.put(line) - letter_to_frequency = LetterCounter() - threads = [] - for idx in range(TOTAL_WORKERS): - worker = Thread(target=count_letters, args=(queue_of_texts, letter_to_frequency, idx)) - worker.start() - threads.append(worker) - queue_of_texts.join() - for _ in range(TOTAL_WORKERS): - queue_of_texts.put(None) - for thread in threads: - thread.join() - return letter_to_frequency.value diff --git a/exercises/practice/parallel-letter-frequency/.meta/tests.toml b/exercises/practice/parallel-letter-frequency/.meta/tests.toml deleted file mode 100644 index 6cf36e6fd2d..00000000000 --- a/exercises/practice/parallel-letter-frequency/.meta/tests.toml +++ /dev/null @@ -1,62 +0,0 @@ -# This is an auto-generated file. -# -# Regenerating this file via `configlet sync` will: -# - Recreate every `description` key/value pair -# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications -# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) -# - Preserve any other key/value pair -# -# As user-added comments (using the # character) will be removed when this file -# is regenerated, comments can be added via a `comment` key. - -[c054d642-c1fa-4234-8007-9339f2337886] -description = "no texts" -include = false - -[818031be-49dc-4675-b2f9-c4047f638a2a] -description = "one text with one letter" -include = false - -[c0b81d1b-940d-4cea-9f49-8445c69c17ae] -description = "one text with multiple letters" -include = false - -[708ff1e0-f14a-43fd-adb5-e76750dcf108] -description = "two texts with one letter" -include = false - -[1b5c28bb-4619-4c9d-8db9-a4bb9c3bdca0] -description = "two texts with multiple letters" -include = false - -[6366e2b8-b84c-4334-a047-03a00a656d63] -description = "ignore letter casing" -include = false - -[92ebcbb0-9181-4421-a784-f6f5aa79f75b] -description = "ignore whitespace" -include = false - -[bc5f4203-00ce-4acc-a5fa-f7b865376fd9] -description = "ignore punctuation" -include = false - -[68032b8b-346b-4389-a380-e397618f6831] -description = "ignore numbers" -include = false - -[aa9f97ac-3961-4af1-88e7-6efed1bfddfd] -description = "Unicode letters" -include = false - -[7b1da046-701b-41fc-813e-dcfb5ee51813] -description = "combination of lower- and uppercase letters, punctuation and white space" -include = false - -[4727f020-df62-4dcf-99b2-a6e58319cb4f] -description = "large texts" -include = false - -[adf8e57b-8e54-4483-b6b8-8b32c115884c] -description = "many small texts" -include = false diff --git a/exercises/practice/parallel-letter-frequency/parallel_letter_frequency.py b/exercises/practice/parallel-letter-frequency/parallel_letter_frequency.py deleted file mode 100644 index 1b9e1f7f9cf..00000000000 --- a/exercises/practice/parallel-letter-frequency/parallel_letter_frequency.py +++ /dev/null @@ -1,2 +0,0 @@ -def calculate(text_input): - pass diff --git a/exercises/practice/parallel-letter-frequency/parallel_letter_frequency_test.py b/exercises/practice/parallel-letter-frequency/parallel_letter_frequency_test.py deleted file mode 100644 index 23860c7f47b..00000000000 --- a/exercises/practice/parallel-letter-frequency/parallel_letter_frequency_test.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -from collections import Counter -import unittest - -from parallel_letter_frequency import calculate - - -class ParallelLetterFrequencyTest(unittest.TestCase): - def test_one_letter(self): - actual = calculate(['a']) - expected = {'a': 1} - self.assertDictEqual(actual, expected) - - def test_case_insensitivity(self): - actual = calculate(['aA']) - expected = {'a': 2} - self.assertDictEqual(actual, expected) - - def test_numbers(self): - actual = calculate(['012', '345', '6789']) - expected = {} - self.assertDictEqual(actual, expected) - - def test_punctuations(self): - actual = calculate([r'[]\;,', './{}|', ':"<>?']) - expected = {} - self.assertDictEqual(actual, expected) - - def test_whitespaces(self): - actual = calculate([' ', '\t ', '\n\n']) - expected = {} - self.assertDictEqual(actual, expected) - - def test_repeated_string_with_known_frequencies(self): - letter_frequency = 3 - text_input = 'abc\n' * letter_frequency - actual = calculate(text_input.split('\n')) - expected = {'a': letter_frequency, 'b': letter_frequency, - 'c': letter_frequency} - self.assertDictEqual(actual, expected) - - def test_multiline_text(self): - text_input = "3 Quotes from Excerism Homepage:\n" + \ - "\tOne moment you feel like you're\n" + \ - "getting it. The next moment you're\n" + \ - "stuck.\n" + \ - "\tYou know what it’s like to be fluent.\n" + \ - "Suddenly you’re feeling incompetent\n" + \ - "and clumsy.\n" + \ - "\tHaphazard, convoluted code is\n" + \ - "infuriating, not to mention costly. That\n" + \ - "slapdash explosion of complexity is an\n" + \ - "expensive yak shave waiting to\n" + \ - "happen." - actual = calculate(text_input.split('\n')) - expected = Counter([x for x in text_input.lower() if x.isalpha()]) - self.assertDictEqual(actual, expected) - - -if __name__ == '__main__': - unittest.main() From 70acb845ed48d3a428666c6ef4accb799208d3e2 Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Thu, 25 Jan 2024 14:09:26 +0000 Subject: [PATCH 040/277] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#3604)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/42a096591ac4a748b6c4daf402d46c7eed640a1a --- .../workflows/no-important-files-changed.yml | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/no-important-files-changed.yml diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml new file mode 100644 index 00000000000..39a6551747b --- /dev/null +++ b/.github/workflows/no-important-files-changed.yml @@ -0,0 +1,67 @@ +name: No important files changed + +on: + pull_request: + types: [opened] + branches: [main] + +permissions: + pull-requests: write + +jobs: + no_important_files_changed: + name: No important files changed + runs-on: ubuntu-22.04 + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Check if important files changed + id: check + run: | + set -exo pipefail + + # fetch a ref to the main branch so we can diff against it + git remote set-branches origin main + git fetch --depth 1 origin main + + for changed_file in $(git diff --diff-filter=M --name-only origin/main); do + if ! echo "$changed_file" | grep --quiet --extended-regexp 'exercises/(practice|concept)' ; then + continue + fi + slug="$(echo "$changed_file" | sed --regexp-extended 's#exercises/[^/]+/([^/]+)/.*#\1#' )" + path_before_slug="$(echo "$changed_file" | sed --regexp-extended "s#(.*)/$slug/.*#\\1#" )" + path_after_slug="$( echo "$changed_file" | sed --regexp-extended "s#.*/$slug/(.*)#\\1#" )" + + if ! [ -f "$path_before_slug/$slug/.meta/config.json" ]; then + # cannot determine if important files changed without .meta/config.json + continue + fi + + # returns 0 if the filter matches, 1 otherwise + # | contains($path_after_slug) + if jq --exit-status \ + --arg path_after_slug "$path_after_slug" \ + '[.files.test, .files.invalidator, .files.editor] | flatten | index($path_after_slug)' \ + "$path_before_slug/$slug/.meta/config.json" \ + > /dev/null; + then + echo "important_files_changed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + done + + echo "important_files_changed=false" >> "$GITHUB_OUTPUT" + + - name: Suggest to add [no important files changed] + if: steps.check.outputs.important_files_changed == 'true' + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + with: + script: | + const body = "This PR touches files which potentially affect the outcome of the tests of an exercise. This will cause all students' solutions to affected exercises to be re-tested.\n\nIf this PR does **not** affect the result of the test (or, for example, adds an edge case that is not worth rerunning all tests for), **please add the following to the merge-commit message** which will stops student's tests from re-running. Please copy-paste to avoid typos.\n```\n[no important files changed]\n```\n\n For more information, refer to the [documentation](https://exercism.org/docs/building/tracks#h-avoiding-triggering-unnecessary-test-runs). If you are unsure whether to add the message or not, please ping `@exercism/maintainers-admin` in a comment. Thank you!" + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: body + }) From 93278ea72554199a1c4a8fd7737e8bccb21882a9 Mon Sep 17 00:00:00 2001 From: Tomasz Gieorgijewski <24649931+Nerwosolek@users.noreply.github.com> Date: Fri, 26 Jan 2024 19:19:52 +0100 Subject: [PATCH 041/277] Update instructions.md (#3606) A few instructions missed additional parameter in function declaration. Added. --- exercises/concept/making-the-grade/.docs/instructions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/making-the-grade/.docs/instructions.md b/exercises/concept/making-the-grade/.docs/instructions.md index 2f0c6617ae3..f7e0f3a3078 100644 --- a/exercises/concept/making-the-grade/.docs/instructions.md +++ b/exercises/concept/making-the-grade/.docs/instructions.md @@ -39,7 +39,7 @@ A student needs a score greater than **40** to achieve a passing grade on the ex The teacher you're assisting wants to find the group of students who've performed "the best" on this exam. What qualifies as "the best" fluctuates, so you need to find the student scores that are **greater than or equal to** the current threshold. -Create the function `above_threshold(student_scores)` taking `student_scores` (a `list` of grades), and `threshold` (the "top score" threshold) as parameters. +Create the function `above_threshold(student_scores, threshold)` taking `student_scores` (a `list` of grades), and `threshold` (the "top score" threshold) as parameters. This function should return a `list` of all scores that are `>=` to `threshold`. ```python @@ -85,7 +85,7 @@ Create the function `letter_grades(highest)` that takes the "highest" score on t You have a list of exam scores in descending order, and another list of student names also sorted in descending order by their exam scores. You would like to match each student name with their exam score and print out an overall class ranking. -Create the function `student_ranking(student_scores)` with parameters `student_scores` and `student_names`. +Create the function `student_ranking(student_scores, student_names)` with parameters `student_scores` and `student_names`. Match each student name on the student_names `list` with their score from the student_scores `list`. You can assume each argument `list` will be sorted from highest score(er) to lowest score(er). The function should return a `list` of strings with the format `. : `. From 7965a4f5b6d722eec0a9e76d3b9dd60336ce0605 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 Jan 2024 22:30:55 -0800 Subject: [PATCH 042/277] Bump juliangruber/read-file-action from 1.1.6 to 1.1.7 (#3607) Bumps [juliangruber/read-file-action](https://github.com/juliangruber/read-file-action) from 1.1.6 to 1.1.7. - [Release notes](https://github.com/juliangruber/read-file-action/releases) - [Commits](https://github.com/juliangruber/read-file-action/compare/02bbba9876a8f870efd4ad64e3b9088d3fb94d4b...b549046febe0fe86f8cb4f93c24e284433f9ab58) --- updated-dependencies: - dependency-name: juliangruber/read-file-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/issue-commenter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index cbe6388366d..37a459ce50f 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -13,7 +13,7 @@ jobs: - name: Read issue-comment.md id: issue-comment - uses: juliangruber/read-file-action@02bbba9876a8f870efd4ad64e3b9088d3fb94d4b + uses: juliangruber/read-file-action@b549046febe0fe86f8cb4f93c24e284433f9ab58 with: path: .github/issue-comment.md From af8053d8a7115f8092fa1ce09a958d99a3e6b600 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 30 Jan 2024 01:56:01 -0800 Subject: [PATCH 043/277] [Raindrops]: Added Instruction Append for `divmod()`, `math.fmod()`, `math.remainder()`, `%` & `operator.modulo` (#3611) * Added refences for divmod, fmod, remainder, modulo and operator.modulo * Added comma on line 6 for clarity. --- .../raindrops/.docs/instructions.append.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 exercises/practice/raindrops/.docs/instructions.append.md diff --git a/exercises/practice/raindrops/.docs/instructions.append.md b/exercises/practice/raindrops/.docs/instructions.append.md new file mode 100644 index 00000000000..9558c339345 --- /dev/null +++ b/exercises/practice/raindrops/.docs/instructions.append.md @@ -0,0 +1,21 @@ +# Instructions append + +## How this Exercise is Structured in Python + +This exercise is best solved with Python's `%` ([modulo][modulo]) operator, which returns the remainder of positive integer division. +It has a method equivalent, `operator.mod()` in the [operator module][operator-mod]. + + +Python also offers additional 'remainder' methods in the [math module][math-module]. +[`math.fmod()`][fmod] behaves like `%`, but operates on floats. +[`math.remainder()`][remainder] implements a "step closest to zero" algorithm for the remainder of division. +While we encourage you to get familiar with these methods, neither of these will exactly match the result of `%`, and are not recommended for use with this exercise. + +The built-in function [`divmod()`][divmod] will also give a remainder than matches `%` if used with two positive integers, but returns a `tuple` that needs to be unpacked. + +[divmod]: https://docs.python.org/3/library/functions.html#divmod +[fmod]: https://docs.python.org/3/library/math.html#math.fmod +[math-module]: https://docs.python.org/3/library/math.html +[modulo]: https://www.programiz.com/python-programming/operators#arithmetic +[operator-mod]: https://docs.python.org/3/library/operator.html#operator.mod +[remainder]: https://docs.python.org/3/library/math.html#math.remainder From cbbfcfdbf0ff10ad65b61098221a7226f6db3439 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 30 Jan 2024 10:56:42 +0100 Subject: [PATCH 044/277] Sync the `raindrops` exercise's docs with the latest data. (#3610) --- .../practice/raindrops/.docs/instructions.md | 26 +++++++++++-------- .../practice/raindrops/.docs/introduction.md | 3 +++ 2 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 exercises/practice/raindrops/.docs/introduction.md diff --git a/exercises/practice/raindrops/.docs/instructions.md b/exercises/practice/raindrops/.docs/instructions.md index fc61d36e99b..df644107516 100644 --- a/exercises/practice/raindrops/.docs/instructions.md +++ b/exercises/practice/raindrops/.docs/instructions.md @@ -1,20 +1,24 @@ # Instructions -Your task is to convert a number into a string that contains raindrop sounds corresponding to certain potential factors. -A factor is a number that evenly divides into another number, leaving no remainder. -The simplest way to test if one number is a factor of another is to use the [modulo operation][modulo]. +Your task is to convert a number into its corresponding raindrop sounds. -The rules of `raindrops` are that if a given number: +If a given number: -- has 3 as a factor, add 'Pling' to the result. -- has 5 as a factor, add 'Plang' to the result. -- has 7 as a factor, add 'Plong' to the result. -- _does not_ have any of 3, 5, or 7 as a factor, the result should be the digits of the number. +- is divisible by 3, add "Pling" to the result. +- is divisible by 5, add "Plang" to the result. +- is divisible by 7, add "Plong" to the result. +- **is not** divisible by 3, 5, or 7, the result should be the number as a string. ## Examples -- 28 has 7 as a factor, but not 3 or 5, so the result would be "Plong". -- 30 has both 3 and 5 as factors, but not 7, so the result would be "PlingPlang". -- 34 is not factored by 3, 5, or 7, so the result would be "34". +- 28 is divisible by 7, but not 3 or 5, so the result would be `"Plong"`. +- 30 is divisible by 3 and 5, but not 7, so the result would be `"PlingPlang"`. +- 34 is not divisible by 3, 5, or 7, so the result would be `"34"`. +~~~~exercism/note +A common way to test if one number is evenly divisible by another is to compare the [remainder][remainder] or [modulus][modulo] to zero. +Most languages provide operators or functions for one (or both) of these. + +[remainder]: https://exercism.org/docs/programming/operators/remainder [modulo]: https://en.wikipedia.org/wiki/Modulo_operation +~~~~ diff --git a/exercises/practice/raindrops/.docs/introduction.md b/exercises/practice/raindrops/.docs/introduction.md new file mode 100644 index 00000000000..ba12100f3b8 --- /dev/null +++ b/exercises/practice/raindrops/.docs/introduction.md @@ -0,0 +1,3 @@ +# Introduction + +Raindrops is a slightly more complex version of the FizzBuzz challenge, a classic interview question. From 4e698334dc52ca14e63d727d55e7dac59622b41b Mon Sep 17 00:00:00 2001 From: colinleach Date: Wed, 31 Jan 2024 03:13:46 -0700 Subject: [PATCH 045/277] [Roman Numerals] Draft of Approaches (#3605) * [Roman Numerals] early draft of approaches * updated approach documents, many changes * fixed typos and other glitches * Update exercises/practice/roman-numerals/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/recurse-match/content.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/recurse-match/content.md Co-authored-by: BethanyG * Update exercises/practice/roman-numerals/.approaches/recurse-match/content.md Co-authored-by: BethanyG * Fixed TODO in `itertools-starmap` * Update content.md Found a mistake already * Update content.md again * Update exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md * Update exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md --------- Co-authored-by: BethanyG --- .../roman-numerals/.approaches/config.json | 60 +++++ .../.approaches/if-else/content.md | 103 +++++++++ .../.approaches/if-else/snippet.txt | 8 + .../.approaches/introduction.md | 205 ++++++++++++++++++ .../.approaches/itertools-starmap/content.md | 90 ++++++++ .../.approaches/itertools-starmap/snippet.txt | 7 + .../.approaches/loop-over-romans/content.md | 125 +++++++++++ .../.approaches/loop-over-romans/snippet.txt | 8 + .../.approaches/recurse-match/content.md | 57 +++++ .../.approaches/recurse-match/snippet.txt | 8 + .../.approaches/table-lookup/content.md | 68 ++++++ .../.approaches/table-lookup/snippet.txt | 8 + 12 files changed, 747 insertions(+) create mode 100644 exercises/practice/roman-numerals/.approaches/config.json create mode 100644 exercises/practice/roman-numerals/.approaches/if-else/content.md create mode 100644 exercises/practice/roman-numerals/.approaches/if-else/snippet.txt create mode 100644 exercises/practice/roman-numerals/.approaches/introduction.md create mode 100644 exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md create mode 100644 exercises/practice/roman-numerals/.approaches/itertools-starmap/snippet.txt create mode 100644 exercises/practice/roman-numerals/.approaches/loop-over-romans/content.md create mode 100644 exercises/practice/roman-numerals/.approaches/loop-over-romans/snippet.txt create mode 100644 exercises/practice/roman-numerals/.approaches/recurse-match/content.md create mode 100644 exercises/practice/roman-numerals/.approaches/recurse-match/snippet.txt create mode 100644 exercises/practice/roman-numerals/.approaches/table-lookup/content.md create mode 100644 exercises/practice/roman-numerals/.approaches/table-lookup/snippet.txt diff --git a/exercises/practice/roman-numerals/.approaches/config.json b/exercises/practice/roman-numerals/.approaches/config.json new file mode 100644 index 00000000000..d824f6dd801 --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/config.json @@ -0,0 +1,60 @@ +{ + "introduction": { + "authors": [ + "colinleach", + "BethanyG" + ] + }, + "approaches": [ + { + "uuid": "69c84b6b-5e58-4b92-a2c4-17dd7d353087", + "slug": "if-else", + "title": "If Else", + "blurb": "Use booleans to find the correct translation for each digit.", + "authors": [ + "BethanyG", + "colinleach" + ] + }, + { + "uuid": "4ed8396c-f2c4-4072-abc9-cc8fe2780a5a", + "slug": "loop-over-romans", + "title": "Loop Over Romans", + "blurb": "Test Roman Numerals from the largest down and eat the maximum possible at each step.", + "authors": [ + "BethanyG", + "colinleach" + ] + }, + { + "uuid": "ba022b7e-5ea8-4432-ab94-6e9be200a70b", + "slug": "table-lookup", + "title": "Table Lookup", + "blurb": "Use a 2-D lookup table to eliminate loop nesting.", + "authors": [ + "BethanyG", + "colinleach" + ] + }, + { + "uuid": "3d6df007-455f-4210-922b-63d5a24bfaf8", + "slug": "itertools-starmap", + "title": "Itertools Starmap", + "blurb": "Use itertools.starmap() for an ingenious functional approach.", + "authors": [ + "BethanyG", + "colinleach" + ] + }, + { + "uuid": "a492c6b4-3780-473d-a2e6-a1c3e3da4f81", + "slug": "recurse-match", + "title": "Recurse Match", + "blurb": "Combine recursive programming with the recently-introduced structural pattern matching.", + "authors": [ + "BethanyG", + "colinleach" + ] + } + ] +} diff --git a/exercises/practice/roman-numerals/.approaches/if-else/content.md b/exercises/practice/roman-numerals/.approaches/if-else/content.md new file mode 100644 index 00000000000..798075f1fe9 --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/if-else/content.md @@ -0,0 +1,103 @@ +# If Else + +```python +def roman(number): + # The notation: I, V, X, L, C, D, M = 1, 5, 10, 50, 100, 500, 1000 + m = number // 1000 + m_rem = number % 1000 + c = m_rem // 100 + c_rem = m_rem % 100 + x = c_rem // 10 + x_rem = c_rem % 10 + i = x_rem + + res = '' + + if m > 0: + res += m * 'M' + + if 4 > c > 0: + res += c * 'C' + elif c == 4: + res += 'CD' + elif 9 > c > 4: + res += 'D' + ((c - 5) * 'C') + elif c == 9: + res += 'CM' + + if 4 > x > 0: + res += x * 'X' + elif x == 4: + res += 'XL' + elif 9 > x > 4: + res += 'L' + ((x - 5) * 'X') + elif x == 9: + res += 'XC' + + if 4 > i > 0: + res += i * 'I' + elif i == 4: + res += 'IV' + elif 9 > i > 4: + res += 'V' + ((i - 5) * 'I') + elif i == 9: + res += 'IX' + + return res +``` + +This gets the job done. +Something like it would work in most languages, though Python's range test (`a > x > b`) saves some boolean logic. + +## Refactoring + +The code above is quite long and a bit repetitive. +We should explore ways to make it more concise. + +The first block is just a way to extract the digits from the input number. +This can be done with a list comprehension, left-padding with zeros as necessary: + +```python +digits = ([0, 0, 0, 0] + [int(d) for d in str(number)])[-4:] +``` + +The blocks for hundreds, tens and units are all essentially the same, so we can put that code in a function. +We just need to pass in the digit, plus a tuple of translations for `(1, 4, 5, 9)` or their 10x and 100x equivalents. + +It is also unnecessary to keep retesting the lower bounds within an `elif`, as the code line will only be reached if that is satisfied. + +Using `return` instead of `elif` is a matter of personal preference. +Given that, the code simplifies to: + +```python +def roman(number: int) -> str: + def translate_digit(digit: int, translations: iter) -> str: + assert isinstance(digit, int) and 0 <= digit <= 9 + + units, four, five, nine = translations + if digit < 4: + return digit * units + if digit == 4: + return four + if digit < 9: + return five + (digit - 5) * units + return nine + + assert isinstance(number, int) + m, c, x, i = ([0, 0, 0, 0] + [int(d) for d in str(number)])[-4:] + res = '' + + if m > 0: + res += m * 'M' + if c > 0: + res += translate_digit(c, ('C', 'CD', 'D', 'CM')) + if x > 0: + res += translate_digit(x, ('X', 'XL', 'L', 'XC')) + if i > 0: + res += translate_digit(i, ('I', 'IV', 'V', 'IX')) + + return res +``` + +The last few lines are quite similar and it would be possible to refactor them into a loop, but this is enough to illustrate the principle. + diff --git a/exercises/practice/roman-numerals/.approaches/if-else/snippet.txt b/exercises/practice/roman-numerals/.approaches/if-else/snippet.txt new file mode 100644 index 00000000000..829bb41dd23 --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/if-else/snippet.txt @@ -0,0 +1,8 @@ + def translate_digit(digit: int, translations: iter) -> str: + units, four, five, nine = translations + if digit < 4: return digit * units + if digit == 4: return four + if digit < 9: return five + (digit - 5) * units + return nine + + if c > 0: res += translate_digit(c, ('C', 'CD', 'D', 'CM')) diff --git a/exercises/practice/roman-numerals/.approaches/introduction.md b/exercises/practice/roman-numerals/.approaches/introduction.md new file mode 100644 index 00000000000..4468862f0cb --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/introduction.md @@ -0,0 +1,205 @@ +# Introduction + +There is no single, obvious solution to this exercise, but a diverse array of working solutions have been used. + +## General guidance + +Roman numerals are limited to positive integers from 1 to 3999 (MMMCMXCIX). +In the version used for this exercise, the longest string needed to represent a Roman numeral is 14 characters (MMDCCCLXXXVIII). +Minor variants of the system have been used which represent 4 as IIII rather than IV, allowing for longer strings, but those are not relevant here. + +The system is inherently decimal: the number of human fingers has not changed since ancient Rome, nor the habit of using them for counting. +However, there is no zero value available, so Roman numerals represent powers of 10 with different letters (I, X, C, M), not by position (1, 10, 100, 1000). + +The approaches to this exercise break down into two groups, with many variants in each: +1. Split the input number into digits, and translate each separately. +2. Iterate through the Roman numbers, from large to small, and convert the largest valid number at each step. + +## Digit-by-digit approaches + +The concept behind this class of approaches: +1. Split the input number into decimal digits. +2. For each digit, get the Roman equivalent and append to a list. +3. Join the list into a string and return it. +Depending on the implementation, there may need to be a list-reverse step. + +### With `if` conditions + +```python +def roman(number: int) -> str: + assert isinstance(number, int) + + def translate_digit(digit: int, translations: iter) -> str: + assert isinstance(digit, int) and 0 <= digit <= 9 + + units, four, five, nine = translations + if digit < 4: + return digit * units + if digit == 4: + return four + if digit < 9: + return five + (digit - 5) * units + return nine + + m, c, x, i = ([0, 0, 0, 0] + [int(d) for d in str(number)])[-4:] + res = '' + if m > 0: + res += m * 'M' + if c > 0: + res += translate_digit(c, ('C', 'CD', 'D', 'CM')) + if x > 0: + res += translate_digit(x, ('X', 'XL', 'L', 'XC')) + if i > 0: + res += translate_digit(i, ('I', 'IV', 'V', 'IX')) + return res +``` + +See [`if-else`][if-else] for details. + +### With table lookup + +```python +def roman(number): + assert (number > 0) + + # define lookup table (as a tuple of tuples, in this case) + table = ( + ("I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"), + ("X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"), + ("C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"), + ("M", "MM", "MMM")) + + # convert the input integer to a list of single digits + digits = [int(d) for d in str(number)] + + # we need the row in the lookup table for our most-significant decimal digit + inverter = len(digits) - 1 + + # translate decimal digits list to Roman numerals list + roman_digits = [table[inverter - i][d - 1] for (i, d) in enumerate(digits) if d != 0] + + # convert the list of Roman numerals to a single string + return ''.join(roman_digits) +``` + +See [`table-lookup`][table-lookup] for details. + + +## Loop over Romans approaches + +In this class of approaches we: +1. Create a mapping from Roman to Arabic numbers, in some suitable format. (_`dicts` or `tuples` work well_) +2. Iterate nested loops, a `for` and a `while`, in either order. +3. At each step, append the largest possible Roman number to a list and subtract the corresponding value from the number being converted. +4. When the number being converted drops to zero, join the list into a string and return it. +Depending on the implementation, there may need to be a list-reverse step. + +This is one example using a dictionary: + +```python +ROMAN = {1000: 'M', 900: 'CM', 500: 'D', 400: 'CD', + 100: 'C', 90: 'XC', 50: 'L', 40: 'XL', + 10: 'X', 9: 'IX', 5: 'V', 4: 'IV', 1: 'I'} + +def roman(number: int) -> str: + result = '' + while number: + for arabic in ROMAN.keys(): + if number >= arabic: + result += ROMAN[arabic] + number -= arabic + break + return result +``` + +There are a number of variants. +See [`loop-over-romans`][loop-over-romans] for details. + +## Other approaches + +### Built-in methods + +Python has a package for pretty much everything, and Roman numerals are [no exception][roman-module]. + +```python +>>> import roman +>>> roman.toRoman(23) +'XXIII' +>>> roman.fromRoman('MMDCCCLXXXVIII') +2888 +``` + +First it is necessary to install the package with `pip` or `conda`. +Like most external packages, `roman` is not available in the Exercism test runner. + +This is the key part of the implementation on GitHub, which may look familiar: + +```python +def toRoman(n): + result = "" + for numeral, integer in romanNumeralMap: + while n >= integer: + result += numeral + n -= integer + return result +``` + +The library function is a wrapper around a `loop-over-romans` approach! + +### Recursion + +This is a recursive version of the `loop-over-romans` approach, which only works in Python 3.10 and later: + +```python +ARABIC_NUM = (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1) +ROMAN_NUM = ("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I") + +def roman(number: int) -> str: + return roman_recur(number, 0, []) + +def roman_recur(num: int, idx: int, digits: list[str]): + match (num, idx, digits): + case [_, 13, digits]: + return ''.join(digits[::-1]) + case [num, idx, digits] if num >= ARABIC_NUM[idx]: + return roman_recur(num - ARABIC_NUM[idx], idx, [ROMAN_NUM[idx],] + digits) + case [num, idx, digits]: + return roman_recur(num, idx + 1, digits) +``` + +See [`recurse-match`][recurse-match] for details. + + +### Over-use a functional approach + +```python +def roman(number): + return ''.join(one*digit if digit<4 else one+five if digit==4 else five+one*(digit-5) if digit<9 else one+ten + for digit, (one,five,ten) + in zip([int(d) for d in str(number)], ["--MDCLXVI"[-i*2-1:-i*2-4:-1] for i in range(len(str(number))-1,-1,-1)])) +``` + +*This is Python, but not as we know it*. + +As the textbooks say, further analysis of this approach is left as an exercise for the reader. + +## Which approach to use? + +In production, it would make sense to use the `roman` package. +It is debugged and supports Roman-to-Arabic conversions in addtion to the Arabic-to-Roman approaches discussed here. + +Most submissions, like the `roman` package implementation, use some variant of [`loop-over-romans`][loop-over-romans]. + +Using a [2-D lookup table][table-lookup] takes a bit more initialization, but then everthing can be done in a list comprehension instead of nested loops. +Python is relatively unusual in supporting both tuples-of-tuples and relatively fast list comprehensions, so the approach seems a good fit for this language. + +No performance article is currently included for this exercise. +The problem is inherently limited in scope by the design of Roman numerals, so any of the approaches is likely to be "fast enough". + + + +[if-else]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/if-else +[table-lookup]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/table-lookup +[loop-over-romans]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/loop-over-roman +[recurse-match]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/recurse-match +[roman-module]: https://github.com/zopefoundation/roman diff --git a/exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md b/exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md new file mode 100644 index 00000000000..95b820ec1b0 --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md @@ -0,0 +1,90 @@ +# With `itertools.starmap()` + +```python +from itertools import starmap + +def roman(number: int) -> str: + orders = [(1000, "M "), (100, "CDM"), (10, "XLC"), (1, "IVX")] + options = lambda I, V, X: ["", I, I * 2, I * 3, I + V, V, V + I, V + I * 2, V + I * 3, I + X] + compute = lambda n, chars: options(*chars)[number % (n * 10) // n] + return "".join(starmap(compute, orders)) +``` + +This approach is certainly concise and ingenious, though it takes functional programming to a level that some Python programmers might consider a little cryptic. + +The [`itertools.starmap()`][starmap] method is a variant of `map()` that takes its argument parameters pre-zipped in tuples. +It has the signature `starmap(f: function, i: iter) -> iter`. + +## Linting + +One issue with this code is the use of named lambdas. +This is discouraged by [PEP-8][pep8], and linters will complain about it. + +The underlying reason is that lambdas are intended to be anonymous functions embedded within other expressions. +Internally, they are all given the same name ``, which can greatly complicate debugging. +Their use is a particular bugbear of the Python track maintainer. + +We can refactor the code to satisfy the linter by using named `def` statements, and lowercase argument names. +Type hints are also added for documentation: + +```python +from itertools import starmap + + +def roman(number: int) -> str: + def options(i: str, v: str, x: str): + return ["", i, i * 2, i * 3, i + v, v, v + i, v + i * 2, v + i * 3, i + x] + + def compute(n: int, chars: str) -> iter: + return options(*chars)[number % (n * 10) // n] + + orders = [(1000, "M "), (100, "CDM"), (10, "XLC"), (1, "IVX")] + return "".join(starmap(compute, orders)) +``` + +## Analysis + +The central concept is that Roman letters are defined for 1, 5 and 10, times various powers of 10. + +`orders` is relatively straightforward: a list of tuples, with each tuple containing the powers of 10 and (as far as possible) the letters for that number times (1, 5, 10). +Roman numerals for 5,000 and 10,000 are not defined, so are replaced here by spaces. + +The `options()` function just takes the three letters from one of these tuples and returns a list of numerals that can be constructed from them. +For example, the 10 to 90 range: + +```python +options('X', 'L', 'C') +# => ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'] +``` + +There is no zero, so that is replaced by an empty string. + +The `compute()` function takes a tuple from `orders` plus the top-level parameter `number`, and converts the appropriate decimal digit to its Roman equivalent, returning an `iterator`. +For example the first digit of 723: + +```python +number = 723 +[x for x in compute(100, "CDM")] +# => ['D', 'C', 'C'] +``` + +The `starmap()` function ties `orders`, `options()` and `compute` together, splitting up strings and tuples as necessary to give each function the parameters it needs. +Again, an iterator is returned: + +```python +number = 723 +[x for x in starmap(compute, orders)] +# => ['', 'DCC', 'XX', 'III'] +``` + +Finally, `''.join()` converts this iterator to a single string that can be returned as the desired answer. + +Once we get past the deliberate obfuscation, it is quite an elegant approach. +Though perhaps not the most idiomatic Python. + +## Credit + +We owe this approach to @MAPKarrenbelt, who must have had fun with it. + +[starmap]: https://docs.python.org/3/library/itertools.html#itertools.starmap +[pep8]: https://peps.python.org/pep-0008/#programming-recommendations diff --git a/exercises/practice/roman-numerals/.approaches/itertools-starmap/snippet.txt b/exercises/practice/roman-numerals/.approaches/itertools-starmap/snippet.txt new file mode 100644 index 00000000000..4f5e75e6db5 --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/itertools-starmap/snippet.txt @@ -0,0 +1,7 @@ +from itertools import starmap + +def roman(number: int) -> str: + orders = [(1000, "M "), (100, "CDM"), (10, "XLC"), (1, "IVX")] + options = lambda I, V, X: ["", I, I * 2, I * 3, I + V, V, V + I, V + I * 2, V + I * 3, I + X] + compute = lambda n, chars: options(*chars)[number % (n * 10) // n] + return "".join(starmap(compute, orders)) diff --git a/exercises/practice/roman-numerals/.approaches/loop-over-romans/content.md b/exercises/practice/roman-numerals/.approaches/loop-over-romans/content.md new file mode 100644 index 00000000000..6e1d4cc847b --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/loop-over-romans/content.md @@ -0,0 +1,125 @@ +# Loop Over Roman Numerals + +```python +ROMAN = {1000: 'M', 900: 'CM', 500: 'D', 400: 'CD', + 100: 'C', 90: 'XC', 50: 'L', 40: 'XL', + 10: 'X', 9: 'IX', 5: 'V', 4: 'IV', 1: 'I'} + +def roman(number: int) -> str: + result = '' + while number: + for arabic in ROMAN.keys(): + if number >= arabic: + result += ROMAN[arabic] + number -= arabic + break + return result +``` + +This approach is one of a family, using some mapping from Arabic (decimal) to Roman numbers. + +The code above uses a dictionary. +With minor changes, we could also use nested tuples: + +```python +ROMANS = ((1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), + (90, "XC"), (50, "L"), (40, "XL"), (10, "X"), + (9, "IX"), (5, "V"), (4, "IV"), (1, "I")) + +def roman(number: int) -> str: + assert(number > 0) + + roman_num = "" + for (k, v) in ROMANS: + while k <= number: + roman_num += v + number -= k + return roman_num +``` + +Using a pair of lists is also possible, with a shared index from the `enumerate()`. + +```python +# Use a translation +numbers = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1] +names = [ 'M', 'CM','D','CD', 'C','XC','L','XL', 'X','IX','V','IV', 'I'] + +def roman(number: int) -> str: + "Take a decimal number and return Roman Numeral Representation" + + # List of Roman symbols + res = [] + + while (number > 0): + # Find the largest amount we can chip off + for i, val in enumerate(numbers): + if (number >= val): + res.append(names[i]) + number -= val + break + + return ''.join(res) +``` + +However, for a read-only lookup it may be better to use (immutable) tuples for `numbers` and `names`. + +As Roman numerals are built up from letters for 1, 5, 10 times powers of 10, it is possible to shorten the lookup and build up most of the digits programmatically: + +```python +# The 10's, 5's and 1's position chars for 1, 10, 100, 1000. +DIGIT_CHARS = ["XVI", "CLX", "MDC", "??M"] + + +def roman(number: int) -> str: + """Return the Roman numeral for a number.""" + # Generate a mapping from numeric value to Roman numeral. + mapping = [] + for position in range(len(DIGIT_CHARS) - 1, -1, -1): + # Values: 1000, 100, 10, 1 + scale = 10 ** position + chars = DIGIT_CHARS[position] + # This might be: (9, IX) or (90, XC) + mapping.append((9 * scale, chars[2] + chars[0])) + # This might be: (5, V) or (50, D) + mapping.append((5 * scale, chars[1])) + # This might be: (4, IV) or (40, XD) + mapping.append((4 * scale, chars[2] + chars[1])) + mapping.append((1 * scale, chars[2])) + + out = "" + for num, numerals in mapping: + while number >= num: + out += numerals + number -= num + return out +``` + +The code below is doing something similar to the dictionary approach at the top of this page, but more concisely: + +```python +def roman(number: int) -> str: + result = '' + divisor_map = {1000: 'M', 900: 'CM', 500: 'D', 400: 'CD', 100: 'C', 90: 'XC', + 50: 'L', 40: 'XL', 10: 'X', 9: 'IX', 5: 'V', 4: 'IV', 1: 'I'} + for divisor, symbol in divisor_map.items(): + major, number = divmod(number, divisor) + result += symbol * major + return result +``` + + +These five solutions all share some common features: +- Some sort of translation lookup. +- Nested loops, a `while`and a `for`, in either order. +- At each step, find the largest number that can be subtracted from the decimal input and appended to the Roman representation. + +When building a string gradually, it is often better to build an intermediate list, then do a `join()` at the end, as in the third example. +This is because strings are immutable, so need to be copied at each step, and the old strings need to be garbage-collected. + +However, Roman numerals are always so short that the difference is minimal in this case. + +Incidentally, notice the use of type hints: `def roman(number: int) -> str`. +This is optional in Python and (currently) ignored by the interpreter, but is useful for documentation purposes. + +Increasingly, IDE's such as VSCode and PyCharm understand the type hints, using them to flag problems and provide advice. + diff --git a/exercises/practice/roman-numerals/.approaches/loop-over-romans/snippet.txt b/exercises/practice/roman-numerals/.approaches/loop-over-romans/snippet.txt new file mode 100644 index 00000000000..2ce6c141c2c --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/loop-over-romans/snippet.txt @@ -0,0 +1,8 @@ +def roman(number): + assert(number > 0) + roman_num = "" + for (k, v) in ROMANS: + while k <= number: + roman_num += v + number -= k + return roman_num diff --git a/exercises/practice/roman-numerals/.approaches/recurse-match/content.md b/exercises/practice/roman-numerals/.approaches/recurse-match/content.md new file mode 100644 index 00000000000..3e0bb0ca5f4 --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/recurse-match/content.md @@ -0,0 +1,57 @@ +# Recursion with Pattern Matching + +```python +ARABIC_NUM = (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1) +ROMAN_NUM = ("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I") + +def roman(number: int) -> str: + return roman_recur(number, 0, []) + +def roman_recur(num: int, idx: int, digits: list[str]): + match (num, idx, digits): + case [_, 13, digits]: + return ''.join(digits[::-1]) + case [num, idx, digits] if num >= ARABIC_NUM[idx]: + return roman_recur(num - ARABIC_NUM[idx], idx, [ROMAN_NUM[idx],] + digits) + case [num, idx, digits]: + return roman_recur(num, idx + 1, digits) +``` + +[Recursion][recursion] is possible in Python, but it is much less commonly used than in some other languages. + +A limitation is the lack of tail-recursion optimization, which can easily trigger stack overflow if the recursion goes too deep. +The maximum recursion depth for Python defaults to 1000 to avoid this overflow. + +However, Roman numerals are so limited in scale that they could be an ideal use case for playing with recursion. +In practice, there is no obvious advantage to recursion over using a loop (_everything you can do with recursion you can do with a loop and vice-versa_) . + +Note the use of [structural pattern matching][pep-636], available in Python since version 3.10. +There is also an [official tutorial][structural-pattern-matching] for this new feature. + +The code above is adapted from a Scala approach, where it may be more appropriate. + +Once we get past the unfamiliar-in-Python syntax, this code is doing essentially the same as other [`loop-over-romans`][loop-over-romans] approaches. + +Without the pattern matching, a recursive approach might look something like this: + +```python +LOOKUP = [(1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), (90, "XC"), (50, "L"), + (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I")] + +def convert (number, idx, output): + if idx > 12: + return output + val, ltr = LOOKUP[idx] + if number >= val: + return convert(number - val, idx, output + ltr) + return convert(number, idx + 1, output) + +def roman(number): + return convert(number, 0, "") +``` + + +[recursion]: https://diveintopython.org/learn/functions/recursion +[pep-636]: https://peps.python.org/pep-0636/ +[structural-pattern-matching]: https://docs.python.org/3/tutorial/controlflow.html#match-statements +[loop-over-romans]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/loop-over-roman diff --git a/exercises/practice/roman-numerals/.approaches/recurse-match/snippet.txt b/exercises/practice/roman-numerals/.approaches/recurse-match/snippet.txt new file mode 100644 index 00000000000..d5b6d091a3a --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/recurse-match/snippet.txt @@ -0,0 +1,8 @@ +def roman_recur(num: int, idx: int, digits: list[str]): + match (num, idx, digits): + case [_, 13, digits]: + return ''.join(digits[::-1]) + case [num, idx, digits] if num >= ARABIC_NUM[idx]: + return roman_recur(num - ARABIC_NUM[idx], idx, [ROMAN_NUM[idx],] + digits) + case [num, idx, digits]: + return roman_recur(num, idx + 1, digits) \ No newline at end of file diff --git a/exercises/practice/roman-numerals/.approaches/table-lookup/content.md b/exercises/practice/roman-numerals/.approaches/table-lookup/content.md new file mode 100644 index 00000000000..e0ea07539f0 --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/table-lookup/content.md @@ -0,0 +1,68 @@ +# Table Lookup + +```python +def roman(number): + assert (number > 0) + + # define lookup table (as a tuple of tuples, in this case) + table = ( + ("I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"), + ("X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"), + ("C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"), + ("M", "MM", "MMM")) + + # convert the input integer to a list of single digits + digits = [int(d) for d in str(number)] + + # we need the row in the lookup table for our most-significant decimal digit + inverter = len(digits) - 1 + + # translate decimal digits list to Roman numerals list + roman_digits = [table[inverter - i][d - 1] for (i, d) in enumerate(digits) if d != 0] + + # convert the list of Roman numerals to a single string + return ''.join(roman_digits) +``` + +In this approach we loop over decimal digits, not their Roman equivalents. + +The key point is to have a 2-dimensional lookup table, with each row corresponding to a separate digit: ones, tens, hundreds, thousands. +Each digit can then be converted to its Roman equivalent with a single lookup. + +Note that we need to compensate for Python's zero-based indexing by (in effect) subtracting 1 from each row and column. + +## Optional modification + +In the code above, we used the `inverter` variable to work bottom-to-top through the lookup table. +This allows working left-to-right through the decimal digits. + +Alternatively, we could reverse the `digits` list, go top-to-bottom through the lookup table, then reverse the `roman_digits` list before the final `join()`. + +```python +def roman(number): + assert (number > 0) + + # define lookup table (as a tuple of tuples, in this case) + table = ( + ("I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"), + ("X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"), + ("C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"), + ("M", "MM", "MMM")) + + # convert the input integer to a list of single digits, in reverse order + digits = [int(d) for d in str(number)][::-1] + + # translate decimal digits list to Roman numerals list + roman_digits = [table[i][d - 1] for (i, d) in enumerate(digits) if d != 0] + + # reverse the list of Roman numerals and convert to a single string + return ''.join(roman_digits[::-1]) +``` + +This eliminates one line of code, at the cost of adding two list reverses. + +The `[::-1]` indexing is idiomatic Python, but less experienced programmers may not find it very readable. + +## Credit + +This approach was adapted from one created by @cmcaine on the Julia track. diff --git a/exercises/practice/roman-numerals/.approaches/table-lookup/snippet.txt b/exercises/practice/roman-numerals/.approaches/table-lookup/snippet.txt new file mode 100644 index 00000000000..9d69b8c5dae --- /dev/null +++ b/exercises/practice/roman-numerals/.approaches/table-lookup/snippet.txt @@ -0,0 +1,8 @@ + table = ( + ("I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"), + ("X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"), + ("C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"), + ("M", "MM", "MMM")) + digits = [int(d) for d in str(number)][::-1] + roman_digits = [table[i][d - 1] for (i, d) in enumerate(digits) if d != 0] + return ''.join(roman_digits[::-1]) From 24e9d6d5dc06cb65b74ec42d52fe86f06130fee3 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 1 Feb 2024 02:38:52 -0800 Subject: [PATCH 046/277] [Raindrops] Approaches and Performance Article (#3362) * First commit of raindrops approaches. * Placeholders and starts to content to make CI happier. * Edited to conform to configlet line limit. * One more line trimmed. * Adding regenerated darts test file to stop CI complaint. * Added approaches and benchmark article for Raindrops. * Updated branch with current track config.json from main. * Fixed config titles and directories. * Shortened performance snippet to 8 lines. * Added colin to the performance article. --- .../raindrops/.approaches/config.json | 56 ++++ .../.approaches/dict-and-join/content.md | 53 ++++ .../.approaches/dict-and-join/snippet.txt | 8 + .../.approaches/functools-reduce/content.md | 40 +++ .../.approaches/functools-reduce/snippet.txt | 8 + .../.approaches/if-statements/content.md | 47 +++ .../.approaches/if-statements/snippet.txt | 8 + .../raindrops/.approaches/introduction.md | 285 ++++++++++++++++++ .../.approaches/itertools-compress/content.md | 48 +++ .../itertools-compress/snippet.txt | 6 + .../.approaches/loop-and-fstring/content.md | 44 +++ .../.approaches/loop-and-fstring/snippet.txt | 8 + .../.approaches/sequence-with-join/content.md | 37 +++ .../sequence-with-join/snippet.txt | 8 + .../truthy-and-falsey-with-fstring/content.md | 39 +++ .../snippet.txt | 6 + .../practice/raindrops/.articles/config.json | 11 + .../.articles/performance/code/Benchmark.py | 167 ++++++++++ .../.articles/performance/content.md | 60 ++++ .../.articles/performance/snippet.md | 8 + 20 files changed, 947 insertions(+) create mode 100644 exercises/practice/raindrops/.approaches/config.json create mode 100644 exercises/practice/raindrops/.approaches/dict-and-join/content.md create mode 100644 exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt create mode 100644 exercises/practice/raindrops/.approaches/functools-reduce/content.md create mode 100644 exercises/practice/raindrops/.approaches/functools-reduce/snippet.txt create mode 100644 exercises/practice/raindrops/.approaches/if-statements/content.md create mode 100644 exercises/practice/raindrops/.approaches/if-statements/snippet.txt create mode 100644 exercises/practice/raindrops/.approaches/introduction.md create mode 100644 exercises/practice/raindrops/.approaches/itertools-compress/content.md create mode 100644 exercises/practice/raindrops/.approaches/itertools-compress/snippet.txt create mode 100644 exercises/practice/raindrops/.approaches/loop-and-fstring/content.md create mode 100644 exercises/practice/raindrops/.approaches/loop-and-fstring/snippet.txt create mode 100644 exercises/practice/raindrops/.approaches/sequence-with-join/content.md create mode 100644 exercises/practice/raindrops/.approaches/sequence-with-join/snippet.txt create mode 100644 exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/content.md create mode 100644 exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/snippet.txt create mode 100644 exercises/practice/raindrops/.articles/config.json create mode 100644 exercises/practice/raindrops/.articles/performance/code/Benchmark.py create mode 100644 exercises/practice/raindrops/.articles/performance/content.md create mode 100644 exercises/practice/raindrops/.articles/performance/snippet.md diff --git a/exercises/practice/raindrops/.approaches/config.json b/exercises/practice/raindrops/.approaches/config.json new file mode 100644 index 00000000000..ec50a5f52dd --- /dev/null +++ b/exercises/practice/raindrops/.approaches/config.json @@ -0,0 +1,56 @@ +{ + "introduction": { + "authors": ["BethanyG"] + }, + "approaches": [ + { + "uuid": "afc3b20c-c84b-45b0-97e5-5c927397274c", + "slug": "if-statements", + "title": "if statements", + "blurb": "Use a series of if statements and string concatenation.", + "authors": ["BethanyG"] + }, + { + "uuid": "55b0a949-e84e-4772-8a2c-368b491d72e2", + "slug": "loop-and-fstring", + "title": "Loop and f-string", + "blurb": "Loop through a tuple and assemble output via an f-string.", + "authors": ["BethanyG"] + }, + { + "uuid": "9785ad3a-fc52-409d-bfa4-d97926a87d22", + "slug": "sequence-with-join", + "title": "Sequence(s) with str.join()", + "blurb": "Use one or more sequences and str.join() with a generator-expression.", + "authors": ["BethanyG"] + }, + { + "uuid": "b4c3608e-d4f4-4a50-8da7-66c655241950", + "slug": "truthy-and-falsey-with-fstring", + "title": "Truthy and Falsey Values with f-strings", + "blurb": "Use ternaries to build words and an f-string to assemble the result.", + "authors": ["BethanyG"] + }, + { + "uuid": "78392dda-921b-4308-bdc7-8993898987ba", + "slug": "dict-and-join", + "title": "Dict and str.join()", + "blurb": "Use a dict to hold factors:values and str.join() with a generator-expression.", + "authors": ["BethanyG"] + }, + { + "uuid": "7e485b55-6f47-4288-ac18-d9f57fbdc0c2", + "slug": "itertools-compress", + "title": "itertools.compress", + "blurb": "Use itertools.compress() with a list mask.", + "authors": ["BethanyG"] + }, + { + "uuid": "298fbc79-7946-4cb1-b820-e2e282b1f155", + "slug": "functools-reduce", + "title": "functools.reduce", + "blurb": "Use functools.reduce() and zip().", + "authors": ["BethanyG"] + } + ] +} diff --git a/exercises/practice/raindrops/.approaches/dict-and-join/content.md b/exercises/practice/raindrops/.approaches/dict-and-join/content.md new file mode 100644 index 00000000000..ae302ada8dd --- /dev/null +++ b/exercises/practice/raindrops/.approaches/dict-and-join/content.md @@ -0,0 +1,53 @@ +# Dict and str.join() + + +```python +def convert(number): + sounds = {3: 'Pling', 5: 'Plang', 7: 'Plong'} + + results = ''.join(sounds[divisor] for + divisor, sound in sounds.items() + if number % divisor == 0) + + return results or str(number) +``` + +This approach uses a [dictionary][dict] called 'sounds' with factors as `keys` and sound strings as `values`. +A [generator-expression][generator-expressions] inside of [`str.join()`][str.join] loops through the [dictionary view object][dict-view-object] [`sounds.items()`][dict.items()], which is a series of (key, value) `tuples`. + Each `value` is looked up for every factor where number % divisor == 0. + `str.join()` then compiles the results. + +This is the equivalent of: + +```python +def convert(number): + sounds = {3: 'Pling', 5: 'Plang', 7: 'Plong'} + results = [] + + for divisor in sounds.keys(): + if number % divisor == 0: + # Looks up the value by the divisor key and appends to the results list. + results.append(sounds[divisor]) + + return ''.join(results) or str(number) +``` + +The advantage of the generator expression is that no intermediary `list` is created in memory. +This will definitely save memory, and might also be slightly faster than a "classic" loop that appends to a `list`. + +Finally, this could all be done as a 'one liner'. +But this becomes both harder to read and harder to maintain: + +```python +def convert(number): + return ''.join(sound for divisor, sound in + {3: 'Pling', 5: 'Plang', 7: 'Plong'}.items() + if (number % divisor == 0)) or str(number) +``` + +[dict-view-object]: https://docs.python.org/3/library/stdtypes.html#dictionary-view-objects +[dict.items()]: https://docs.python.org/3/library/stdtypes.html#dict.items +[dict]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries +[generator-expressions]: https://www.pythonmorsels.com/how-write-generator-expression/ +[str.join]: https://docs.python.org/3/library/stdtypes.html#str.join + diff --git a/exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt b/exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt new file mode 100644 index 00000000000..6c24abb8df0 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt @@ -0,0 +1,8 @@ +def convert(number): + sounds = {3: 'Pling', 5: 'Plang', 7: 'Plong'} + + results = ''.join(sounds[divisor] for + divisor, sound in sounds.items() + if number % divisor == 0) + + return results or str(number) diff --git a/exercises/practice/raindrops/.approaches/functools-reduce/content.md b/exercises/practice/raindrops/.approaches/functools-reduce/content.md new file mode 100644 index 00000000000..36dd2222fd7 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/functools-reduce/content.md @@ -0,0 +1,40 @@ +# Use functools.reduce() + + +```python +from functools import reduce + +def convert(number): + sounds = ('Pling','Plang','Plong') + factors = ((number % factor) == 0 for factor in (3,5,7)) #1 if no remainder, 0 if a remainder. + result = reduce(lambda sound, item : sound + (item[0] * item[1]), zip(sounds, factors), '') + + return result or str(number) +``` + +This is essentially the same strategy as the [itertools.compress()][approach-itertools-compress] approach, but uses [`functools.reduce`][functools-reduce] with a [`lambda` expression][lambda] and string multiplication as a kind of mask. + +The factors are calculated and then [`zip()`ed][zip] with the sounds into `tuples`. +Each string (_position 0 in the `tuple`_) is multiplied by the 'factor' (_a 1 if there is no remainder and a 0 if there is a remainder_). +The result is then 'reduced' to a combined result string with an empty string as an initializer. +If the result is empty, the number as a string is returned instead. + +This is an interesting use of `functools.reduce()`, but not necessarily the most readable way to solve this exercise. +Importing `functools` adds overhead and separating the sounds from the factors risks errors as the factor list expands. +The syntax of `reduce()` also obfuscates string creation/concatenation. +Unless the team maintaining this code is comfortable with `functools.reduce()`, this might be better re-written using `join()`: + + +```python +def convert(number): + sounds = ('Pling','Plang','Plong') + factors = ((number % factor) == 0 for factor in (3,5,7)) + result = ''.join((item[0] * item[1]) for item in zip(sounds, factors)) + + return result or str(number) +``` + +[lambda]: https://dbader.org/blog/python-lambda-functions +[functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[approach-itertools-compress]: https://exercism.org/tracks/python/exercises/raindrops/approaches/itertools-compress +[zip]: https://docs.python.org/3/library/functions.html#zip diff --git a/exercises/practice/raindrops/.approaches/functools-reduce/snippet.txt b/exercises/practice/raindrops/.approaches/functools-reduce/snippet.txt new file mode 100644 index 00000000000..d3b48650131 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/functools-reduce/snippet.txt @@ -0,0 +1,8 @@ +from functools import reduce + +def convert(number): + sounds = ('Pling','Plang','Plong') + factors = ((number % factor) == 0 for factor in (3,5,7)) + result = reduce(lambda sound, item : sound + (item[0] * item[1]), zip(sounds, factors), '') + + return result or str(number) \ No newline at end of file diff --git a/exercises/practice/raindrops/.approaches/if-statements/content.md b/exercises/practice/raindrops/.approaches/if-statements/content.md new file mode 100644 index 00000000000..80fe84bd926 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/if-statements/content.md @@ -0,0 +1,47 @@ +# `if` Statements + +```python +def convert(num): + sounds = '' + + if num % 3 == 0: sounds += 'Pling' + if num % 5 == 0: sounds += 'Plang' + if num % 7 == 0: sounds += 'Plong' + + return sounds or str(num) +``` + + +This approach is the most straightforward or 'naive' - it replicates in code what the instructions say, using `if` statements to check the modulo for each factor. +If the number is evenly divisible by the factor (modulo == 0), the corresponding string is concatenated to _sounds_ via the `+` operator. +Sounds is returned if it is not empty (_see [Truth Value Testing][truth-value-testing] for more info_). +Otherwise, a `str` version of the input number is returned. + +This, of course incurs the 'penalty' of string concatenation. +But since there are only three factors to check and the strings are small, the concatenation is at a minimum. + +In fact, this solution - and most others described in the approaches here - are `O(1)` time complexity. +There are a constant number of factors to iterate through, and the work that is done never increases, even as the input numbers get bigger. +This holds true for space complexity as well. + +The compact form for the `if` statements might be harder to read for some people. +These can be re-written to be nested, and the return can be re-written to use a ternary expression: + +```python +def convert(num): + sounds = '' + + if num % 3 == 0: + sounds += 'Pling' + if num % 5 == 0: + sounds += 'Plang' + if num % 7 == 0: + sounds += 'Plong' + + return sounds if sounds else str(num) +``` + +While this solution is nicely readable and to-the-point, it will grow in length and get harder to read if many more factors are added or business logic changes. +Other solutions using data structures to hold factors might be a better option in 'high change' situations. + +[truth-value-testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing diff --git a/exercises/practice/raindrops/.approaches/if-statements/snippet.txt b/exercises/practice/raindrops/.approaches/if-statements/snippet.txt new file mode 100644 index 00000000000..2cbc9a5d96a --- /dev/null +++ b/exercises/practice/raindrops/.approaches/if-statements/snippet.txt @@ -0,0 +1,8 @@ +def convert(num): + sounds = '' + + if num % 3 == 0: sounds += 'Pling' + if num % 5 == 0: sounds += 'Plang' + if num % 7 == 0: sounds += 'Plong' + + return sounds or str(num) \ No newline at end of file diff --git a/exercises/practice/raindrops/.approaches/introduction.md b/exercises/practice/raindrops/.approaches/introduction.md new file mode 100644 index 00000000000..1783fa33abf --- /dev/null +++ b/exercises/practice/raindrops/.approaches/introduction.md @@ -0,0 +1,285 @@ +# Introduction + + +The goal of the Raindrops exercise is to convert a number to a string of "raindrop sounds", based on whether the number +is divisible by any combination of 3, 5, or 7. +Numbers not divisible by any of these factors are returned as a string. + +There are many different ways to solve this exercise in Python, with many many variations. +Some strategies optimize for simplicity, others for efficiency or extendability. +Others explore interesting features of Python. + +Here, we go over 7 approaches: + + +1. Using `if` statements and `+` to assemble a return string. +2. Exploiting "Truthy" and "Falsy" values in ternary checks and an `f-string` +3. Using one or more sequences, a `loop`, and an `f-string`. +4. Using one or more sequences and a `generator-expression` within `join()`. +5. Using a `dict` of `{factors : sounds}` for lookup and a `generator-expression` within `join()`. +6. Using `itertools.compress()` and a `generator expression` within `join()`. +7. Using `functools.reduce()` and `join()`. + + +## General Guidance + + +The goal of the Raindrops exercise is to return a string that represents which factors (`3`, `5` or `7`) evenly divide the input number. +Each factor has a _sound_ assigned ('Pling' (3), 'Plang' (5), or 'Plong'(7)). + + +Determining which factors divide into the input number without a remainder can most easily be accomplished via the [modulo][modulo] `%` operator. +However, it is also possible to import the [`math`][math-module] module and use the [`math.fmod()`][fmod] function for similar results, or the general built-in [`divmod()`][divmod] . +Keep in mind that `math.fmod()` returns a _float_ and `divmod()` returns a `tuple` of (quotient, remainder) that must be unpacked before use. +Using [`math.remainder()`][remainder] is **not** recommended for this exercise. + + +The challenge is to efficiently assemble a results string while also keeping in mind how the code might be maintained or extended. +How might you add additional factors without a complete rewrite of code? +How can you keep the code concise? +Do you use string concatenation, or are there other options you can choose? +What tradeoffs will you make between readability, space, and speed of processing? + + +## Approach: Using `if` statements + +```python +def convert(num): + sounds = '' + + if num % 3 == 0: sounds += 'Pling' + if num % 5 == 0: sounds += 'Plang' + if num % 7 == 0: sounds += 'Plong' + + return sounds if sounds else str(num) +``` + +This approach is the most straightforward or 'naive' - it replicates in code what the instructions say, using `if` statements to check the modulus for each factor. +Each is then concatenated to the 'sounds' string. +If the 'sounds' string is empty, a string version of the number is returned instead. + +For more details, see the [if statement][approach-if-statements] approach. + + +## Approach: Using "Truthy" and "Falsy" Values with `f-string`s + +```python +def convert(number): + threes = '' if number % 3 else 'Pling' # Empty string if there is a remainder + fives = '' if number % 5 else 'Plang' + sevens = '' if number % 7 else 'Plong' + + return f'{threes}{fives}{sevens}' or str(number) + + ###OR### + +def convert(number): + threes = 'Pling' if not number % 3 else '' # Sound if there is NOT a remainder + fives = 'Plang' if not number % 5 else '' + sevens = 'Plong' if not number % 7 else '' + + return f'{threes}{fives}{sevens}' or str(number) +``` + +This approach uses [ternary expressions][ternary expression] (_also known as 'conditional expressions'_) to build strings for each factor. +The result strings are combined via an `f-string`, avoiding the use of `join()`, or a `loop`. +If the `f-string` is empty _(evaluating to False in a [boolean context][truth-value-testing]_), a `str` of the input number is returned instead. + +For more information, see the [Truthy and Falsy with f-string][approach-truthy-and-falsey-with-fstring] approach. + + +## Approach: Using a `loop`, and an `f-string` + + +```python +def convert(number): + sounds = '' + drops = ("i", 3), ("a", 5), ("o", 7) + + for vowel, factor in drops: + if number % factor == 0: + sounds += f'Pl{vowel}ng' + + return sounds or str(number) +``` + +This approach loops through the _drops_ `tuple` (_although any iterable sequence can be used_), unpacking each `vowel` and `factor`. +If the input number is evenly divisible by the factor (_modulus == 0_), the corresponding vowel is inserted into the `f-string` for that factor. +The `f-string` is then concatenated to _sounds_ string via `+`. + _Sounds_ is returned if it is not empty. + Otherwise, a string version of the input number is returned. + +For more details, see the [loop and f-string][approach-loop-and-fstring] approach. + + +## Approach: Using Sequence(s) with `join()` + +```python +def convert(number): + drops = ["Pling","Plang","Plong"] + factors = [3,5,7] + sounds = ''.join(drops[index] for + index, factor in + enumerate(factors) if (number % factor == 0)) + + return sounds or str(number) +``` + + +This approach is very similar to the [loop and f-string][approach-loop-and-fstring] approach, but uses two `lists` to hold factors and sounds. + It also converts the loop that calculates the remainders and sounds into a [`generator expression`][generator-expression] within `join()`, which assembles the 'sounds' string. +_Sounds_ is returned if it is not empty. + Otherwise, a `str` version of the input number is returned. + +For more information, see the [tuple with join][approach-sequence-with-join] approach. + + +## Approach: Using a `dict` and `join()` + +```python +def convert(number): + + sounds = {3: 'Pling', + 5: 'Plang', + 7: 'Plong'} + + results = ''.join(sounds[divisor] for + divisor, sound in sounds.items() + if number % divisor == 0) + + return results or str(number) +``` + +This approach uses a dictionary to hold the factor -> sound mappings and a `generator-expression` within `join()` to assemble results. +If 'results' is empty, a string version of the input number is returned. + +For more details, read the [`dict` and `join()`][approach-dict-and-join] approach. + + +## Approach: Using `itertools.compress` and a `list` Mask + +```python +from itertools import compress + +def convert(number): + sounds =('Pling','Plang','Plong') + mask = ((number % factor) == 0 for factor in (3,5,7)) + return ''.join(compress(sounds, mask)) or str(number) + +``` + + +This approach uses [`itertools.compress`][itertools-compress] to filter a list of sounds using a mask of `True` and `False` values. +The mask is formed by calculating `bool((input % factor) == 0)` for each factor (_which will return True or False_). +If the result of `itertools.compress` is empty, a string version of the input number is returned instead. + +For more details, see the [itertools.compress][approach-itertools-compress] approach. + + +## Approach: Using `functools.reduce())` and `zip()` + + +```python +from functools import reduce + +def convert(number): + sounds = ('Pling','Plang','Plong') + factors = ((number % factor) == 0 for factor in (3,5,7)) + result = reduce(lambda sound, item : sound + (item[0] * item[1]), zip(sounds, factors), '') + + return result or str(number) +``` + +This approach uses `functools.reduce` to join _sounds_ together using the `int` value of `True` (1) and `False` (0). +Sounds are combined with their corresponding factor values in pairs via `zip()`, and subsequently unpacked for use in the [`functools.reduce`][functools-reduce] [`lambda` expression][lambda]. +It is very similar to the `itertools.compress` method, but uses multiplication rather than mask to add or omit a given string value. + +For more information, read the [functools.reduce][approach-functools-reduce] approach. + + +## Other approaches + +Besides these seven approaches, there are a multitude of possible variations using different data structures and joining methods. + +One can also use the new [structural pattern matching][structural-pattern-matching], although it is both more verbose and harder to read. +It (unnecessarily) lists out all of the mask variations from the [itertools compress][itertools-compress] approach and would be hard to extend without the potential of making a mistake with the factors, the sounds, or the masks: + + +```python +def convert(number): + + match [(number % factor) == 0 for factor in (3,5,7)]: + case [True, True, True]: + return 'PlingPlangPlong' + case [True, True, False]: + return 'PlingPlang' + case [False, True, True]: + return 'PlangPlong' + case [True, False, True]: + return 'PlingPlong' + case [True, False, False]: + return 'Pling' + case [False, False, True]: + return 'Plong' + case [False, True, False]: + return 'Plang' + case _: + return str(number) +``` + + +## Which Approach to Use? + +All approaches are idiomatic, and show multiple paradigms and possibilities. + +Some additional considerations include readability and maintainability. +Approaches using separate data structures to hold sounds/factors are very helpful when additional data needs to be added, even if there is memory or performance overhead associated with them. +No one wants to add to an ever-growing block of `if-statements`, or reap the consequences of troubleshooting a typo in some strange embedded set of parenthesis. +Approaches using `join()` or `loops` are fairly succinct, and might be more easily understood by others reading your code, so that they can adjust or add to the logic. +Additionally, using an `f-string` can cut down on visual "noise" as you assemble the return string. + +So an approach that balances maintenance needs with succinctness is likely the best option: + +```python +def convert(number): + #This is clear and easily added to. Unless the factors get + # really long, this won't take up too much memory. + sounds = {3: 'Pling', + 5: 'Plang', + 7: 'Plong'} + + results = (sounds[divisor] for + divisor in sounds.keys() + if number % divisor == 0) + + return ''.join(results) or str(number) +``` + + +This separates the code that calculates the results string from the factors themselves. +If a factor needs to be added, only the dictionary needs to be touched. +This code need only iterate over the keys of the dictionary to do its calculation, making this `O(1)` in time complexity. +This does take `O(m)` space, where `m` is equal to the number of factor entries. +Since the number of factors is fixed here, this is unlikely to create issues unless a great many more are added to the 'sounds' `dict`. + +To compare the performance of this and the other approaches, take a look at the [Performance article][article-performance]. + +[approach-dict-and-join ]: https://exercism.org/tracks/python/exercises/raindrops/approaches/dict-and-join +[approach-functools-reduce]: https://exercism.org/tracks/python/exercises/raindrops/approaches/functools-reduce +[approach-if-statements]: https://exercism.org/tracks/python/exercises/raindrops/approaches/if-statements +[approach-itertools-compress]: https://exercism.org/tracks/python/exercises/raindrops/approaches/itertools-compress +[approach-loop-and-fstring]: https://exercism.org/tracks/python/exercises/raindrops/approaches/loop-and-fstring +[approach-sequence-with-join]: https://exercism.org/tracks/python/exercises/raindrops/approaches/sequence-with-join +[approach-truthy-and-falsey-with-fstring]: https://exercism.org/tracks/python/exercises/raindrops/approaches/truthy-and-falsey-with-fstring +[article-performance]: https://exercism.org/tracks/python/exercises/raindrops/articles/performance +[divmod]: https://docs.python.org/3/library/functions.html#divmod +[fmod]: https://docs.python.org/3/library/math.html#math.fmod +[functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[generator-expression]: https://peps.python.org/pep-0289/ +[itertools-compress]: https://docs.python.org/3/library/itertools.html#itertools.compress +[lambda]: https://dbader.org/blog/python-lambda-functions +[math-module]: https://docs.python.org/3/library/math.html +[modulo]: https://www.freecodecamp.org/news/the-python-modulo-operator-what-does-the-symbol-mean-in-python-solved/ +[remainder]: https://docs.python.org/3/library/math.html#math.remainder +[ternary expression]: https://docs.python.org/3/reference/expressions.html#conditional-expressions +[truth-value-testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing diff --git a/exercises/practice/raindrops/.approaches/itertools-compress/content.md b/exercises/practice/raindrops/.approaches/itertools-compress/content.md new file mode 100644 index 00000000000..b5da4768622 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/itertools-compress/content.md @@ -0,0 +1,48 @@ +# Use itertools.compress() + + +```python +from itertools import compress + +def convert(number): + sounds ='Pling','Plang','Plong' + mask = ((number % factor) == 0 for factor in (3,5,7)) + return ''.join(compress(sounds, mask)) or str(number) +``` + +This approach uses both a `tuple` for storing sound strings and mask to filter them with. +For each factor, the `generator-expression` calculates `True` (_evenly divisible_) or `False` (_remainder left over_). +This mask is used with [`itertools.compress()`][compress] to 'mask over' any unwanted values in 'sounds'. +Finally, the returned string is created with [`str.join()`][join]. +If the 'sounds' string is empty, a string version of the number is returned instead. + +This is very succinct code that avoids string concatenation. +However, it does require the overhead of importing `compress()` from the [itertools][itertools] module. +The code is also harder to maintain should there be additional factors/sounds needed. +Because the factors and sounds are seperated, there is a chance mistakes could be made like forgetting a number or swapping which factor is paired with which sound. + +A better approach for maintenance might be to turn the 'sounds' `tuple` into a dictionary where the factors and sounds can be stored separate from the logic that does the calculations and string creation: + +```python +from itertools import compress + +def convert(number): + sounds = {3 : 'Pling', + 5 : 'Plang', + 7 : 'Plong' + } + mask = ((number % factor) == 0 for factor in sounds.keys()) + return ''.join(compress(sounds.values(), mask)) or str(number) +``` + + +In this rewrite, factors are keys with the sounds as values in a dictionary. +The mask then uses a [`dict.keys()`][dict-keys] [view][view objects] to iterate over the factors and calculate the `True`/`False` values. +This mask is used with `compress()` to filter a `dict.values()` view that is used by `join()` to construct the return string. +If the string is empty, a string version of the input number is returned instead. + +[compress]: https://docs.python.org/3/library/itertools.html#itertools.compress +[dict-keys]: https://docs.python.org/3/library/stdtypes.html#dict.keys +[itertools]: https://docs.python.org/3/library/itertools.html +[join]: https://docs.python.org/3/library/stdtypes.html#str.join +[view objects]: https://docs.python.org/3/library/stdtypes.html#dictionary-view-objects diff --git a/exercises/practice/raindrops/.approaches/itertools-compress/snippet.txt b/exercises/practice/raindrops/.approaches/itertools-compress/snippet.txt new file mode 100644 index 00000000000..5a58c913f95 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/itertools-compress/snippet.txt @@ -0,0 +1,6 @@ +from itertools import compress + +def convert(number): + sounds ='Pling','Plang','Plong' + mask = ((number % factor) == 0 for factor in (3,5,7)) + return ''.join(compress(sounds, mask)) or str(number) \ No newline at end of file diff --git a/exercises/practice/raindrops/.approaches/loop-and-fstring/content.md b/exercises/practice/raindrops/.approaches/loop-and-fstring/content.md new file mode 100644 index 00000000000..41d4ce4951a --- /dev/null +++ b/exercises/practice/raindrops/.approaches/loop-and-fstring/content.md @@ -0,0 +1,44 @@ +# Sequence(s) with a Loop and f-string + + +```python +def convert(number): + sounds = '' + drops = ("i", 3), ("a", 5), ("o", 7) + + for vowel, factor in drops: + if number % factor == 0: + sounds += f'Pl{vowel}ng' + + return sounds or str(number) +``` + + +This approach loops through the _drops_ `tuple` (_although any iterable sequence(s) can be used_), unpacking each `vowel` and `factor`. +If the input number is evenly divisible by the factor (_modulus == 0_), the corresponding vowel is inserted into the `f-string` for that factor. +The `f-string` is then concatenated to _sounds_ string via `+`. + _Sounds_ is returned if it is not empty. + Otherwise, a string version of the input number is returned. + + This takes `O(1)` time and `O(1)` space. + +It is a very efficient and clever way of building up the return string, since only one vowel is changing per 'drop'. +However, it might take a moment for others reading the code to understand what exactly is going on. +It also (may) create maintenance difficulties should there be future factors and sounds that do not conform to the pattern of only changing the vowel in the sound. + +A much less exciting (_but perhaps easier to maintain_) rewrite would be to store the whole drop sound and build up the return string out of whole drops: + + +```python +def convert(number): + sounds = (3, 'Pling'), (5, 'Plang'), (7, 'Plong') + output = '' + + for factor, sound in sounds: + if number % factor == 0: + output += sound + + return output or str(number) +``` + +This has the same time and space complexity as the first variation. diff --git a/exercises/practice/raindrops/.approaches/loop-and-fstring/snippet.txt b/exercises/practice/raindrops/.approaches/loop-and-fstring/snippet.txt new file mode 100644 index 00000000000..a97a7f70b53 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/loop-and-fstring/snippet.txt @@ -0,0 +1,8 @@ +def convert(number): + sounds = '' + drops = ("i", 3), ("a", 5), ("o", 7) + + for vowel, factor in drops: + if number % factor == 0: + sounds += f'Pl{vowel}ng' + return sounds or str(number) \ No newline at end of file diff --git a/exercises/practice/raindrops/.approaches/sequence-with-join/content.md b/exercises/practice/raindrops/.approaches/sequence-with-join/content.md new file mode 100644 index 00000000000..6895052a61b --- /dev/null +++ b/exercises/practice/raindrops/.approaches/sequence-with-join/content.md @@ -0,0 +1,37 @@ +# Sequence(s) with str.join() + +```python +def convert(number): + drops = ["Pling","Plang","Plong"] + factors = [3,5,7] + sounds = ''.join(drops[index] for + index, factor in + enumerate(factors) if (number % factor == 0)) + + return sounds or str(number) +``` + +This approach is very similar to the [loop and f-string][approach-loop-and-fstring] approach, but uses two `lists` to hold factors and sounds and [`enumerate()`][enumerate] to extract an index. + It also converts the loop that calculates the remainders and sounds into a [`generator expression`][generator-expression] within `join()`. +_Sounds_ is returned if it is not empty. + Otherwise, a `str` version of the input number is returned. + + This, like most approaches described here is `O(1)` time and space complexity. + In benchmark timings, the `generator-expression` and multiple variables add a bit of overhead. + + Readability here might not be the easiest for those not used to reading generator expressions inside calls to other functions, so if that is the case, this can be re-written as: + + + ```python +def convert(number): + drops = ["Pling","Plang","Plong"] + factors = [3,5,7] + sounds = (drops[index] for index, factor in + enumerate(factors) if (number % factor == 0)) + + return ''.join(sounds) or str(number) +``` + +[approach-loop-and-fstring]: https://exercism.org/tracks/python/exercises/raindrops/approaches/loop-and-fstring +[generator-expression]: https://peps.python.org/pep-0289/ +[enumerate]: https://docs.python.org/3/library/functions.html#enumerate diff --git a/exercises/practice/raindrops/.approaches/sequence-with-join/snippet.txt b/exercises/practice/raindrops/.approaches/sequence-with-join/snippet.txt new file mode 100644 index 00000000000..2df433229e5 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/sequence-with-join/snippet.txt @@ -0,0 +1,8 @@ +def convert(number): + drops = ["Pling","Plang","Plong"] + factors = [3,5,7] + sounds = ''.join(drops[index] for + index, factor in + enumerate(factors) if (number % factor == 0)) + + return sounds or str(number) \ No newline at end of file diff --git a/exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/content.md b/exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/content.md new file mode 100644 index 00000000000..816052c3b12 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/content.md @@ -0,0 +1,39 @@ +# Truthy and Falsey Values with f-strings + + +```python +def convert(number): + threes = '' if number % 3 else 'Pling' # Empty string if modulo == 1 (0 evaluates to False) + fives = '' if number % 5 else 'Plang' + sevens = '' if number % 7 else 'Plong' + + return f'{threes}{fives}{sevens}' or str(number) + + #OR# + +def convert(number): + threes = 'Pling' if not number % 3 else '' # Sound if NOT modulo == 0 + fives = 'Plang' if not number % 5 else '' + sevens = 'Plong' if not number % 7 else '' + + return f'{threes}{fives}{sevens}' or str(number) +``` + +This is very similar to the [`if-statement`][approach-if-statements] approach logic, but uses [ternary expressions][ternary expression] to assign either an empty string or a drop sound to a variable. +The variables are then used in an `f-string` to compose the result, avoiding the use of `join()`, or a `loop`. +If the `f-string` is empty _(evaluating to False in a [boolean context][truth-value-testing]_), a `str` of the input number is returned instead. + +This has `O(1)` time and space complexity. + +These two variations both exploit the fact that boolean `True` and `False` are a subtype of `int` in Python. +0 evaluates to `False`, and 1 to `True`. +So the expression `'Pling' if not number % 3 else ''` can be read as "return 'Pling" if number % 3 not False" where `False` is 0, and (not `False`) is 1. +The expression `'' if number % 3 else 'Pling'` is the inverse: "return '' if number % 3 is True" - where number % 3 > 0 is `True`, and number % 3 == 0 is `False`. + +Like the `if-statement` approach, these solutions are nicely readable and to-the-point, but will grow in length and get harder to read if many more factors are added or business logic changes. +The `f-string` in particular could get unwieldy beyond about 5 factors. +Other solutions using data structures to hold factors and `join()` to assemble strings might be a better option in 'high change' situations. + +[approach-if-statements]: https://exercism.org/tracks/python/exercises/raindrops/approaches/if-statements +[ternary expression]: https://docs.python.org/3/reference/expressions.html#conditional-expressions +[truth-value-testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing diff --git a/exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/snippet.txt b/exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/snippet.txt new file mode 100644 index 00000000000..83a16958e56 --- /dev/null +++ b/exercises/practice/raindrops/.approaches/truthy-and-falsey-with-fstring/snippet.txt @@ -0,0 +1,6 @@ +def convert(number): + threes = '' if number % 3 else 'Pling' + fives = '' if number % 5 else 'Plang' + sevens = '' if number % 7 else 'Plong' + + return f'{threes}{fives}{sevens}' or str(number) \ No newline at end of file diff --git a/exercises/practice/raindrops/.articles/config.json b/exercises/practice/raindrops/.articles/config.json new file mode 100644 index 00000000000..c85ae72079a --- /dev/null +++ b/exercises/practice/raindrops/.articles/config.json @@ -0,0 +1,11 @@ +{ + "articles": [ + { + "uuid": "79248d29-772d-4618-bd7d-49a9bba693fd", + "slug": "performance", + "title": "Performance deep dive", + "blurb": "Deep dive to find out the most performant approach to the Raindrops exercise.", + "authors": ["BethanyG", "colinleach"] + } + ] +} \ No newline at end of file diff --git a/exercises/practice/raindrops/.articles/performance/code/Benchmark.py b/exercises/practice/raindrops/.articles/performance/code/Benchmark.py new file mode 100644 index 00000000000..a01978a7efe --- /dev/null +++ b/exercises/practice/raindrops/.articles/performance/code/Benchmark.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Script for timing Raindrops Solutions. + +Creates timing table and timing graphs for +multiple approaches to the Randrops problem in Python. + +Created Jan 2024 +@authors: bethanygarcia, @colinleach +""" + +import timeit + +import pandas as pd +import numpy as np + + +# ------------ FUNCTIONS TO TIME ------------- # + +def convert_if_statements(num): + sounds = '' + + if num % 3 == 0: sounds += 'Pling' + if num % 5 == 0: sounds += 'Plang' + if num % 7 == 0: sounds += 'Plong' + + return sounds if sounds else str(num) + + +def convert_truthy_falsy(number): + threes = '' if number % 3 else 'Pling' # Empty string if there is a remainder + fives = '' if number % 5 else 'Plang' + sevens = '' if number % 7 else 'Plong' + + return f'{threes}{fives}{sevens}' or str(number) + + +def convert_loop(number): + sounds = '' + drops = ("i", 3), ("a", 5), ("o", 7) + + for vowel, factor in drops: + if number % factor == 0: + sounds += f'Pl{vowel}ng' + + return sounds or str(number) + + +def convert_sequence_join(number): + drops = ["Pling", "Plang", "Plong"] + factors = [3, 5, 7] + sounds = ''.join(drops[index] for + index, factor in + enumerate(factors) if (number % factor == 0)) + + return sounds or str(number) + + +def convert_dict(number): + + sounds = {3: 'Pling', + 5: 'Plang', + 7: 'Plong'} + + results = ''.join(sounds[divisor] for + divisor, sound in sounds.items() + if number % divisor == 0) + + return results or str(number) + + +from itertools import compress + +def convert_itertools(number): + sounds = ('Pling', 'Plang', 'Plong') + mask = ((number % factor) == 0 for factor in (3, 5, 7)) + return ''.join(compress(sounds, mask)) or str(number) + + +from functools import reduce + +def convert_functools(number): + sounds = ('Pling', 'Plang', 'Plong') + factors = ((number % factor) == 0 for factor in (3, 5, 7)) + result = reduce(lambda sound, item: sound + (item[0] * item[1]), zip(sounds, factors), '') + + return result or str(number) + + +def convert_pattern_matching(number): + + match [(number % factor) == 0 for factor in (3, 5, 7)]: + case [True, True, True]: + return 'PlingPlangPlong' + case [True, True, False]: + return 'PlingPlang' + case [False, True, True]: + return 'PlangPlong' + case [True, False, True]: + return 'PlingPlong' + case [True, False, False]: + return 'Pling' + case [False, False, True]: + return 'Plong' + case [False, True, False]: + return 'Plang' + case _: + return str(number) + + +## ---------END FUNCTIONS TO BE TIMED-------------------- ## + +## -------- Timing Code Starts Here ---------------------## + + +# Input Data Setup +inputs = [1,5,7,6,8,9,10,14,15,21,27, 35, 49, 52, 70, 105, 144, 182, + 189, 195, 198, 203, 204, 210, 228, 231, 252, 315, 318, 329, + 340, 349, 379, 399, 409, 415, 497, 500, 525, 625, 735, 813, + 1575, 3125, 3250] + + +# #Set up columns and rows for Pandas Data Frame +col_headers = [f'Number: {number}'for number in inputs] +row_headers = ["if statements", + "ternary with truthy/falsy", + "loop with tuple", + "sequence with join", + "dictionary with join", + "itertools with join", + "functools reduce", + "structural pattern matching"] + +# # empty dataframe will be filled in one cell at a time later +df = pd.DataFrame(np.nan, index=row_headers, columns=col_headers) + +# #Function List to Call When Timing +functions = [convert_if_statements, + convert_truthy_falsy, + convert_loop, + convert_sequence_join, + convert_dict, + convert_itertools, + convert_functools, + convert_pattern_matching] + +# Run timings using timeit.autorange(). Run Each Set 3 Times. +for function, title in zip(functions, row_headers): + timings = [[ + timeit.Timer(lambda: function(data), globals=globals()).autorange()[1] / + timeit.Timer(lambda: function(data), globals=globals()).autorange()[0] + for data in inputs] for rounds in range(3)] + + # Only the fastest Cycle counts. + timing_result = min(timings) + + # timing_result = [round(min(timeit.repeat(lambda: function(data), repeat=3, number=1000000, globals=globals())), 6) for data in words_II] + print(f'{title}', f'Timings : {timing_result}') + + # Insert results into the dataframe + df.loc[title, 'Number: 1':'Number: 3250'] = timing_result + +# The next bit is useful for `introduction.md` +pd.options.display.float_format = '{:,.2e}'.format +print('\nDataframe in Markdown format:\n') +print(df.to_markdown(floatfmt=".2e")) + diff --git a/exercises/practice/raindrops/.articles/performance/content.md b/exercises/practice/raindrops/.articles/performance/content.md new file mode 100644 index 00000000000..522ff1016ee --- /dev/null +++ b/exercises/practice/raindrops/.articles/performance/content.md @@ -0,0 +1,60 @@ +# Performance + +In this article, we will find out how to most efficiently complete the Raindrops exercise. + +The [introduction page][approaches-intro] lists seven idiomatic approaches to this exercise: + +1. Using [`if` statements and `+`][approach-if-statements] to assemble a return string +2. Exploiting ["Truthy" and "Falsy" values in ternary checks][approach-truthy-and-falsey-with-fstring] and an `f-string` +3. Using one or more sequences, a [`loop`, and an `f-string`][approach-loop-and-fstring] +4. Using one or more [sequences and a `generator-expression` within `join()`][approach-sequence-with-join]. +5. Using a [`dict` of `{factors : sounds}` for lookup and `join()`][approach-dict-and-join] +6. Using [`itertools.compress()`][approach-itertools-compress] +7. Using [`functools.reduce()`][approach-functools-reduce] + + +For our performance investigation, we'll also include an 8th approach that [uses Python 3.10's `structural pattern matching`][PEP0622]. + + +## Benchmarks + +To benchmark these functions, we wrote a small [benchmarking script][benchmark-application] using the [timeit][timeit] module along with third-party libraries [numpy][numpy] and [pandas][pandas]. + + +| | 10 | 14 | 15 | 70 | 105 | 182 | 189 | 203 | 204 | 399 | 409 | 525 | 735 | 1575 | 3250 | +|----------------------------- |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: | +| if statements | 2.12e-07 | 2.16e-07 | 2.60e-07 | 2.76e-07 | 2.98e-07 | 2.10e-07 | 2.59e-07 | 2.15e-07 | 2.10e-07 | 2.75e-07 | 2.61e-07 | 3.41e-07 | 2.99e-07 | 2.98e-07 | 2.13e-07 | +| ternary with truthy/falsy | 2.65e-07 | 2.65e-07 | 2.80e-07 | 2.70e-07 | 2.91e-07 | 2.67e-07 | 2.73e-07 | 2.75e-07 | 2.69e-07 | 3.56e-07 | 2.02e-07 | 3.06e-07 | 3.06e-07 | 3.04e-07 | 2.89e-07 | +| loop with tuple | 4.01e-07 | 4.09e-07 | 5.05e-07 | 4.94e-07 | 6.48e-07 | 3.97e-07 | 5.25e-07 | 4.10e-07 | 2.04e-07 | 5.51e-07 | 4.06e-07 | 9.04e-07 | 6.16e-07 | 6.89e-07 | 4.33e-07 | +| structural pattern matching | 7.55e-07 | 7.31e-07 | 6.09e-07 | 5.87e-07 | 5.21e-07 | 7.11e-07 | 6.42e-07 | 7.19e-07 | 6.90e-07 | 6.49e-07 | 8.43e-07 | 5.00e-07 | 5.12e-07 | 5.21e-07 | 7.48e-07 | +| dictionary with join | 8.31e-07 | 8.18e-07 | 9.34e-07 | 1.02e-06 | 9.75e-07 | 8.55e-07 | 9.13e-07 | 8.25e-07 | 8.32e-07 | 2.28e-06 | 9.22e-07 | 1.05e-06 | 2.42e-06 | 9.94e-07 | 8.46e-07 | +| sequence with join | 8.29e-07 | 8.17e-07 | 9.27e-07 | 9.10e-07 | 9.62e-07 | 8.73e-07 | 9.70e-07 | 8.87e-07 | 9.40e-07 | 2.52e-06 | 9.74e-07 | 2.44e-06 | 2.57e-06 | 9.79e-07 | 8.67e-07 | +| itertools with join | 9.46e-07 | 9.33e-07 | 4.04e-07 | 9.88e-07 | 1.01e-06 | 9.41e-07 | 9.91e-07 | 9.65e-07 | 9.80e-07 | 2.51e-06 | 1.10e-06 | 2.50e-06 | 1.02e-06 | 1.00e-06 | 9.60e-07 | +| functools reduce | 1.35e-06 | 1.38e-06 | 1.41e-06 | 1.39e-06 | 1.48e-06 | 1.33e-06 | 1.42e-06 | 1.37e-06 | 1.34e-06 | 1.39e-06 | 1.43e-06 | 1.45e-06 | 1.46e-06 | 1.45e-06 | 1.37e-06 | + + +Keep in mind that all these approaches are _very fast_, and that benchmarking at this granularity can be unstable. +That caveat in mind, the two `if-statement` based approaches benchmark fastest, and the approach using `functools.reduce()` was the slowest. + +The 'recommended' approach came in 4th, though it can be argued that the slowdown is justified by the increased readability and maintainability. + +Measurements were taken on a 3.1 GHz Quad-Core Intel Core i7 Mac running MacOS Ventura. +Tests used `timeit.Timer.autorange()`, repeated 3 times. +Time is reported in seconds taken per string after calculating the 'best of' time. +The [`timeit`][timeit] module docs have more details, and [note.nkmk.me][note_nkmk_me] has a nice summary of methods. + + +[PEP0622]: https://peps.python.org/pep-0622/ +[approach-dict-and-join ]: https://exercism.org/tracks/python/exercises/raindrops/approaches/dict-and-join +[approach-functools-reduce]: https://exercism.org/tracks/python/exercises/raindrops/approaches/functools-reduce +[approach-if-statements]: https://exercism.org/tracks/python/exercises/raindrops/approaches/if-statements +[approach-itertools-compress]: https://exercism.org/tracks/python/exercises/raindrops/approaches/itertools-compress +[approach-loop-and-fstring]: https://exercism.org/tracks/python/exercises/raindrops/approaches/loop-and-fstring +[approach-sequence-with-join]: https://exercism.org/tracks/python/exercises/raindrops/approaches/sequence-with-join +[approach-truthy-and-falsey-with-fstring]: https://exercism.org/tracks/python/exercises/raindrops/approaches/truthy-and-falsey-with-fstring +[approaches-intro]: https://exercism.org/tracks/python/exercises/raindrops/approaches/introduction.md +[benchmark-application]: https://exercism.org/tracks/python/exercises/raindrops/.articles/code/Benchmark.py +[note_nkmk_me]: https://note.nkmk.me/en/python-timeit-measure/ +[numpy]: https://numpy.org/ +[pandas]: https://pandas.pydata.org/ +[timeit]: https://docs.python.org/3/library/timeit.html#python-interface diff --git a/exercises/practice/raindrops/.articles/performance/snippet.md b/exercises/practice/raindrops/.articles/performance/snippet.md new file mode 100644 index 00000000000..d3449e8656e --- /dev/null +++ b/exercises/practice/raindrops/.articles/performance/snippet.md @@ -0,0 +1,8 @@ +| | 10 | 14 | 15 | 70 | 105 | 182 | 189 | 203 | 204 | 399 | 409 | 525 | 735 | 1575 | 3250 | +|----------------------------- |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: | +| if statements | 2.12e-07 | 2.16e-07 | 2.60e-07 | 2.76e-07 | 2.98e-07 | 2.10e-07 | 2.59e-07 | 2.15e-07 | 2.10e-07 | 2.75e-07 | 2.61e-07 | 3.41e-07 | 2.99e-07 | 2.98e-07 | 2.13e-07 | +| loop with tuple | 4.01e-07 | 4.09e-07 | 5.05e-07 | 4.94e-07 | 6.48e-07 | 3.97e-07 | 5.25e-07 | 4.10e-07 | 2.04e-07 | 5.51e-07 | 4.06e-07 | 9.04e-07 | 6.16e-07 | 6.89e-07 | 4.33e-07 | +| structural pattern matching | 7.55e-07 | 7.31e-07 | 6.09e-07 | 5.87e-07 | 5.21e-07 | 7.11e-07 | 6.42e-07 | 7.19e-07 | 6.90e-07 | 6.49e-07 | 8.43e-07 | 5.00e-07 | 5.12e-07 | 5.21e-07 | 7.48e-07 | +| dictionary with join | 8.31e-07 | 8.18e-07 | 9.34e-07 | 1.02e-06 | 9.75e-07 | 8.55e-07 | 9.13e-07 | 8.25e-07 | 8.32e-07 | 2.28e-06 | 9.22e-07 | 1.05e-06 | 2.42e-06 | 9.94e-07 | 8.46e-07 | +| itertools with join | 9.46e-07 | 9.33e-07 | 4.04e-07 | 9.88e-07 | 1.01e-06 | 9.41e-07 | 9.91e-07 | 9.65e-07 | 9.80e-07 | 2.51e-06 | 1.10e-06 | 2.50e-06 | 1.02e-06 | 1.00e-06 | 9.60e-07 | +| functools reduce | 1.35e-06 | 1.38e-06 | 1.41e-06 | 1.39e-06 | 1.48e-06 | 1.33e-06 | 1.42e-06 | 1.37e-06 | 1.34e-06 | 1.39e-06 | 1.43e-06 | 1.45e-06 | 1.46e-06 | 1.45e-06 | 1.37e-06 | \ No newline at end of file From f3016b0ca9dde4eed05b61ef428899b6ac46e444 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 1 Feb 2024 03:23:56 -0800 Subject: [PATCH 047/277] Fixed some typos. (#3612) --- exercises/practice/raindrops/.approaches/introduction.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/practice/raindrops/.approaches/introduction.md b/exercises/practice/raindrops/.approaches/introduction.md index 1783fa33abf..b55ceabecbd 100644 --- a/exercises/practice/raindrops/.approaches/introduction.md +++ b/exercises/practice/raindrops/.approaches/introduction.md @@ -176,7 +176,7 @@ If the result of `itertools.compress` is empty, a string version of the input nu For more details, see the [itertools.compress][approach-itertools-compress] approach. -## Approach: Using `functools.reduce())` and `zip()` +## Approach: Using `functools.reduce()` and `zip()` ```python @@ -283,3 +283,4 @@ To compare the performance of this and the other approaches, take a look at the [remainder]: https://docs.python.org/3/library/math.html#math.remainder [ternary expression]: https://docs.python.org/3/reference/expressions.html#conditional-expressions [truth-value-testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing +[structural-pattern-matching]: https://peps.python.org/pep-0622/ From 7eae72e8de425874ee1d30f5fecaabd837d8a015 Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Thu, 1 Feb 2024 14:11:20 +0000 Subject: [PATCH 048/277] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#3613)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/bfc6fe525e057a43f41e4fd83dfac00569d14086 --- .github/workflows/no-important-files-changed.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml index 39a6551747b..26b068bc46c 100644 --- a/.github/workflows/no-important-files-changed.yml +++ b/.github/workflows/no-important-files-changed.yml @@ -57,6 +57,7 @@ jobs: if: steps.check.outputs.important_files_changed == 'true' uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: + github-token: ${{ github.token }} script: | const body = "This PR touches files which potentially affect the outcome of the tests of an exercise. This will cause all students' solutions to affected exercises to be re-tested.\n\nIf this PR does **not** affect the result of the test (or, for example, adds an edge case that is not worth rerunning all tests for), **please add the following to the merge-commit message** which will stops student's tests from re-running. Please copy-paste to avoid typos.\n```\n[no important files changed]\n```\n\n For more information, refer to the [documentation](https://exercism.org/docs/building/tracks#h-avoiding-triggering-unnecessary-test-runs). If you are unsure whether to add the message or not, please ping `@exercism/maintainers-admin` in a comment. Thank you!" github.rest.issues.createComment({ From 51f8c8dc2ea09921942321ae1271f0b237b8a659 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 2 Feb 2024 09:31:42 -0800 Subject: [PATCH 049/277] Corrected spelling error. (#3616) --- .../concept/mecha-munch-management/.docs/instructions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/concept/mecha-munch-management/.docs/instructions.md b/exercises/concept/mecha-munch-management/.docs/instructions.md index f5fa6f79012..4c7661d64e3 100644 --- a/exercises/concept/mecha-munch-management/.docs/instructions.md +++ b/exercises/concept/mecha-munch-management/.docs/instructions.md @@ -94,11 +94,11 @@ Create the function `sort_entries()` that takes a shopping cart/dictionary ## 5. Send User Shopping Cart to Store for Fulfillment The app needs to send a given users cart to the store for fulfillment. -However, the shoppers in the store need to know which store isle the item can be found in and if the item needs refrigeration. +However, the shoppers in the store need to know which store aisle the item can be found in and if the item needs refrigeration. So (_rather arbitrarily_) the "fulfillment cart" needs to be sorted in reverse alphabetical order with item quantities combined with location and refrigeration information. -Create the function `send_to_store(, )` that takes a user shopping cart and a dictionary that has store isle number and a `True`/`False` for refrigeration needed for each item. -The function should `return` a combined "fulfillment cart" that has (quantity, isle, and refrigeration) for each item the customer is ordering. +Create the function `send_to_store(, )` that takes a user shopping cart and a dictionary that has store aisle number and a `True`/`False` for refrigeration needed for each item. +The function should `return` a combined "fulfillment cart" that has (quantity, aisle, and refrigeration) for each item the customer is ordering. Items should appear in _reverse_ alphabetical order. ```python From cadb6120e04d460fd891f5cf98ed395630722f52 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 2 Feb 2024 10:05:24 -0800 Subject: [PATCH 050/277] Mark nucleotied-count as foregone. (#3617) --- config.json | 11 +---- .../nucleotide-count/.docs/instructions.md | 23 ---------- .../nucleotide-count/.meta/config.json | 32 ------------- .../nucleotide-count/.meta/example.py | 18 -------- .../nucleotide-count/.meta/tests.toml | 18 -------- .../nucleotide-count/nucleotide_count.py | 6 --- .../nucleotide-count/nucleotide_count_test.py | 46 ------------------- 7 files changed, 1 insertion(+), 153 deletions(-) delete mode 100644 exercises/practice/nucleotide-count/.docs/instructions.md delete mode 100644 exercises/practice/nucleotide-count/.meta/config.json delete mode 100644 exercises/practice/nucleotide-count/.meta/example.py delete mode 100644 exercises/practice/nucleotide-count/.meta/tests.toml delete mode 100644 exercises/practice/nucleotide-count/nucleotide_count.py delete mode 100644 exercises/practice/nucleotide-count/nucleotide_count_test.py diff --git a/config.json b/config.json index 754ca9f3fdc..06f64077d92 100644 --- a/config.json +++ b/config.json @@ -2143,15 +2143,6 @@ "difficulty": 2, "status": "deprecated" }, - { - "slug": "nucleotide-count", - "name": "Nucleotide Count", - "uuid": "105f25ec-7ce2-4797-893e-05e3792ebd91", - "practices": [], - "prerequisites": [], - "difficulty": 2, - "status": "deprecated" - }, { "slug": "binary", "name": "Binary", @@ -2234,7 +2225,7 @@ "status": "deprecated" } ], - "foregone": ["lens-person", "parallel-letter-frequency"] + "foregone": ["lens-person", "nucleotide-count", "parallel-letter-frequency"] }, "concepts": [ { diff --git a/exercises/practice/nucleotide-count/.docs/instructions.md b/exercises/practice/nucleotide-count/.docs/instructions.md deleted file mode 100644 index 548d9ba5a5e..00000000000 --- a/exercises/practice/nucleotide-count/.docs/instructions.md +++ /dev/null @@ -1,23 +0,0 @@ -# Instructions - -Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed. -All known life depends on DNA! - -> Note: You do not need to understand anything about nucleotides or DNA to complete this exercise. - -DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine. -A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important! -We call the order of these nucleotides in a bit of DNA a "DNA sequence". - -We represent a DNA sequence as an ordered collection of these four nucleotides and a common way to do that is with a string of characters such as "ATTACG" for a DNA sequence of 6 nucleotides. -'A' for adenine, 'C' for cytosine, 'G' for guanine, and 'T' for thymine. - -Given a string representing a DNA sequence, count how many of each nucleotide is present. -If the string contains characters that aren't A, C, G, or T then it is invalid and you should signal an error. - -For example: - -```text -"GATTACA" -> 'A': 3, 'C': 1, 'G': 1, 'T': 2 -"INVALID" -> error -``` diff --git a/exercises/practice/nucleotide-count/.meta/config.json b/exercises/practice/nucleotide-count/.meta/config.json deleted file mode 100644 index e0c108f7b88..00000000000 --- a/exercises/practice/nucleotide-count/.meta/config.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "blurb": "Given a DNA string, compute how many times each nucleotide occurs in the string.", - "authors": [], - "contributors": [ - "behrtam", - "cmccandless", - "Dog", - "ikhadykin", - "kytrinyx", - "lowks", - "mostlybadfly", - "N-Parsons", - "Oniwa", - "orozcoadrian", - "pheanex", - "sjakobi", - "tqa236" - ], - "files": { - "solution": [ - "nucleotide_count.py" - ], - "test": [ - "nucleotide_count_test.py" - ], - "example": [ - ".meta/example.py" - ] - }, - "source": "The Calculating DNA Nucleotides_problem at Rosalind", - "source_url": "https://rosalind.info/problems/dna/" -} diff --git a/exercises/practice/nucleotide-count/.meta/example.py b/exercises/practice/nucleotide-count/.meta/example.py deleted file mode 100644 index e79a6a7ec7d..00000000000 --- a/exercises/practice/nucleotide-count/.meta/example.py +++ /dev/null @@ -1,18 +0,0 @@ -NUCLEOTIDES = 'ATCG' - - -def count(strand, abbreviation): - _validate(abbreviation) - return strand.count(abbreviation) - - -def nucleotide_counts(strand): - return { - abbr: strand.count(abbr) - for abbr in NUCLEOTIDES - } - - -def _validate(abbreviation): - if abbreviation not in NUCLEOTIDES: - raise ValueError(f'{abbreviation} is not a nucleotide.') diff --git a/exercises/practice/nucleotide-count/.meta/tests.toml b/exercises/practice/nucleotide-count/.meta/tests.toml deleted file mode 100644 index 79b22f7a855..00000000000 --- a/exercises/practice/nucleotide-count/.meta/tests.toml +++ /dev/null @@ -1,18 +0,0 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. - -[3e5c30a8-87e2-4845-a815-a49671ade970] -description = "empty strand" - -[a0ea42a6-06d9-4ac6-828c-7ccaccf98fec] -description = "can count one nucleotide in single-character input" - -[eca0d565-ed8c-43e7-9033-6cefbf5115b5] -description = "strand with repeated nucleotide" - -[40a45eac-c83f-4740-901a-20b22d15a39f] -description = "strand with multiple nucleotides" - -[b4c47851-ee9e-4b0a-be70-a86e343bd851] -description = "strand with invalid nucleotides" diff --git a/exercises/practice/nucleotide-count/nucleotide_count.py b/exercises/practice/nucleotide-count/nucleotide_count.py deleted file mode 100644 index 7f794acbfb3..00000000000 --- a/exercises/practice/nucleotide-count/nucleotide_count.py +++ /dev/null @@ -1,6 +0,0 @@ -def count(strand, nucleotide): - pass - - -def nucleotide_counts(strand): - pass diff --git a/exercises/practice/nucleotide-count/nucleotide_count_test.py b/exercises/practice/nucleotide-count/nucleotide_count_test.py deleted file mode 100644 index bd5b5bddcb7..00000000000 --- a/exercises/practice/nucleotide-count/nucleotide_count_test.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Tests for the nucleotide-count exercise - -Implementation note: -The count function must raise a ValueError with a meaningful error message -in case of a bad argument. -""" -import unittest - -from nucleotide_count import count, nucleotide_counts - - -class NucleotideCountTest(unittest.TestCase): - def test_empty_dna_string_has_no_adenosine(self): - self.assertEqual(count('', 'A'), 0) - - def test_empty_dna_string_has_no_nucleotides(self): - expected = {'A': 0, 'T': 0, 'C': 0, 'G': 0} - self.assertEqual(nucleotide_counts(""), expected) - - def test_repetitive_cytidine_gets_counted(self): - self.assertEqual(count('CCCCC', 'C'), 5) - - def test_repetitive_sequence_has_only_guanosine(self): - expected = {'A': 0, 'T': 0, 'C': 0, 'G': 8} - self.assertEqual(nucleotide_counts('GGGGGGGG'), expected) - - def test_counts_only_thymidine(self): - self.assertEqual(count('GGGGGTAACCCGG', 'T'), 1) - - def test_validates_nucleotides(self): - with self.assertRaisesWithMessage(ValueError): - count("GACT", 'X') - - def test_counts_all_nucleotides(self): - dna = ('AGCTTTTCATTCTGACTGCAACGGGCAATATGTCT' - 'CTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC') - expected = {'A': 20, 'T': 21, 'G': 17, 'C': 12} - self.assertEqual(nucleotide_counts(dna), expected) - - # Utility functions - def assertRaisesWithMessage(self, exception): - return self.assertRaisesRegex(exception, r".+") - - -if __name__ == '__main__': - unittest.main() From 59a459abe8ee5e7f2fe69040d4d020284aa6d1a8 Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Fri, 2 Feb 2024 18:05:54 +0000 Subject: [PATCH 051/277] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#3614)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/f72e90478cac439f8ded661b9b650dd923898985 --- CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index df8e36761c1..3f7813de10a 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -90,4 +90,4 @@ This policy was initially adopted from the Front-end London Slack community and A version history can be seen on [GitHub](https://github.com/exercism/website-copy/edit/main/pages/code_of_conduct.md). _This policy is a "living" document, and subject to refinement and expansion in the future. -This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Slack, Twitter, email) and any other Exercism entity or event._ +This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Discord, Forum, Twitter, email) and any other Exercism entity or event._ From 98e9bfeede05d0c24adb00a74ad2f7ea21d52e3e Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Fri, 2 Feb 2024 19:34:26 +0100 Subject: [PATCH 052/277] `pop-count`: rename to `eliuds-eggs` (#3615) * Rename the `pop-count` exercise to `eliuds-eggs`. * Regenerated test file and renamed stub and test file. * Changed exercise config file to match change in sub and test file naming. * Corrected typo in exercise config for test file location. --------- Co-authored-by: BethanyG --- config.json | 2 +- .../{pop-count => eliuds-eggs}/.docs/instructions.md | 0 .../{pop-count => eliuds-eggs}/.docs/introduction.md | 0 .../practice/{pop-count => eliuds-eggs}/.meta/config.json | 4 ++-- .../practice/{pop-count => eliuds-eggs}/.meta/example.py | 0 .../practice/{pop-count => eliuds-eggs}/.meta/template.j2 | 0 .../practice/{pop-count => eliuds-eggs}/.meta/tests.toml | 0 .../pop_count.py => eliuds-eggs/eliuds_eggs.py} | 0 .../pop_count_test.py => eliuds-eggs/eliuds_eggs_test.py} | 8 ++++---- 9 files changed, 7 insertions(+), 7 deletions(-) rename exercises/practice/{pop-count => eliuds-eggs}/.docs/instructions.md (100%) rename exercises/practice/{pop-count => eliuds-eggs}/.docs/introduction.md (100%) rename exercises/practice/{pop-count => eliuds-eggs}/.meta/config.json (89%) rename exercises/practice/{pop-count => eliuds-eggs}/.meta/example.py (100%) rename exercises/practice/{pop-count => eliuds-eggs}/.meta/template.j2 (100%) rename exercises/practice/{pop-count => eliuds-eggs}/.meta/tests.toml (100%) rename exercises/practice/{pop-count/pop_count.py => eliuds-eggs/eliuds_eggs.py} (100%) rename exercises/practice/{pop-count/pop_count_test.py => eliuds-eggs/eliuds_eggs_test.py} (79%) diff --git a/config.json b/config.json index 06f64077d92..99d23d77c31 100644 --- a/config.json +++ b/config.json @@ -967,7 +967,7 @@ "difficulty": 2 }, { - "slug": "pop-count", + "slug": "eliuds-eggs", "name": "Eliud's Eggs", "uuid": "356e2d29-7efc-4fa3-bec7-8b61c3e967da", "practices": ["loops"], diff --git a/exercises/practice/pop-count/.docs/instructions.md b/exercises/practice/eliuds-eggs/.docs/instructions.md similarity index 100% rename from exercises/practice/pop-count/.docs/instructions.md rename to exercises/practice/eliuds-eggs/.docs/instructions.md diff --git a/exercises/practice/pop-count/.docs/introduction.md b/exercises/practice/eliuds-eggs/.docs/introduction.md similarity index 100% rename from exercises/practice/pop-count/.docs/introduction.md rename to exercises/practice/eliuds-eggs/.docs/introduction.md diff --git a/exercises/practice/pop-count/.meta/config.json b/exercises/practice/eliuds-eggs/.meta/config.json similarity index 89% rename from exercises/practice/pop-count/.meta/config.json rename to exercises/practice/eliuds-eggs/.meta/config.json index 0c402acfad7..a230751738b 100644 --- a/exercises/practice/pop-count/.meta/config.json +++ b/exercises/practice/eliuds-eggs/.meta/config.json @@ -4,10 +4,10 @@ ], "files": { "solution": [ - "pop_count.py" + "eliuds_eggs.py" ], "test": [ - "pop_count_test.py" + "eliuds_eggs_test.py" ], "example": [ ".meta/example.py" diff --git a/exercises/practice/pop-count/.meta/example.py b/exercises/practice/eliuds-eggs/.meta/example.py similarity index 100% rename from exercises/practice/pop-count/.meta/example.py rename to exercises/practice/eliuds-eggs/.meta/example.py diff --git a/exercises/practice/pop-count/.meta/template.j2 b/exercises/practice/eliuds-eggs/.meta/template.j2 similarity index 100% rename from exercises/practice/pop-count/.meta/template.j2 rename to exercises/practice/eliuds-eggs/.meta/template.j2 diff --git a/exercises/practice/pop-count/.meta/tests.toml b/exercises/practice/eliuds-eggs/.meta/tests.toml similarity index 100% rename from exercises/practice/pop-count/.meta/tests.toml rename to exercises/practice/eliuds-eggs/.meta/tests.toml diff --git a/exercises/practice/pop-count/pop_count.py b/exercises/practice/eliuds-eggs/eliuds_eggs.py similarity index 100% rename from exercises/practice/pop-count/pop_count.py rename to exercises/practice/eliuds-eggs/eliuds_eggs.py diff --git a/exercises/practice/pop-count/pop_count_test.py b/exercises/practice/eliuds-eggs/eliuds_eggs_test.py similarity index 79% rename from exercises/practice/pop-count/pop_count_test.py rename to exercises/practice/eliuds-eggs/eliuds_eggs_test.py index c3ca1921560..d093e023be8 100644 --- a/exercises/practice/pop-count/pop_count_test.py +++ b/exercises/practice/eliuds-eggs/eliuds_eggs_test.py @@ -1,15 +1,15 @@ # These tests are auto-generated with test data from: -# https://github.com/exercism/problem-specifications/tree/main/exercises/pop-count/canonical-data.json -# File last updated on 2023-10-18 +# https://github.com/exercism/problem-specifications/tree/main/exercises/eliuds-eggs/canonical-data.json +# File last updated on 2024-02-02 import unittest -from pop_count import ( +from eliuds_eggs import ( egg_count, ) -class PopCountTest(unittest.TestCase): +class EliudsEggsTest(unittest.TestCase): def test_0_eggs(self): expected = 0 self.assertEqual(egg_count(0), expected) From 9d7aba46b650e36c1fa5740f3fa203cb8d3d8bcb Mon Sep 17 00:00:00 2001 From: izmirli Date: Fri, 2 Feb 2024 23:57:47 +0200 Subject: [PATCH 053/277] [Inventory Management] New test for decrement_items not in inventory (#3618) [no important files changed] --- .../concept/inventory-management/dicts_test.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/exercises/concept/inventory-management/dicts_test.py b/exercises/concept/inventory-management/dicts_test.py index f7faba35a5a..71a8ff2b72b 100644 --- a/exercises/concept/inventory-management/dicts_test.py +++ b/exercises/concept/inventory-management/dicts_test.py @@ -77,6 +77,19 @@ def test_not_below_zero(self): self.assertEqual(actual_result, expected, msg=error_message) + @pytest.mark.task(taskno=3) + def test_decrement_items_not_in_inventory(self): + actual_result = decrement_items({"iron": 3, "gold": 2}, + ["iron", "wood", "iron", "diamond"]) + + expected = {"iron": 1, "gold": 2} + error_message = ('Called decrement_items({"iron": 3, "gold": 2}, ' + '["iron", "wood", "iron", "diamond"]). The function ' + f'returned {actual_result}, but the tests ' + f'expected {expected}.') + + self.assertEqual(actual_result, expected, msg=error_message) + @pytest.mark.task(taskno=4) def test_remove_item(self): actual_result = remove_item({"iron": 1, "diamond": 2, "gold": 1}, "diamond") From d22877f62bb1a4581df45bd2d76d5d7682875b25 Mon Sep 17 00:00:00 2001 From: Ryan Hartlage Date: Sun, 4 Feb 2024 12:16:34 -0500 Subject: [PATCH 054/277] Fix misspelling of 'aisle' as 'isle' (#3619) --- .../.docs/instructions.md | 16 ++--- .../mecha-munch-management/.meta/exemplar.py | 8 +-- .../mecha-munch-management/dict_methods.py | 6 +- .../dict_methods_test.py | 70 +++++++++---------- 4 files changed, 50 insertions(+), 50 deletions(-) diff --git a/exercises/concept/mecha-munch-management/.docs/instructions.md b/exercises/concept/mecha-munch-management/.docs/instructions.md index 4c7661d64e3..c7cf733276e 100644 --- a/exercises/concept/mecha-munch-management/.docs/instructions.md +++ b/exercises/concept/mecha-munch-management/.docs/instructions.md @@ -17,11 +17,11 @@ Create the function `add_items(, )` that takes a car It should return a new/updated shopping cart dictionary for the user. ```python ->>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, +>>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, ('Apple', 'Apple', 'Orange', 'Apple', 'Banana')) {'Banana': 4, 'Apple': 5, 'Orange': 2} ->>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, +>>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, ['Banana', 'Orange', 'Blueberries', 'Banana']) {'Banana': 5, 'Apple': 2, 'Orange': 2, 'Blueberries': 1} ``` @@ -102,9 +102,9 @@ The function should `return` a combined "fulfillment cart" that has (quantity, a Items should appear in _reverse_ alphabetical order. ```python ->>> send_to_store({'Banana': 3, 'Apple': 2, 'Orange': 1, 'Milk': 2}, - {'Banana': ['Isle 5', False], 'Apple': ['Isle 4', False], 'Orange': ['Isle 4', False], 'Milk': ['Isle 2', True]}) -{'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]} +>>> send_to_store({'Banana': 3, 'Apple': 2, 'Orange': 1, 'Milk': 2}, + {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}) +{'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]} ``` ## 6. Update the Store Inventory to Reflect what a User Has Ordered. @@ -119,10 +119,10 @@ The function should reduce the store inventory amounts by the number "ordered" i Where a store item count falls to 0, the count should be replaced by the message 'Out of Stock'. ```python ->>> update_store_inventory({'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]}, -{'Banana': [15, 'Isle 5', False], 'Apple': [12, 'Isle 4', False], 'Orange': [1, 'Isle 4', False], 'Milk': [4, 'Isle 2', True]}) +>>> update_store_inventory({'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]}, +{'Banana': [15, 'Aisle 5', False], 'Apple': [12, 'Aisle 4', False], 'Orange': [1, 'Aisle 4', False], 'Milk': [4, 'Aisle 2', True]}) -{'Banana': [12, 'Isle 5', False], 'Apple': [10, 'Isle 4', False], 'Orange': ['Out of Stock', 'Isle 4', False], 'Milk': [2, 'Isle 2', True]} +{'Banana': [12, 'Aisle 5', False], 'Apple': [10, 'Aisle 4', False], 'Orange': ['Out of Stock', 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True]} ``` [feature creep]: https://en.wikipedia.org/wiki/Feature_creep diff --git a/exercises/concept/mecha-munch-management/.meta/exemplar.py b/exercises/concept/mecha-munch-management/.meta/exemplar.py index 0390944dbbd..ea25110a3be 100644 --- a/exercises/concept/mecha-munch-management/.meta/exemplar.py +++ b/exercises/concept/mecha-munch-management/.meta/exemplar.py @@ -48,17 +48,17 @@ def sort_entries(cart): return dict(sorted(cart.items())) -def send_to_store(cart, isle_mapping): - """Combine users order to isle and refrigeration information. +def send_to_store(cart, aisle_mapping): + """Combine users order to aisle and refrigeration information. :param cart: dict - users shopping cart dictionary. - :param isle_mapping: dict - isle and refrigeration information dictionary. + :param aisle_mapping: dict - aisle and refrigeration information dictionary. :return: dict - fulfillment dictionary ready to send to store. """ fulfillment_cart = {} for key in cart.keys(): - fulfillment_cart[key] = [cart[key]] + isle_mapping[key] + fulfillment_cart[key] = [cart[key]] + aisle_mapping[key] return dict(sorted(fulfillment_cart.items(), reverse=True)) diff --git a/exercises/concept/mecha-munch-management/dict_methods.py b/exercises/concept/mecha-munch-management/dict_methods.py index d443c8bca54..f502fe00ab9 100644 --- a/exercises/concept/mecha-munch-management/dict_methods.py +++ b/exercises/concept/mecha-munch-management/dict_methods.py @@ -43,11 +43,11 @@ def sort_entries(cart): pass -def send_to_store(cart, isle_mapping): - """Combine users order to isle and refrigeration information. +def send_to_store(cart, aisle_mapping): + """Combine users order to aisle and refrigeration information. :param cart: dict - users shopping cart dictionary. - :param isle_mapping: dict - isle and refrigeration information dictionary. + :param aisle_mapping: dict - aisle and refrigeration information dictionary. :return: dict - fulfillment dictionary ready to send to store. """ diff --git a/exercises/concept/mecha-munch-management/dict_methods_test.py b/exercises/concept/mecha-munch-management/dict_methods_test.py index 2c9e36948ca..8ec0211d530 100644 --- a/exercises/concept/mecha-munch-management/dict_methods_test.py +++ b/exercises/concept/mecha-munch-management/dict_methods_test.py @@ -122,31 +122,31 @@ def test_sort_entries(self): def test_send_to_store(self): input_data = [ ({'Banana': 3, 'Apple': 2, 'Orange': 1, 'Milk': 2}, - {'Banana': ['Isle 5', False], 'Apple': ['Isle 4', False], - 'Orange': ['Isle 4', False], 'Milk': ['Isle 2', True]}), + {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], + 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}), ({'Kiwi': 3, 'Juice': 5, 'Yoghurt': 2, 'Milk': 5}, - {'Kiwi': ['Isle 6', False], 'Juice': ['Isle 5', False], - 'Yoghurt': ['Isle 2', True], 'Milk': ['Isle 2', True]}), + {'Kiwi': ['Aisle 6', False], 'Juice': ['Aisle 5', False], + 'Yoghurt': ['Aisle 2', True], 'Milk': ['Aisle 2', True]}), ({'Apple': 2, 'Raspberry': 2, 'Blueberries': 5, 'Broccoli' : 2, 'Kiwi': 1, 'Melon': 4}, - {'Apple': ['Isle 1', False], 'Raspberry': ['Isle 6', False], - 'Blueberries': ['Isle 6', False], 'Broccoli': ['Isle 3', False], - 'Kiwi': ['Isle 6', False], 'Melon': ['Isle 6', False]}) + {'Apple': ['Aisle 1', False], 'Raspberry': ['Aisle 6', False], + 'Blueberries': ['Aisle 6', False], 'Broccoli': ['Aisle 3', False], + 'Kiwi': ['Aisle 6', False], 'Melon': ['Aisle 6', False]}) ] output_data = [ - {'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], - 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]}, + {'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], + 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]}, - {'Yoghurt': [2, 'Isle 2', True], 'Milk': [5, 'Isle 2', True], - 'Kiwi': [3, 'Isle 6', False], 'Juice': [5, 'Isle 5', False]}, + {'Yoghurt': [2, 'Aisle 2', True], 'Milk': [5, 'Aisle 2', True], + 'Kiwi': [3, 'Aisle 6', False], 'Juice': [5, 'Aisle 5', False]}, - {'Raspberry': [2, 'Isle 6', False], 'Melon': [4, 'Isle 6', False], - 'Kiwi': [1, 'Isle 6', False], 'Broccoli': [2, 'Isle 3', False], - 'Blueberries': [5, 'Isle 6', False], 'Apple': [2, 'Isle 1', False]} + {'Raspberry': [2, 'Aisle 6', False], 'Melon': [4, 'Aisle 6', False], + 'Kiwi': [1, 'Aisle 6', False], 'Broccoli': [2, 'Aisle 3', False], + 'Blueberries': [5, 'Aisle 6', False], 'Apple': [2, 'Aisle 1', False]} ] for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): @@ -164,32 +164,32 @@ def test_send_to_store(self): @pytest.mark.task(taskno=6) def test_update_store_inventory(self): input_data = [ - ({'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], - 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]}, - {'Banana': [15, 'Isle 5', False], 'Apple': [12, 'Isle 4', False], - 'Orange': [1, 'Isle 4', False], 'Milk': [4, 'Isle 2', True]}), - - ({'Kiwi': [3, 'Isle 6', False]},{'Kiwi': [3, 'Isle 6', False], 'Juice': [5, 'Isle 5', False], - 'Yoghurt': [2, 'Isle 2', True], 'Milk': [5, 'Isle 2', True]}), - - ({'Kiwi': [1, 'Isle 6', False], 'Melon': [4, 'Isle 6', False], 'Apple': [2, 'Isle 1', False], - 'Raspberry': [2, 'Isle 6', False], 'Blueberries': [5, 'Isle 6', False], - 'Broccoli': [1, 'Isle 3', False]}, - {'Apple': [2, 'Isle 1', False], 'Raspberry': [5, 'Isle 6', False], - 'Blueberries': [10, 'Isle 6', False], 'Broccoli': [4, 'Isle 3', False], - 'Kiwi': [1, 'Isle 6', False], 'Melon': [8, 'Isle 6', False]}) + ({'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], + 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]}, + {'Banana': [15, 'Aisle 5', False], 'Apple': [12, 'Aisle 4', False], + 'Orange': [1, 'Aisle 4', False], 'Milk': [4, 'Aisle 2', True]}), + + ({'Kiwi': [3, 'Aisle 6', False]},{'Kiwi': [3, 'Aisle 6', False], 'Juice': [5, 'Aisle 5', False], + 'Yoghurt': [2, 'Aisle 2', True], 'Milk': [5, 'Aisle 2', True]}), + + ({'Kiwi': [1, 'Aisle 6', False], 'Melon': [4, 'Aisle 6', False], 'Apple': [2, 'Aisle 1', False], + 'Raspberry': [2, 'Aisle 6', False], 'Blueberries': [5, 'Aisle 6', False], + 'Broccoli': [1, 'Aisle 3', False]}, + {'Apple': [2, 'Aisle 1', False], 'Raspberry': [5, 'Aisle 6', False], + 'Blueberries': [10, 'Aisle 6', False], 'Broccoli': [4, 'Aisle 3', False], + 'Kiwi': [1, 'Aisle 6', False], 'Melon': [8, 'Aisle 6', False]}) ] output_data = [ - {'Banana': [12, 'Isle 5', False], 'Apple': [10, 'Isle 4', False], - 'Orange': ['Out of Stock', 'Isle 4', False], 'Milk': [2, 'Isle 2', True]}, + {'Banana': [12, 'Aisle 5', False], 'Apple': [10, 'Aisle 4', False], + 'Orange': ['Out of Stock', 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True]}, - {'Juice': [5, 'Isle 5', False], 'Yoghurt': [2, 'Isle 2', True], - 'Milk': [5, 'Isle 2', True], 'Kiwi': ["Out of Stock", 'Isle 6', False]}, + {'Juice': [5, 'Aisle 5', False], 'Yoghurt': [2, 'Aisle 2', True], + 'Milk': [5, 'Aisle 2', True], 'Kiwi': ["Out of Stock", 'Aisle 6', False]}, - {'Kiwi': ['Out of Stock', 'Isle 6', False], 'Melon': [4, 'Isle 6', False], - 'Apple': ['Out of Stock', 'Isle 1', False], 'Raspberry': [3, 'Isle 6', False], - 'Blueberries': [5, 'Isle 6', False], 'Broccoli': [3, 'Isle 3', False]} + {'Kiwi': ['Out of Stock', 'Aisle 6', False], 'Melon': [4, 'Aisle 6', False], + 'Apple': ['Out of Stock', 'Aisle 1', False], 'Raspberry': [3, 'Aisle 6', False], + 'Blueberries': [5, 'Aisle 6', False], 'Broccoli': [3, 'Aisle 3', False]} ] for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): From 5c7eb1defb93e0e90a16d219f733b12a0824d890 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 6 Feb 2024 19:49:58 +0100 Subject: [PATCH 055/277] roman-numerals: sync (#3622) * Sync the `roman-numerals` exercise's docs with the latest data. * Sync the `roman-numerals` exercise's metadata with the latest data. --- .../roman-numerals/.docs/instructions.md | 45 +++----------- .../roman-numerals/.docs/introduction.md | 59 +++++++++++++++++++ .../practice/roman-numerals/.meta/config.json | 2 +- 3 files changed, 68 insertions(+), 38 deletions(-) create mode 100644 exercises/practice/roman-numerals/.docs/introduction.md diff --git a/exercises/practice/roman-numerals/.docs/instructions.md b/exercises/practice/roman-numerals/.docs/instructions.md index 247ea0892e0..50e2f5bf1c6 100644 --- a/exercises/practice/roman-numerals/.docs/instructions.md +++ b/exercises/practice/roman-numerals/.docs/instructions.md @@ -1,41 +1,12 @@ -# Instructions +# Introduction -Write a function to convert from normal numbers to Roman Numerals. +Your task is to convert a number from Arabic numerals to Roman numerals. -The Romans were a clever bunch. -They conquered most of Europe and ruled it for hundreds of years. -They invented concrete and straight roads and even bikinis. -One thing they never discovered though was the number zero. -This made writing and dating extensive histories of their exploits slightly more challenging, but the system of numbers they came up with is still in use today. -For example the BBC uses Roman numerals to date their programs. +For this exercise, we are only concerned about traditional Roman numerals, in which the largest number is MMMCMXCIX (or 3,999). -The Romans wrote numbers using letters - I, V, X, L, C, D, M. -(notice these letters have lots of straight lines and are hence easy to hack into stone tablets). +~~~~exercism/note +There are lots of different ways to convert between Arabic and Roman numerals. +We recommend taking a naive approach first to familiarise yourself with the concept of Roman numerals and then search for more efficient methods. -```text - 1 => I -10 => X - 7 => VII -``` - -The maximum number supported by this notation is 3,999. -(The Romans themselves didn't tend to go any higher) - -Wikipedia says: Modern Roman numerals ... are written by expressing each digit separately starting with the left most digit and skipping any digit with a value of zero. - -To see this in practice, consider the example of 1990. - -In Roman numerals 1990 is MCMXC: - -1000=M -900=CM -90=XC - -2008 is written as MMVIII: - -2000=MM -8=VIII - -Learn more about [Roman numerals on Wikipedia][roman-numerals]. - -[roman-numerals]: https://wiki.imperivm-romanvm.com/wiki/Roman_Numerals +Make sure to check out our Deep Dive video at the end to explore the different approaches you can take! +~~~~ diff --git a/exercises/practice/roman-numerals/.docs/introduction.md b/exercises/practice/roman-numerals/.docs/introduction.md new file mode 100644 index 00000000000..6fd942fef30 --- /dev/null +++ b/exercises/practice/roman-numerals/.docs/introduction.md @@ -0,0 +1,59 @@ +# Description + +Today, most people in the world use Arabic numerals (0–9). +But if you travelled back two thousand years, you'd find that most Europeans were using Roman numerals instead. + +To write a Roman numeral we use the following Latin letters, each of which has a value: + +| M | D | C | L | X | V | I | +| ---- | --- | --- | --- | --- | --- | --- | +| 1000 | 500 | 100 | 50 | 10 | 5 | 1 | + +A Roman numeral is a sequence of these letters, and its value is the sum of the letters' values. +For example, `XVIII` has the value 18 (`10 + 5 + 1 + 1 + 1 = 18`). + +There's one rule that makes things trickier though, and that's that **the same letter cannot be used more than three times in succession**. +That means that we can't express numbers such as 4 with the seemingly natural `IIII`. +Instead, for those numbers, we use a subtraction method between two letters. +So we think of `4` not as `1 + 1 + 1 + 1` but instead as `5 - 1`. +And slightly confusingly to our modern thinking, we write the smaller number first. +This applies only in the following cases: 4 (`IV`), 9 (`IX`), 40 (`XL`), 90 (`XC`), 400 (`CD`) and 900 (`CM`). + +Order matters in Roman numerals! +Letters (and the special compounds above) must be ordered by decreasing value from left to right. + +Here are some examples: + +```text + 105 => CV +---- => -- + 100 => C ++ 5 => V +``` + +```text + 106 => CVI +---- => -- + 100 => C ++ 5 => V ++ 1 => I +``` + +```text + 104 => CIV +---- => --- + 100 => C ++ 4 => IV +``` + +And a final more complex example: + +```text + 1996 => MCMXCVI +----- => ------- + 1000 => M ++ 900 => CM ++ 90 => XC ++ 5 => V ++ 1 => I +``` diff --git a/exercises/practice/roman-numerals/.meta/config.json b/exercises/practice/roman-numerals/.meta/config.json index eab034a9f92..e66d34f29d4 100644 --- a/exercises/practice/roman-numerals/.meta/config.json +++ b/exercises/practice/roman-numerals/.meta/config.json @@ -26,7 +26,7 @@ ".meta/example.py" ] }, - "blurb": "Write a function to convert from normal numbers to Roman Numerals.", + "blurb": "Convert modern Arabic numbers into Roman numerals.", "source": "The Roman Numeral Kata", "source_url": "https://codingdojo.org/kata/RomanNumerals/" } From 51f379877c18e283407267c1d10082093541d19c Mon Sep 17 00:00:00 2001 From: Tomasz Gieorgijewski <24649931+Nerwosolek@users.noreply.github.com> Date: Tue, 6 Feb 2024 19:57:26 +0100 Subject: [PATCH 056/277] dict-and-join simplify for raindrops approaches (#3620) * dict-and-join simplify * Swapped dictionary approaches Swapped the two dictionary approaches and adjusted `introduction.md`, `Benchmark.py`, `Performance`, and the approaches doc accordingly. --------- Co-authored-by: BethanyG --- .../.approaches/dict-and-join/content.md | 8 ++++---- .../.approaches/dict-and-join/snippet.txt | 2 +- .../raindrops/.approaches/introduction.md | 8 ++++---- .../.articles/performance/code/Benchmark.py | 15 +++++++++++++++ .../.articles/performance/content.md | 19 ++++++++++--------- 5 files changed, 34 insertions(+), 18 deletions(-) diff --git a/exercises/practice/raindrops/.approaches/dict-and-join/content.md b/exercises/practice/raindrops/.approaches/dict-and-join/content.md index ae302ada8dd..94be03cb0a1 100644 --- a/exercises/practice/raindrops/.approaches/dict-and-join/content.md +++ b/exercises/practice/raindrops/.approaches/dict-and-join/content.md @@ -6,15 +6,15 @@ def convert(number): sounds = {3: 'Pling', 5: 'Plang', 7: 'Plong'} results = ''.join(sounds[divisor] for - divisor, sound in sounds.items() + divisor in sounds.keys() if number % divisor == 0) return results or str(number) ``` This approach uses a [dictionary][dict] called 'sounds' with factors as `keys` and sound strings as `values`. -A [generator-expression][generator-expressions] inside of [`str.join()`][str.join] loops through the [dictionary view object][dict-view-object] [`sounds.items()`][dict.items()], which is a series of (key, value) `tuples`. - Each `value` is looked up for every factor where number % divisor == 0. +A [generator-expression][generator-expressions] inside of [`str.join()`][str.join] loops through the [dictionary view object][dict-view-object] [`sounds.keys()`][dict.keys()], which is a sequence of all the dictionary keys. + Each `value` is looked up for every factor (key) where `number % divisor == 0`. `str.join()` then compiles the results. This is the equivalent of: @@ -46,7 +46,7 @@ def convert(number): ``` [dict-view-object]: https://docs.python.org/3/library/stdtypes.html#dictionary-view-objects -[dict.items()]: https://docs.python.org/3/library/stdtypes.html#dict.items +[dict.keys()]: https://docs.python.org/3/library/stdtypes.html#dict.keys [dict]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries [generator-expressions]: https://www.pythonmorsels.com/how-write-generator-expression/ [str.join]: https://docs.python.org/3/library/stdtypes.html#str.join diff --git a/exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt b/exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt index 6c24abb8df0..662c7c6e54e 100644 --- a/exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt +++ b/exercises/practice/raindrops/.approaches/dict-and-join/snippet.txt @@ -2,7 +2,7 @@ def convert(number): sounds = {3: 'Pling', 5: 'Plang', 7: 'Plong'} results = ''.join(sounds[divisor] for - divisor, sound in sounds.items() + divisor in sounds.keys() if number % divisor == 0) return results or str(number) diff --git a/exercises/practice/raindrops/.approaches/introduction.md b/exercises/practice/raindrops/.approaches/introduction.md index b55ceabecbd..50e83d706ee 100644 --- a/exercises/practice/raindrops/.approaches/introduction.md +++ b/exercises/practice/raindrops/.approaches/introduction.md @@ -144,7 +144,7 @@ def convert(number): 7: 'Plong'} results = ''.join(sounds[divisor] for - divisor, sound in sounds.items() + divisor in sounds.keys() if number % divisor == 0) return results or str(number) @@ -248,8 +248,8 @@ def convert(number): 5: 'Plang', 7: 'Plong'} - results = (sounds[divisor] for - divisor in sounds.keys() + results = (sound for + divisor, sound in sounds.items() if number % divisor == 0) return ''.join(results) or str(number) @@ -258,7 +258,7 @@ def convert(number): This separates the code that calculates the results string from the factors themselves. If a factor needs to be added, only the dictionary needs to be touched. -This code need only iterate over the keys of the dictionary to do its calculation, making this `O(1)` in time complexity. +This code need only iterate over the items of the dictionary to do its calculation, making this `O(1)` in time complexity. This does take `O(m)` space, where `m` is equal to the number of factor entries. Since the number of factors is fixed here, this is unlikely to create issues unless a great many more are added to the 'sounds' `dict`. diff --git a/exercises/practice/raindrops/.articles/performance/code/Benchmark.py b/exercises/practice/raindrops/.articles/performance/code/Benchmark.py index a01978a7efe..b30a091e60c 100644 --- a/exercises/practice/raindrops/.articles/performance/code/Benchmark.py +++ b/exercises/practice/raindrops/.articles/performance/code/Benchmark.py @@ -63,6 +63,19 @@ def convert_dict(number): 7: 'Plong'} results = ''.join(sounds[divisor] for + divisor in sounds.keys() + if number % divisor == 0) + + return results or str(number) + + +def convert_dict_recommended(number): + + sounds = {3: 'Pling', + 5: 'Plang', + 7: 'Plong'} + + results = ''.join(sound for divisor, sound in sounds.items() if number % divisor == 0) @@ -127,6 +140,7 @@ def convert_pattern_matching(number): "loop with tuple", "sequence with join", "dictionary with join", + "dictionary recommended" "itertools with join", "functools reduce", "structural pattern matching"] @@ -140,6 +154,7 @@ def convert_pattern_matching(number): convert_loop, convert_sequence_join, convert_dict, + convert_dict_recommended, convert_itertools, convert_functools, convert_pattern_matching] diff --git a/exercises/practice/raindrops/.articles/performance/content.md b/exercises/practice/raindrops/.articles/performance/content.md index 522ff1016ee..7894d2f94c2 100644 --- a/exercises/practice/raindrops/.articles/performance/content.md +++ b/exercises/practice/raindrops/.articles/performance/content.md @@ -23,20 +23,21 @@ To benchmark these functions, we wrote a small [benchmarking script][benchmark-a | | 10 | 14 | 15 | 70 | 105 | 182 | 189 | 203 | 204 | 399 | 409 | 525 | 735 | 1575 | 3250 | |----------------------------- |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |:--------: | -| if statements | 2.12e-07 | 2.16e-07 | 2.60e-07 | 2.76e-07 | 2.98e-07 | 2.10e-07 | 2.59e-07 | 2.15e-07 | 2.10e-07 | 2.75e-07 | 2.61e-07 | 3.41e-07 | 2.99e-07 | 2.98e-07 | 2.13e-07 | -| ternary with truthy/falsy | 2.65e-07 | 2.65e-07 | 2.80e-07 | 2.70e-07 | 2.91e-07 | 2.67e-07 | 2.73e-07 | 2.75e-07 | 2.69e-07 | 3.56e-07 | 2.02e-07 | 3.06e-07 | 3.06e-07 | 3.04e-07 | 2.89e-07 | -| loop with tuple | 4.01e-07 | 4.09e-07 | 5.05e-07 | 4.94e-07 | 6.48e-07 | 3.97e-07 | 5.25e-07 | 4.10e-07 | 2.04e-07 | 5.51e-07 | 4.06e-07 | 9.04e-07 | 6.16e-07 | 6.89e-07 | 4.33e-07 | -| structural pattern matching | 7.55e-07 | 7.31e-07 | 6.09e-07 | 5.87e-07 | 5.21e-07 | 7.11e-07 | 6.42e-07 | 7.19e-07 | 6.90e-07 | 6.49e-07 | 8.43e-07 | 5.00e-07 | 5.12e-07 | 5.21e-07 | 7.48e-07 | -| dictionary with join | 8.31e-07 | 8.18e-07 | 9.34e-07 | 1.02e-06 | 9.75e-07 | 8.55e-07 | 9.13e-07 | 8.25e-07 | 8.32e-07 | 2.28e-06 | 9.22e-07 | 1.05e-06 | 2.42e-06 | 9.94e-07 | 8.46e-07 | -| sequence with join | 8.29e-07 | 8.17e-07 | 9.27e-07 | 9.10e-07 | 9.62e-07 | 8.73e-07 | 9.70e-07 | 8.87e-07 | 9.40e-07 | 2.52e-06 | 9.74e-07 | 2.44e-06 | 2.57e-06 | 9.79e-07 | 8.67e-07 | -| itertools with join | 9.46e-07 | 9.33e-07 | 4.04e-07 | 9.88e-07 | 1.01e-06 | 9.41e-07 | 9.91e-07 | 9.65e-07 | 9.80e-07 | 2.51e-06 | 1.10e-06 | 2.50e-06 | 1.02e-06 | 1.00e-06 | 9.60e-07 | -| functools reduce | 1.35e-06 | 1.38e-06 | 1.41e-06 | 1.39e-06 | 1.48e-06 | 1.33e-06 | 1.42e-06 | 1.37e-06 | 1.34e-06 | 1.39e-06 | 1.43e-06 | 1.45e-06 | 1.46e-06 | 1.45e-06 | 1.37e-06 | +| if statements | 2.15e-07 | 2.13e-07 | 2.63e-07 | 2.62e-07 | 3.03e-07 | 2.13e-07 | 2.69e-07 | 2.12e-07 | 2.12e-07 | 2.75e-07 | 2.64e-07 | 3.04e-07 | 3.04e-07 | 3.06e-07 | 2.20e-07 | +| ternary with truthy/falsy | 2.79e-07 | 2.80e-07 | 2.89e-07 | 2.85e-07 | 3.02e-07 | 2.80e-07 | 3.13e-07 | 2.81e-07 | 2.79e-07 | 2.82e-07 | 3.30e-07 | 3.02e-07 | 3.02e-07 | 3.02e-07 | 2.80e-07 | +| loop with tuple | 7.91e-07 | 4.08e-07 | 5.07e-07 | 5.14e-07 | 6.13e-07 | 7.95e-07 | 5.10e-07 | 2.01e-07 | 8.18e-07 | 5.06e-07 | 3.85e-07 | 6.25e-07 | 6.10e-07 | 6.10e-07 | 2.00e-07 | +| structural pattern matching | 7.91e-07 | 7.39e-07 | 5.98e-07 | 6.24e-07 | 5.40e-07 | 7.47e-07 | 6.73e-07 | 7.72e-07 | 7.15e-07 | 6.69e-07 | 8.80e-07 | 5.43e-07 | 5.38e-07 | 5.69e-07 | 8.04e-07 | +| dictionary with join | 8.04e-07 | 7.98e-07 | 8.95e-07 | 9.81e-07 | 4.07e-07 | 8.15e-07 | 9.05e-07 | 8.14e-07 | 8.18e-07 | 9.05e-07 | 8.94e-07 | 9.51e-07 | 4.05e-07 | 9.80e-07 | 8.17e-07 | +| dictionary recommended | 8.55e-07 | 8.13e-07 | 8.99e-07 | 8.96e-07 | 9.36e-07 | 8.12e-07 | 9.27e-07 | 8.18e-07 | 8.24e-07 | 9.07e-07 | 9.13e-07 | 9.40e-07 | 9.36e-07 | 9.30e-07 | 8.15e-07 | +| sequence with  join | 8.59e-07 | 8.67e-07 | 9.56e-07 | 9.64e-07 | 4.04e-07 | 8.78e-07 | 9.65e-07 | 8.71e-07 | 8.74e-07 | 9.68e-07 | 9.27e-07 | 1.00e-06 | 1.02e-06 | 4.07e-07 | 8.59e-07 | +| itertools with join | 9.90e-07 | 9.82e-07 | 1.05e-06 | 1.03e-06 | 1.07e-06 | 9.85e-07 | 1.06e-06 | 9.83e-07 | 9.82e-07 | 1.04e-06 | 1.11e-06 | 1.07e-06 | 1.07e-06 | 1.04e-06 | 4.07e-07 | +| functools reduce | 1.42e-06 | 1.56e-06 | 1.45e-06 | 1.43e-06 | 1.50e-06 | 1.41e-06 | 1.46e-06 | 1.44e-06 | 1.41e-06 | 1.47e-06 | 1.49e-06 | 1.52e-06 | 1.49e-06 | 1.51e-06 | 1.43e-06 | Keep in mind that all these approaches are _very fast_, and that benchmarking at this granularity can be unstable. That caveat in mind, the two `if-statement` based approaches benchmark fastest, and the approach using `functools.reduce()` was the slowest. -The 'recommended' approach came in 4th, though it can be argued that the slowdown is justified by the increased readability and maintainability. +The 'dictionary with join' approach came in 5th, with the 'recommended dictionary' approach in 6th, though it can be argued that the slowdown for either dictionary approach is justified by the increased readability and maintainability. Measurements were taken on a 3.1 GHz Quad-Core Intel Core i7 Mac running MacOS Ventura. Tests used `timeit.Timer.autorange()`, repeated 3 times. From 2e5051aab0b64681ee8c53356625fb1cf4452871 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 6 Feb 2024 11:34:53 -0800 Subject: [PATCH 057/277] [Raindrops]: Update content.md Benchmark URL (#3624) Updated `Benchmark.py` link to point at GitHub Repo rather than the website (_which returns a 404_) --- exercises/practice/raindrops/.articles/performance/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/raindrops/.articles/performance/content.md b/exercises/practice/raindrops/.articles/performance/content.md index 7894d2f94c2..82ade02c90f 100644 --- a/exercises/practice/raindrops/.articles/performance/content.md +++ b/exercises/practice/raindrops/.articles/performance/content.md @@ -54,7 +54,7 @@ The [`timeit`][timeit] module docs have more details, and [note.nkmk.me][note_nk [approach-sequence-with-join]: https://exercism.org/tracks/python/exercises/raindrops/approaches/sequence-with-join [approach-truthy-and-falsey-with-fstring]: https://exercism.org/tracks/python/exercises/raindrops/approaches/truthy-and-falsey-with-fstring [approaches-intro]: https://exercism.org/tracks/python/exercises/raindrops/approaches/introduction.md -[benchmark-application]: https://exercism.org/tracks/python/exercises/raindrops/.articles/code/Benchmark.py +[benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/raindrops/.articles/performance/code/Benchmark.py [note_nkmk_me]: https://note.nkmk.me/en/python-timeit-measure/ [numpy]: https://numpy.org/ [pandas]: https://pandas.pydata.org/ From 67b37573e72304c3ff53d570463d710372a017af Mon Sep 17 00:00:00 2001 From: Zak <141658747+zakweb3@users.noreply.github.com> Date: Tue, 6 Feb 2024 20:49:03 +0100 Subject: [PATCH 058/277] Fixed small typo in introduction.md (#3621) * Update introduction.md Fixed small typo in line 65 replaced : with = * Update about.md Replaced : with = in line 101 * Apply suggestions from code review * Update concepts/tuples/about.md --------- Co-authored-by: BethanyG --- concepts/tuples/about.md | 6 +++--- .../concept/tisbury-treasure-hunt/.docs/introduction.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/concepts/tuples/about.md b/concepts/tuples/about.md index bc0e81462e5..3658ac1e39d 100644 --- a/concepts/tuples/about.md +++ b/concepts/tuples/about.md @@ -98,7 +98,7 @@ Other data structures can be included as `tuple` elements, including other `tupl >>> nested_data_structures = ({"fish": "gold", "monkey": "brown", "parrot" : "grey"}, ("fish", "mammal", "bird")) ({"fish": "gold", "monkey": "brown", "parrot" : "grey"}, ("fish", "mammal", "bird")) ->>> nested_data_structures_1 : (["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird")) +>>> nested_data_structures_1 = (["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird")) (["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird")) ``` @@ -225,5 +225,5 @@ Additionally, users can adapt a [`dataclass`][dataclass] to provide similar name [mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types [namedtuple]: https://docs.python.org/3/library/collections.html#collections.namedtuple [sequence]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range -[set]: https://github.com/exercism/v3/blob/master/languages/python/reference/concepts/builtin_types/set.md -[tuple]: https://github.com/exercism/v3/blob/master/languages/python/reference/concepts/builtin_types/tuple.md \ No newline at end of file +[set]: https://docs.python.org/3/library/stdtypes.html#set +[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple diff --git a/exercises/concept/tisbury-treasure-hunt/.docs/introduction.md b/exercises/concept/tisbury-treasure-hunt/.docs/introduction.md index 1bfbe3e6150..e48857f20a9 100644 --- a/exercises/concept/tisbury-treasure-hunt/.docs/introduction.md +++ b/exercises/concept/tisbury-treasure-hunt/.docs/introduction.md @@ -62,7 +62,7 @@ Nested data structures can be included as `tuple` elements, including other `tup >>> nested_data_structures = ({"fish": "gold", "monkey": "brown", "parrot" : "grey"}, ("fish", "mammal", "bird")) ({"fish": "gold", "monkey": "brown", "parrot" : "grey"}, ("fish", "mammal", "bird")) ->>> nested_data_structures_1 : (["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird")) +>>> nested_data_structures_1 = (["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird")) (["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird")) ``` From 63cbe2bbfc0dde6f7804ba3917bea004199c4608 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 6 Feb 2024 12:40:08 -0800 Subject: [PATCH 059/277] [Tuples]: Fix Bad Links in About.md (#3625) Multiple links were pointed at the deprecated V3 repo instead of the Python docs. --- concepts/tuples/about.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/concepts/tuples/about.md b/concepts/tuples/about.md index 3658ac1e39d..ea8179e2d8a 100644 --- a/concepts/tuples/about.md +++ b/concepts/tuples/about.md @@ -219,9 +219,9 @@ Additionally, users can adapt a [`dataclass`][dataclass] to provide similar name [common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations [dataclass pros and cons]: https://stackoverflow.com/questions/51671699/data-classes-vs-typing-namedtuple-primary-use-cases [dataclass]: https://docs.python.org/3/library/dataclasses.html -[dict]: https://github.com/exercism/v3/blob/master/languages/python/reference/concepts/builtin_types/dict.md +[dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict [hashability]: https://docs.python.org/3/glossary.html#hashable -[list]: https://github.com/exercism/v3/blob/master/languages/python/reference/concepts/builtin_types/list.md +[list]: https://docs.python.org/3/library/stdtypes.html#list [mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types [namedtuple]: https://docs.python.org/3/library/collections.html#collections.namedtuple [sequence]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range From 5dd5af161c09a7ac64ed342d5b6093ed204c0c16 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 11 Feb 2024 16:55:09 -0800 Subject: [PATCH 060/277] Update README.md (#3629) Add in missing 'in'. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 99357745f09..d8ec6644585 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Please read this [community blog post][freeing-maintainers] for details. Here to suggest a new feature or new exercise?? **Hooray!**  🎉   We'd love if you did that via our [Exercism Community Forum](https://forum.exercism.org/). Please read [Suggesting Exercise Improvements][suggesting-improvements] & [Chesterton's Fence][chestertons-fence]. -_Thoughtful suggestions will likely result faster & more enthusiastic responses from volunteers._ +_Thoughtful suggestions will likely result in faster & more enthusiastic responses from volunteers._
From 7e3a633b180fba589fdcb0965953ba43b31726cc Mon Sep 17 00:00:00 2001 From: colinleach Date: Sun, 11 Feb 2024 23:32:46 -0600 Subject: [PATCH 061/277] [Sieve]: Draft approaches (#3626) * [Sieve]: Draft approaches * fixes various typos and random gibberish * Update introduction.md * Update exercises/practice/sieve/.approaches/comprehensions/content.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/comprehensions/content.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/comprehensions/content.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/comprehensions/content.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/nested-loops/content.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/comprehensions/content.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/comprehensions/snippet.txt Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/introduction.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/nested-loops/content.md Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/nested-loops/snippet.txt Co-authored-by: BethanyG * Update exercises/practice/sieve/.approaches/comprehensions/content.md Does this add a spurious extra space after the link? Co-authored-by: BethanyG * Removed graph from content.md To save us forgetting it later. * Delete timeit_bar_plot.svg I didn't intend to commit this in the first place. * removed space from content.md * Update exercises/practice/sieve/.approaches/nested-loops/content.md * Update exercises/practice/sieve/.approaches/nested-loops/content.md * Update exercises/practice/sieve/.approaches/introduction.md * Update exercises/practice/sieve/.approaches/introduction.md * Update exercises/practice/sieve/.approaches/introduction.md * Code Block Corrections Somehow, the closing of the codeblocks got dropped. Added them back in, along with final typo corrections. --------- Co-authored-by: BethanyG --- .../.approaches/comprehensions/content.md | 36 + .../.approaches/comprehensions/snippet.txt | 3 + .../practice/sieve/.approaches/config.json | 40 + .../sieve/.approaches/introduction.md | 99 ++ .../sieve/.approaches/nested-loops/content.md | 49 + .../.approaches/nested-loops/snippet.txt | 8 + .../.approaches/set-operations/content.md | 69 + .../.approaches/set-operations/snippet.txt | 8 + .../practice/sieve/.articles/config.json | 14 + .../.articles/performance/code/Benchmark.py | 126 ++ .../performance/code/create_plots.py | 43 + .../performance/code/fit_gradients.py | 56 + .../performance/code/run_times.feather | Bin 0 -> 5946 bytes .../performance/code/transposed_logs.feather | Bin 0 -> 5090 bytes .../sieve/.articles/performance/content.md | 59 + .../sieve/.articles/performance/slopes.svg | 1555 +++++++++++++++++ .../sieve/.articles/performance/snippet.md | 8 + 17 files changed, 2173 insertions(+) create mode 100644 exercises/practice/sieve/.approaches/comprehensions/content.md create mode 100644 exercises/practice/sieve/.approaches/comprehensions/snippet.txt create mode 100644 exercises/practice/sieve/.approaches/config.json create mode 100644 exercises/practice/sieve/.approaches/introduction.md create mode 100644 exercises/practice/sieve/.approaches/nested-loops/content.md create mode 100644 exercises/practice/sieve/.approaches/nested-loops/snippet.txt create mode 100644 exercises/practice/sieve/.approaches/set-operations/content.md create mode 100644 exercises/practice/sieve/.approaches/set-operations/snippet.txt create mode 100644 exercises/practice/sieve/.articles/config.json create mode 100644 exercises/practice/sieve/.articles/performance/code/Benchmark.py create mode 100644 exercises/practice/sieve/.articles/performance/code/create_plots.py create mode 100644 exercises/practice/sieve/.articles/performance/code/fit_gradients.py create mode 100644 exercises/practice/sieve/.articles/performance/code/run_times.feather create mode 100644 exercises/practice/sieve/.articles/performance/code/transposed_logs.feather create mode 100644 exercises/practice/sieve/.articles/performance/content.md create mode 100644 exercises/practice/sieve/.articles/performance/slopes.svg create mode 100644 exercises/practice/sieve/.articles/performance/snippet.md diff --git a/exercises/practice/sieve/.approaches/comprehensions/content.md b/exercises/practice/sieve/.approaches/comprehensions/content.md new file mode 100644 index 00000000000..664ea32c11a --- /dev/null +++ b/exercises/practice/sieve/.approaches/comprehensions/content.md @@ -0,0 +1,36 @@ +# Comprehensions + +```python +def primes(number): + prime = (item for item in range(2, number+1) + if item not in (not_prime for item in range(2, number+1) + for not_prime in range(item*item, number+1, item))) + return list(prime) +``` + +Many of the solutions to Sieve use `comprehensions` or `generator-expressions` at some point, but this page is about examples that put almost *everything* into a single, elaborate `generator-expression` or `comprehension`. + +The above example uses a `generator-expression` to do all the calculation. + +There are at least two problems with this: +- Readability is poor. +- Performance is exceptionally bad, making this the slowest solution tested, for all input sizes. + +Notice the many `for` clauses in the generator. + +This makes the code similar to [nested loops][nested-loops], and run time scales quadratically with the size of `number`. +In fact, when this code is compiled, it _compiles to nested loops_ that have the additional overhead of generator setup and tracking. + +```python +def primes(limit): + return [number for number in range(2, limit + 1) + if all(number % divisor != 0 for divisor in range(2, number))] +``` + +This second example using a `list-comprehension` with `all()` is certainly concise and _relatively_ readable, but the performance is again quite poor. + +This is not quite a fully nested loop (_there is a short-circuit when `all()` evaluates to `False`_), but it is by no means "performant". +In this case, scaling with input size is intermediate between linear and quadratic, so not quite as bad as the first example. + + +[nested-loops]: https://exercism.org/tracks/python/exercises/sieve/approaches/nested-loops diff --git a/exercises/practice/sieve/.approaches/comprehensions/snippet.txt b/exercises/practice/sieve/.approaches/comprehensions/snippet.txt new file mode 100644 index 00000000000..125d1fb8385 --- /dev/null +++ b/exercises/practice/sieve/.approaches/comprehensions/snippet.txt @@ -0,0 +1,3 @@ +def primes(limit): + return [number for number in range(2, limit + 1) if + all(number % divisor != 0 for divisor in range(2, number))] diff --git a/exercises/practice/sieve/.approaches/config.json b/exercises/practice/sieve/.approaches/config.json new file mode 100644 index 00000000000..f67a4bb244f --- /dev/null +++ b/exercises/practice/sieve/.approaches/config.json @@ -0,0 +1,40 @@ +{ + "introduction": { + "authors": [ + "colinleach", + "BethanyG" + ] + }, + "approaches": [ + { + "uuid": "85752386-a3e0-4ba5-aca7-22f5909c8cb1", + "slug": "nested-loops", + "title": "Nested Loops", + "blurb": "Relativevly clear solutions with explicit loops.", + "authors": [ + "colinleach", + "BethanyG" + ] + }, + { + "uuid": "04701848-31bf-4799-8093-5d3542372a2d", + "slug": "set-operations", + "title": "Set Operations", + "blurb": "Performance enhancements with Python sets.", + "authors": [ + "colinleach", + "BethanyG" + ] + }, + { + "uuid": "183c47e3-79b4-4afb-8dc4-0deaf094ce5b", + "slug": "comprehensions", + "title": "Comprehensions", + "blurb": "Ultra-concise code and its downsides.", + "authors": [ + "colinleach", + "BethanyG" + ] + } + ] +} diff --git a/exercises/practice/sieve/.approaches/introduction.md b/exercises/practice/sieve/.approaches/introduction.md new file mode 100644 index 00000000000..8a064c2d7bc --- /dev/null +++ b/exercises/practice/sieve/.approaches/introduction.md @@ -0,0 +1,99 @@ +# Introduction + +The key to this exercise is to keep track of: +- A list of numbers. +- Their status of possibly being prime. + + +## General Guidance + +To solve this exercise, it is necessary to choose one or more appropriate data structures to store numbers and status, then decide the best way to scan through them. + +There are many ways to implement the code, and the three broad approaches listed below are not sharply separated. + + +## Approach: Using nested loops + +```python +def primes(number): + not_prime = [] + prime = [] + + for item in range(2, number+1): + if item not in not_prime: + prime.append(item) + for element in range(item*item, number+1, item): + not_prime.append(element) + + return prime +``` + +The theme here is nested, explicit `for` loops to move through ranges, testing validity as we go. + +For details and another example see [`nested-loops`][approaches-nested]. + + +## Approach: Using set operations + +```python +def primes(number): + not_prime = set() + primes = [] + + for num in range(2, number+1): + if num not in not_prime: + primes.append(num) + not_prime.update(range (num*num, number+1, num)) + + return primes +``` + +In this group, the code uses the special features of the Python [`set`][sets] to improve efficiency. + +For details and other examples see [`set-operations`][approaches-sets]. + + +## Approach: Using complex or nested comprehensions + + +```python +def primes(limit): + return [number for number in range(2, limit + 1) if + all(number % divisor != 0 for divisor in range(2, number))] +``` + +Here, the emphasis is on implementing a solution in the minimum number of lines, even at the expense of readability or performance. + +For details and another example see [`comprehensions`][approaches-comps]. + + +## Using packages outside base Python + + +In statically typed languages, common approaches include bit arrays and arrays of booleans. + +Neither of these is a natural fit for core Python, but there are external packages that could perhaps provide a better implementation: + +- For bit arrays, there is the [`bitarray`][bitarray] package and [`bitstring.BitArray()`][bitstring]. +- For arrays of booleans, we could use the NumPy package: `np.ones((number,), dtype=np.bool_)` will create a pre-dimensioned array of `True`. + +It should be stressed that these will not work in the Exercism test runner, and are mentioned here only for completeness. + +## Which Approach to Use? + + +This exercise is for learning, and is not directly relevant to production code. + +The point is to find a solution which is correct, readable, and remains reasonably fast for larger input values. + +The "set operations" example above is clean, readable, and in benchmarking was the fastest code tested. + +Further details of performance testing are given in the [Performance article][article-performance]. + +[approaches-nested]: https://exercism.org/tracks/python/exercises/sieve/approaches/nested-loops +[approaches-sets]: https://exercism.org/tracks/python/exercises/sieve/approaches/set-operations +[approaches-comps]: https://exercism.org/tracks/python/exercises/sieve/approaches/comprehensions +[article-performance]:https://exercism.org/tracks/python/exercises/sieve/articles/performance +[sets]: https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset +[bitarray]: https://pypi.org/project/bitarray/ +[bitstring]: https://bitstring.readthedocs.io/en/latest/ diff --git a/exercises/practice/sieve/.approaches/nested-loops/content.md b/exercises/practice/sieve/.approaches/nested-loops/content.md new file mode 100644 index 00000000000..75a733a27e9 --- /dev/null +++ b/exercises/practice/sieve/.approaches/nested-loops/content.md @@ -0,0 +1,49 @@ +# Nested Loops + + +```python +def primes(number): + not_prime = [] + prime = [] + + for item in range(2, number+1): + if item not in not_prime: + prime.append(item) + for element in range (item*item, number+1, item): + not_prime.append(element) + + return prime +``` + +This is the type of code that many people might write as a first attempt. + +It is very readable and passes the tests. + +The clear disadvantage is that run time is quadratic in the input size: `O(n**2)`, so this approach scales poorly to large input values. + +Part of the problem is the line `if item not in not_prime`, where `not-prime` is a list that may be long and unsorted. + +This operation requires searching the entire list, so run time is linear in list length: not ideal within a loop repeated many times. + +```python +def primes(number): + number += 1 + prime = [True for item in range(number)] + for index in range(2, number): + if not prime[index]: + continue + for candidate in range(2 * index, number, index): + prime[candidate] = False + return [index for index, value in enumerate(prime) if index > 1 and value] +``` + + +At first sight, this second example looks quite similar to the first. + +However, on testing it performs much better, scaling linearly with `number` rather than quadratically. + +A key difference is that list entries are tested by index: `if not prime[index]`. + +This is a constant-time operation independent of the list length. + +Relatively few programmers would have predicted such a major difference just by looking at the code, so if performance matters we should always test, not guess. diff --git a/exercises/practice/sieve/.approaches/nested-loops/snippet.txt b/exercises/practice/sieve/.approaches/nested-loops/snippet.txt new file mode 100644 index 00000000000..f765ece129f --- /dev/null +++ b/exercises/practice/sieve/.approaches/nested-loops/snippet.txt @@ -0,0 +1,8 @@ +def primes(number): + number += 1 + prime = [True for item in range(number)] + for index in range(2, number): + if not prime[index]: continue + for candidate in range(2 * index, number, index): + prime[candidate] = False + return [index for index, value in enumerate(prime) if index > 1 and value] \ No newline at end of file diff --git a/exercises/practice/sieve/.approaches/set-operations/content.md b/exercises/practice/sieve/.approaches/set-operations/content.md new file mode 100644 index 00000000000..7af5f987223 --- /dev/null +++ b/exercises/practice/sieve/.approaches/set-operations/content.md @@ -0,0 +1,69 @@ +# Set Operations + + +```python +def primes(number): + not_prime = set() + primes = [] + + for num in range(2, number+1): + if num not in not_prime: + primes.append(num) + not_prime.update(range(num*num, number+1, num)) + + return primes +``` + + +This is the fastest method so far tested, at all input sizes. + +With only a single loop, performance scales linearly: `O(n)`. + +A key step is the set `update()`. + +Less commonly seen than `add()`, which takes single element, `update()` takes any iterator of hashable values as its parameter and efficiently adds all the elements in a single operation. + +In this case, the iterator is a range resolving to all multiples, up to the limit, of the prime we just found. + +Primes are collected in a list, in ascending order, so there is no need for a separate sort operation at the end. + + +```python +def primes(number): + numbers = set(item for item in range(2, number+1)) + + not_prime = set(not_prime for item in range(2, number+1) + for not_prime in range(item**2, number+1, item)) + + return sorted(list((numbers - not_prime))) +``` + +After a set comprehension in place of an explicit loop, the second example uses set-subtraction as a key feature in the return statement. + +The resulting set needs to be converted to a list then sorted, which adds some overhead, [scaling as O(n *log* n)][sort-performance]. + +In performance testing, this code is about 4x slower than the first example, but still scales as `O(n)`. + + +```python +def primes(number: int) -> list[int]: + start = set(range(2, number + 1)) + return sorted(start - {m for n in start for m in range(2 * n, number + 1, n)}) +``` + +The third example is quite similar to the second, just moving the comprehension into the return statement. + +Performance is very similar between examples 2 and 3 at all input values. + + +## Sets: strengths and weaknesses + +Sets offer two main benefits which can be useful in this exercise: +- Entries are guaranteed to be unique. +- Determining whether the set contains a given value is a fast, constant-time operation. + +Less positively: +- The exercise specification requires a list to be returned, which may involve a conversion. +- Sets have no guaranteed ordering, so two of the above examples incur the time penalty of sorting a list at the end. + +[sort-performance]: https://en.wikipedia.org/wiki/Timsort diff --git a/exercises/practice/sieve/.approaches/set-operations/snippet.txt b/exercises/practice/sieve/.approaches/set-operations/snippet.txt new file mode 100644 index 00000000000..70f58e046be --- /dev/null +++ b/exercises/practice/sieve/.approaches/set-operations/snippet.txt @@ -0,0 +1,8 @@ +def primes(number): + not_prime = set() + primes = [] + for num in range(2, number+1): + if num not in not_prime: + primes.append(num) + not_prime.update(range(num*num, number+1, num)) + return primes \ No newline at end of file diff --git a/exercises/practice/sieve/.articles/config.json b/exercises/practice/sieve/.articles/config.json new file mode 100644 index 00000000000..60b70c7b508 --- /dev/null +++ b/exercises/practice/sieve/.articles/config.json @@ -0,0 +1,14 @@ +{ + "articles": [ + { + "slug": "performance", + "uuid": "fdbee56a-b4db-4776-8aab-3f7788c612aa", + "title": "Performance deep dive", + "authors": [ + "BethanyG", + "colinleach" + ], + "blurb": "Results and analysis of timing tests for the various approaches." + } + ] +} diff --git a/exercises/practice/sieve/.articles/performance/code/Benchmark.py b/exercises/practice/sieve/.articles/performance/code/Benchmark.py new file mode 100644 index 00000000000..7027fc0ef9f --- /dev/null +++ b/exercises/practice/sieve/.articles/performance/code/Benchmark.py @@ -0,0 +1,126 @@ +import timeit + +import pandas as pd +import numpy as np + + +# ------------ FUNCTIONS TO TIME ------------- # + +def nested_loops_1(number): + not_prime = [] + prime = [] + + for item in range(2, number + 1): + if item not in not_prime: + prime.append(item) + for element in range(item * item, number + 1, item): + not_prime.append(element) + + return prime + + +def nested_loops_2(limit): + limit += 1 + l = [True for _ in range(limit)] + for i in range(2, limit): + if not l[i]: + continue + for j in range(2 * i, limit, i): + l[j] = False + return [i for i, v in enumerate(l) if i > 1 and v] + + +def set_ops_1(number): + numbers = set(item for item in range(2, number + 1)) + + not_prime = set(not_prime for item in range(2, number + 1) + for not_prime in range(item ** 2, number + 1, item)) + + # sorting adds .2ms, but the tests won't pass with an unsorted list + return sorted(list((numbers - not_prime))) + + +def set_ops_2(number): + # fastest + not_prime = set() + primes = [] + + for num in range(2, number + 1): + if num not in not_prime: + primes.append(num) + not_prime.update(range(num * num, number + 1, num)) + + return primes + + +def set_ops_3(limit: int) -> list[int]: + start = set(range(2, limit + 1)) + return sorted(start - {m for n in start for m in range(2 * n, limit + 1, n)}) + + +def generator_comprehension(number): + # slowest + primes = (item for item in range(2, number + 1) if item not in + (not_prime for item in range(2, number + 1) for + not_prime in range(item * item, number + 1, item))) + return list(primes) + + +def list_comprehension(limit): + return [x for x in range(2, limit + 1) + if all(x % y != 0 for y in range(2, x))] if limit >= 2 else [] + + +## ---------END FUNCTIONS TO BE TIMED-------------------- ## + +## -------- Timing Code Starts Here ---------------------## + + +# Input Data Setup +inputs = [10, 30, 100, 300, 1_000, 3_000, 10_000, 30_000, 100_000] + +# #Set up columns and rows for Pandas Data Frame +col_headers = [f'Number: {number}' for number in inputs] +row_headers = ["nested_loops_1", + "nested_loops_2", + "set_ops_1", + "set_ops_2", + "set_ops_3", + "generator_comprehension", + "list_comprehension"] + +# Empty dataframe will be filled in one cell at a time later +df = pd.DataFrame(np.nan, index=row_headers, columns=col_headers) + +# Function List to Call When Timing +functions = [nested_loops_1, + nested_loops_2, + set_ops_1, + set_ops_2, + set_ops_3, + generator_comprehension, + list_comprehension] + +# Run timings using timeit.autorange(). Run Each Set 3 Times. +for function, title in zip(functions, row_headers): + timings = [[ + timeit.Timer(lambda: function(data), globals=globals()).autorange()[1] / + timeit.Timer(lambda: function(data), globals=globals()).autorange()[0] + for data in inputs] for rounds in range(3)] + + # Only the fastest Cycle counts. + timing_result = min(timings) + + print(f'{title}', f'Timings : {timing_result}') + + # Insert results into the dataframe + df.loc[title, 'Number: 10':'Number: 100000'] = timing_result + +# Save the data to avoid constantly regenerating it +df.to_feather('run_times.feather') +print("\nDataframe saved to './run_times.feather'") +# +# The next bit is useful for `introduction.md` +pd.options.display.float_format = '{:,.2e}'.format +print('\nDataframe in Markdown format:\n') +print(df.to_markdown(floatfmt=".2e")) diff --git a/exercises/practice/sieve/.articles/performance/code/create_plots.py b/exercises/practice/sieve/.articles/performance/code/create_plots.py new file mode 100644 index 00000000000..43f256ab28f --- /dev/null +++ b/exercises/practice/sieve/.articles/performance/code/create_plots.py @@ -0,0 +1,43 @@ +import matplotlib as mpl +import matplotlib.pyplot as plt +import pandas as pd + + +# These dataframes are slow to create, so they should be saved in Feather format + +try: + df = pd.read_feather('./run_times.feather') +except FileNotFoundError: + print("File './run_times.feather' not found!") + print("Please run './Benchmark.py' to create it.") + exit(1) + +try: + transposed = pd.read_feather('./transposed_logs.feather') +except FileNotFoundError: + print("File './transposed_logs.feather' not found!") + print("Please run './Benchmark.py' to create it.") + exit(1) + +# Ready to start creating plots + +mpl.rcParams['axes.labelsize'] = 18 + +# bar plot of actual run times +ax = df.plot.bar(figsize=(10, 7), + logy=True, + ylabel="time (s)", + fontsize=14, + width=0.8, + rot=-30) +plt.tight_layout() +plt.savefig('../timeit_bar_plot.svg') + +# log-log plot of times vs n, to see slopes +transposed.plot(figsize=(8, 6), + marker='.', + markersize=10, + ylabel="$log_{10}(time)$ (s)", + xlabel="$log_{10}(n)$", + fontsize=14) +plt.savefig('../slopes.svg') diff --git a/exercises/practice/sieve/.articles/performance/code/fit_gradients.py b/exercises/practice/sieve/.articles/performance/code/fit_gradients.py new file mode 100644 index 00000000000..f90d8010a36 --- /dev/null +++ b/exercises/practice/sieve/.articles/performance/code/fit_gradients.py @@ -0,0 +1,56 @@ +import pandas as pd +import numpy as np +from numpy.linalg import lstsq + + +# These dataframes are slow to create, so they should be saved in Feather format + +try: + df = pd.read_feather('./run_times.feather') +except FileNotFoundError: + print("File './run_times.feather' not found!") + print("Please run './Benchmark.py' to create it.") + exit(1) + +# To plot and fit the slopes, the df needs to be log10-transformed and transposed + +inputs = [10, 30, 100, 300, 1_000, 3_000, 10_000, 30_000, 100_000] + +pd.options.display.float_format = '{:,.2g}'.format +log_n_values = np.log10(inputs) +df[df == 0.0] = np.nan +transposed = np.log10(df).T +transposed = transposed.set_axis(log_n_values, axis=0) +transposed.to_feather('transposed_logs.feather') +print("\nDataframe saved to './transposed_logs.feather'") + +n_values = (10, 30, 100, 300, 1_000, 3_000, 10_000, 30_000, 100_000) +log_n_values = np.log10(n_values) +row_headers = ["nested_loops_1", + "nested_loops_2", + "set_ops_1", + "set_ops_2", + "set_ops_3", + "generator_comprehension", + "list_comprehension"] + + +# Do a least-squares fit to get the slopes, working around missing values +# Apparently, it does need to be this complicated + +def find_slope(name): + log_times = transposed[name] + missing = np.isnan(log_times) + log_times = log_times[~missing] + valid_entries = len(log_times) + A = np.vstack([log_n_values[:valid_entries], np.ones(valid_entries)]).T + m, _ = lstsq(A, log_times, rcond=None)[0] + return m + + +# Print the slope results +slopes = [(name, find_slope(name)) for name in row_headers] +print('\nSlopes of log-log plots:') +for name, slope in slopes: + print(f'{name:>14} : {slope:.2f}') + diff --git a/exercises/practice/sieve/.articles/performance/code/run_times.feather b/exercises/practice/sieve/.articles/performance/code/run_times.feather new file mode 100644 index 0000000000000000000000000000000000000000..5663a046ab52d4764746ca2585505deeec888dc8 GIT binary patch literal 5946 zcmeHLeQZ-z6u)-em|GbG$DBb&^MbGhR$E$O4EA2Z3>JcXEQrYF-a7iWm9($5A9H1y z&LI)wM+g{*3C6~uf2b&e2u7G}1hLyNd?TNWyP` zL9jm=o`^vhDi{k)h|3~cEgnLyBob1`*hH&P%dd3WeGbvXv^7e zeu|a}S%e^g8rCtQ#UX%;=(F3wqS7YVt^B~g5Et+DyU>vFiMEwatH6M@=ySOIVZd3j zN?7T|euv<-SS?-)nAb6a2G`-Ly z=N2qpr<*21yRE`)ar-e3y8ITm+xa49s~W-Wu{lNTHB8m%bp{6VlFY6Er|Jwk6H^bl zAI}f9ije(qNW2NM97GFp8Tz+_)Pk5n@EkGS?dyZnBoWVz9H|dpWHiVp~on$}_kVHn%${<~;A42Oe0es}~Dg6y}MoH8U>jpwpv@Tf=`zM3ySZ8EOm5GK% zCJAbS^Q}&h4B(}Ko^)WqdIZLz&;}|P<4Dwzi^(~ITWL?Gs5xA_& zcy9zQ>oRVJF)Ev^%lP^TT-Ig$Yy>XrGOmX;R5n?c@rDRo)@A%i1TO2-rJ%{u`j;w5 z@NcVJZ-oA9dY&|1HS=^{eT<+$m?%l^vByrKkw`PCa3m>LV z6A zZ$*saYH3&cmtxIc4BJ5A$!+<_Z8y?ftT#{ITr&n;`KpCb)FEoeJuf_T;6 z2+h}`$7`tSxLGjGY&04tbrtz>KJ_QZZRQG6I_IsJ?BgmcE3aj&+XDV?jH=BPAzsxQ z{X-Ms4NXg*kY7Xl8Po>s!J`mU;Nu`V5d61Y2vQD0Te{0c$s76p%2u<~}N)8*j} zX=KS%kKpAWCh7-C;uvycq_Ij61vi!qaGi6w+(NY=VtHVx=4DAjc<7NPrT^y;g2E_2uetC#1ITo$a$z?Q7i;gpo!5)U=M31wJ vK_78M@V2cO@CIXX)!cfwKCPWYjqJR(`2mvQVUBC%WVV?Wi2d`@@{85a5>?ZfU zd(Q87?>YD0zH{$s!OWS@%+eFmhh=sQAq=5q5Q!&Bq9C!@!V(dESFb`b+NYnYplyV) zQhh8wDHbcXy<;h{Z8r&KlM`j+FJ&!)nP0+H+N{-8f|H%7D`Pp%tF!Voyp_x0ICkDx z9qVOyAzmadWd&0ek6nans}&jZExgst`Rly4IG5W_iEOoCskE7S7TH2|mEC<8uvIML zD_vAy#k)*qlgotkr7SNX-6AZYeM?IU$34l`)!lQ(-wRgYoi5(YS#37Elhf0&{5%SR z_w#PP)n;-{7$4}J43N1;r(HJ|z4T4l-$3qt91!s^JK*G9T!_j0K@Z7nkSR-MALeZH z0#{lxD?(wO?E+rl9VVB}fgeqk-N7&91*gR(1Um6A#4x*LwK!e(^Cmbayp9A51VO?H zewksgE5-o_Pj^C$6;?}y!{l(&P+@nQ91dG8-I5yKA?^-p^|{$O*?JZ~S;@Q%%+1zk zk7w&JJc{vhBL>mgSTnG8qwX`TFJd)fr7?EZoxVPdv{5uZXZUpZA{wj#@e_5+M1Cas zGDN>bzA^Uy(=z$>F%S0~qftFOPA%17%4`1I&vcAKRFZGSX_T3K!+oMHZB(XjK zKNtDW`}i#nz{ewvH2C;OWqcj#C4b_v?H9w*$bMfRhG=nLlq5zx*%;sX=UspKdIXF? z4P!srm!5ruM2b%wwyz8KRx3*Myi7x!p0Fy>0*Q(ybggcA=^{&o8V0?UIGNa=a_AUz zEu|8T96BfO*)3`P-<(e@_g521mT>5YOR|LP&C)7i9Vn*x~5S56%2{mV0=xb4;f_KK%q@vOh6WSul@J=m#^eY3|P zjvQqC(c>!DXXK~B+gqleKYS?#th-OQzMqy1XPTkm+T%&kvHGbK6;~Np->~@S9bIY| zzgyFs-Vp~Q^S86AOA1)AGQE-683CcjZ#k8JEdTXX=y|&4#yV9B%yZ{UQsAQfCGA%ewXmw_;_nUViD0Z9Gp8Xv z0kVWgatn?Rf?>y!K2a)_F!$V{J+HPzK&ipeG4{t>A^qRrso1%KONJd=N6x!km89LiR1zepo)Ku2m>lr%t(YT~%uXz4!E1hh*aN5vN)}C{Cc#mOJPG0$B zb(AsG`h0Pyb;IWMDbUt*F5&x;Ha`wrwBV&>z2TuT~zAm@;0 v-E4@vAiW8Pc>@A(tAQC%a87s~8OS*Xa*lgBggmGDKjj?tc&-+R|Ly$)+iG3y literal 0 HcmV?d00001 diff --git a/exercises/practice/sieve/.articles/performance/content.md b/exercises/practice/sieve/.articles/performance/content.md new file mode 100644 index 00000000000..9a8495b7a0b --- /dev/null +++ b/exercises/practice/sieve/.articles/performance/content.md @@ -0,0 +1,59 @@ +# Performance + +The [Approaches page][approaches-page] discusses various ways to approach this exercise, with substantially different performance. + +## Measured timings + +The 7 code implementations described in the various approaches were [benchmarked][benchmark-code], using appropriate values for the upper limit of `n` and number of runs to average over, to keep the total testing time reasonable. + +Numerical results are tabulated below, for 9 values of the upper search limit (chosen to be about equally spaced on a log scale). + +| | 10 | 30 | 100 | 300 | 1000 | 3000 | 10,000 | 30,000 | 100,000 | +|:------------------------|---------:|---------:|----------:|----------:|-----------:|-----------:|----------:|------------:|----------:| +| nested quadratic | 4.64e-07 | 2.19e-06 | 1.92e-05 | 1.68e-04 | 1.96e-03 | 1.78e-02 | 2.03e-01 | 1.92e+00 | 2.22e+01 | +| nested linear | 8.72e-07 | 1.89e-06 | 5.32e-06 | 1.60e-05 | 5.90e-05 | 1.83e-04 | 6.09e-04 | 1.84e-03 | 6.17e-03 | +| set with update | 1.30e-06 | 3.07e-06 | 9.47e-06 | 2.96e-05 | 1.18e-04 | 3.92e-04 | 1.47e-03 | 5.15e-03 | 2.26e-02 | +| set with sort 1 | 4.97e-07 | 1.23e-06 | 3.25e-06 | 9.57e-06 | 3.72e-05 | 1.19e-04 | 4.15e-04 | 1.38e-03 | 5.17e-03 | +| set with sort 2 | 9.60e-07 | 2.61e-06 | 8.76e-06 | 2.92e-05 | 1.28e-04 | 4.46e-04 | 1.77e-03 | 6.29e-03 | 2.79e-02 | +| generator comprehension | 4.54e-06 | 2.70e-05 | 2.23e-04 | 1.91e-03 | 2.17e-02 | 2.01e-01 | 2.28e+00 | 2.09e+01 | 2.41e+02 | +| list comprehension | 2.23e-06 | 8.94e-06 | 4.36e-05 | 2.35e-04 | 1.86e-03 | 1.42e-02 | 1.39e-01 | 1.11e+00 | 1.10e+01 | + +For the smallest input, all times are fairly close to a microsecond, with about a 10-fold difference between fastest and slowest. + +In contrast, for searches up to 100,000 the timings varied by almost 5 orders of magnitude. + +This is a difference between milliseconds and minutes, which is very hard to ignore. + +## Testing algorithmic complexity + +We have discussed these solutions as `quadratic` or `linear`. +Do the experimental data support this? + +For a [power law][power-law] relationship, we have a run time `t` given by `t = a * n**x`, where `a` is a proportionality constant and `x` is the power. + +Taking logs of both sides, `log(t) = x * log(n) + constant.` + +Plots of `log(t)` against `log(n)` will be a straight line with slope equal to the power `x`. + +Graphs of the data (not included here) show that these are all straight lines for larger values of `n`, as we expected. + +Linear least-squares fits to each line gave these slope values: + +| Method | Slope | +|:-----------------|:-----:| +| nested quadratic | 1.95 | +| nested linear | 0.98 | +| set with update | 1.07 | +| set with sort 1 | 1.02 | +| set with sort 2 | 1.13 | +| generator comprehension | 1.95 | +| list comprehension | 1.69 | + +Clearly, most approaches have a slope of approximately 1 (linear) or 2 (quadratic). + +The `list-comprehension` approach is an oddity, intermediate between these extremes. + + +[approaches-page]: https://exercism.org/tracks/python/exercises/sieve/approaches +[benchmark-code]: https://github.com/exercism/python/blob/main/exercises/practice/sieve/.articles/performance/code/Benchmark.py +[power-law]: https://en.wikipedia.org/wiki/Power_law diff --git a/exercises/practice/sieve/.articles/performance/slopes.svg b/exercises/practice/sieve/.articles/performance/slopes.svg new file mode 100644 index 00000000000..447d4e5083d --- /dev/null +++ b/exercises/practice/sieve/.articles/performance/slopes.svg @@ -0,0 +1,1555 @@ + + + + + + + + 2024-02-06T14:47:33.159377 + image/svg+xml + + + Matplotlib v3.8.0, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exercises/practice/sieve/.articles/performance/snippet.md b/exercises/practice/sieve/.articles/performance/snippet.md new file mode 100644 index 00000000000..d741c4e5fac --- /dev/null +++ b/exercises/practice/sieve/.articles/performance/snippet.md @@ -0,0 +1,8 @@ +def primes(number): + not_prime = set() + primes = [] + for num in range(2, number+1): + if num not in not_prime: + primes.append(num) + not_prime.update(range (num*num, number+1, num)) + return primes From 5a4dc19878235e2beef0d069b9d0a8c7eb6bfa3e Mon Sep 17 00:00:00 2001 From: Knute Snortum Date: Mon, 12 Feb 2024 09:59:39 -0800 Subject: [PATCH 062/277] Clarify that in Python there is a week descriptor called 'fifth' (#3630) * Clarify that in Python there is a week descriptor called 'fifth' * Update exercises/practice/meetup/.docs/instructions.append.md Co-authored-by: BethanyG --------- Co-authored-by: BethanyG --- exercises/practice/meetup/.docs/instructions.append.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/exercises/practice/meetup/.docs/instructions.append.md b/exercises/practice/meetup/.docs/instructions.append.md index 67d3957bc74..b3fdd747418 100644 --- a/exercises/practice/meetup/.docs/instructions.append.md +++ b/exercises/practice/meetup/.docs/instructions.append.md @@ -1,5 +1,10 @@ # Instructions append +## How this Exercise is Structured in Python + +We have added an additional week descriptor (`fifth`) for the fifth weekday of the month, if there is one. +If there is not a fifth weekday in a month, you should raise an exception. + ## Customizing and Raising Exceptions Sometimes it is necessary to both [customize](https://docs.python.org/3/tutorial/errors.html#user-defined-exceptions) and [`raise`](https://docs.python.org/3/tutorial/errors.html#raising-exceptions) exceptions in your code. When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. From 0f8d02049d4c51c48980f9acf33d6d554b469bcc Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 20 Feb 2024 02:21:03 -0800 Subject: [PATCH 063/277] Update introduction.md (#3635) Corrected Real Python link and line 21 reference to palette instead of palette_I. --- .../concept/mecha-munch-management/.docs/introduction.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/mecha-munch-management/.docs/introduction.md b/exercises/concept/mecha-munch-management/.docs/introduction.md index f1c5744e69a..983b905c276 100644 --- a/exercises/concept/mecha-munch-management/.docs/introduction.md +++ b/exercises/concept/mecha-munch-management/.docs/introduction.md @@ -18,7 +18,7 @@ If the key is **not** found, it will _insert_ the (`key`, `default value`) pair # Looking for the value associated with key "Rock Brown". # The key does not exist, so it is added with the default value, and the value is returned. ->>> palette.setdefault('Rock Brown', '#694605') +>>> palette_I.setdefault('Rock Brown', '#694605') '#694605' # The (key, default value) pair has now been added to the dictionary. @@ -217,5 +217,5 @@ For a detailed explanation of dictionaries and methods for working with them, th [dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries [fi-dict-guide]: https://blog.finxter.com/python-dictionary [fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys -[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp +[how-to-dicts]: https://realpython.com/python-dicts/ [mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict From f1a064be5abe8e25e9ab10626b030b7cc26cd545 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 20 Feb 2024 02:37:54 -0800 Subject: [PATCH 064/277] Update loops.py (#3636) Correct ascending to descending in stub file. --- exercises/concept/making-the-grade/loops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/making-the-grade/loops.py b/exercises/concept/making-the-grade/loops.py index f1071b23b52..ecf7d06774c 100644 --- a/exercises/concept/making-the-grade/loops.py +++ b/exercises/concept/making-the-grade/loops.py @@ -50,7 +50,7 @@ def letter_grades(highest): def student_ranking(student_scores, student_names): - """Organize the student's rank, name, and grade information in ascending order. + """Organize the student's rank, name, and grade information in descending order. :param student_scores: list - of scores in descending order. :param student_names: list - of string names by exam score in descending order. From 3187fb4e16753c0ca2ddf5b1a3492fc42109a4aa Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 27 Feb 2024 22:58:32 -0800 Subject: [PATCH 065/277] [Acronym] Add Approaches (#3357) * First draft of acronym approaches. * Performance directory. * Added starter content to performance to clear CI check. * Adding snippet placeholder for performance. * Updated and edited approaches for acronym. Modified snipetts and corrected links. * Finished up approaches and added benchmarks Finished up approaches and added benchmarks. Deleted unneeded Jupyter notebooks. * Update snippet.md Removed genexp so file would be 8 lines. * Update content.md Fixed benchmark table and broken link in performance article. --- .../practice/acronym/.articles/config.json | 11 ++ .../.articles/performance/code/Benchmark.py | 157 +++++++++++++++++ .../acronym/.articles/performance/content.md | 74 ++++++++ .../acronym/.articles/performance/snippet.md | 8 + .../practice/acronym/approaches/config.json | 56 ++++++ .../approaches/functools-reduce/content.md | 54 ++++++ .../approaches/functools-reduce/snippet.txt | 6 + .../generator-expression/content.md | 48 ++++++ .../generator-expression/snippet.txt | 5 + .../acronym/approaches/introduction.md | 160 ++++++++++++++++++ .../approaches/list-comprehension/content.md | 46 +++++ .../approaches/list-comprehension/snippet.txt | 4 + .../acronym/approaches/loop/content.md | 40 +++++ .../acronym/approaches/loop/snippet.txt | 8 + .../approaches/map-function/content.md | 49 ++++++ .../approaches/map-function/snippet.txt | 4 + .../acronym/approaches/regex-join/content.md | 98 +++++++++++ .../acronym/approaches/regex-join/snippet.txt | 6 + .../acronym/approaches/regex-sub/content.md | 75 ++++++++ .../acronym/approaches/regex-sub/snippet.txt | 6 + 20 files changed, 915 insertions(+) create mode 100644 exercises/practice/acronym/.articles/config.json create mode 100644 exercises/practice/acronym/.articles/performance/code/Benchmark.py create mode 100644 exercises/practice/acronym/.articles/performance/content.md create mode 100644 exercises/practice/acronym/.articles/performance/snippet.md create mode 100644 exercises/practice/acronym/approaches/config.json create mode 100644 exercises/practice/acronym/approaches/functools-reduce/content.md create mode 100644 exercises/practice/acronym/approaches/functools-reduce/snippet.txt create mode 100644 exercises/practice/acronym/approaches/generator-expression/content.md create mode 100644 exercises/practice/acronym/approaches/generator-expression/snippet.txt create mode 100644 exercises/practice/acronym/approaches/introduction.md create mode 100644 exercises/practice/acronym/approaches/list-comprehension/content.md create mode 100644 exercises/practice/acronym/approaches/list-comprehension/snippet.txt create mode 100644 exercises/practice/acronym/approaches/loop/content.md create mode 100644 exercises/practice/acronym/approaches/loop/snippet.txt create mode 100644 exercises/practice/acronym/approaches/map-function/content.md create mode 100644 exercises/practice/acronym/approaches/map-function/snippet.txt create mode 100644 exercises/practice/acronym/approaches/regex-join/content.md create mode 100644 exercises/practice/acronym/approaches/regex-join/snippet.txt create mode 100644 exercises/practice/acronym/approaches/regex-sub/content.md create mode 100644 exercises/practice/acronym/approaches/regex-sub/snippet.txt diff --git a/exercises/practice/acronym/.articles/config.json b/exercises/practice/acronym/.articles/config.json new file mode 100644 index 00000000000..ad22d1e171d --- /dev/null +++ b/exercises/practice/acronym/.articles/config.json @@ -0,0 +1,11 @@ +{ + "articles": [ + { + "uuid": "4c0e0a02-0bc0-4921-8016-20b0ae57804a", + "slug": "performance", + "title": "Performance deep dive", + "blurb": "Deep dive to find out the most performant approach to forming an acronym.", + "authors": ["bethanyg, colinleach"] + } + ] +} \ No newline at end of file diff --git a/exercises/practice/acronym/.articles/performance/code/Benchmark.py b/exercises/practice/acronym/.articles/performance/code/Benchmark.py new file mode 100644 index 00000000000..44ae8db7b24 --- /dev/null +++ b/exercises/practice/acronym/.articles/performance/code/Benchmark.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Script for timing Acronym Solutions. + +Creates timing table and timing graphs for +multiple approaches to the Acronym problem in Python. + +Created Feb 2024 +@authors: bethanygarcia, colinleach +""" + +import timeit +import re +from functools import reduce + +import pandas as pd +import numpy as np + + +# ------------ FUNCTIONS TO TIME ------------- # +def abbreviate_list_comprehension(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + return ''.join([word[0] for word in phrase]) + + +def abbreviate_genex(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + letters = (word[0] for word in phrase) + return ''.join(letters) + + +def abbreviate_loop(to_abbreviate): + phrase = to_abbreviate.replace('-', ' ').replace("_", " ").upper().split() + acronym = "" + + for word in phrase: + acronym += word[0] + + return acronym + + +def abbreviate_map(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + + return ''.join(map(lambda word: word[0], phrase)) + + +def abbreviate_reduce(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + + return reduce(lambda start, word: start + word[0], phrase, "") + + +def abbreviate_regex_join(phrase): + removed = re.findall(r"[a-zA-Z']+", phrase) + return ''.join(word[0] for word in removed).upper() + + +def abbreviate_finditer_join(to_abbreviate): + return ''.join(word[0][0] for word in + re.finditer(r"[a-zA-Z']+", to_abbreviate)).upper() + + +def abbreviate_regex_sub(to_abbreviate): + pattern = re.compile(r"(?>>** | **13** | **14** | **19** | **20** | **25** | **30** | **35** | **39** | **42** | **45** | **60** | **63** | **74** | **150** | **210** | **360** | **400** | **2940** | +|------------------------------ |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:------------: |:------------: |:------------: |:------------: |:-------------: | +| **loop** | 5.79e-07 | 4.96e-07 | 6.98e-07 | 7.41e-07 | 6.18e-07 | 7.25e-07 | 1.03e-06 | 7.33e-07 | 1.16e-06 | 8.71e-07 | 1.51e-06 | 1.65e-06 | 1.83e-06 | 2.43e-06 | 4.63e-06 | 7.76e-06 | 4.85e-06 | 5.94e-05 | +| **list comprehension** | 7.28e-07 | 6.57e-07 | 8.26e-07 | 8.62e-07 | 7.67e-07 | 8.30e-07 | 1.08e-06 | 8.68e-07 | 1.24e-06 | 4.00e-07 | 1.49e-06 | 1.55e-06 | 1.76e-06 | 2.19e-06 | 4.08e-06 | 7.21e-06 | 4.40e-06 | 5.42e-05 | +| **functools.reduce()** | 7.93e-07 | 6.65e-07 | 9.50e-07 | 2.43e-06 | 8.19e-07 | 9.56e-07 | 1.36e-06 | 4.12e-07 | 1.64e-06 | 1.21e-06 | 2.03e-06 | 2.14e-06 | 2.45e-06 | 3.15e-06 | 6.03e-06 | 1.03e-05 | 6.19e-06 | 8.10e-05 | +| **map()** | 8.05e-07 | 7.21e-07 | 9.34e-07 | 9.46e-07 | 8.32e-07 | 9.16e-07 | 1.23e-06 | 9.52e-07 | 1.44e-06 | 1.14e-06 | 1.71e-06 | 1.80e-06 | 2.00e-06 | 2.58e-06 | 4.81e-06 | 8.02e-06 | 4.95e-06 | 5.64e-05 | +| **generator expression** | 8.85e-07 | 7.90e-07 | 1.01e-06 | 1.01e-06 | 9.26e-07 | 2.49e-06 | 1.30e-06 | 1.06e-06 | 1.49e-06 | 1.19e-06 | 1.81e-06 | 1.86e-06 | 2.10e-06 | 2.67e-06 | 5.12e-06 | 8.61e-06 | 5.12e-06 | 5.81e-05 | +| **re.finditer()** | 1.05e-06 | 1.74e-06 | 2.44e-06 | 2.40e-06 | 2.09e-06 | 2.45e-06 | 3.28e-06 | 2.42e-06 | 8.15e-06 | 3.12e-06 | 5.15e-06 | 5.18e-06 | 5.94e-06 | 7.89e-06 | 1.46e-05 | 2.35e-05 | 1.48e-05 | 1.68e-04 | +| **regex with str.join()** | 1.62e-06 | 1.42e-06 | 1.85e-06 | 1.91e-06 | 1.66e-06 | 1.88e-06 | 2.61e-06 | 4.41e-06 | 3.14e-06 | 2.47e-06 | 3.92e-06 | 4.11e-06 | 4.61e-06 | 6.24e-06 | 1.13e-05 | 1.86e-05 | 1.19e-05 | 1.36e-04 | +| **re.findall() 1st letters** | 1.63e-06 | 1.57e-06 | 2.04e-06 | 2.12e-06 | 2.16e-06 | 2.50e-06 | 3.18e-06 | 2.90e-06 | 3.73e-06 | 3.41e-06 | 4.84e-06 | 5.22e-06 | 5.94e-06 | 1.00e-05 | 1.54e-05 | 2.48e-05 | 2.28e-05 | 1.95e-04 | +| **re.sub()** | 2.35e-06 | 1.10e-06 | 3.06e-06 | 2.94e-06 | 2.51e-06 | 2.92e-06 | 4.10e-06 | 2.91e-06 | 4.95e-06 | 3.80e-06 | 6.48e-06 | 6.39e-06 | 6.90e-06 | 9.29e-06 | 1.90e-05 | 2.98e-05 | 1.83e-05 | 2.03e-04 | + + +Keep in mind that all these approaches are very fast, and that benchmarking at this granularity can be unstable. + +Measurements were taken on a 3.1 GHz Quad-Core Intel Core i7 Mac running MacOS Ventura. +Tests used `timeit.Timer.autorange()`, repeated 3 times. +Time is reported in seconds taken per string after calculating the 'best of' time. +The [timeit module][timeit] docs have more details, and [note.nkmk.me][note_nkmk_me] has a nice summary of methods. + +[approaches]: https://exercism.org/tracks/python/exercises/acronym/dig_deeper +[approach-functools-reduce]: https://exercism.org/tracks/python/exercises/acronym/approaches/functools-reduce +[approach-generator-expression]: https://exercism.org/tracks/python/exercises/acronym/approaches/generator-expression +[approach-list-comprehension]: https://exercism.org/tracks/python/exercises/acronym/approaches/list-comprehension +[approach-loop]: https://exercism.org/tracks/python/exercises/acronym/approaches/loop +[approach-map-function]: https://exercism.org/tracks/python/exercises/acronym/approaches/map-function +[approach-regex-join]: https://exercism.org/tracks/python/exercises/acronym/approaches/regex-join +[approach-regex-sub]: https://exercism.org/tracks/python/exercises/acronym/approaches/regex-sub +[benchmark-application]: https://github.com/exercism/python/tree/main/exercises/practice/acronym/.articles/performance/code/Benchmark.py +[note_nkmk_me]: https://note.nkmk.me/en/python-timeit-measure/ +[numpy]: https://numpy.org/ +[pandas]: https://pandas.pydata.org/docs/index.html +[timeit]: https://docs.python.org/3.11/library/timeit.html diff --git a/exercises/practice/acronym/.articles/performance/snippet.md b/exercises/practice/acronym/.articles/performance/snippet.md new file mode 100644 index 00000000000..00e1067fd98 --- /dev/null +++ b/exercises/practice/acronym/.articles/performance/snippet.md @@ -0,0 +1,8 @@ +| | **Len: 13** | **Len: 30** | **Len: 74** | **Len: 210** | **Len: 2940** | +|------------------------------ |:-----------: |:-----------: |:-----------: |:------------: |:-------------: | +| **loop** | 5.79e-07 | 7.25e-07 | 1.83e-06 | 4.63e-06 | 5.94e-05 | +| **list_comprehension** | 7.28e-07 | 8.30e-07 | 1.76e-06 | 4.08e-06 | 5.42e-05 | +| **functools.reduce()** | 7.93e-07 | 9.56e-07 | 2.45e-06 | 6.03e-06 | 8.10e-05 | +| **map()** | 8.05e-07 | 9.16e-07 | 2.00e-06 | 4.81e-06 | 5.64e-05 | +| **re.findall() 1st letters** | 1.63e-06 | 2.50e-06 | 5.94e-06 | 1.54e-05 | 1.95e-04 | +| **re.sub()** | 2.35e-06 | 2.92e-06 | 6.90e-06 | 1.90e-05 | 2.03e-04 | \ No newline at end of file diff --git a/exercises/practice/acronym/approaches/config.json b/exercises/practice/acronym/approaches/config.json new file mode 100644 index 00000000000..a3ed8e6d93b --- /dev/null +++ b/exercises/practice/acronym/approaches/config.json @@ -0,0 +1,56 @@ +{ + "introduction": { + "authors": ["BethanyG"] + }, + "approaches": [ + { + "uuid": "8ee6ac18-270b-4a62-80e6-5efb09139274", + "slug": "functools-reduce", + "title": "Functools Reduce", + "blurb": "Use functools.reduce() to form an acronym from text cleaned using str.replace().", + "authors": ["BethanyG"] + }, + { + "uuid": "d568ea30-b839-46ad-9c9b-73321a274325", + "slug": "generator-expression", + "title": "Generator Expression", + "blurb": "Use a generator expression with str.join() to form an acronym from text cleaned using str.replace().", + "authors": ["BethanyG"] + }, + { + "uuid": "da53b1bc-35c7-47a7-88d5-56ebb9d3658d", + "slug": "list-comprehension", + "title": "List Comprehension", + "blurb": "Use a list comprehension with str.join() to form an acronym from text cleaned using str.replace().", + "authors": ["BethanyG"] + }, + { + "uuid": "abd51d7d-3743-448d-b8f1-49f484ae6b30", + "slug": "loop", + "title": "Loop", + "blurb": "Use str.replace() to clean the input string and a loop with string concatenation to form the acronym.", + "authors": ["BethanyG"] + }, + { + "uuid": "9eee8db9-80f8-4ee4-aaaf-e55b78221283", + "slug": "map-function", + "title": "Map Built-in", + "blurb": "Use the built-in map() function to form an acronym after cleaning the input string with str.replace().", + "authors": ["BethanyG"] + }, + { + "uuid": "8f4dc8ba-fd1c-4c85-bcc3-8ef9dca34c7f", + "slug": "regex-join", + "title": "Regex join", + "blurb": "Use regex to clean the input string and form the acronym with str.join().", + "authors": ["BethanyG"] + }, + { + "uuid": "8830be43-44c3-45ab-8311-f588f60dfc5f", + "slug": "regex-sub", + "title": "Regex Sub", + "blurb": "Use re.sub() to clean the input string and create the acronym in one step.", + "authors": ["BethanyG"] + } + ] +} \ No newline at end of file diff --git a/exercises/practice/acronym/approaches/functools-reduce/content.md b/exercises/practice/acronym/approaches/functools-reduce/content.md new file mode 100644 index 00000000000..074db3fa284 --- /dev/null +++ b/exercises/practice/acronym/approaches/functools-reduce/content.md @@ -0,0 +1,54 @@ +# Scrub with `replace()` and join via `functools.reduce()` + + +```python +from functools import reduce + + +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + + return reduce(lambda start, word: start + word[0], phrase, "") +``` + + +- This approach begins by using [`str.replace()`][str-replace] to "scrub" (_remove_) non-letter characters such as `'`,`-`,`_`, and white space from `to_abbreviate`. +- The phrase is then upper-cased by calling [`str.upper()`][str-upper], +- Finally, the phrase is turned into a `list` of words by calling [`str.split()`][str-split]. + +The three methods above are all [chained][chaining] together, with the output of one method serving as the input to the next method in the "chain". +This works because both `replace()` and `upper()` return strings, and both `upper()` and `split()` take strings as arguments. +However, if `split()` were called first, `replace()` and `upper()` would fail, since neither method will take a `list` as input. + +~~~~exercism/note +`re.findall()` or `re.finditer()` can also be used to "scrub" `to_abbreviate`. +These two methods from the `re` module will return a `list` or a lazy `iterator` of results, respectively. +As of this writing, both of these methods benchmark slower than using `str.replace()` for scrubbing. +~~~~ + + +Once the phrase is scrubbed and turned into a word `list`, the acronym is created via `reduce()`. +`reduce()` is a method from the [`functools`][functools] module, which provides support for higher-order functions and functional programming in Python. + + +[`functools.reduce()`][reduce] applies an anonymous two-argument function (_the [lambda][python lambdas] in the code example_) to the items of an iterable. + The application of the function travels from left to right, so that the iterable becomes a single value (_it is "reduced" to a single value_). + + + Using code from the example above, `reduce(lambda start, word: start + word[0], ['GNU', 'IMAGE', 'MANIPULATION', 'PROGRAM'])` would calculate `((('GNU'[0] + 'IMAGE'[0])+'MANIPULATION'[0])+'PROGRAM'[0])`, or `GIMP`. + The left argument, `start`, is the _accumulated value_ and the right argument, `word`, is the value from the iterable that is used to update the accumulated 'total'. + The optional 'initializer' value '' is used here, and is placed ahead/before the items of the iterable in the calculation, and serves as a default if the iterable that is passed is empty. + + +Since using `reduce()` is fairly succinct, it is put directly on the `return` line to produce the acronym rather than assigning and returning an intermediate variable. + + +In benchmarks, this solution performed about as well as both the `loops` and the `list-comprehension` solutions. + +[chaining]: https://pyneng.readthedocs.io/en/latest/book/04_data_structures/method_chaining.html +[functools]: https://docs.python.org/3/library/functools.html +[reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[str-replace]: https://docs.python.org/3/library/stdtypes.html#str.replace +[str-split]: https://docs.python.org/3/library/stdtypes.html#str.split +[str-upper]: https://docs.python.org/3/library/stdtypes.html#str.upper +[python lambdas]: https://docs.python.org/3/tutorial/controlflow.html#lambda-expressions diff --git a/exercises/practice/acronym/approaches/functools-reduce/snippet.txt b/exercises/practice/acronym/approaches/functools-reduce/snippet.txt new file mode 100644 index 00000000000..190d5d4aeff --- /dev/null +++ b/exercises/practice/acronym/approaches/functools-reduce/snippet.txt @@ -0,0 +1,6 @@ +from functools import reduce + +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + + return reduce(lambda start, word: start + word[0], phrase, "") \ No newline at end of file diff --git a/exercises/practice/acronym/approaches/generator-expression/content.md b/exercises/practice/acronym/approaches/generator-expression/content.md new file mode 100644 index 00000000000..f5b590ccaa6 --- /dev/null +++ b/exercises/practice/acronym/approaches/generator-expression/content.md @@ -0,0 +1,48 @@ +# Scrub with `replace()` and join via `generator-expression` + + +```python +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace('-', ' ').replace('_', ' ').upper().split() + + # note the lack of square brackets around the comprehension. + return ''.join(word[0] for word in phrase) +``` + + +- This approach begins by using [`str.replace()`][str-replace] to "scrub" (_remove_) non-letter characters such as `'`,`-`,`_`, and white space from `to_abbreviate`. +- The phrase is then upper-cased by calling [`str.upper()`][str-upper], +- Finally, the phrase is turned into a `list` of words by calling [`str.split()`][str-split]. + +The three methods above are all [chained][chaining] together, with the output of one method serving as the input to the next method in the "chain". +This works because both `replace()` and `upper()` return strings, and both `upper()` and `split()` take strings as arguments. +However, if `split()` were called first, `replace()` and `upper()` would fail, since neither method will take a `list` as input. + +~~~~exercism/note +`re.findall()` or `re.finditer()` can also be used to "scrub" `to_abbreviate`. +These two methods from the `re` module will return a `list` or a lazy `iterator` of results, respectively. +As of this writing, both of these methods benchmark slower than using `str.replace()` for scrubbing. +~~~~ + + +A [`generator-expression`][generator-expression] is then used to iterate through the phrase and select the first letters of each word via [`bracket notation`][subscript notation]. + + +Generator expressions are short-form [generators][generators] - lazy iterators that produce their values _on demand_, instead of saving them to memory. +This generator expression is consumed by [`str.join()`][str-join], which joins the generated letters together using an empty string. +Other "seperator" strings can be used with `str.join()` - see [concept:python/string-methods]() for some additional examples. +Since the generator expression and `join()` are fairly succinct, they are put directly on the `return` line rather than assigning and returning an intermediate variable for the acronym. + + +In benchmarks, this solution was surprisingly slower than the `list comprehension` version. +[This article][Oscar Alsing] from Oscar Alsing briefly explains why. + +[Oscar Alsing]: https://www.oscaralsing.com/list-comprehension-vs-generator-expression/#:~:text=List%20comprehensions%20are%20usually%20faster,difference%20is%20often%20quite%20small. +[chaining]: https://pyneng.readthedocs.io/en/latest/book/04_data_structures/method_chaining.html +[generator-expression]: https://dbader.org/blog/python-generator-expressions +[generators]: https://dbader.org/blog/python-generators +[str-join]: https://docs.python.org/3/library/stdtypes.html#str.join +[str-replace]: https://docs.python.org/3/library/stdtypes.html#str.replace +[str-split]: https://docs.python.org/3/library/stdtypes.html#str.split +[str-upper]: https://docs.python.org/3/library/stdtypes.html#str.upper +[subscript notation]: https://docs.python.org/3/glossary.html#term-slice diff --git a/exercises/practice/acronym/approaches/generator-expression/snippet.txt b/exercises/practice/acronym/approaches/generator-expression/snippet.txt new file mode 100644 index 00000000000..eb4a143df80 --- /dev/null +++ b/exercises/practice/acronym/approaches/generator-expression/snippet.txt @@ -0,0 +1,5 @@ +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace('-', ' ').replace('_', ' ').upper().split() + + # note the lack of square brackets around the comprehension. + return ''.join(word[0] for word in phrase) \ No newline at end of file diff --git a/exercises/practice/acronym/approaches/introduction.md b/exercises/practice/acronym/approaches/introduction.md new file mode 100644 index 00000000000..38b606b4a26 --- /dev/null +++ b/exercises/practice/acronym/approaches/introduction.md @@ -0,0 +1,160 @@ +# Introduction + +There are multiple Pythonic ways to solve the Acronym exercise. +Among them are: + +- Using `str.replace()` to scrub the input, and: + - joining with a `for loop` with string concatenation via the `+` operator. + - joining via `str.join()`, passing a `list-comprehension` or `generator-expression`. + - joining via `str.join()`, passing `map()`. + - joining via `functools.reduce()`. + +- Using `re.findall()`/`re.finditer()` to scrub the input, and: + - joining via `str.join()`, passing a `generator-expression`. + + - Using `re.sub()` for both cleaning and joining (_using "only" regex for almost everything_)` + + +## General Guidance + +The goal of the Acronym exercise is to collect the first letters of each word in the input phrase and return them as a single capitalized string (_the acronym_). +The challenge is to efficiently identify and capitalize the first letters while removing or ignoring non-letter characters such as `'`,`-`,`_`, and white space. + + +There are two idiomatic strategies for non-letter character removal: +- Python's built-in [`str.replace()`][str-replace]. +- The [`re`][re] module, (_regular expressions_). + +For all but the most complex scenarios, using `str.replace()` is generally more efficient than using a regular expression. + + +Forming the final acronym is most easily done with a direct or indirect `loop`, after splitting the input into a word list via [`str.split()`][str-split]. +The majority of these approaches demonstrate alternatives to the "classic" looping structure using various other iteration techniques. +Some `regex` methods can avoid looping altogether, although they can become very non-performant due to excessive backtracking. + +Strings are _immutable_, so any method to produce an acronym will be creating and returning a new `str`. + + +## Approach: scrub with `replace()` and join via `for` loop + +```python +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace('-', ' ').replace('_', ' ').upper().split() + acronym = '' + + for word in phrase: + acronym += word[0] + + return acronym +``` + +For more information, take a look at the [loop approach][approach-loop]. + + +## Approach: scrub with `replace()` and join via `list comprehension` or `Generator expression` + + +```python +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace('-', ' ').replace('_', ' ').upper().split() + + return ''.join([word[0] for word in phrase]) + +###OR### + +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace('-', ' ').replace('_', ' ').upper().split() + + # note the parenthesis instead of square brackets. + return ''.join((word[0] for word in phrase)) +``` + +For more information, check out the [list-comprehension][approach-list-comprehension] approach or the [generator-expression][approach-generator-expression] approach. + + +## Approach: scrub with `replace()` and join via `map()` + +```python +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + + return ''.join(map(lambda word: word[0], phrase)) +``` + +For more information, read the [map][approach-map-function] approach. + + +## Approach: scrub with `replace()` and join via `functools.reduce()` + +```python +from functools import reduce + + +def abbreviate(to_abbreviate): + phrase = to_abbreviate.replace("_", " ").replace("-", " ").upper().split() + + return reduce(lambda start, word: start + word[0], phrase, "") +``` + +For more information, take a look at the [functools.reduce()][approach-functools-reduce] approach. + + +## Approach: filter with `re.findall()` and join via `str.join()` + +```python +import re + + +def abbreviate(phrase): + removed = re.findall(r"[a-zA-Z']+", phrase) + + return ''.join(word[0] for word in removed).upper() +``` + +For more information, take a look at the [regex-join][approach-regex-join] approach. + + +## Approach: use `re.sub()` + +```python +import re + + +def abbreviate_regex_sub(to_abbreviate): + pattern = re.compile(r"(? Date: Tue, 27 Feb 2024 23:12:31 -0800 Subject: [PATCH 066/277] Update config.json (#3642) corrected author name to see if it changed rep awarded. --- .../practice/acronym/approaches/config.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/exercises/practice/acronym/approaches/config.json b/exercises/practice/acronym/approaches/config.json index a3ed8e6d93b..b5bbca7a232 100644 --- a/exercises/practice/acronym/approaches/config.json +++ b/exercises/practice/acronym/approaches/config.json @@ -1,6 +1,6 @@ { "introduction": { - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, "approaches": [ { @@ -8,49 +8,49 @@ "slug": "functools-reduce", "title": "Functools Reduce", "blurb": "Use functools.reduce() to form an acronym from text cleaned using str.replace().", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "d568ea30-b839-46ad-9c9b-73321a274325", "slug": "generator-expression", "title": "Generator Expression", "blurb": "Use a generator expression with str.join() to form an acronym from text cleaned using str.replace().", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "da53b1bc-35c7-47a7-88d5-56ebb9d3658d", "slug": "list-comprehension", "title": "List Comprehension", "blurb": "Use a list comprehension with str.join() to form an acronym from text cleaned using str.replace().", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "abd51d7d-3743-448d-b8f1-49f484ae6b30", "slug": "loop", "title": "Loop", "blurb": "Use str.replace() to clean the input string and a loop with string concatenation to form the acronym.", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "9eee8db9-80f8-4ee4-aaaf-e55b78221283", "slug": "map-function", "title": "Map Built-in", "blurb": "Use the built-in map() function to form an acronym after cleaning the input string with str.replace().", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "8f4dc8ba-fd1c-4c85-bcc3-8ef9dca34c7f", "slug": "regex-join", "title": "Regex join", "blurb": "Use regex to clean the input string and form the acronym with str.join().", - "authors": ["BethanyG"] + "authors": ["bethanyg"] }, { "uuid": "8830be43-44c3-45ab-8311-f588f60dfc5f", "slug": "regex-sub", "title": "Regex Sub", "blurb": "Use re.sub() to clean the input string and create the acronym in one step.", - "authors": ["BethanyG"] + "authors": ["bethanyg"] } ] -} \ No newline at end of file +} From 8bf0fb2d1bea9f2db5431143c327a08f1284e361 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 27 Feb 2024 23:25:51 -0800 Subject: [PATCH 067/277] Rename approaches to .approaches (#3643) Renamed approaches directory to .approaches, so that the articles will show up on site. --- .../practice/acronym/{approaches => .approaches}/config.json | 0 .../{approaches => .approaches}/functools-reduce/content.md | 0 .../{approaches => .approaches}/functools-reduce/snippet.txt | 0 .../{approaches => .approaches}/generator-expression/content.md | 0 .../{approaches => .approaches}/generator-expression/snippet.txt | 0 .../practice/acronym/{approaches => .approaches}/introduction.md | 0 .../{approaches => .approaches}/list-comprehension/content.md | 0 .../{approaches => .approaches}/list-comprehension/snippet.txt | 0 .../practice/acronym/{approaches => .approaches}/loop/content.md | 0 .../practice/acronym/{approaches => .approaches}/loop/snippet.txt | 0 .../acronym/{approaches => .approaches}/map-function/content.md | 0 .../acronym/{approaches => .approaches}/map-function/snippet.txt | 0 .../acronym/{approaches => .approaches}/regex-join/content.md | 0 .../acronym/{approaches => .approaches}/regex-join/snippet.txt | 0 .../acronym/{approaches => .approaches}/regex-sub/content.md | 0 .../acronym/{approaches => .approaches}/regex-sub/snippet.txt | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename exercises/practice/acronym/{approaches => .approaches}/config.json (100%) rename exercises/practice/acronym/{approaches => .approaches}/functools-reduce/content.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/functools-reduce/snippet.txt (100%) rename exercises/practice/acronym/{approaches => .approaches}/generator-expression/content.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/generator-expression/snippet.txt (100%) rename exercises/practice/acronym/{approaches => .approaches}/introduction.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/list-comprehension/content.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/list-comprehension/snippet.txt (100%) rename exercises/practice/acronym/{approaches => .approaches}/loop/content.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/loop/snippet.txt (100%) rename exercises/practice/acronym/{approaches => .approaches}/map-function/content.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/map-function/snippet.txt (100%) rename exercises/practice/acronym/{approaches => .approaches}/regex-join/content.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/regex-join/snippet.txt (100%) rename exercises/practice/acronym/{approaches => .approaches}/regex-sub/content.md (100%) rename exercises/practice/acronym/{approaches => .approaches}/regex-sub/snippet.txt (100%) diff --git a/exercises/practice/acronym/approaches/config.json b/exercises/practice/acronym/.approaches/config.json similarity index 100% rename from exercises/practice/acronym/approaches/config.json rename to exercises/practice/acronym/.approaches/config.json diff --git a/exercises/practice/acronym/approaches/functools-reduce/content.md b/exercises/practice/acronym/.approaches/functools-reduce/content.md similarity index 100% rename from exercises/practice/acronym/approaches/functools-reduce/content.md rename to exercises/practice/acronym/.approaches/functools-reduce/content.md diff --git a/exercises/practice/acronym/approaches/functools-reduce/snippet.txt b/exercises/practice/acronym/.approaches/functools-reduce/snippet.txt similarity index 100% rename from exercises/practice/acronym/approaches/functools-reduce/snippet.txt rename to exercises/practice/acronym/.approaches/functools-reduce/snippet.txt diff --git a/exercises/practice/acronym/approaches/generator-expression/content.md b/exercises/practice/acronym/.approaches/generator-expression/content.md similarity index 100% rename from exercises/practice/acronym/approaches/generator-expression/content.md rename to exercises/practice/acronym/.approaches/generator-expression/content.md diff --git a/exercises/practice/acronym/approaches/generator-expression/snippet.txt b/exercises/practice/acronym/.approaches/generator-expression/snippet.txt similarity index 100% rename from exercises/practice/acronym/approaches/generator-expression/snippet.txt rename to exercises/practice/acronym/.approaches/generator-expression/snippet.txt diff --git a/exercises/practice/acronym/approaches/introduction.md b/exercises/practice/acronym/.approaches/introduction.md similarity index 100% rename from exercises/practice/acronym/approaches/introduction.md rename to exercises/practice/acronym/.approaches/introduction.md diff --git a/exercises/practice/acronym/approaches/list-comprehension/content.md b/exercises/practice/acronym/.approaches/list-comprehension/content.md similarity index 100% rename from exercises/practice/acronym/approaches/list-comprehension/content.md rename to exercises/practice/acronym/.approaches/list-comprehension/content.md diff --git a/exercises/practice/acronym/approaches/list-comprehension/snippet.txt b/exercises/practice/acronym/.approaches/list-comprehension/snippet.txt similarity index 100% rename from exercises/practice/acronym/approaches/list-comprehension/snippet.txt rename to exercises/practice/acronym/.approaches/list-comprehension/snippet.txt diff --git a/exercises/practice/acronym/approaches/loop/content.md b/exercises/practice/acronym/.approaches/loop/content.md similarity index 100% rename from exercises/practice/acronym/approaches/loop/content.md rename to exercises/practice/acronym/.approaches/loop/content.md diff --git a/exercises/practice/acronym/approaches/loop/snippet.txt b/exercises/practice/acronym/.approaches/loop/snippet.txt similarity index 100% rename from exercises/practice/acronym/approaches/loop/snippet.txt rename to exercises/practice/acronym/.approaches/loop/snippet.txt diff --git a/exercises/practice/acronym/approaches/map-function/content.md b/exercises/practice/acronym/.approaches/map-function/content.md similarity index 100% rename from exercises/practice/acronym/approaches/map-function/content.md rename to exercises/practice/acronym/.approaches/map-function/content.md diff --git a/exercises/practice/acronym/approaches/map-function/snippet.txt b/exercises/practice/acronym/.approaches/map-function/snippet.txt similarity index 100% rename from exercises/practice/acronym/approaches/map-function/snippet.txt rename to exercises/practice/acronym/.approaches/map-function/snippet.txt diff --git a/exercises/practice/acronym/approaches/regex-join/content.md b/exercises/practice/acronym/.approaches/regex-join/content.md similarity index 100% rename from exercises/practice/acronym/approaches/regex-join/content.md rename to exercises/practice/acronym/.approaches/regex-join/content.md diff --git a/exercises/practice/acronym/approaches/regex-join/snippet.txt b/exercises/practice/acronym/.approaches/regex-join/snippet.txt similarity index 100% rename from exercises/practice/acronym/approaches/regex-join/snippet.txt rename to exercises/practice/acronym/.approaches/regex-join/snippet.txt diff --git a/exercises/practice/acronym/approaches/regex-sub/content.md b/exercises/practice/acronym/.approaches/regex-sub/content.md similarity index 100% rename from exercises/practice/acronym/approaches/regex-sub/content.md rename to exercises/practice/acronym/.approaches/regex-sub/content.md diff --git a/exercises/practice/acronym/approaches/regex-sub/snippet.txt b/exercises/practice/acronym/.approaches/regex-sub/snippet.txt similarity index 100% rename from exercises/practice/acronym/approaches/regex-sub/snippet.txt rename to exercises/practice/acronym/.approaches/regex-sub/snippet.txt From e6be68abbbd6c76c12d33fd5321a77beb8a81baa Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 28 Feb 2024 00:39:09 -0800 Subject: [PATCH 068/277] Update no-important-files-changed.yml (#3644) fix for html access for [no important files changed] --- .../workflows/no-important-files-changed.yml | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml index 26b068bc46c..2f31421581f 100644 --- a/.github/workflows/no-important-files-changed.yml +++ b/.github/workflows/no-important-files-changed.yml @@ -1,9 +1,16 @@ name: No important files changed on: - pull_request: + pull_request_target: types: [opened] branches: [main] + paths: + - "exercises/concept/**" + - "exercises/practice/**" + - "!exercises/*/*/.meta/config.json" + - "!exercises/*/*/.meta/tests.toml" + - "!exercises/*/*/.docs/instructions.md" + - "!exercises/*/*/.docs/introduction.md" permissions: pull-requests: write @@ -14,50 +21,40 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + with: + repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} + ref: ${{ github.head_ref }} - name: Check if important files changed id: check run: | set -exo pipefail - # fetch a ref to the main branch so we can diff against it git remote set-branches origin main git fetch --depth 1 origin main + git diff --diff-filter=M --name-only origin/main + for changed_file in $(git diff --diff-filter=M --name-only origin/main); do - if ! echo "$changed_file" | grep --quiet --extended-regexp 'exercises/(practice|concept)' ; then - continue - fi - slug="$(echo "$changed_file" | sed --regexp-extended 's#exercises/[^/]+/([^/]+)/.*#\1#' )" - path_before_slug="$(echo "$changed_file" | sed --regexp-extended "s#(.*)/$slug/.*#\\1#" )" - path_after_slug="$( echo "$changed_file" | sed --regexp-extended "s#.*/$slug/(.*)#\\1#" )" + slug="$(echo "${changed_file}" | sed --regexp-extended 's#exercises/[^/]+/([^/]+)/.*#\1#' )" + path_before_slug="$(echo "${changed_file}" | sed --regexp-extended "s#(.*)/${slug}/.*#\\1#" )" + path_after_slug="$( echo "${changed_file}" | sed --regexp-extended "s#.*/${slug}/(.*)#\\1#" )" + config_json_file="${path_before_slug}/${slug}/.meta/config.json" - if ! [ -f "$path_before_slug/$slug/.meta/config.json" ]; then + if ! [ -f "${config_json_file}" ]; then # cannot determine if important files changed without .meta/config.json continue fi - # returns 0 if the filter matches, 1 otherwise - # | contains($path_after_slug) - if jq --exit-status \ - --arg path_after_slug "$path_after_slug" \ - '[.files.test, .files.invalidator, .files.editor] | flatten | index($path_after_slug)' \ - "$path_before_slug/$slug/.meta/config.json" \ - > /dev/null; - then - echo "important_files_changed=true" >> "$GITHUB_OUTPUT" - exit 0 - fi + changed=$(jq --arg path "${path_after_slug}" '[.files.test, .files.invalidator, .files.editor] | flatten | index($path) != null' "${config_json_file}") + echo "important_files_changed=${changed}" >> "$GITHUB_OUTPUT" done - echo "important_files_changed=false" >> "$GITHUB_OUTPUT" - - name: Suggest to add [no important files changed] if: steps.check.outputs.important_files_changed == 'true' uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: - github-token: ${{ github.token }} script: | const body = "This PR touches files which potentially affect the outcome of the tests of an exercise. This will cause all students' solutions to affected exercises to be re-tested.\n\nIf this PR does **not** affect the result of the test (or, for example, adds an edge case that is not worth rerunning all tests for), **please add the following to the merge-commit message** which will stops student's tests from re-running. Please copy-paste to avoid typos.\n```\n[no important files changed]\n```\n\n For more information, refer to the [documentation](https://exercism.org/docs/building/tracks#h-avoiding-triggering-unnecessary-test-runs). If you are unsure whether to add the message or not, please ping `@exercism/maintainers-admin` in a comment. Thank you!" github.rest.issues.createComment({ From aae2c8902d07b57b1b4d19644a102ec9ebc86a01 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 28 Feb 2024 07:51:59 -0800 Subject: [PATCH 069/277] Synced docs and metadata to problem specificiations. (#3646) --- exercises/practice/meetup/.meta/config.json | 3 +-- exercises/practice/raindrops/.meta/config.json | 2 +- exercises/practice/say/.docs/instructions.md | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/exercises/practice/meetup/.meta/config.json b/exercises/practice/meetup/.meta/config.json index 9a08a5da713..f269bc387fe 100644 --- a/exercises/practice/meetup/.meta/config.json +++ b/exercises/practice/meetup/.meta/config.json @@ -29,6 +29,5 @@ ] }, "blurb": "Calculate the date of meetups.", - "source": "Jeremy Hinegardner mentioned a Boulder meetup that happens on the Wednesteenth of every month", - "source_url": "http://www.copiousfreetime.org/" + "source": "Jeremy Hinegardner mentioned a Boulder meetup that happens on the Wednesteenth of every month" } diff --git a/exercises/practice/raindrops/.meta/config.json b/exercises/practice/raindrops/.meta/config.json index 463a523712b..70763b83e26 100644 --- a/exercises/practice/raindrops/.meta/config.json +++ b/exercises/practice/raindrops/.meta/config.json @@ -26,7 +26,7 @@ ".meta/example.py" ] }, - "blurb": "Convert a number to a string, the content of which depends on the number's factors.", + "blurb": "Convert a number into its corresponding raindrop sounds - Pling, Plang and Plong.", "source": "A variation on FizzBuzz, a famous technical interview question that is intended to weed out potential candidates. That question is itself derived from Fizz Buzz, a popular children's game for teaching division.", "source_url": "https://en.wikipedia.org/wiki/Fizz_buzz" } diff --git a/exercises/practice/say/.docs/instructions.md b/exercises/practice/say/.docs/instructions.md index fb4a6dfb988..ad3d347782e 100644 --- a/exercises/practice/say/.docs/instructions.md +++ b/exercises/practice/say/.docs/instructions.md @@ -30,8 +30,6 @@ Implement breaking a number up into chunks of thousands. So `1234567890` should yield a list like 1, 234, 567, and 890, while the far simpler `1000` should yield just 1 and 0. -The program must also report any values that are out of range. - ## Step 3 Now handle inserting the appropriate scale word between those chunks. From fd73808a2a70e0b13d798249d1ff189de909a877 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 28 Feb 2024 07:52:15 -0800 Subject: [PATCH 070/277] [Anagram]: Updated Tests from `problem-specifications` (#3647) * Updated anagram tests from problem specs. * Removed accidental solution code from stub file. --- exercises/practice/anagram/.meta/tests.toml | 6 ++++++ exercises/practice/anagram/anagram_test.py | 12 +++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/exercises/practice/anagram/.meta/tests.toml b/exercises/practice/anagram/.meta/tests.toml index 4f43811d26a..4d90562705e 100644 --- a/exercises/practice/anagram/.meta/tests.toml +++ b/exercises/practice/anagram/.meta/tests.toml @@ -78,3 +78,9 @@ include = false [33d3f67e-fbb9-49d3-a90e-0beb00861da7] description = "words other than themselves can be anagrams" reimplements = "a0705568-628c-4b55-9798-82e4acde51ca" + +[a6854f66-eec1-4afd-a137-62ef2870c051] +description = "handles case of greek letters" + +[fd3509e5-e3ba-409d-ac3d-a9ac84d13296] +description = "different characters may have the same bytes" diff --git a/exercises/practice/anagram/anagram_test.py b/exercises/practice/anagram/anagram_test.py index bed1d80fc93..48b99ec2d39 100644 --- a/exercises/practice/anagram/anagram_test.py +++ b/exercises/practice/anagram/anagram_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/anagram/canonical-data.json -# File last updated on 2023-12-27 +# File last updated on 2024-02-28 import unittest @@ -93,3 +93,13 @@ def test_words_other_than_themselves_can_be_anagrams(self): candidates = ["LISTEN", "Silent"] expected = ["Silent"] self.assertCountEqual(find_anagrams("LISTEN", candidates), expected) + + def test_handles_case_of_greek_letters(self): + candidates = ["ΒΓΑ", "ΒΓΔ", "γβα", "αβγ"] + expected = ["ΒΓΑ", "γβα"] + self.assertCountEqual(find_anagrams("ΑΒΓ", candidates), expected) + + def test_different_characters_may_have_the_same_bytes(self): + candidates = ["€a"] + expected = [] + self.assertCountEqual(find_anagrams("a⬂", candidates), expected) From 412deec1d45272fbc9d1a532902a2d83f4bd6bc7 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 28 Feb 2024 07:53:57 -0800 Subject: [PATCH 071/277] Updated reverse string tests from problem specifications. (#3648) [no important files changed] This test case (though new) is easily passed by the canonical/vast majority solution, so student solution re-testing is not needed at this time. --- .../practice/reverse-string/.meta/tests.toml | 24 ++++++++++++++++--- .../reverse-string/reverse_string_test.py | 5 +++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/exercises/practice/reverse-string/.meta/tests.toml b/exercises/practice/reverse-string/.meta/tests.toml index 2113a533643..555b8e4f2ef 100644 --- a/exercises/practice/reverse-string/.meta/tests.toml +++ b/exercises/practice/reverse-string/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [c3b7d806-dced-49ee-8543-933fd1719b1c] description = "an empty string" @@ -19,3 +26,14 @@ description = "a palindrome" [b9e7dec1-c6df-40bd-9fa3-cd7ded010c4c] description = "an even-sized word" + +[1bed0f8a-13b0-4bd3-9d59-3d0593326fa2] +description = "wide characters" + +[93d7e1b8-f60f-4f3c-9559-4056e10d2ead] +description = "grapheme cluster with pre-combined form" +include = false + +[1028b2c1-6763-4459-8540-2da47ca512d9] +description = "grapheme clusters" +include = false diff --git a/exercises/practice/reverse-string/reverse_string_test.py b/exercises/practice/reverse-string/reverse_string_test.py index 0c3298704cc..83168418198 100644 --- a/exercises/practice/reverse-string/reverse_string_test.py +++ b/exercises/practice/reverse-string/reverse_string_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/reverse-string/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2024-02-28 import unittest @@ -27,3 +27,6 @@ def test_a_palindrome(self): def test_an_even_sized_word(self): self.assertEqual(reverse("drawer"), "reward") + + def test_wide_characters(self): + self.assertEqual(reverse("子猫"), "猫子") From ddef41a1b4e8f59aa0cac908b034d77914a5679c Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Thu, 29 Feb 2024 15:50:17 +0000 Subject: [PATCH 072/277] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#3649)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/0c0972d1df4cd18d98c7df316348315b06ef49b4 --- .../workflows/no-important-files-changed.yml | 60 +++---------------- 1 file changed, 9 insertions(+), 51 deletions(-) diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml index 2f31421581f..b940c5991c6 100644 --- a/.github/workflows/no-important-files-changed.yml +++ b/.github/workflows/no-important-files-changed.yml @@ -7,59 +7,17 @@ on: paths: - "exercises/concept/**" - "exercises/practice/**" - - "!exercises/*/*/.meta/config.json" - - "!exercises/*/*/.meta/tests.toml" - - "!exercises/*/*/.docs/instructions.md" - - "!exercises/*/*/.docs/introduction.md" + - "!exercises/*/*/.approaches/**" + - "!exercises/*/*/.articles/**" + - "!exercises/*/*/.docs/**" + - "!exercises/*/*/.meta/**" permissions: pull-requests: write jobs: - no_important_files_changed: - name: No important files changed - runs-on: ubuntu-22.04 - steps: - - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - with: - repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} - ref: ${{ github.head_ref }} - - - name: Check if important files changed - id: check - run: | - set -exo pipefail - - git remote set-branches origin main - git fetch --depth 1 origin main - - git diff --diff-filter=M --name-only origin/main - - for changed_file in $(git diff --diff-filter=M --name-only origin/main); do - slug="$(echo "${changed_file}" | sed --regexp-extended 's#exercises/[^/]+/([^/]+)/.*#\1#' )" - path_before_slug="$(echo "${changed_file}" | sed --regexp-extended "s#(.*)/${slug}/.*#\\1#" )" - path_after_slug="$( echo "${changed_file}" | sed --regexp-extended "s#.*/${slug}/(.*)#\\1#" )" - config_json_file="${path_before_slug}/${slug}/.meta/config.json" - - if ! [ -f "${config_json_file}" ]; then - # cannot determine if important files changed without .meta/config.json - continue - fi - - changed=$(jq --arg path "${path_after_slug}" '[.files.test, .files.invalidator, .files.editor] | flatten | index($path) != null' "${config_json_file}") - echo "important_files_changed=${changed}" >> "$GITHUB_OUTPUT" - done - - - name: Suggest to add [no important files changed] - if: steps.check.outputs.important_files_changed == 'true' - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea - with: - script: | - const body = "This PR touches files which potentially affect the outcome of the tests of an exercise. This will cause all students' solutions to affected exercises to be re-tested.\n\nIf this PR does **not** affect the result of the test (or, for example, adds an edge case that is not worth rerunning all tests for), **please add the following to the merge-commit message** which will stops student's tests from re-running. Please copy-paste to avoid typos.\n```\n[no important files changed]\n```\n\n For more information, refer to the [documentation](https://exercism.org/docs/building/tracks#h-avoiding-triggering-unnecessary-test-runs). If you are unsure whether to add the message or not, please ping `@exercism/maintainers-admin` in a comment. Thank you!" - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: body - }) + pause: + uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main + with: + repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} + ref: ${{ github.head_ref }} From 052ae68d18ae102c6ed356804dff81f3ea75fecf Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 29 Feb 2024 08:12:16 -0800 Subject: [PATCH 073/277] [BlackJack]: Clarify Blackjack Hand Definition in instructions.md (#3650) Per [forum discussion](http://forum.exercism.org/t/suggested-changes-to-python-learning-exercise-instructions/9896/11). --- exercises/concept/black-jack/.docs/instructions.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/exercises/concept/black-jack/.docs/instructions.md b/exercises/concept/black-jack/.docs/instructions.md index 199fabc900e..d6c8fc502f0 100644 --- a/exercises/concept/black-jack/.docs/instructions.md +++ b/exercises/concept/black-jack/.docs/instructions.md @@ -79,7 +79,9 @@ Remember: the value of the hand with the ace needs to be as high as possible _wi ## 4. Determine a "Natural" or "Blackjack" Hand -If the first two cards a player is dealt are an ace (`A`) and a ten-card (10, `K`, `Q` or `J`), giving a score of 21 in two cards, the hand is considered a `natural` or `blackjack`. +If the first two cards a player is dealt are an ace (A) and a ten-card (_10, K , Q or J_), then the player has a score of 21. +This is known as a blackjack hand. + Define the `is_blackjack(, )` function with parameters `card_one` and `card_two`, which are a pair of cards. Determine if the two-card hand is a `blackjack`, and return the boolean `True` if it is, `False` otherwise. From 55a0ab42851acbac183b5045f86f551b375eb30b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 29 Feb 2024 10:28:31 -0800 Subject: [PATCH 074/277] Superset subset and operator typo corrections. (#3651) --- concepts/sets/about.md | 4 ++-- exercises/concept/cater-waiter/.docs/introduction.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/concepts/sets/about.md b/concepts/sets/about.md index 0b8fc842f11..321c5116c9a 100644 --- a/concepts/sets/about.md +++ b/concepts/sets/about.md @@ -172,7 +172,7 @@ Traceback (most recent call last): Sets have methods that generally mimic [mathematical set operations][mathematical-sets]. Most (_not all_) of these methods have an [operator][operator] equivalent. -Methods generally take any `iterable` as an argument, while operators require that both things being compared are `sets` or `frozensets`. +Methods generally take any `iterable` as an argument, while operators require that both sides of the operation are `sets` or `frozensets`. ### Membership Testing Between Sets @@ -266,7 +266,7 @@ False False # A set is always a loose superset of itself. ->>> set(animals) <= set(animals) +>>> set(animals) >= set(animals) True ``` diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index 235ae869374..94a0b4c7669 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -114,7 +114,7 @@ TypeError: unhashable type: 'set' Sets have methods that generally mimic [mathematical set operations][mathematical-sets]. Most (_not all_) of these methods have an [operator][operator] equivalent. -Methods generally take any `iterable` as an argument, while operators require that both things being compared are `sets` or `frozensets`. +Methods generally take any `iterable` as an argument, while operators require that both sides of the operation are `sets` or `frozensets`. ### Disjoint Sets @@ -212,7 +212,7 @@ False False # A set is always a loose superset of itself. ->>> set(animals) <= set(animals) +>>> set(animals) >= set(animals) True ``` From c224d7cb439717d656b03c5e46f771cb7680f1ba Mon Sep 17 00:00:00 2001 From: izmirli Date: Sat, 2 Mar 2024 23:30:11 +0200 Subject: [PATCH 075/277] [Mecha Munch Management] Add test case for send_to_store of extra item on isle_mapping (#3653) --- .../dict_methods_test.py | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/exercises/concept/mecha-munch-management/dict_methods_test.py b/exercises/concept/mecha-munch-management/dict_methods_test.py index 8ec0211d530..1bb09b242dc 100644 --- a/exercises/concept/mecha-munch-management/dict_methods_test.py +++ b/exercises/concept/mecha-munch-management/dict_methods_test.py @@ -31,7 +31,6 @@ def test_add_item(self): f'expected: {expected} once the item was added.') self.assertEqual(actual_result, expected, msg=error_msg) - @pytest.mark.task(taskno=2) def test_read_notes(self): @@ -130,12 +129,20 @@ def test_send_to_store(self): 'Yoghurt': ['Aisle 2', True], 'Milk': ['Aisle 2', True]}), ({'Apple': 2, 'Raspberry': 2, 'Blueberries': 5, - 'Broccoli' : 2, 'Kiwi': 1, 'Melon': 4}, + 'Broccoli': 2, 'Kiwi': 1, 'Melon': 4}, {'Apple': ['Aisle 1', False], 'Raspberry': ['Aisle 6', False], 'Blueberries': ['Aisle 6', False], 'Broccoli': ['Aisle 3', False], - 'Kiwi': ['Aisle 6', False], 'Melon': ['Aisle 6', False]}) - ] + 'Kiwi': ['Aisle 6', False], 'Melon': ['Aisle 6', False]}), + + ({'Orange': 1}, + {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], + 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}), + + ({'Banana': 3, 'Apple': 2, 'Orange': 1}, + {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], + 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}), + ] output_data = [ {'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], @@ -146,8 +153,13 @@ def test_send_to_store(self): {'Raspberry': [2, 'Aisle 6', False], 'Melon': [4, 'Aisle 6', False], 'Kiwi': [1, 'Aisle 6', False], 'Broccoli': [2, 'Aisle 3', False], - 'Blueberries': [5, 'Aisle 6', False], 'Apple': [2, 'Aisle 1', False]} - ] + 'Blueberries': [5, 'Aisle 6', False], 'Apple': [2, 'Aisle 1', False]}, + + {'Orange': [1, 'Aisle 4', False]}, + + {'Orange': [1, 'Aisle 4', False], 'Banana': [3, 'Aisle 5', False], + 'Apple': [2, 'Aisle 4', False]}, + ] for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): From 8f5286fed62318380c6b5f0ae30cfdab8ee794bf Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 2 Mar 2024 14:00:58 -0800 Subject: [PATCH 076/277] [Mecha Munch Management]: Corrected Brackets in dict_methods_test.py (#3654) [no important files changed] Trivial, but it was bugging me. --- exercises/concept/mecha-munch-management/dict_methods_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/mecha-munch-management/dict_methods_test.py b/exercises/concept/mecha-munch-management/dict_methods_test.py index 1bb09b242dc..622f425d5ac 100644 --- a/exercises/concept/mecha-munch-management/dict_methods_test.py +++ b/exercises/concept/mecha-munch-management/dict_methods_test.py @@ -142,7 +142,7 @@ def test_send_to_store(self): ({'Banana': 3, 'Apple': 2, 'Orange': 1}, {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}), - ] + ] output_data = [ {'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], @@ -159,7 +159,7 @@ def test_send_to_store(self): {'Orange': [1, 'Aisle 4', False], 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]}, - ] + ] for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): From 125a3f69038cce43c6f74176710088da1858b4fe Mon Sep 17 00:00:00 2001 From: colinleach Date: Tue, 5 Mar 2024 01:33:10 -0700 Subject: [PATCH 077/277] [Circular Buffer] draft approaches (#3640) * [Circular Buffer] draft approaches * introduction - add guidance * Links and Additions Added `memoryview`, `buffer protocol`, `array.array` and supporting links for various things. --------- Co-authored-by: BethanyG --- .../.approaches/built-in-types/content.md | 91 ++++++++++++++ .../.approaches/built-in-types/snippet.txt | 5 + .../circular-buffer/.approaches/config.json | 30 +++++ .../.approaches/introduction.md | 100 +++++++++++++++ .../.approaches/standard-library/content.md | 119 ++++++++++++++++++ .../.approaches/standard-library/snippet.txt | 4 + 6 files changed, 349 insertions(+) create mode 100644 exercises/practice/circular-buffer/.approaches/built-in-types/content.md create mode 100644 exercises/practice/circular-buffer/.approaches/built-in-types/snippet.txt create mode 100644 exercises/practice/circular-buffer/.approaches/config.json create mode 100644 exercises/practice/circular-buffer/.approaches/introduction.md create mode 100644 exercises/practice/circular-buffer/.approaches/standard-library/content.md create mode 100644 exercises/practice/circular-buffer/.approaches/standard-library/snippet.txt diff --git a/exercises/practice/circular-buffer/.approaches/built-in-types/content.md b/exercises/practice/circular-buffer/.approaches/built-in-types/content.md new file mode 100644 index 00000000000..616153969f9 --- /dev/null +++ b/exercises/practice/circular-buffer/.approaches/built-in-types/content.md @@ -0,0 +1,91 @@ +# Built In Types + + +```python +class CircularBuffer: + def __init__(self, capacity: int) -> None: + self.capacity = capacity + self.content = [] + + def read(self) -> str: + if not self.content: + raise BufferEmptyException("Circular buffer is empty") + return self.content.pop(0) + + def write(self, data: str) -> None: + if len(self.content) == self.capacity: + raise BufferFullException("Circular buffer is full") + self.content.append(data) + + def overwrite(self, data: str) -> None: + if len(self.content) == self.capacity: + self.content.pop(0) + self.write(data) + + def clear(self) -> None: + self.content = [] +``` + +In Python, the `list` type is ubiquitous and exceptionally versatile. +Code similar to that shown above is a very common way to implement this exercise. +Though lists can do much more, here we use `append()` to add an entry to the end of the list, and `pop(0)` to remove an entry from the beginning. + + +By design, lists have no built-in length limit and can grow arbitrarily, so the main task of the programmer here is to keep track of capacity, and limit it when needed. +A `list` is also designed to hold an arbitrary mix of Python objects, and this flexibility in content is emphasized over performance. +For more precise control, at the price of some increased programming complexity, it is possible to use a [`bytearray`][bytearray], or the [`array.array`][array.array] type from the [array][[array-module] module. +For details on using `array.array`, see the [standard library][approaches-standard-library] approach. + +In the case of a `bytearray`, entries are of fixed type: integers in the range `0 <= n < 256`. + +The tests are designed such that this is sufficient to solve the exercise, and byte handling may be quite a realistic view of how circular buffers are often used in practice. + +The code below shows an implementation using this lower-level collection class: + + +```python +class CircularBuffer: + def __init__(self, capacity): + self.capacity = bytearray(capacity) + self.read_start = 0 + self.write_start = 0 + + def read(self): + if not any(self.capacity): + raise BufferEmptyException('Circular buffer is empty') + + data = chr(self.capacity[self.read_start]) + self.capacity[self.read_start] = 0 + self.read_start = (self.read_start + 1) % len(self.capacity) + + return data + + def write(self, data): + if all(self.capacity): + raise BufferFullException('Circular buffer is full') + + try: + self.capacity[self.write_start] = data + except TypeError: + self.capacity[self.write_start] = ord(data) + + self.write_start = (self.write_start + 1) % len(self.capacity) + + def overwrite(self, data): + try: + self.capacity[self.write_start] = data + except TypeError: + self.capacity[self.write_start] = ord(data) + + if all(self.capacity) and self.write_start == self.read_start: + self.read_start = (self.read_start + 1) % len(self.capacity) + self.write_start = (self.write_start + 1) % len(self.capacity) + + def clear(self): + self.capacity = bytearray(len(self.capacity)) +``` + +[approaches-standard-library]: https://exercism.org/tracks/python/exercises/circular-buffer/approaches/standard-library +[array-module]: https://docs.python.org/3/library/array.html#module-array +[array.array]: https://docs.python.org/3/library/array.html#array.array +[bytearray]: https://docs.python.org/3/library/stdtypes.html#binary-sequence-types-bytes-bytearray-memoryview diff --git a/exercises/practice/circular-buffer/.approaches/built-in-types/snippet.txt b/exercises/practice/circular-buffer/.approaches/built-in-types/snippet.txt new file mode 100644 index 00000000000..bf0bc4cb184 --- /dev/null +++ b/exercises/practice/circular-buffer/.approaches/built-in-types/snippet.txt @@ -0,0 +1,5 @@ +from queue import Queue + +class CircularBuffer: + def __init__(self, capacity): + self.buffer = Queue(capacity) diff --git a/exercises/practice/circular-buffer/.approaches/config.json b/exercises/practice/circular-buffer/.approaches/config.json new file mode 100644 index 00000000000..3773282790c --- /dev/null +++ b/exercises/practice/circular-buffer/.approaches/config.json @@ -0,0 +1,30 @@ +{ + "introduction": { + "authors": [ + "colinleach", + "BethanyG" + ] + }, + "approaches": [ + { + "uuid": "a560804f-1486-451d-98ab-31251926881e", + "slug": "built-in-types", + "title": "Built In Types", + "blurb": "Use a Python list or bytearray.", + "authors": [ + "colinleach", + "BethanyG" + ] + }, + { + "uuid": "f01b8a10-a3d9-4779-9a8b-497310fcbc73", + "slug": "standard-library", + "title": "Standard Library", + "blurb": "Use a Queue or deque object for an easier implementation.", + "authors": [ + "colinleach", + "BethanyG" + ] + } + ] +} diff --git a/exercises/practice/circular-buffer/.approaches/introduction.md b/exercises/practice/circular-buffer/.approaches/introduction.md new file mode 100644 index 00000000000..920d469cdef --- /dev/null +++ b/exercises/practice/circular-buffer/.approaches/introduction.md @@ -0,0 +1,100 @@ +# Introduction + +The key to this exercise is to: + +- Create a suitable collection object to hold the values. +- Keep track of size as elements are added and removed. + +## General Guidance + +Approaches to this exercise vary from easy but rather boring, to complex but educational. + +It would be useful to think about what you want from completing the exercise, then choose an appropriate collection class that fits your aims. + + +## Exception classes + +All the approaches rely on being able to raise two custom exceptions, with suitable error messages. + +```python +class BufferFullException(BufferError): + """Exception raised when CircularBuffer is full.""" + + def __init__(self, message): + self.message = message + +class BufferEmptyException(BufferError): + """Exception raised when CircularBuffer is empty.""" + + def __init__(self, message): + self.message = message +``` + +Code for these error handling scenarios is always quite similar, so for brevity this aspect will be omitted from the various approaches to the exercise. + + +## Approach: Using built-in types + +Python has an exceptionally flexible and widely-used `list` type. +Most submitted solutions to `Circular Buffer` are based on this data type. + +A less versatile variants include [`bytearray`][bytearray] and [`array.array`][array.array]. + +`bytearray`s are similar to `list`s in many ways, but they are limited to holding only bytes (_represented as integers in the range `0 <= n < 256`_). + +For details, see the [built-in types][approaches-built-in] approach. + + +Finally, [`memoryview`s][memoryview] allow for direct access to the binary data (_ without copying_) of Python objects that support the [`Buffer Protocol`][buffer-protocol]. + `memoryview`s can be used to directly access the underlying memory of types such as `bytearray`, `array.array`, `queue`, `dequeue`, and `list` as well as working with [ctypes][ctypes] from outside libraries and C [structs][struct]. + + For additional information on the `buffer protocol`, see [Emulating Buffer Types][emulating-buffer-types] in the Python documentation. + As of Python `3.12`, the abstract class [collections.abc.Buffer][abc-Buffer] is also available for classes that provide the [`__buffer__()`][dunder-buffer] method and implement the `buffer protocol`. + + +## Approach: Using collection classes from the standard library + +A circular buffer is a type of fixed-size queue, and Python provides various implementations of this very useful type of collection. + +- The [`queue`][queue-module] module contains the [`Queue`][Queue-class] class, which can be initialized with a maximum capacity. +- The [`collections`][collections-module] module contains a [`deque`][deque-class] class (short for Double Ended QUEue), which can also be set to a maximum capacity. +- The [`array`][array.array] module contains an [`array`][array-array] class that is similar to Python's built-in `list`, but is limited to a single datatype (_available datatypes are mapped to C datatypes_). +This allows values to be stored in a more compact and efficient fashion. + + +For details, see the [standard library][approaches-standard-library] approach. + + +## Which Approach to Use? + +Anyone just wanting to use a circular buffer to get other things done and is not super-focused on performance is likely to pick a `Queue` or `deque`, as either of these will handle much of the low-level bookkeeping. + +For a more authentic learning experience, using a `list` will provide practice in keeping track of capacity, with `bytearray` or `array.array` taking the capacity and read/write tracking a stage further. + + +For a really deep dive into low-level Python operations, you can explore using `memoryview`s into `bytearray`s or [`numpy` arrays][numpy-arrays], or customize your own `buffer protocol`-supporting Python object, `ctype` or `struct`. +Some 'jumping off' articles for this are [circular queue or ring buffer (Python and C)][circular-buffer], [memoryview Python Performance][memoryview-py-performance], and [Less Copies in Python with the buffer protocol and memoryviews][less-copies-in-Python]. + + +In reality, anyone wanting to get a deeper understanding of how these collection structures work "from scratch" might do even better to try solving the exercise in a statically-typed system language such as C, Rust, or even try an assembly language like MIPS. + +[Queue-class]: https://docs.python.org/3.11/library/queue.html#queue.Queue +[abc-Buffer]: https://docs.python.org/3/library/collections.abc.html#collections.abc.Buffer +[approaches-built-in]: https://exercism.org/tracks/python/exercises/circular-buffer/approaches/built-in-types +[approaches-standard-library]: https://exercism.org/tracks/python/exercises/circular-buffer/approaches/standard-library +[array-array]: https://docs.python.org/3.11/library/array.html#array.array +[array.array]: https://docs.python.org/3.11/library/array.html#module-array +[buffer-protocol]: https://docs.python.org/3/c-api/buffer.html +[bytearray]: https://docs.python.org/3/library/stdtypes.html#bytearray +[circular-buffer]: https://towardsdatascience.com/circular-queue-or-ring-buffer-92c7b0193326 +[collections-module]: https://docs.python.org/3.11/library/collections.html +[ctypes]: https://docs.python.org/3/library/ctypes.html +[deque-class]: https://docs.python.org/3.11/library/collections.html#collections.deque +[dunder-buffer]: https://docs.python.org/3/reference/datamodel.html#object.__buffer__ +[emulating-buffer-types]: https://docs.python.org/3/reference/datamodel.html#emulating-buffer-types +[less-copies-in-Python]: https://eli.thegreenplace.net/2011/11/28/less-copies-in-python-with-the-buffer-protocol-and-memoryviews +[memoryview-py-performance]: https://prrasad.medium.com/memory-view-python-performance-improvement-method-c241a79e9843 +[memoryview]: https://docs.python.org/3/library/stdtypes.html#memoryview +[numpy-arrays]: https://numpy.org/doc/stable/reference/generated/numpy.array.html +[queue-module]: https://docs.python.org/3.11/library/queue.html +[struct]: https://docs.python.org/3/library/struct.html diff --git a/exercises/practice/circular-buffer/.approaches/standard-library/content.md b/exercises/practice/circular-buffer/.approaches/standard-library/content.md new file mode 100644 index 00000000000..2a1acf5d67f --- /dev/null +++ b/exercises/practice/circular-buffer/.approaches/standard-library/content.md @@ -0,0 +1,119 @@ +# Standard Library + + +```python +from queue import Queue + +class CircularBuffer: + def __init__(self, capacity): + self.buffer = Queue(capacity) + + def read(self): + if self.buffer.empty(): + raise BufferEmptyException("Circular buffer is empty") + return self.buffer.get() + + def write(self, data): + if self.buffer.full(): + raise BufferFullException("Circular buffer is full") + self.buffer.put(data) + + def overwrite(self, data): + if self.buffer.full(): + _ = self.buffer.get() + self.buffer.put(data) + + def clear(self): + while not self.buffer.empty(): + _ = self.buffer.get() +``` + +The above code uses a [`Queue` object][queue] to "implement" the buffer, a collection class which assumes entries will be added at the end and removed at the beginning. +This is a "queue" in British English, though Americans call it a "line". + + +Alternatively, the `collections` module provides a [`deque` object][deque], a double-ended queue class. +A `deque` allows adding and removing entries at both ends, which is not something we need for a circular buffer. +However, the syntax may be even more concise than for a `queue`: + + +```python +from collections import deque +from typing import Any + +class CircularBuffer: + def __init__(self, capacity: int): + self.buffer = deque(maxlen=capacity) + + def read(self) -> Any: + if len(self.buffer) == 0: + raise BufferEmptyException("Circular buffer is empty") + return self.buffer.popleft() + + def write(self, data: Any) -> None: + if len(self.buffer) == self.buffer.maxlen: + raise BufferFullException("Circular buffer is full") + self.buffer.append(data) + + def overwrite(self, data: Any) -> None: + self.buffer.append(data) + + def clear(self) -> None: + if len(self.buffer) > 0: + self.buffer.popleft() +``` + +Both `Queue` and `deque` have the ability to limit the queues length by declaring a 'capacity' or 'maxlen' attribute. +This simplifies empty/full and read/write tracking. + + +Finally, the [`array`][array-array] class from the [`array`][array.array] module can be used to initialize a 'buffer' that works similarly to a built-in `list` or `bytearray`, but with efficiencies in storage and access: + + +```python +from array import array + + +class CircularBuffer: + def __init__(self, capacity): + self.buffer = array('u') + self.capacity = capacity + self.marker = 0 + + def read(self): + if not self.buffer: + raise BufferEmptyException('Circular buffer is empty') + + else: + data = self.buffer.pop(self.marker) + if self.marker > len(self.buffer)-1: self.marker = 0 + + return data + + def write(self, data): + if len(self.buffer) < self.capacity: + try: + self.buffer.append(data) + except TypeError: + self.buffer.append(data) + + else: raise BufferFullException('Circular buffer is full') + + def overwrite(self, data): + if len(self.buffer) < self.capacity: self.buffer.append(data) + + else: + self.buffer[self.marker] = data + + if self.marker < self.capacity - 1: self.marker += 1 + else: self.marker = 0 + + def clear(self): + self.marker = 0 + self.buffer = array('u') +``` + +[queue]: https://docs.python.org/3/library/queue.html +[deque]: https://docs.python.org/3/library/collections.html#deque-objects +[array-array]: https://docs.python.org/3.11/library/array.html#array.array +[array.array]: https://docs.python.org/3.11/library/array.html#module-array diff --git a/exercises/practice/circular-buffer/.approaches/standard-library/snippet.txt b/exercises/practice/circular-buffer/.approaches/standard-library/snippet.txt new file mode 100644 index 00000000000..51114cadba7 --- /dev/null +++ b/exercises/practice/circular-buffer/.approaches/standard-library/snippet.txt @@ -0,0 +1,4 @@ +class CircularBuffer: + def __init__(self, capacity: int) -> None: + self.capacity = capacity + self.content = [] From daae57bca6ccb224ba213cd6e54060b3be31e7ef Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 5 Mar 2024 06:55:34 -0800 Subject: [PATCH 078/277] Synced Two Bucket to Problem Specs. Instructions updated. (#3655) --- exercises/practice/two-bucket/.docs/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/two-bucket/.docs/instructions.md b/exercises/practice/two-bucket/.docs/instructions.md index 7249deb361c..30d779aa922 100644 --- a/exercises/practice/two-bucket/.docs/instructions.md +++ b/exercises/practice/two-bucket/.docs/instructions.md @@ -11,7 +11,7 @@ There are some rules that your solution must follow: b) the second bucket is full 2. Emptying a bucket and doing nothing to the other. 3. Filling a bucket and doing nothing to the other. -- After an action, you may not arrive at a state where the starting bucket is empty and the other bucket is full. +- After an action, you may not arrive at a state where the initial starting bucket is empty and the other bucket is full. Your program will take as input: From efc33f46d2197a718ddf80f5501788062697bc57 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 5 Mar 2024 07:14:34 -0800 Subject: [PATCH 079/277] Updated tests.toml and regenerated test cases for the Change exercise. (#3656) New test case added. Retesting needed. See Prob-Specs PR#2385: https://github.com/exercism/problem-specifications/pull/2385 --- exercises/practice/change/.meta/tests.toml | 3 +++ exercises/practice/change/change_test.py | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/exercises/practice/change/.meta/tests.toml b/exercises/practice/change/.meta/tests.toml index d2cf3ed9021..2d2f44bc219 100644 --- a/exercises/practice/change/.meta/tests.toml +++ b/exercises/practice/change/.meta/tests.toml @@ -33,6 +33,9 @@ description = "possible change without unit coins available" [9a166411-d35d-4f7f-a007-6724ac266178] description = "another possible change without unit coins available" +[ce0f80d5-51c3-469d-818c-3e69dbd25f75] +description = "a greedy approach is not optimal" + [bbbcc154-e9e9-4209-a4db-dd6d81ec26bb] description = "no coins make 0 change" diff --git a/exercises/practice/change/change_test.py b/exercises/practice/change/change_test.py index f8699e2f6db..584edee454f 100644 --- a/exercises/practice/change/change_test.py +++ b/exercises/practice/change/change_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/change/canonical-data.json -# File last updated on 2023-07-20 +# File last updated on 2024-03-05 import unittest @@ -37,6 +37,9 @@ def test_possible_change_without_unit_coins_available(self): def test_another_possible_change_without_unit_coins_available(self): self.assertEqual(find_fewest_coins([4, 5], 27), [4, 4, 4, 5, 5, 5]) + def test_a_greedy_approach_is_not_optimal(self): + self.assertEqual(find_fewest_coins([1, 10, 11], 20), [10, 10]) + def test_no_coins_make_0_change(self): self.assertEqual(find_fewest_coins([1, 5, 10, 21, 25], 0), []) From 59c98a03597e82c00f52c8e065f41a74dcf9d982 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 5 Mar 2024 08:35:39 -0800 Subject: [PATCH 080/277] [Circular Buffer Approaches]: Update introduction.md ln 41 (#3657) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrected the tense mismatch on line 41. 😄 --- exercises/practice/circular-buffer/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/circular-buffer/.approaches/introduction.md b/exercises/practice/circular-buffer/.approaches/introduction.md index 920d469cdef..55fbc81bc25 100644 --- a/exercises/practice/circular-buffer/.approaches/introduction.md +++ b/exercises/practice/circular-buffer/.approaches/introduction.md @@ -38,7 +38,7 @@ Code for these error handling scenarios is always quite similar, so for brevity Python has an exceptionally flexible and widely-used `list` type. Most submitted solutions to `Circular Buffer` are based on this data type. -A less versatile variants include [`bytearray`][bytearray] and [`array.array`][array.array]. +Less versatile variants include [`bytearray`][bytearray] and [`array.array`][array.array]. `bytearray`s are similar to `list`s in many ways, but they are limited to holding only bytes (_represented as integers in the range `0 <= n < 256`_). From d06f9f15d7c63cf10a407597dac3ca01357a61f3 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 6 Mar 2024 11:25:06 -0800 Subject: [PATCH 081/277] First draft of representations.md doc (#3570) --- exercises/shared/.docs/representations.md | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 exercises/shared/.docs/representations.md diff --git a/exercises/shared/.docs/representations.md b/exercises/shared/.docs/representations.md new file mode 100644 index 00000000000..8439ca9aab4 --- /dev/null +++ b/exercises/shared/.docs/representations.md @@ -0,0 +1,34 @@ +# Representations + +The [Python representer][representer] processes and normalizes student solutions into a more "generic" form: + +- Code is converted to an AST using the [Python AST module][python-ast]. +- Final AST tree is converted to a string without whitespace or indentation, and output as `representation.txt`. +- For troubleshooting purposes, `representation.out` includes starting AST, edited AST, and a code representation with normalizations applied. + +- Removals: + - typehints + - `print()` statements + - `if __name__ == __main__` blocks + - comments + - docstrings + +- Replacements: + - user-defined names are replaced with placeholders (_including function names, parameters, and variables in lambdas_) + +- Normalizations: + - stringquotes `'` are changed to `"` (_doublequotes_), unless escapes are needed, then they remain unchanged. + - number literals have any underscores removed and scientific notation is calculated by place: + - **66_777_888_433** --> 66777888433 + - **1_999_878_473.66** --> 1999878473.66 + - **77_555_998_125.445_779** --> 77555998125.44579 + - **44_573_123.445_312+123_674.889_12j** --> 44573123.445312 + 123674.88912j + - **1e6** --> 1000000.0 #1000000 + - **1e2+.23** --> 100.0 + 0.23 #100.23 + - **1e2+1_23e0+4.4e-1** --> 100.0 + 123.0 + 0.44 #223.44 + - **7e6+7e5+5e4+9.98e2+4.45_779e-1** -->7000000.0 + 700000.0 + 50000.0 + 998.0 + 0.445779 #7750998.445779 + - **(7e6+7e5+5e4+9.98e1+4.457_79e-1)+(1e2+1.23e1+4.444_23e-1)*1*j** --> (7000000.0 + 700000.0 + 50000.0 + 99.8 + 0.445779 + (100.0 + 12.3 + 0.444423) * 1j) #7750100.245779+112.744423j + +[representer]: https://github.com/exercism/python-representer/tree/main/representer +[python-ast]: https://docs.python.org/3/library/ast.html#module-ast + From 07a3adf9891ce19c0def15d912ac11542e311175 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:36:40 -0700 Subject: [PATCH 082/277] Bump actions/checkout from 4.1.1 to 4.1.2 (#3658) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/b4ffde65f46336ab88eb53be808477a3936bae11...9bb56186c3b09b4f86b1c65136769dd318469633) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 944c2f531f7..1ea5b87bcbe 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - name: Set up Python uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index 37a459ce50f..9eb4d90d970 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index 1228b8f104d..35c9109251b 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - name: Run test-runner run: docker-compose run test-runner From 6c9fb4de60316950b78ff7413d30e492a1a2e998 Mon Sep 17 00:00:00 2001 From: fefulowe <15332193+fefulowe@users.noreply.github.com> Date: Sat, 23 Mar 2024 17:13:13 -0300 Subject: [PATCH 083/277] =?UTF-8?q?[Black=20Jack]:=20=E2=9C=8F=EF=B8=8F=20?= =?UTF-8?q?Updates=20to=20instructions.=20(#3660)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✏️ Update instructions.md if we already have an ace, the upcoming ace's value should be 1 https://en.wikipedia.org/wiki/Blackjack?lang=en#Rules_of_play_at_casinos [...] and aces count as either 1 or 11 according to the player's choice. * ✏️ Update instructions.md The instructions on the exercise [black_jack](https://exercism.org/tracks/python/exercises/black-jack) might be improved. It could be better to refer to the value of the upcoming ace instead of the value of those in hand since this doesn't apply if we have two aces in hand. Current instructions: `Hint: if we already have an ace in hand then its value would be 11.` Proposed instructions: `Hint: if we already have an ace in hand, then the value for the upcoming ace would be 1.` Sources: "[...] and aces count as either 1 or 11 according to the player's choice." from [Wikipedia](https://en.wikipedia.org/wiki/Blackjack?lang=en#Rules_of_play_at_casinos). --------- Co-authored-by: Federico Ponce de Leon --- exercises/concept/black-jack/.docs/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/black-jack/.docs/instructions.md b/exercises/concept/black-jack/.docs/instructions.md index d6c8fc502f0..21fc971e23b 100644 --- a/exercises/concept/black-jack/.docs/instructions.md +++ b/exercises/concept/black-jack/.docs/instructions.md @@ -67,7 +67,7 @@ Define the `value_of_ace(, )` function with parameters `card Your function will have to decide if the upcoming ace will get a value of 1 or a value of 11, and return that value. Remember: the value of the hand with the ace needs to be as high as possible _without_ going over 21. -**Hint**: if we already have an ace in hand then its value would be 11. +**Hint**: if we already have an ace in hand, then the value for the upcoming ace would be 1. ```python >>> value_of_ace('6', 'K') From 5c3ff61a051e5ce871fd7b73304b2c5de09f9239 Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Wed, 27 Mar 2024 17:14:34 +0000 Subject: [PATCH 084/277] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#3662)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/45ce43faa93a84c84f407748aae3aa028383ec77 --- .github/workflows/no-important-files-changed.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml index b940c5991c6..812e9129668 100644 --- a/.github/workflows/no-important-files-changed.yml +++ b/.github/workflows/no-important-files-changed.yml @@ -16,7 +16,7 @@ permissions: pull-requests: write jobs: - pause: + check: uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main with: repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} From d8b3979fa69fce8b726fbb35d3dcc43ebbd3bb0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Mar 2024 10:15:09 -0700 Subject: [PATCH 085/277] Bump actions/setup-python from 5.0.0 to 5.1.0 (#3661) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.0.0 to 5.1.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/0a5c61591373683505ea898e09a3ea4f39ef2b9c...82c7e631bb3cdc910f68e0081d67478d79c6982d) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 1ea5b87bcbe..7168ccd5d35 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - name: Set up Python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d with: python-version: ${{ matrix.python-version }} From 41366ab8713aca657ad5b76ad52ed539aa04a161 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sun, 31 Mar 2024 20:19:26 +0200 Subject: [PATCH 086/277] Sync the `sieve` exercise's docs with the latest data. (#3664) --- .../practice/sieve/.docs/instructions.md | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index 3adf1d551be..085c0a57d96 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -1,28 +1,42 @@ # Instructions -Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find prime numbers. +Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find all prime numbers less than or equal to a given number. -A prime number is a number that is only divisible by 1 and itself. +A prime number is a number larger than 1 that is only divisible by 1 and itself. For example, 2, 3, 5, 7, 11, and 13 are prime numbers. - -The Sieve of Eratosthenes is an ancient algorithm that works by taking a list of numbers and crossing out all the numbers that aren't prime. - -A number that is **not** prime is called a "composite number". +By contrast, 6 is _not_ a prime number as it not only divisible by 1 and itself, but also by 2 and 3. To use the Sieve of Eratosthenes, you first create a list of all the numbers between 2 and your given number. Then you repeat the following steps: -1. Find the next unmarked number in your list. This is a prime number. -2. Mark all the multiples of that prime number as composite (not prime). +1. Find the next unmarked number in your list (skipping over marked numbers). + This is a prime number. +2. Mark all the multiples of that prime number as **not** prime. You keep repeating these steps until you've gone through every number in your list. At the end, all the unmarked numbers are prime. ~~~~exercism/note -[Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm. - The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. -A good first test is to check that you do not use division or remainder operations. - -[eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +To check you are implementing the Sieve correctly, a good first test is to check that you do not use division or remainder operations. ~~~~ + +## Example + +Let's say you're finding the primes less than or equal to 10. + +- List out 2, 3, 4, 5, 6, 7, 8, 9, 10, leaving them all unmarked. +- 2 is unmarked and is therefore a prime. + Mark 4, 6, 8 and 10 as "not prime". +- 3 is unmarked and is therefore a prime. + Mark 6 and 9 as not prime _(marking 6 is optional - as it's already been marked)_. +- 4 is marked as "not prime", so we skip over it. +- 5 is unmarked and is therefore a prime. + Mark 10 as not prime _(optional - as it's already been marked)_. +- 6 is marked as "not prime", so we skip over it. +- 7 is unmarked and is therefore a prime. +- 8 is marked as "not prime", so we skip over it. +- 9 is marked as "not prime", so we skip over it. +- 10 is marked as "not prime", so we stop as there are no more numbers to check. + +You've examined all numbers and found 2, 3, 5, and 7 are still unmarked, which means they're the primes less than or equal to 10. From 128196aeb7fbc5ca08bd4c81d01482a01e862c05 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sun, 31 Mar 2024 22:07:53 +0200 Subject: [PATCH 087/277] Sync the `two-fer` exercise's docs with the latest data. (#3665) --- exercises/practice/two-fer/.docs/instructions.md | 5 ++--- exercises/practice/two-fer/.docs/introduction.md | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/exercises/practice/two-fer/.docs/instructions.md b/exercises/practice/two-fer/.docs/instructions.md index 37aa75297ea..adc53487981 100644 --- a/exercises/practice/two-fer/.docs/instructions.md +++ b/exercises/practice/two-fer/.docs/instructions.md @@ -2,14 +2,13 @@ Your task is to determine what you will say as you give away the extra cookie. -If your friend likes cookies, and is named Do-yun, then you will say: +If you know the person's name (e.g. if they're named Do-yun), then you will say: ```text One for Do-yun, one for me. ``` -If your friend doesn't like cookies, you give the cookie to the next person in line at the bakery. -Since you don't know their name, you will say _you_ instead. +If you don't know the person's name, you will say _you_ instead. ```text One for you, one for me. diff --git a/exercises/practice/two-fer/.docs/introduction.md b/exercises/practice/two-fer/.docs/introduction.md index 8c124394aac..5947a2230bd 100644 --- a/exercises/practice/two-fer/.docs/introduction.md +++ b/exercises/practice/two-fer/.docs/introduction.md @@ -5,4 +5,4 @@ Two-for-one is a way of saying that if you buy one, you also get one for free. So the phrase "two-fer" often implies a two-for-one offer. Imagine a bakery that has a holiday offer where you can buy two cookies for the price of one ("two-fer one!"). -You go for the offer and (very generously) decide to give the extra cookie to a friend. +You take the offer and (very generously) decide to give the extra cookie to someone else in the queue. From ae694338c1b69f8fa075dea14ec635b68c3e1305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20P=C3=A9gouri=C3=A9-Gonnard?= Date: Mon, 1 Apr 2024 20:08:32 +0200 Subject: [PATCH 088/277] Fix maximum length of a roman numeral (#3667) --- exercises/practice/roman-numerals/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/roman-numerals/.approaches/introduction.md b/exercises/practice/roman-numerals/.approaches/introduction.md index 4468862f0cb..35f73e3acee 100644 --- a/exercises/practice/roman-numerals/.approaches/introduction.md +++ b/exercises/practice/roman-numerals/.approaches/introduction.md @@ -5,7 +5,7 @@ There is no single, obvious solution to this exercise, but a diverse array of wo ## General guidance Roman numerals are limited to positive integers from 1 to 3999 (MMMCMXCIX). -In the version used for this exercise, the longest string needed to represent a Roman numeral is 14 characters (MMDCCCLXXXVIII). +In the version used for this exercise, the longest string needed to represent a Roman numeral is 15 characters (MMMDCCCLXXXVIII). Minor variants of the system have been used which represent 4 as IIII rather than IV, allowing for longer strings, but those are not relevant here. The system is inherently decimal: the number of human fingers has not changed since ancient Rome, nor the habit of using them for counting. From c421d43f55cb7715bf0eadc240beed97ece7f32a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 1 Apr 2024 19:08:15 -0700 Subject: [PATCH 089/277] Update resistor_color_expert_test.py (#3669) [no important files changed] Per [issue on forum](https://forum.exercism.org/t/python-resistor-color-expert/10587). --- .../resistor-color-expert/resistor_color_expert_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/resistor-color-expert/resistor_color_expert_test.py b/exercises/practice/resistor-color-expert/resistor_color_expert_test.py index bcf2052c021..2ba4b877d9d 100644 --- a/exercises/practice/resistor-color-expert/resistor_color_expert_test.py +++ b/exercises/practice/resistor-color-expert/resistor_color_expert_test.py @@ -35,7 +35,7 @@ def test_red_green_yellow_yellow_and_brown(self): resistor_label(["red", "green", "yellow", "yellow", "brown"]), "2.54 megaohms ±1%" ) - def test_blue_grey_white_red_and_brown(self): + def test_blue_grey_white_brown_and_brown(self): self.assertEqual( resistor_label(["blue", "grey", "white", "brown", "brown"]), "6.89 kiloohms ±1%" ) From da6e28aad23941f951dd5be062b9fcada91b266d Mon Sep 17 00:00:00 2001 From: Dmitry Cheremushkin Date: Sun, 14 Apr 2024 19:50:05 +0300 Subject: [PATCH 090/277] [Essays] Fix a typo in the exercise introduction (#3672) --- concepts/string-methods/about.md | 2 +- exercises/concept/little-sisters-essay/.docs/introduction.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/concepts/string-methods/about.md b/concepts/string-methods/about.md index 983e604e719..308b89d9d6a 100644 --- a/concepts/string-methods/about.md +++ b/concepts/string-methods/about.md @@ -43,7 +43,7 @@ There may also be [locale][locale] rules in place for a language or character se ```python >>> man_in_hat_th = 'ผู้ชายใส่หมวก' ->>> man_in_hat_ru = 'mужчина в шляпе' +>>> man_in_hat_ru = 'мужчина в шляпе' >>> man_in_hat_ko = '모자를 쓴 남자' >>> man_in_hat_en = 'the man in the hat.' diff --git a/exercises/concept/little-sisters-essay/.docs/introduction.md b/exercises/concept/little-sisters-essay/.docs/introduction.md index 204c893e925..fc327a12505 100644 --- a/exercises/concept/little-sisters-essay/.docs/introduction.md +++ b/exercises/concept/little-sisters-essay/.docs/introduction.md @@ -22,7 +22,7 @@ There may also be [locale][locale] rules in place for a language or character se ```python man_in_hat_th = 'ผู้ชายใส่หมวก' -man_in_hat_ru = 'mужчина в шляпе' +man_in_hat_ru = 'мужчина в шляпе' man_in_hat_ko = '모자를 쓴 남자' man_in_hat_en = 'the man in the hat.' From cca033bc93c6312051f82dd7bf61b3860d8ec56e Mon Sep 17 00:00:00 2001 From: Dmitry Cheremushkin Date: Tue, 16 Apr 2024 02:23:17 +0300 Subject: [PATCH 091/277] [Card Games] Update 'approx_average_is_average' docstring summary (#3675) Co-authored-by: Dmitrii Cheremushkin --- exercises/concept/card-games/.meta/exemplar.py | 2 +- exercises/concept/card-games/lists.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/card-games/.meta/exemplar.py b/exercises/concept/card-games/.meta/exemplar.py index 840aea6aa9a..d6531f01865 100644 --- a/exercises/concept/card-games/.meta/exemplar.py +++ b/exercises/concept/card-games/.meta/exemplar.py @@ -47,7 +47,7 @@ def card_average(hand): def approx_average_is_average(hand): - """Return if an average is using (first + last index values ) OR ('middle' card) == calculated average. + """Return if the (average of first and last card values) OR ('middle' card) == calculated average. :param hand: list - cards in hand. :return: bool - does one of the approximate averages equal the `true average`? diff --git a/exercises/concept/card-games/lists.py b/exercises/concept/card-games/lists.py index 11dff666de6..03fb417330a 100644 --- a/exercises/concept/card-games/lists.py +++ b/exercises/concept/card-games/lists.py @@ -47,7 +47,7 @@ def card_average(hand): def approx_average_is_average(hand): - """Return if an average is using (first + last index values ) OR ('middle' card) == calculated average. + """Return if the (average of first and last card values) OR ('middle' card) == calculated average. :param hand: list - cards in hand. :return: bool - does one of the approximate averages equal the `true average`? From 8e849c025ce831769abaff4d3cf178af6909421b Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 17 Apr 2024 22:45:47 +0200 Subject: [PATCH 092/277] Sync the `zebra-puzzle` exercise's docs with the latest data. (#3676) --- .../practice/zebra-puzzle/.docs/instructions.md | 16 ++++++++++++---- .../practice/zebra-puzzle/.docs/introduction.md | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 exercises/practice/zebra-puzzle/.docs/introduction.md diff --git a/exercises/practice/zebra-puzzle/.docs/instructions.md b/exercises/practice/zebra-puzzle/.docs/instructions.md index 6d62d18e4cc..c666e33cb3f 100644 --- a/exercises/practice/zebra-puzzle/.docs/instructions.md +++ b/exercises/practice/zebra-puzzle/.docs/instructions.md @@ -1,6 +1,13 @@ # Instructions -Solve the zebra puzzle. +Your task is to solve the Zebra Puzzle to find the answer to these two questions: + +- Which of the residents drinks water? +- Who owns the zebra? + +## Puzzle + +The following 15 statements are all known to be true: 1. There are five houses. 2. The Englishman lives in the red house. @@ -18,7 +25,8 @@ Solve the zebra puzzle. 14. The Japanese smokes Parliaments. 15. The Norwegian lives next to the blue house. -Each of the five houses is painted a different color, and their inhabitants are of different national extractions, own different pets, drink different beverages and smoke different brands of cigarettes. +Additionally, each of the five houses is painted a different color, and their inhabitants are of different national extractions, own different pets, drink different beverages and smoke different brands of cigarettes. -Which of the residents drinks water? -Who owns the zebra? +~~~~exercism/note +There are 24 billion (5!⁵ = 24,883,200,000) possible solutions, so try ruling out as many solutions as possible. +~~~~ diff --git a/exercises/practice/zebra-puzzle/.docs/introduction.md b/exercises/practice/zebra-puzzle/.docs/introduction.md new file mode 100644 index 00000000000..c8b7ef751ec --- /dev/null +++ b/exercises/practice/zebra-puzzle/.docs/introduction.md @@ -0,0 +1,14 @@ +# Introduction + +The Zebra Puzzle is a famous logic puzzle in which there are five houses, each painted a different color. +The houses have different inhabitants, who have different nationalities, own different pets, drink different beverages and smoke different brands of cigarettes. + +To help you solve the puzzle, you're given 15 statements describing the solution. +However, only by combining the information in _all_ statements will you be able to find the solution to the puzzle. + +~~~~exercism/note +The Zebra Puzzle is a [Constraint satisfaction problem (CSP)][constraint-satisfaction-problem]. +In such a problem, you have a set of possible values and a set of constraints that limit which values are valid. +Another well-known CSP is Sudoku. +[constraint-satisfaction-problem]: https://en.wikipedia.org/wiki/Constraint_satisfaction_problem +~~~~ From aaa8c4d553c99f1491780f99652cda309dddb7a0 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 17 Apr 2024 22:46:22 +0200 Subject: [PATCH 093/277] Sync the `scrabble-score` exercise's docs with the latest data. (#3677) --- .../scrabble-score/.docs/instructions.md | 47 +++++++------------ .../scrabble-score/.docs/introduction.md | 7 +++ 2 files changed, 23 insertions(+), 31 deletions(-) create mode 100644 exercises/practice/scrabble-score/.docs/introduction.md diff --git a/exercises/practice/scrabble-score/.docs/instructions.md b/exercises/practice/scrabble-score/.docs/instructions.md index 3f986c947be..738f928c5b6 100644 --- a/exercises/practice/scrabble-score/.docs/instructions.md +++ b/exercises/practice/scrabble-score/.docs/instructions.md @@ -1,40 +1,25 @@ # Instructions -Given a word, compute the Scrabble score for that word. +Your task is to compute a word's Scrabble score by summing the values of its letters. -## Letter Values +The letters are valued as follows: -You'll need these: +| Letter | Value | +| ---------------------------- | ----- | +| A, E, I, O, U, L, N, R, S, T | 1 | +| D, G | 2 | +| B, C, M, P | 3 | +| F, H, V, W, Y | 4 | +| K | 5 | +| J, X | 8 | +| Q, Z | 10 | -```text -Letter Value -A, E, I, O, U, L, N, R, S, T 1 -D, G 2 -B, C, M, P 3 -F, H, V, W, Y 4 -K 5 -J, X 8 -Q, Z 10 -``` - -## Examples - -"cabbage" should be scored as worth 14 points: +For example, the word "cabbage" is worth 14 points: - 3 points for C -- 1 point for A, twice -- 3 points for B, twice +- 1 point for A +- 3 points for B +- 3 points for B +- 1 point for A - 2 points for G - 1 point for E - -And to total: - -- `3 + 2*1 + 2*3 + 2 + 1` -- = `3 + 2 + 6 + 3` -- = `5 + 9` -- = 14 - -## Extensions - -- You can play a double or a triple letter. -- You can play a double or a triple word. diff --git a/exercises/practice/scrabble-score/.docs/introduction.md b/exercises/practice/scrabble-score/.docs/introduction.md new file mode 100644 index 00000000000..8821f240ba9 --- /dev/null +++ b/exercises/practice/scrabble-score/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +[Scrabble][wikipedia] is a word game where players place letter tiles on a board to form words. +Each letter has a value. +A word's score is the sum of its letters' values. + +[wikipedia]: https://en.wikipedia.org/wiki/Scrabble From fd646d7bcf702895e5af9fb6685c1de0d32619e7 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 17 Apr 2024 22:46:51 +0200 Subject: [PATCH 094/277] Sync the `minesweeper` exercise's docs with the latest data. (#3678) --- .../minesweeper/.docs/instructions.md | 20 +++++++------------ .../minesweeper/.docs/introduction.md | 5 +++++ 2 files changed, 12 insertions(+), 13 deletions(-) create mode 100644 exercises/practice/minesweeper/.docs/introduction.md diff --git a/exercises/practice/minesweeper/.docs/instructions.md b/exercises/practice/minesweeper/.docs/instructions.md index f5f918bdff9..7c1df2e4ba5 100644 --- a/exercises/practice/minesweeper/.docs/instructions.md +++ b/exercises/practice/minesweeper/.docs/instructions.md @@ -1,19 +1,13 @@ # Instructions -Add the mine counts to a completed Minesweeper board. +Your task is to add the mine counts to empty squares in a completed Minesweeper board. +The board itself is a rectangle composed of squares that are either empty (`' '`) or a mine (`'*'`). -Minesweeper is a popular game where the user has to find the mines using numeric hints that indicate how many mines are directly adjacent (horizontally, vertically, diagonally) to a square. +For each empty square, count the number of mines adjacent to it (horizontally, vertically, diagonally). +If the empty square has no adjacent mines, leave it empty. +Otherwise replace it with the adjacent mines count. -In this exercise you have to create some code that counts the number of mines adjacent to a given empty square and replaces that square with the count. - -The board is a rectangle composed of blank space (' ') characters. -A mine is represented by an asterisk (`*`) character. - -If a given space has no adjacent mines at all, leave that square blank. - -## Examples - -For example you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): +For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): ```text ·*·*· @@ -22,7 +16,7 @@ For example you may receive a 5 x 4 board like this (empty spaces are represente ····· ``` -And your code will transform it into this: +Which your code should transform into this: ```text 1*3*1 diff --git a/exercises/practice/minesweeper/.docs/introduction.md b/exercises/practice/minesweeper/.docs/introduction.md new file mode 100644 index 00000000000..5f74a742b02 --- /dev/null +++ b/exercises/practice/minesweeper/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +[Minesweeper][wikipedia] is a popular game where the user has to find the mines using numeric hints that indicate how many mines are directly adjacent (horizontally, vertically, diagonally) to a square. + +[wikipedia]: https://en.wikipedia.org/wiki/Minesweeper_(video_game) From 1b8e59abd71e4c51d4eb85aac630241ed968d84b Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 17 Apr 2024 22:47:21 +0200 Subject: [PATCH 095/277] Sync the `dnd-character` exercise's docs with the latest data. (#3679) --- .../practice/dnd-character/.docs/instructions.md | 11 ++++++----- .../practice/dnd-character/.docs/introduction.md | 10 ++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 exercises/practice/dnd-character/.docs/introduction.md diff --git a/exercises/practice/dnd-character/.docs/instructions.md b/exercises/practice/dnd-character/.docs/instructions.md index b0a603591ec..e14e7949d60 100644 --- a/exercises/practice/dnd-character/.docs/instructions.md +++ b/exercises/practice/dnd-character/.docs/instructions.md @@ -3,13 +3,13 @@ For a game of [Dungeons & Dragons][dnd], each player starts by generating a character they can play with. This character has, among other things, six abilities; strength, dexterity, constitution, intelligence, wisdom and charisma. These six abilities have scores that are determined randomly. -You do this by rolling four 6-sided dice and record the sum of the largest three dice. +You do this by rolling four 6-sided dice and recording the sum of the largest three dice. You do this six times, once for each ability. Your character's initial hitpoints are 10 + your character's constitution modifier. You find your character's constitution modifier by subtracting 10 from your character's constitution, divide by 2 and round down. -Write a random character generator that follows the rules above. +Write a random character generator that follows the above rules. For example, the six throws of four dice may look like: @@ -22,10 +22,11 @@ For example, the six throws of four dice may look like: Because constitution is 3, the constitution modifier is -4 and the hitpoints are 6. -## Notes - +~~~~exercism/note Most programming languages feature (pseudo-)random generators, but few programming languages are designed to roll dice. One such language is [Troll][troll]. -[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons [troll]: https://di.ku.dk/Ansatte/?pure=da%2Fpublications%2Ftroll-a-language-for-specifying-dicerolls(84a45ff0-068b-11df-825d-000ea68e967b)%2Fexport.html +~~~~ + +[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons diff --git a/exercises/practice/dnd-character/.docs/introduction.md b/exercises/practice/dnd-character/.docs/introduction.md new file mode 100644 index 00000000000..5301f61829d --- /dev/null +++ b/exercises/practice/dnd-character/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +After weeks of anticipation, you and your friends get together for your very first game of [Dungeons & Dragons][dnd] (D&D). +Since this is the first session of the game, each player has to generate a character to play with. +The character's abilities are determined by rolling 6-sided dice, but where _are_ the dice? +With a shock, you realize that your friends are waiting for _you_ to produce the dice; after all it was your idea to play D&D! +Panicking, you realize you forgot to bring the dice, which would mean no D&D game. +As you have some basic coding skills, you quickly come up with a solution: you'll write a program to simulate dice rolls. + +[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons From 0ad89c596fb1c07e762d3ebb0a40421e6e9af724 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 17 Apr 2024 22:47:55 +0200 Subject: [PATCH 096/277] Sync the `all-your-base` exercise's docs with the latest data. (#3680) --- .../all-your-base/.docs/instructions.md | 21 +++++++------------ .../all-your-base/.docs/introduction.md | 8 +++++++ 2 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 exercises/practice/all-your-base/.docs/introduction.md diff --git a/exercises/practice/all-your-base/.docs/instructions.md b/exercises/practice/all-your-base/.docs/instructions.md index 4602b5cfadd..1b688b6915e 100644 --- a/exercises/practice/all-your-base/.docs/instructions.md +++ b/exercises/practice/all-your-base/.docs/instructions.md @@ -1,14 +1,11 @@ # Instructions -Convert a number, represented as a sequence of digits in one base, to any other base. +Convert a sequence of digits in one base, representing a number, into a sequence of digits in another base, representing the same number. -Implement general base conversion. -Given a number in base **a**, represented as a sequence of digits, convert it to base **b**. - -## Note - -- Try to implement the conversion yourself. - Do not use something else to perform the conversion for you. +~~~~exercism/note +Try to implement the conversion yourself. +Do not use something else to perform the conversion for you. +~~~~ ## About [Positional Notation][positional-notation] @@ -16,17 +13,15 @@ In positional notation, a number in base **b** can be understood as a linear com The number 42, _in base 10_, means: -`(4 * 10^1) + (2 * 10^0)` +`(4 × 10¹) + (2 × 10⁰)` The number 101010, _in base 2_, means: -`(1 * 2^5) + (0 * 2^4) + (1 * 2^3) + (0 * 2^2) + (1 * 2^1) + (0 * 2^0)` +`(1 × 2⁵) + (0 × 2⁴) + (1 × 2³) + (0 × 2²) + (1 × 2¹) + (0 × 2⁰)` The number 1120, _in base 3_, means: -`(1 * 3^3) + (1 * 3^2) + (2 * 3^1) + (0 * 3^0)` - -I think you got the idea! +`(1 × 3³) + (1 × 3²) + (2 × 3¹) + (0 × 3⁰)` _Yes. Those three numbers above are exactly the same. Congratulations!_ diff --git a/exercises/practice/all-your-base/.docs/introduction.md b/exercises/practice/all-your-base/.docs/introduction.md new file mode 100644 index 00000000000..68aaffbed95 --- /dev/null +++ b/exercises/practice/all-your-base/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +You've just been hired as professor of mathematics. +Your first week went well, but something is off in your second week. +The problem is that every answer given by your students is wrong! +Luckily, your math skills have allowed you to identify the problem: the student answers _are_ correct, but they're all in base 2 (binary)! +Amazingly, it turns out that each week, the students use a different base. +To help you quickly verify the student answers, you'll be building a tool to translate between bases. From 567ad1b8db596e1bb423b87011b544aa217b4ec1 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 18 Apr 2024 23:27:03 +0200 Subject: [PATCH 097/277] Sync the `zebra-puzzle` exercise's docs with the latest data. (#3681) --- exercises/practice/zebra-puzzle/.docs/introduction.md | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/practice/zebra-puzzle/.docs/introduction.md b/exercises/practice/zebra-puzzle/.docs/introduction.md index c8b7ef751ec..33d688fd51b 100644 --- a/exercises/practice/zebra-puzzle/.docs/introduction.md +++ b/exercises/practice/zebra-puzzle/.docs/introduction.md @@ -10,5 +10,6 @@ However, only by combining the information in _all_ statements will you be able The Zebra Puzzle is a [Constraint satisfaction problem (CSP)][constraint-satisfaction-problem]. In such a problem, you have a set of possible values and a set of constraints that limit which values are valid. Another well-known CSP is Sudoku. + [constraint-satisfaction-problem]: https://en.wikipedia.org/wiki/Constraint_satisfaction_problem ~~~~ From 087d4748c2ec74c1ea9035bd670e23859ee1a8ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:24:08 -0700 Subject: [PATCH 098/277] Bump actions/checkout from 4.1.2 to 4.1.3 (#3682) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.2 to 4.1.3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/9bb56186c3b09b4f86b1c65136769dd318469633...1d96c772d19495a3b5c517cd2bc0cb401ea0529f) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 7168ccd5d35..4aecd9d3460 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index 9eb4d90d970..28be0f2a35e 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index 35c9109251b..e0ea3ca3223 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f - name: Run test-runner run: docker-compose run test-runner From 1fd9f1ccd057aa210c68ca76eaa5e015bf6046fa Mon Sep 17 00:00:00 2001 From: Dmitry Cheremushkin Date: Sun, 21 Apr 2024 20:55:57 +0300 Subject: [PATCH 099/277] [Inventory Management] Update 'list_inventory' docstring summary (#3684) Co-authored-by: Dmitrii Cheremushkin --- exercises/concept/inventory-management/.meta/exemplar.py | 2 +- exercises/concept/inventory-management/dicts.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/inventory-management/.meta/exemplar.py b/exercises/concept/inventory-management/.meta/exemplar.py index ac36ce97581..ac02bad30d4 100644 --- a/exercises/concept/inventory-management/.meta/exemplar.py +++ b/exercises/concept/inventory-management/.meta/exemplar.py @@ -55,7 +55,7 @@ def remove_item(inventory, item): def list_inventory(inventory): - """Create a list containing all (item_name, item_count) pairs in inventory. + """Create a list containing only available (item_name, item_count > 0) pairs in inventory. :param inventory: dict - an inventory dictionary. :return: list of tuples - list of key, value pairs from the inventory dictionary. diff --git a/exercises/concept/inventory-management/dicts.py b/exercises/concept/inventory-management/dicts.py index ba524bba8c2..2600eceb27d 100644 --- a/exercises/concept/inventory-management/dicts.py +++ b/exercises/concept/inventory-management/dicts.py @@ -45,7 +45,7 @@ def remove_item(inventory, item): def list_inventory(inventory): - """Create a list containing all (item_name, item_count) pairs in inventory. + """Create a list containing only available (item_name, item_count > 0) pairs in inventory. :param inventory: dict - an inventory dictionary. :return: list of tuples - list of key, value pairs from the inventory dictionary. From e6b9efe1518f3d7d835411c5fcfbcc39fc8f4118 Mon Sep 17 00:00:00 2001 From: Dmitry Cheremushkin Date: Wed, 24 Apr 2024 20:22:04 +0300 Subject: [PATCH 100/277] [Mecha Munch Management]: Fix hint for task #5 (send_to_store) (#3686) Co-authored-by: Dmitrii Cheremushkin --- exercises/concept/mecha-munch-management/.docs/hints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/mecha-munch-management/.docs/hints.md b/exercises/concept/mecha-munch-management/.docs/hints.md index 9edd70abfe8..3287768ff5f 100644 --- a/exercises/concept/mecha-munch-management/.docs/hints.md +++ b/exercises/concept/mecha-munch-management/.docs/hints.md @@ -38,7 +38,7 @@ The dictionary section of the [official tutorial][dicts-docs] and the mapping ty - What method would you call to get an [iterable view of just the keys][keys] of the dictionary? - Remember that you can get the `value` of a given key by using `[]` syntax. - If you had a `list` or a `tuple`, what [`built-in`][builtins] function might you use to sort them? -- Remember that the `built-in` function can take an optional `reversed=true` argument. +- Remember that the `built-in` function can take an optional `reverse=True` argument. ## 6. Update the Store Inventory to Reflect what a User Has Ordered. From b7e19f0fc8f6b0bbf3e7b3953e223da587e06676 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 23:15:01 -0700 Subject: [PATCH 101/277] Bump actions/checkout from 4.1.3 to 4.1.4 (#3687) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.3 to 4.1.4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/1d96c772d19495a3b5c517cd2bc0cb401ea0529f...0ad4b8fadaa221de15dcec353f45205ec38ea70b) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 4aecd9d3460..a029e2eceb8 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index 28be0f2a35e..8878d8e0c26 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index e0ea3ca3223..38ad82fdb0e 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b - name: Run test-runner run: docker-compose run test-runner From 840f4012969ef4793c44ff7cb4b7d95e3d5ce041 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 30 Apr 2024 18:31:59 +0200 Subject: [PATCH 102/277] Sync the `yacht` exercise's docs with the latest data. (#3688) --- .../practice/yacht/.docs/instructions.md | 21 +++++++------------ .../practice/yacht/.docs/introduction.md | 11 ++++++++++ 2 files changed, 19 insertions(+), 13 deletions(-) create mode 100644 exercises/practice/yacht/.docs/introduction.md diff --git a/exercises/practice/yacht/.docs/instructions.md b/exercises/practice/yacht/.docs/instructions.md index 54fdb452f5b..519b7a68b87 100644 --- a/exercises/practice/yacht/.docs/instructions.md +++ b/exercises/practice/yacht/.docs/instructions.md @@ -1,8 +1,12 @@ # Instructions -The dice game [Yacht][yacht] is from the same family as Poker Dice, Generala and particularly Yahtzee, of which it is a precursor. -In the game, five dice are rolled and the result can be entered in any of twelve categories. -The score of a throw of the dice depends on category chosen. +Given five dice and a category, calculate the score of the dice for that category. + +~~~~exercism/note +You'll always be presented with five dice. +Each dice's value will be between one and six inclusively. +The dice may be unordered. +~~~~ ## Scores in Yacht @@ -21,15 +25,6 @@ The score of a throw of the dice depends on category chosen. | Choice | Sum of the dice | Any combination | 2 3 3 4 6 scores 18 | | Yacht | 50 points | All five dice showing the same face | 4 4 4 4 4 scores 50 | -If the dice do not satisfy the requirements of a category, the score is zero. +If the dice do **not** satisfy the requirements of a category, the score is zero. If, for example, _Four Of A Kind_ is entered in the _Yacht_ category, zero points are scored. A _Yacht_ scores zero if entered in the _Full House_ category. - -## Task - -Given a list of values for five dice and a category, your solution should return the score of the dice for that category. -If the dice do not satisfy the requirements of the category your solution should return 0. -You can assume that five values will always be presented, and the value of each will be between one and six inclusively. -You should not assume that the dice are ordered. - -[yacht]: https://en.wikipedia.org/wiki/Yacht_(dice_game) diff --git a/exercises/practice/yacht/.docs/introduction.md b/exercises/practice/yacht/.docs/introduction.md new file mode 100644 index 00000000000..5b541f5625c --- /dev/null +++ b/exercises/practice/yacht/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +Each year, something new is "all the rage" in your high school. +This year it is a dice game: [Yacht][yacht]. + +The game of Yacht is from the same family as Poker Dice, Generala and particularly Yahtzee, of which it is a precursor. +The game consists of twelve rounds. +In each, five dice are rolled and the player chooses one of twelve categories. +The chosen category is then used to score the throw of the dice. + +[yacht]: https://en.wikipedia.org/wiki/Yacht_(dice_game) From 2998cda3cd671a9095f60daf99ad08ba55717a0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 10:40:15 +0200 Subject: [PATCH 103/277] Bump actions/checkout from 4.1.4 to 4.1.5 (#3691) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.4 to 4.1.5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/0ad4b8fadaa221de15dcec353f45205ec38ea70b...44c2b7a8a4ea60a981eaca3cf939b5f4305c123b) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index a029e2eceb8..3268c168602 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index 8878d8e0c26..ba6f296bbb6 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index 38ad82fdb0e..e191a970465 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - name: Run test-runner run: docker-compose run test-runner From 4688bf8f1ee46a53190df13b91ce2719cd6e82fe Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 10 May 2024 17:32:46 -0700 Subject: [PATCH 104/277] [Basics Concept/Lasagna Exercise]: Changed `return` keyword Examples (#3689) * Changed examples for use of the return keyword in Python functions. See discussion at http://forum.exercism.org/t/missing-print-in-python-basics-functions-return/11025/4 for additonal information. * Further massaging of examples and adding variable assignment example. --- concepts/basics/about.md | 45 ++++++++++++++++--- concepts/basics/introduction.md | 45 ++++++++++++++++--- .../.docs/introduction.md | 43 +++++++++++++++--- 3 files changed, 113 insertions(+), 20 deletions(-) diff --git a/concepts/basics/about.md b/concepts/basics/about.md index 6a6fca716be..dd636219abd 100644 --- a/concepts/basics/about.md +++ b/concepts/basics/about.md @@ -109,7 +109,7 @@ Related functions and classes (_with their methods_) can be grouped together in The `def` keyword begins a [function definition][function definition]. Each function can have zero or more formal [parameters][parameters] in `()` parenthesis, followed by a `:` colon. -Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_. +Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_: ```python @@ -134,24 +134,55 @@ def add_two_numbers(number_one, number_two): IndentationError: unindent does not match any outer indentation level ``` -Functions _explicitly_ return a value or object via the [`return`][return] keyword. -Functions that do not have an _explicit_ `return` expression will _implicitly_ return [`None`][none]. + +Functions _explicitly_ return a value or object via the [`return`][return] keyword: + ```python -# Function definition on first line. +# Function definition on first line, explicit return used on final line. def add_two_numbers(number_one, number_two): - result = number_one + number_two - return result # Returns the sum of the numbers. + return number_one + number_two + +# Calling the function in the Python terminal returns the sum of the numbers. >>> add_two_numbers(3, 4) 7 -# This function will return None. +# Assigning the function call to a variable and printing +# the variable will also return the value. +>>> sum_with_return = add_two_numbers(5, 6) +>>> print(sum_with_return) +7 +``` + +Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object. +The details of `None` will be covered in a later exercise. +For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null: + + +```python +# This function does not have an explicit return. def add_two_numbers(number_one, number_two): result = number_one + number_two + +# Calling the function in the Python terminal appears +# to not return anything at all. +>>> add_two_numbers(5, 7) +>>> + + +# Using print() with the function call shows that +# the function is actually returning the **None** object. >>> print(add_two_numbers(5, 7)) None + + +# Assigning the function call to a variable and printing +# the variable will also show None. +>>> sum_without_return = add_two_numbers(5, 6) +>>> print(sum_without_return) +None ``` diff --git a/concepts/basics/introduction.md b/concepts/basics/introduction.md index 34e2a8804d7..dc062bc70da 100644 --- a/concepts/basics/introduction.md +++ b/concepts/basics/introduction.md @@ -50,7 +50,7 @@ Statements for the _body_ of the function begin on the line following `def` and ```python -# The body of a function is indented by 2 spaces, & prints the sum of the numbers. +# The body of this function is indented by 2 spaces,& prints the sum of the numbers. def add_two_numbers(number_one, number_two): total = number_one + number_two print(total) @@ -71,24 +71,55 @@ def add_two_numbers(number_one, number_two): IndentationError: unindent does not match any outer indentation level ``` -Functions explicitly return a value or object via the [`return`][return] keyword. -Functions that do not have an explicit `return` expression will _implicitly_ return [`None`][none]. + +Functions _explicitly_ return a value or object via the [`return`][return] keyword: + ```python -# Function definition on first line. +# Function definition on first line, explicit return used on final line. def add_two_numbers(number_one, number_two): - result = number_one + number_two - return result # Returns the sum of the numbers. + return number_one + number_two + +# Calling the function in the Python terminal returns the sum of the numbers. >>> add_two_numbers(3, 4) 7 -# This function will return None. +# Assigning the function call to a variable and printing +# the variable will also return the value. +>>> sum_with_return = add_two_numbers(5, 6) +>>> print(sum_with_return) +7 +``` + +Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object. +The details of `None` will be covered in a later exercise. +For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null: + + +```python +# This function does not have an explicit return. def add_two_numbers(number_one, number_two): result = number_one + number_two + +# Calling the function in the Python terminal appears +# to not return anything at all. +>>> add_two_numbers(5, 7) +>>> + + +# Using print() with the function call shows that +# the function is actually returning the **None** object. >>> print(add_two_numbers(5, 7)) None + + +# Assigning the function call to a variable and printing +# the variable will also show None. +>>> sum_without_return = add_two_numbers(5, 6) +>>> print(sum_without_return) +None ``` diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index 6c8de4e69f2..6c6812f6d89 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -87,24 +87,55 @@ def add_two_numbers(number_one, number_two): IndentationError: unindent does not match any outer indentation level ``` -Functions explicitly return a value or object via the [`return`][return] keyword. -Functions that do not have an _explicit_ `return` expression will _implicitly_ return [`None`][none]. + +Functions _explicitly_ return a value or object via the [`return`][return] keyword: ```python -# Function definition on first line. +# Function definition on first line, explicit return used on final line. def add_two_numbers(number_one, number_two): - result = number_one + number_two - return result # Returns the sum of the numbers. + return number_one + number_two + +# Calling the function in the Python terminal returns the sum of the numbers. >>> add_two_numbers(3, 4) 7 -# This function will return None. +# Assigning the function call to a variable and printing +# the variable will also return the value. +>>> sum_with_return = add_two_numbers(5, 6) +>>> print(sum_with_return) +7 +``` + + +Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object. +The details of `None` will be covered in a later exercise. +For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null: + + +```python +# This function does not have an explicit return. def add_two_numbers(number_one, number_two): result = number_one + number_two + +# Calling the function in the Python terminal appears +# to not return anything at all. +>>> add_two_numbers(5, 7) +>>> + + +# Using print() with the function call shows that +# the function is actually returning the **None** object. >>> print(add_two_numbers(5, 7)) None + + +# Assigning the function call to a variable and printing +# the variable will also show None. +>>> sum_without_return = add_two_numbers(5, 6) +>>> print(sum_without_return) +None ``` From 35ec95423683fded6a6f4a89b6782ea27dfa0e45 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 15 May 2024 20:52:12 +0200 Subject: [PATCH 105/277] Sync the `pig-latin` exercise's docs with the latest data. (#3694) --- .../practice/pig-latin/.docs/instructions.md | 18 ++++-------------- .../practice/pig-latin/.docs/introduction.md | 8 ++++++++ 2 files changed, 12 insertions(+), 14 deletions(-) create mode 100644 exercises/practice/pig-latin/.docs/introduction.md diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md index 032905aa9b0..571708814cd 100644 --- a/exercises/practice/pig-latin/.docs/instructions.md +++ b/exercises/practice/pig-latin/.docs/instructions.md @@ -1,20 +1,10 @@ # Instructions -Implement a program that translates from English to Pig Latin. +Your task is to translate text from English to Pig Latin using the following rules: -Pig Latin is a made-up children's language that's intended to be confusing. -It obeys a few simple rules (below), but when it's spoken quickly it's really difficult for non-children (and non-native speakers) to understand. - -- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word. +- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word (e.g. "apple" -> "appleay"). Please note that "xr" and "yt" at the beginning of a word make vowel sounds (e.g. "xray" -> "xrayay", "yttria" -> "yttriaay"). -- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word. +- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word (e.g. "pig" -> "igpay"). Consonant sounds can be made up of multiple consonants, such as the "ch" in "chair" or "st" in "stand" (e.g. "chair" -> "airchay"). -- **Rule 3**: If a word starts with a consonant sound followed by "qu", move it to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay"). +- **Rule 3**: If a word starts with a consonant sound followed by "qu", move them to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay"). - **Rule 4**: If a word contains a "y" after a consonant cluster or as the second letter in a two letter word it makes a vowel sound (e.g. "rhythm" -> "ythmrhay", "my" -> "ymay"). - -There are a few more rules for edge cases, and there are regional variants too. -Check the tests for all the details. - -Read more about [Pig Latin on Wikipedia][pig-latin]. - -[pig-latin]: https://en.wikipedia.org/wiki/Pig_latin diff --git a/exercises/practice/pig-latin/.docs/introduction.md b/exercises/practice/pig-latin/.docs/introduction.md new file mode 100644 index 00000000000..04baa47586f --- /dev/null +++ b/exercises/practice/pig-latin/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +Your parents have challenged you and your sibling to a game of two-on-two basketball. +Confident they'll win, they let you score the first couple of points, but then start taking over the game. +Needing a little boost, you start speaking in [Pig Latin][pig-latin], which is a made-up children's language that's difficult for non-children to understand. +This will give you the edge to prevail over your parents! + +[pig-latin]: https://en.wikipedia.org/wiki/Pig_latin From cbd4deb9cb05d115f2f0a858b141e662c2de006d Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Fri, 17 May 2024 03:56:47 +0200 Subject: [PATCH 106/277] Sync the `spiral-matrix` exercise's docs with the latest data. (#3695) --- .../practice/spiral-matrix/.docs/instructions.md | 2 +- .../practice/spiral-matrix/.docs/introduction.md | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 exercises/practice/spiral-matrix/.docs/introduction.md diff --git a/exercises/practice/spiral-matrix/.docs/instructions.md b/exercises/practice/spiral-matrix/.docs/instructions.md index ba99e12c731..01e8a77f808 100644 --- a/exercises/practice/spiral-matrix/.docs/instructions.md +++ b/exercises/practice/spiral-matrix/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Given the size, return a square matrix of numbers in spiral order. +Your task is to return a square matrix of a given size. The matrix should be filled with natural numbers, starting from 1 in the top-left corner, increasing in an inward, clockwise spiral order, like these examples: diff --git a/exercises/practice/spiral-matrix/.docs/introduction.md b/exercises/practice/spiral-matrix/.docs/introduction.md new file mode 100644 index 00000000000..25c7eb595aa --- /dev/null +++ b/exercises/practice/spiral-matrix/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +In a small village near an ancient forest, there was a legend of a hidden treasure buried deep within the woods. +Despite numerous attempts, no one had ever succeeded in finding it. +This was about to change, however, thanks to a young explorer named Elara. +She had discovered an old document containing instructions on how to locate the treasure. +Using these instructions, Elara was able to draw a map that revealed the path to the treasure. + +To her surprise, the path followed a peculiar clockwise spiral. +It was no wonder no one had been able to find the treasure before! +With the map in hand, Elara embarks on her journey to uncover the hidden treasure. From 2eede521e08dddc0c99435e771f0e0732d0852f0 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Fri, 17 May 2024 03:57:22 +0200 Subject: [PATCH 107/277] Sync the `anagram` exercise's docs with the latest data. (#3697) --- exercises/practice/anagram/.docs/instructions.md | 6 +++--- exercises/practice/anagram/.docs/introduction.md | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 exercises/practice/anagram/.docs/introduction.md diff --git a/exercises/practice/anagram/.docs/instructions.md b/exercises/practice/anagram/.docs/instructions.md index 7d1c8283ef9..a7298485b3f 100644 --- a/exercises/practice/anagram/.docs/instructions.md +++ b/exercises/practice/anagram/.docs/instructions.md @@ -1,9 +1,9 @@ # Instructions -An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`. -A word is not its own anagram: for example, `"stop"` is not an anagram of `"stop"`. +Your task is to, given a target word and a set of candidate words, to find the subset of the candidates that are anagrams of the target. -Given a target word and a set of candidate words, this exercise requests the anagram set: the subset of the candidates that are anagrams of the target. +An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`. +A word is _not_ its own anagram: for example, `"stop"` is not an anagram of `"stop"`. The target and candidates are words of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`). Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `StoP` is not an anagram of `sTOp`. diff --git a/exercises/practice/anagram/.docs/introduction.md b/exercises/practice/anagram/.docs/introduction.md new file mode 100644 index 00000000000..1acbdf00b05 --- /dev/null +++ b/exercises/practice/anagram/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +At a garage sale, you find a lovely vintage typewriter at a bargain price! +Excitedly, you rush home, insert a sheet of paper, and start typing away. +However, your excitement wanes when you examine the output: all words are garbled! +For example, it prints "stop" instead of "post" and "least" instead of "stale." +Carefully, you try again, but now it prints "spot" and "slate." +After some experimentation, you find there is a random delay before each letter is printed, which messes up the order. +You now understand why they sold it for so little money! + +You realize this quirk allows you to generate anagrams, which are words formed by rearranging the letters of another word. +Pleased with your finding, you spend the rest of the day generating hundreds of anagrams. From 46c5f7d7c28d7a9cae62ffa5edbb7d10d5b77d54 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Fri, 17 May 2024 03:58:21 +0200 Subject: [PATCH 108/277] Sync the `knapsack` exercise's docs with the latest data. (#3698) --- .../practice/knapsack/.docs/instructions.md | 20 +++++-------------- .../practice/knapsack/.docs/introduction.md | 8 ++++++++ 2 files changed, 13 insertions(+), 15 deletions(-) create mode 100644 exercises/practice/knapsack/.docs/introduction.md diff --git a/exercises/practice/knapsack/.docs/instructions.md b/exercises/practice/knapsack/.docs/instructions.md index fadcee1b182..3411db9886a 100644 --- a/exercises/practice/knapsack/.docs/instructions.md +++ b/exercises/practice/knapsack/.docs/instructions.md @@ -1,24 +1,15 @@ # Instructions -In this exercise, let's try to solve a classic problem. +Your task is to determine which items to take so that the total value of his selection is maximized, taking into account the knapsack's carrying capacity. -Bob is a thief. -After months of careful planning, he finally manages to crack the security systems of a high-class apartment. - -In front of him are many items, each with a value (v) and weight (w). -Bob, of course, wants to maximize the total value he can get; he would gladly take all of the items if he could. -However, to his horror, he realizes that the knapsack he carries with him can only hold so much weight (W). - -Given a knapsack with a specific carrying capacity (W), help Bob determine the maximum value he can get from the items in the house. -Note that Bob can take only one of each item. - -All values given will be strictly positive. Items will be represented as a list of items. Each item will have a weight and value. +All values given will be strictly positive. +Bob can take only one of each item. For example: -```none +```text Items: [ { "weight": 5, "value": 10 }, { "weight": 4, "value": 40 }, @@ -26,10 +17,9 @@ Items: [ { "weight": 4, "value": 50 } ] -Knapsack Limit: 10 +Knapsack Maximum Weight: 10 ``` For the above, the first item has weight 5 and value 10, the second item has weight 4 and value 40, and so on. - In this example, Bob should take the second and fourth item to maximize his value, which, in this case, is 90. He cannot get more than 90 as his knapsack has a weight limit of 10. diff --git a/exercises/practice/knapsack/.docs/introduction.md b/exercises/practice/knapsack/.docs/introduction.md new file mode 100644 index 00000000000..9b2bed8b4e9 --- /dev/null +++ b/exercises/practice/knapsack/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +Bob is a thief. +After months of careful planning, he finally manages to crack the security systems of a fancy store. + +In front of him are many items, each with a value and weight. +Bob would gladly take all of the items, but his knapsack can only hold so much weight. +Bob has to carefully consider which items to take so that the total value of his selection is maximized. From 657edf94f1f2a1d69ae8d19b08473d0d6eb5f5b5 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Fri, 17 May 2024 03:58:46 +0200 Subject: [PATCH 109/277] Sync the `kindergarten-garden` exercise's docs with the latest data. (#3699) --- .../kindergarten-garden/.docs/instructions.md | 32 +++++++++---------- .../kindergarten-garden/.docs/introduction.md | 6 ++++ 2 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 exercises/practice/kindergarten-garden/.docs/introduction.md diff --git a/exercises/practice/kindergarten-garden/.docs/instructions.md b/exercises/practice/kindergarten-garden/.docs/instructions.md index 472ee26f6c4..6fe11a58cec 100644 --- a/exercises/practice/kindergarten-garden/.docs/instructions.md +++ b/exercises/practice/kindergarten-garden/.docs/instructions.md @@ -1,16 +1,21 @@ # Instructions -Given a diagram, determine which plants each child in the kindergarten class is -responsible for. +Your task is to, given a diagram, determine which plants each child in the kindergarten class is responsible for. -The kindergarten class is learning about growing plants. -The teacher thought it would be a good idea to give them actual seeds, plant them in actual dirt, and grow actual plants. +There are 12 children in the class: + +- Alice, Bob, Charlie, David, Eve, Fred, Ginny, Harriet, Ileana, Joseph, Kincaid, and Larry. + +Four different types of seeds are planted: -They've chosen to grow grass, clover, radishes, and violets. +| Plant | Diagram encoding | +| ------ | ---------------- | +| Grass | G | +| Clover | C | +| Radish | R | +| Violet | V | -To this end, the children have put little cups along the window sills, and -planted one type of plant in each cup, choosing randomly from the available -types of seeds. +Each child gets four cups, two on each row: ```text [window][window][window] @@ -18,16 +23,9 @@ types of seeds. ........................ ``` -There are 12 children in the class: - -- Alice, Bob, Charlie, David, -- Eve, Fred, Ginny, Harriet, -- Ileana, Joseph, Kincaid, and Larry. - -Each child gets 4 cups, two on each row. -Their teacher assigns cups to the children alphabetically by their names. +Their teacher assigns cups to the children alphabetically by their names, which means that Alice comes first and Larry comes last. -The following diagram represents Alice's plants: +Here is an example diagram representing Alice's plants: ```text [window][window][window] diff --git a/exercises/practice/kindergarten-garden/.docs/introduction.md b/exercises/practice/kindergarten-garden/.docs/introduction.md new file mode 100644 index 00000000000..5ad97d23ecc --- /dev/null +++ b/exercises/practice/kindergarten-garden/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +The kindergarten class is learning about growing plants. +The teacher thought it would be a good idea to give the class seeds to plant and grow in the dirt. +To this end, the children have put little cups along the window sills and planted one type of plant in each cup. +The children got to pick their favorites from four available types of seeds: grass, clover, radishes, and violets. From 3f106bb185619aa9d06f683488b03fae9c2c5476 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 May 2024 18:59:23 -0700 Subject: [PATCH 110/277] Bump actions/checkout from 4.1.5 to 4.1.6 (#3700) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.5 to 4.1.6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/44c2b7a8a4ea60a981eaca3cf939b5f4305c123b...a5ac7e51b41094c92402da3b24376905380afc29) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 3268c168602..550e5839d22 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index ba6f296bbb6..1e86c156546 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index e191a970465..241ea7240f0 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - name: Run test-runner run: docker-compose run test-runner From c137fcff42fc30ea241ae60e3ae4f26146383748 Mon Sep 17 00:00:00 2001 From: Thijs De Meester Date: Tue, 21 May 2024 20:58:10 +0200 Subject: [PATCH 111/277] fix semantics to specify a ghost (#3702) --- .../concept/ghost-gobble-arcade-game/.docs/instructions.md | 2 +- exercises/concept/ghost-gobble-arcade-game/.meta/exemplar.py | 2 +- exercises/concept/ghost-gobble-arcade-game/arcade_game.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/concept/ghost-gobble-arcade-game/.docs/instructions.md b/exercises/concept/ghost-gobble-arcade-game/.docs/instructions.md index 4b748395642..04b0b51a423 100644 --- a/exercises/concept/ghost-gobble-arcade-game/.docs/instructions.md +++ b/exercises/concept/ghost-gobble-arcade-game/.docs/instructions.md @@ -8,7 +8,7 @@ You have four rules to implement, all related to the game states. ## 1. Define if Pac-Man eats a ghost -Define the `eat_ghost()` function that takes two parameters (_if Pac-Man has a power pellet active_ and _if Pac-Man is touching a ghost_) and returns a Boolean value if Pac-Man is able to eat the ghost. +Define the `eat_ghost()` function that takes two parameters (_if Pac-Man has a power pellet active_ and _if Pac-Man is touching a ghost_) and returns a Boolean value if Pac-Man is able to eat a ghost. The function should return `True` only if Pac-Man has a power pellet active and is touching a ghost. ```python diff --git a/exercises/concept/ghost-gobble-arcade-game/.meta/exemplar.py b/exercises/concept/ghost-gobble-arcade-game/.meta/exemplar.py index 1adb3e4579c..4de10a25d55 100644 --- a/exercises/concept/ghost-gobble-arcade-game/.meta/exemplar.py +++ b/exercises/concept/ghost-gobble-arcade-game/.meta/exemplar.py @@ -6,7 +6,7 @@ def eat_ghost(power_pellet_active, touching_ghost): :param power_pellet_active: bool - does the player have an active power pellet? :param touching_ghost: bool - is the player touching a ghost? - :return: bool - can the ghost be eaten? + :return: bool - can a ghost be eaten? """ return power_pellet_active and touching_ghost diff --git a/exercises/concept/ghost-gobble-arcade-game/arcade_game.py b/exercises/concept/ghost-gobble-arcade-game/arcade_game.py index c9807d23207..b2848e0c718 100644 --- a/exercises/concept/ghost-gobble-arcade-game/arcade_game.py +++ b/exercises/concept/ghost-gobble-arcade-game/arcade_game.py @@ -6,7 +6,7 @@ def eat_ghost(power_pellet_active, touching_ghost): :param power_pellet_active: bool - does the player have an active power pellet? :param touching_ghost: bool - is the player touching a ghost? - :return: bool - can the ghost be eaten? + :return: bool - can a ghost be eaten? """ pass From 272f1784d3e2664a4a49011fbc543c9d48a02995 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Fri, 24 May 2024 10:08:04 +0200 Subject: [PATCH 112/277] Sync the `bank-account` exercise's docs with the latest data. (#3705) --- .../bank-account/.docs/instructions.md | 12 +++++------ .../bank-account/.docs/introduction.md | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 exercises/practice/bank-account/.docs/introduction.md diff --git a/exercises/practice/bank-account/.docs/instructions.md b/exercises/practice/bank-account/.docs/instructions.md index f536fdbb735..0955520bbff 100644 --- a/exercises/practice/bank-account/.docs/instructions.md +++ b/exercises/practice/bank-account/.docs/instructions.md @@ -1,12 +1,10 @@ # Instructions -Simulate a bank account supporting opening/closing, withdrawals, and deposits of money. -Watch out for concurrent transactions! +Your task is to implement bank accounts supporting opening/closing, withdrawals, and deposits of money. -A bank account can be accessed in multiple ways. -Clients can make deposits and withdrawals using the internet, mobile phones, etc. -Shops can charge against the account. - -Create an account that can be accessed from multiple threads/processes (terminology depends on your programming language). +As bank accounts can be accessed in many different ways (internet, mobile phones, automatic charges), your bank software must allow accounts to be safely accessed from multiple threads/processes (terminology depends on your programming language) in parallel. +For example, there may be many deposits and withdrawals occurring in parallel; you need to ensure there is no [race conditions][wikipedia] between when you read the account balance and set the new balance. It should be possible to close an account; operations against a closed account must fail. + +[wikipedia]: https://en.wikipedia.org/wiki/Race_condition#In_software diff --git a/exercises/practice/bank-account/.docs/introduction.md b/exercises/practice/bank-account/.docs/introduction.md new file mode 100644 index 00000000000..650b5d9c46f --- /dev/null +++ b/exercises/practice/bank-account/.docs/introduction.md @@ -0,0 +1,20 @@ +# Introduction + +After years of filling out forms and waiting, you've finally acquired your banking license. +This means you are now officially eligible to open your own bank, hurray! + +Your first priority is to get the IT systems up and running. +After a day of hard work, you can already open and close accounts, as well as handle withdrawals and deposits. + +Since you couldn't be bothered writing tests, you invite some friends to help test the system. +However, after just five minutes, one of your friends claims they've lost money! +While you're confident your code is bug-free, you start looking through the logs to investigate. + +Ah yes, just as you suspected, your friend is at fault! +They shared their test credentials with another friend, and together they conspired to make deposits and withdrawals from the same account _in parallel_. +Who would do such a thing? + +While you argue that it's physically _impossible_ for someone to access their account in parallel, your friend smugly notifies you that the banking rules _require_ you to support this. +Thus, no parallel banking support, no go-live signal. +Sighing, you create a mental note to work on this tomorrow. +This will set your launch date back at _least_ one more day, but well... From b7cefd19e01f3deb887867f523125f73f4f32b92 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 28 May 2024 15:45:53 +0200 Subject: [PATCH 113/277] Sync the `pig-latin` exercise's docs with the latest data. (#3707) --- .../practice/pig-latin/.docs/instructions.md | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md index 571708814cd..6c843080d44 100644 --- a/exercises/practice/pig-latin/.docs/instructions.md +++ b/exercises/practice/pig-latin/.docs/instructions.md @@ -1,10 +1,46 @@ # Instructions -Your task is to translate text from English to Pig Latin using the following rules: - -- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word (e.g. "apple" -> "appleay"). - Please note that "xr" and "yt" at the beginning of a word make vowel sounds (e.g. "xray" -> "xrayay", "yttria" -> "yttriaay"). -- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word (e.g. "pig" -> "igpay"). - Consonant sounds can be made up of multiple consonants, such as the "ch" in "chair" or "st" in "stand" (e.g. "chair" -> "airchay"). -- **Rule 3**: If a word starts with a consonant sound followed by "qu", move them to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay"). -- **Rule 4**: If a word contains a "y" after a consonant cluster or as the second letter in a two letter word it makes a vowel sound (e.g. "rhythm" -> "ythmrhay", "my" -> "ymay"). +Your task is to translate text from English to Pig Latin. +The translation is defined using four rules, which look at the pattern of vowels and consonants at the beginning of a word. +These rules look at each word's use of vowels and consonants: + +- vowels: the letters `a`, `e`, `i`, `o`, and `u` +- consonants: the other 21 letters of the English alphabet + +## Rule 1 + +If a word begins with a vowel, or starts with `"xr"` or `"yt"`, add an `"ay"` sound to the end of the word. + +For example: + +- `"apple"` -> `"appleay"` (starts with vowel) +- `"xray"` -> `"xrayay"` (starts with `"xr"`) +- `"yttria"` -> `"yttriaay"` (starts with `"yt"`) + +## Rule 2 + +If a word begins with a one or more consonants, first move those consonants to the end of the word and then add an `"ay"` sound to the end of the word. + +For example: + +- `"pig"` -> `"igp"` -> `"igpay"` (starts with single consonant) +- `"chair"` -> `"airch"` -> `"airchay"` (starts with multiple consonants) +- `"thrush"` -> `"ushthr"` -> `"ushthray"` (starts with multiple consonants) + +## Rule 3 + +If a word starts with zero or more consonants followed by `"qu"`, first move those consonants (if any) and the `"qu"` part to the end of the word, and then add an `"ay"` sound to the end of the word. + +For example: + +- `"quick"` -> `"ickqu"` -> `"ay"` (starts with `"qu"`, no preceding consonants) +- `"square"` -> `"aresqu"` -> `"aresquay"` (starts with one consonant followed by `"qu`") + +## Rule 4 + +If a word starts with one or more consonants followed by `"y"`, first move the consonants preceding the `"y"`to the end of the word, and then add an `"ay"` sound to the end of the word. + +Some examples: + +- `"my"` -> `"ym"` -> `"ymay"` (starts with single consonant followed by `"y"`) +- `"rhythm"` -> `"ythmrh"` -> `"ythmrhay"` (starts with multiple consonants followed by `"y"`) From 1b262177a49daac97c5c1860e960d4eb2a175f04 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 4 Jun 2024 22:33:51 +0200 Subject: [PATCH 114/277] Sync the `meetup` exercise's docs with the latest data. (#3711) --- .../practice/meetup/.docs/instructions.md | 35 +++++-------------- .../practice/meetup/.docs/introduction.md | 29 +++++++++++++++ 2 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 exercises/practice/meetup/.docs/introduction.md diff --git a/exercises/practice/meetup/.docs/instructions.md b/exercises/practice/meetup/.docs/instructions.md index 0694ef583c2..000de2fd127 100644 --- a/exercises/practice/meetup/.docs/instructions.md +++ b/exercises/practice/meetup/.docs/instructions.md @@ -1,11 +1,10 @@ # Instructions -Recurring monthly meetups are generally scheduled on the given weekday of a given week each month. -In this exercise you will be given the recurring schedule, along with a month and year, and then asked to find the exact date of the meetup. +Your task is to find the exact date of a meetup, given a month, year, weekday and week. -For example a meetup might be scheduled on the _first Monday_ of every month. -You might then be asked to find the date that this meetup will happen in January 2018. -In other words, you need to determine the date of the first Monday of January 2018. +There are five week values to consider: `first`, `second`, `third`, `fourth`, `last`, `teenth`. + +For example, you might be asked to find the date for the meetup on the first Monday in January 2018 (January 1, 2018). Similarly, you might be asked to find: @@ -13,29 +12,13 @@ Similarly, you might be asked to find: - the teenth Wednesday of May 2020 (May 13, 2020) - the fourth Sunday of July 2021 (July 25, 2021) - the last Thursday of November 2022 (November 24, 2022) +- the teenth Saturday of August 1953 (August 15, 1953) -The descriptors you are expected to process are: `first`, `second`, `third`, `fourth`, `last`, `teenth`. - -Note that descriptor `teenth` is a made-up word. - -It refers to the seven numbers that end in '-teen' in English: 13, 14, 15, 16, 17, 18, and 19. -But general descriptions of dates use ordinal numbers, e.g. the _first_ Monday, the _third_ Tuesday. - -For the numbers ending in '-teen', that becomes: - -- 13th (thirteenth) -- 14th (fourteenth) -- 15th (fifteenth) -- 16th (sixteenth) -- 17th (seventeenth) -- 18th (eighteenth) -- 19th (nineteenth) +## Teenth -So there are seven numbers ending in '-teen'. -And there are also seven weekdays (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday). -Therefore, it is guaranteed that each day of the week (Monday, Tuesday, ...) will have exactly one numbered day ending with "teen" each month. +The teenth week refers to the seven days in a month that end in '-teenth' (13th, 14th, 15th, 16th, 17th, 18th and 19th). -If asked to find the teenth Saturday of August, 1953 (or, alternately the "Saturteenth" of August, 1953), we need to look at the calendar for August 1953: +If asked to find the teenth Saturday of August, 1953, we check its calendar: ```plaintext August 1953 @@ -48,4 +31,4 @@ Su Mo Tu We Th Fr Sa 30 31 ``` -The Saturday that has a number ending in '-teen' is August 15, 1953. +From this we find that the teenth Saturday is August 15, 1953. diff --git a/exercises/practice/meetup/.docs/introduction.md b/exercises/practice/meetup/.docs/introduction.md new file mode 100644 index 00000000000..29170ef1fda --- /dev/null +++ b/exercises/practice/meetup/.docs/introduction.md @@ -0,0 +1,29 @@ +# Introduction + +Every month, your partner meets up with their best friend. +Both of them have very busy schedules, making it challenging to find a suitable date! +Given your own busy schedule, your partner always double-checks potential meetup dates with you: + +- "Can I meet up on the first Friday of next month?" +- "What about the third Wednesday?" +- "Maybe the last Sunday?" + +In this month's call, your partner asked you this question: + +- "I'd like to meet up on the teenth Thursday; is that okay?" + +Confused, you ask what a "teenth" day is. +Your partner explains that a teenth day, a concept they made up, refers to the days in a month that end in '-teenth': + +- 13th (thirteenth) +- 14th (fourteenth) +- 15th (fifteenth) +- 16th (sixteenth) +- 17th (seventeenth) +- 18th (eighteenth) +- 19th (nineteenth) + +As there are also seven weekdays, it is guaranteed that each day of the week has _exactly one_ teenth day each month. + +Now that you understand the concept of a teenth day, you check your calendar. +You don't have anything planned on the teenth Thursday, so you happily confirm the date with your partner. From 367faf4c12c172bbd981d41f4426f1ae2f0b1f49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 06:57:06 -0700 Subject: [PATCH 115/277] Bump actions/checkout from 4.1.6 to 4.1.7 (#3714) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.6 to 4.1.7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/a5ac7e51b41094c92402da3b24376905380afc29...692973e3d937129bcbf40652eb9f2f61becf3332) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 550e5839d22..71dbc4356d2 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index 1e86c156546..f8d9227f3c0 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index 241ea7240f0..d57a2d290c2 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Run test-runner run: docker-compose run test-runner From be41e19ae2a8242c1c03a2a16b802da20f41081b Mon Sep 17 00:00:00 2001 From: Yuriy Syrovetskiy <63495+cblp@users.noreply.github.com> Date: Thu, 27 Jun 2024 22:58:58 +0300 Subject: [PATCH 116/277] Fix typo (#3720) --- exercises/concept/currency-exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/currency-exchange/exchange.py b/exercises/concept/currency-exchange/exchange.py index c7088f61835..b58df5cbe48 100644 --- a/exercises/concept/currency-exchange/exchange.py +++ b/exercises/concept/currency-exchange/exchange.py @@ -1,4 +1,4 @@ -"""Functions for calculating steps in exchaning currency. +"""Functions for calculating steps in exchanging currency. Python numbers documentation: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex From f3fdd2e014f6e3114feed22758588108a6228b6e Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:56:59 -0700 Subject: [PATCH 117/277] Updated tests.toml and regenerated test file for Custom-Set. (#3728) --- exercises/practice/custom-set/.meta/tests.toml | 3 +++ exercises/practice/custom-set/custom_set_test.py | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/exercises/practice/custom-set/.meta/tests.toml b/exercises/practice/custom-set/.meta/tests.toml index 8c450e0baf1..430c139e685 100644 --- a/exercises/practice/custom-set/.meta/tests.toml +++ b/exercises/practice/custom-set/.meta/tests.toml @@ -114,6 +114,9 @@ description = "Difference (or Complement) of a set is a set of all elements that [c5ac673e-d707-4db5-8d69-7082c3a5437e] description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of two non-empty sets is a set of elements that are only in the first set" +[20d0a38f-7bb7-4c4a-ac15-90c7392ecf2b] +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference removes all duplicates in the first set" + [c45aed16-5494-455a-9033-5d4c93589dc6] description = "Union returns a set of all elements in either set -> union of empty sets is an empty set" diff --git a/exercises/practice/custom-set/custom_set_test.py b/exercises/practice/custom-set/custom_set_test.py index edd76564131..80966272ee1 100644 --- a/exercises/practice/custom-set/custom_set_test.py +++ b/exercises/practice/custom-set/custom_set_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/custom-set/canonical-data.json -# File last updated on 2023-12-27 +# File last updated on 2024-07-08 import unittest @@ -196,6 +196,12 @@ def test_difference_of_two_non_empty_sets_is_a_set_of_elements_that_are_only_in_ expected = CustomSet([1, 3]) self.assertEqual(set1 - set2, expected) + def test_difference_removes_all_duplicates_in_the_first_set(self): + set1 = CustomSet([1, 1]) + set2 = CustomSet([1]) + expected = CustomSet() + self.assertEqual(set1 - set2, expected) + def test_union_of_empty_sets_is_an_empty_set(self): set1 = CustomSet() set2 = CustomSet() From fe28366c297d5d727a4dca7722c62b2245a556dd Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:57:17 -0700 Subject: [PATCH 118/277] synced tests.toml and regenerated tests for Protein-Translation. (#3731) --- exercises/practice/protein-translation/.meta/tests.toml | 4 ++++ .../protein-translation/protein_translation_test.py | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/exercises/practice/protein-translation/.meta/tests.toml b/exercises/practice/protein-translation/.meta/tests.toml index c083605251b..f9f13815909 100644 --- a/exercises/practice/protein-translation/.meta/tests.toml +++ b/exercises/practice/protein-translation/.meta/tests.toml @@ -88,6 +88,9 @@ description = "Translation stops if STOP codon in middle of three-codon sequence [2c2a2a60-401f-4a80-b977-e0715b23b93d] description = "Translation stops if STOP codon in middle of six-codon sequence" +[f6f92714-769f-4187-9524-e353e8a41a80] +description = "Sequence of two non-STOP codons does not translate to a STOP codon" + [1e75ea2a-f907-4994-ae5c-118632a1cb0f] description = "Non-existing codon can't translate" include = false @@ -95,6 +98,7 @@ include = false [9eac93f3-627a-4c90-8653-6d0a0595bc6f] description = "Unknown amino acids, not part of a codon, can't translate" include = false +reimplements = "1e75ea2a-f907-4994-ae5c-118632a1cb0f" [9d73899f-e68e-4291-b1e2-7bf87c00f024] description = "Incomplete RNA sequence can't translate" diff --git a/exercises/practice/protein-translation/protein_translation_test.py b/exercises/practice/protein-translation/protein_translation_test.py index 91e6324b6e3..03a20fa5012 100644 --- a/exercises/practice/protein-translation/protein_translation_test.py +++ b/exercises/practice/protein-translation/protein_translation_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/protein-translation/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2024-07-08 import unittest @@ -134,3 +134,8 @@ def test_translation_stops_if_stop_codon_in_middle_of_six_codon_sequence(self): value = "UGGUGUUAUUAAUGGUUU" expected = ["Tryptophan", "Cysteine", "Tyrosine"] self.assertEqual(proteins(value), expected) + + def test_sequence_of_two_non_stop_codons_does_not_translate_to_a_stop_codon(self): + value = "AUGAUG" + expected = ["Methionine", "Methionine"] + self.assertEqual(proteins(value), expected) From fd863a259b5ee75ae7a78ccc1928d6f4264cdf8f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:57:59 -0700 Subject: [PATCH 119/277] Synced tests.toml and regenerated test file for Roman-Numerals. (#3730) --- .../practice/roman-numerals/.meta/tests.toml | 33 ++++++++++--------- .../roman-numerals/roman_numerals_test.py | 5 ++- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/exercises/practice/roman-numerals/.meta/tests.toml b/exercises/practice/roman-numerals/.meta/tests.toml index ca142e9f915..709011b5528 100644 --- a/exercises/practice/roman-numerals/.meta/tests.toml +++ b/exercises/practice/roman-numerals/.meta/tests.toml @@ -30,6 +30,9 @@ description = "6 is VI" [ff3fb08c-4917-4aab-9f4e-d663491d083d] description = "9 is IX" +[6d1d82d5-bf3e-48af-9139-87d7165ed509] +description = "16 is XVI" + [2bda64ca-7d28-4c56-b08d-16ce65716cf6] description = "27 is XXVII" @@ -42,6 +45,9 @@ description = "49 is XLIX" [d5b283d4-455d-4e68-aacf-add6c4b51915] description = "59 is LIX" +[4465ffd5-34dc-44f3-ada5-56f5007b6dad] +description = "66 is LXVI" + [46b46e5b-24da-4180-bfe2-2ef30b39d0d0] description = "93 is XCIII" @@ -51,38 +57,35 @@ description = "141 is CXLI" [267f0207-3c55-459a-b81d-67cec7a46ed9] description = "163 is CLXIII" +[902ad132-0b4d-40e3-8597-ba5ed611dd8d] +description = "166 is CLXVI" + [cdb06885-4485-4d71-8bfb-c9d0f496b404] description = "402 is CDII" [6b71841d-13b2-46b4-ba97-dec28133ea80] description = "575 is DLXXV" +[dacb84b9-ea1c-4a61-acbb-ce6b36674906] +description = "666 is DCLXVI" + [432de891-7fd6-4748-a7f6-156082eeca2f] description = "911 is CMXI" [e6de6d24-f668-41c0-88d7-889c0254d173] description = "1024 is MXXIV" -[bb550038-d4eb-4be2-a9ce-f21961ac3bc6] -description = "3000 is MMM" - -[6d1d82d5-bf3e-48af-9139-87d7165ed509] -description = "16 is XVI" - -[4465ffd5-34dc-44f3-ada5-56f5007b6dad] -description = "66 is LXVI" - -[902ad132-0b4d-40e3-8597-ba5ed611dd8d] -description = "166 is CLXVI" - -[dacb84b9-ea1c-4a61-acbb-ce6b36674906] -description = "666 is DCLXVI" - [efbe1d6a-9f98-4eb5-82bc-72753e3ac328] description = "1666 is MDCLXVI" +[bb550038-d4eb-4be2-a9ce-f21961ac3bc6] +description = "3000 is MMM" + [3bc4b41c-c2e6-49d9-9142-420691504336] description = "3001 is MMMI" +[2f89cad7-73f6-4d1b-857b-0ef531f68b7e] +description = "3888 is MMMDCCCLXXXVIII" + [4e18e96b-5fbb-43df-a91b-9cb511fe0856] description = "3999 is MMMCMXCIX" diff --git a/exercises/practice/roman-numerals/roman_numerals_test.py b/exercises/practice/roman-numerals/roman_numerals_test.py index d2120f9b151..675bca0ea30 100644 --- a/exercises/practice/roman-numerals/roman_numerals_test.py +++ b/exercises/practice/roman-numerals/roman_numerals_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/roman-numerals/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2024-07-08 import unittest @@ -85,5 +85,8 @@ def test_3000_is_mmm(self): def test_3001_is_mmmi(self): self.assertEqual(roman(3001), "MMMI") + def test_3888_is_mmmdccclxxxviii(self): + self.assertEqual(roman(3888), "MMMDCCCLXXXVIII") + def test_3999_is_mmmcmxcix(self): self.assertEqual(roman(3999), "MMMCMXCIX") From 8cc210435a0b02252f3707bbcd58f6ababa4fc89 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:58:21 -0700 Subject: [PATCH 120/277] Synced metadata for Yacht. (#3729) --- exercises/practice/yacht/.meta/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/yacht/.meta/config.json b/exercises/practice/yacht/.meta/config.json index 78c685cb518..352e162cdfd 100644 --- a/exercises/practice/yacht/.meta/config.json +++ b/exercises/practice/yacht/.meta/config.json @@ -20,6 +20,6 @@ ] }, "blurb": "Score a single throw of dice in the game Yacht.", - "source": "James Kilfiger, using wikipedia", + "source": "James Kilfiger, using Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Yacht_(dice_game)" } From c5ca9e348768ae331d71a709d6bb55ad98af660d Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:58:51 -0700 Subject: [PATCH 121/277] [Exercise Sync] Docs and Metadata for Affine-Cipher & Alphametics. (#3721) * Synced docs for Affine-Cipher and Alphametics. * Alphametics metadata file. --- exercises/practice/affine-cipher/.docs/instructions.md | 4 ++-- exercises/practice/alphametics/.docs/instructions.md | 4 +--- exercises/practice/alphametics/.meta/config.json | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/exercises/practice/affine-cipher/.docs/instructions.md b/exercises/practice/affine-cipher/.docs/instructions.md index 26ce1534262..4eff918de78 100644 --- a/exercises/practice/affine-cipher/.docs/instructions.md +++ b/exercises/practice/affine-cipher/.docs/instructions.md @@ -18,10 +18,10 @@ E(x) = (ai + b) mod m Where: -- `i` is the letter's index from `0` to the length of the alphabet - 1 +- `i` is the letter's index from `0` to the length of the alphabet - 1. - `m` is the length of the alphabet. For the Roman alphabet `m` is `26`. -- `a` and `b` are integers which make the encryption key +- `a` and `b` are integers which make up the encryption key. Values `a` and `m` must be _coprime_ (or, _relatively prime_) for automatic decryption to succeed, i.e., they have number `1` as their only common factor (more information can be found in the [Wikipedia article about coprime integers][coprime-integers]). In case `a` is not coprime to `m`, your program should indicate that this is an error. diff --git a/exercises/practice/alphametics/.docs/instructions.md b/exercises/practice/alphametics/.docs/instructions.md index 649576ec7e4..ef2cbb4a712 100644 --- a/exercises/practice/alphametics/.docs/instructions.md +++ b/exercises/practice/alphametics/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Write a function to solve alphametics puzzles. +Given an alphametics puzzle, find the correct solution. [Alphametics][alphametics] is a puzzle where letters in words are replaced with numbers. @@ -26,6 +26,4 @@ This is correct because every letter is replaced by a different number and the w Each letter must represent a different digit, and the leading digit of a multi-digit number must not be zero. -Write a function to solve alphametics puzzles. - [alphametics]: https://en.wikipedia.org/wiki/Alphametics diff --git a/exercises/practice/alphametics/.meta/config.json b/exercises/practice/alphametics/.meta/config.json index 38bc5e3a56c..ba9badf8562 100644 --- a/exercises/practice/alphametics/.meta/config.json +++ b/exercises/practice/alphametics/.meta/config.json @@ -27,5 +27,5 @@ ] }, "test_runner": false, - "blurb": "Write a function to solve alphametics puzzles." + "blurb": "Given an alphametics puzzle, find the correct solution." } From 6ce2795f6c8063fbe5a03458029dcfd3f9651329 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:59:09 -0700 Subject: [PATCH 122/277] Synced docs for Bank-Account and Darts. (#3722) --- exercises/practice/bank-account/.docs/instructions.md | 2 +- exercises/practice/darts/.docs/instructions.md | 4 ++-- exercises/practice/darts/.meta/config.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/practice/bank-account/.docs/instructions.md b/exercises/practice/bank-account/.docs/instructions.md index 0955520bbff..7398fbea188 100644 --- a/exercises/practice/bank-account/.docs/instructions.md +++ b/exercises/practice/bank-account/.docs/instructions.md @@ -3,7 +3,7 @@ Your task is to implement bank accounts supporting opening/closing, withdrawals, and deposits of money. As bank accounts can be accessed in many different ways (internet, mobile phones, automatic charges), your bank software must allow accounts to be safely accessed from multiple threads/processes (terminology depends on your programming language) in parallel. -For example, there may be many deposits and withdrawals occurring in parallel; you need to ensure there is no [race conditions][wikipedia] between when you read the account balance and set the new balance. +For example, there may be many deposits and withdrawals occurring in parallel; you need to ensure there are no [race conditions][wikipedia] between when you read the account balance and set the new balance. It should be possible to close an account; operations against a closed account must fail. diff --git a/exercises/practice/darts/.docs/instructions.md b/exercises/practice/darts/.docs/instructions.md index 5e57a860af8..6518201c77f 100644 --- a/exercises/practice/darts/.docs/instructions.md +++ b/exercises/practice/darts/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Write a function that returns the earned points in a single toss of a Darts game. +Calculate the points scored in a single toss of a Darts game. [Darts][darts] is a game where players throw darts at a [target][darts-target]. @@ -16,7 +16,7 @@ In our particular instance of the game, the target rewards 4 different amounts o The outer circle has a radius of 10 units (this is equivalent to the total radius for the entire target), the middle circle a radius of 5 units, and the inner circle a radius of 1. Of course, they are all centered at the same point — that is, the circles are [concentric][] defined by the coordinates (0, 0). -Write a function that given a point in the target (defined by its [Cartesian coordinates][cartesian-coordinates] `x` and `y`, where `x` and `y` are [real][real-numbers]), returns the correct amount earned by a dart landing at that point. +Given a point in the target (defined by its [Cartesian coordinates][cartesian-coordinates] `x` and `y`, where `x` and `y` are [real][real-numbers]), calculate the correct score earned by a dart landing at that point. ## Credit diff --git a/exercises/practice/darts/.meta/config.json b/exercises/practice/darts/.meta/config.json index a306b38c150..cc71df1a7cd 100644 --- a/exercises/practice/darts/.meta/config.json +++ b/exercises/practice/darts/.meta/config.json @@ -24,6 +24,6 @@ ".meta/example.py" ] }, - "blurb": "Write a function that returns the earned points in a single toss of a Darts game.", + "blurb": "Calculate the points scored in a single toss of a Darts game.", "source": "Inspired by an exercise created by a professor Della Paolera in Argentina" } From 16adf4eaf34e73f3c7716ad90abcdefab60e23ae Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:59:26 -0700 Subject: [PATCH 123/277] Synced docs for Flatten-array and Go-Counting. (#3723) --- exercises/practice/flatten-array/.docs/instructions.md | 2 +- exercises/practice/go-counting/.docs/instructions.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/practice/flatten-array/.docs/instructions.md b/exercises/practice/flatten-array/.docs/instructions.md index 51bea67909f..89dacfa327a 100644 --- a/exercises/practice/flatten-array/.docs/instructions.md +++ b/exercises/practice/flatten-array/.docs/instructions.md @@ -2,7 +2,7 @@ Take a nested list and return a single flattened list with all values except nil/null. -The challenge is to write a function that accepts an arbitrarily-deep nested list-like structure and returns a flattened structure without any nil/null values. +The challenge is to take an arbitrarily-deep nested list-like structure and produce a flattened structure without any nil/null values. For example: diff --git a/exercises/practice/go-counting/.docs/instructions.md b/exercises/practice/go-counting/.docs/instructions.md index 15fdab20ba0..e4b143f2dac 100644 --- a/exercises/practice/go-counting/.docs/instructions.md +++ b/exercises/practice/go-counting/.docs/instructions.md @@ -5,10 +5,10 @@ Count the scored points on a Go board. In the game of go (also known as baduk, igo, cờ vây and wéiqí) points are gained by completely encircling empty intersections with your stones. The encircled intersections of a player are known as its territory. -Write a function that determines the territory of each player. +Calculate the territory of each player. You may assume that any stones that have been stranded in enemy territory have already been taken off the board. -Write a function that determines the territory which includes a specified coordinate. +Determine the territory which includes a specified coordinate. Multiple empty intersections may be encircled at once and for encircling only horizontal and vertical neighbors count. In the following diagram the stones which matter are marked "O" and the stones that don't are marked "I" (ignored). @@ -25,7 +25,7 @@ Empty spaces represent empty intersections. To be more precise an empty intersection is part of a player's territory if all of its neighbors are either stones of that player or empty intersections that are part of that player's territory. -For more information see [wikipedia][go-wikipedia] or [Sensei's Library][go-sensei]. +For more information see [Wikipedia][go-wikipedia] or [Sensei's Library][go-sensei]. [go-wikipedia]: https://en.wikipedia.org/wiki/Go_%28game%29 [go-sensei]: https://senseis.xmp.net/ From 0443b28c4e2c75ca1430e195e04006851789aee6 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 14:59:45 -0700 Subject: [PATCH 124/277] Synced docs for Killer-Sudoku-Helper and Matching-Brackets. (#3724) --- .../.docs/instructions.md | 28 +++++++++++++++++-- .../matching-brackets/.docs/instructions.md | 3 +- .../matching-brackets/.docs/introduction.md | 8 ++++++ 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 exercises/practice/matching-brackets/.docs/introduction.md diff --git a/exercises/practice/killer-sudoku-helper/.docs/instructions.md b/exercises/practice/killer-sudoku-helper/.docs/instructions.md index 8bb05a3a772..fdafdca8fbe 100644 --- a/exercises/practice/killer-sudoku-helper/.docs/instructions.md +++ b/exercises/practice/killer-sudoku-helper/.docs/instructions.md @@ -20,7 +20,17 @@ In a 3-digit cage with a sum of 7, there is only one valid combination: 124. - 1 + 2 + 4 = 7 - Any other combination that adds up to 7, e.g. 232, would violate the rule of not repeating digits within a cage. -![Sudoku grid, with three killer cages that are marked as grouped together. The first killer cage is in the 3×3 box in the top left corner of the grid. The middle column of that box forms the cage, with the followings cells from top to bottom: first cell contains a 1 and a pencil mark of 7, indicating a cage sum of 7, second cell contains a 2, third cell contains a 5. The numbers are highlighted in red to indicate a mistake. The second killer cage is in the central 3×3 box of the grid. The middle column of that box forms the cage, with the followings cells from top to bottom: first cell contains a 1 and a pencil mark of 7, indicating a cage sum of 7, second cell contains a 2, third cell contains a 4. None of the numbers in this cage are highlighted and therefore don't contain any mistakes. The third killer cage follows the outside corner of the central 3×3 box of the grid. It is made up of the following three cells: the top left cell of the cage contains a 2, highlighted in red, and a cage sum of 7. The top right cell of the cage contains a 3. The bottom right cell of the cage contains a 2, highlighted in red. All other cells are empty.][one-solution-img] +![Sudoku grid, with three killer cages that are marked as grouped together. +The first killer cage is in the 3×3 box in the top left corner of the grid. +The middle column of that box forms the cage, with the followings cells from top to bottom: first cell contains a 1 and a pencil mark of 7, indicating a cage sum of 7, second cell contains a 2, third cell contains a 5. +The numbers are highlighted in red to indicate a mistake. +The second killer cage is in the central 3×3 box of the grid. +The middle column of that box forms the cage, with the followings cells from top to bottom: first cell contains a 1 and a pencil mark of 7, indicating a cage sum of 7, second cell contains a 2, third cell contains a 4. +None of the numbers in this cage are highlighted and therefore don't contain any mistakes. +The third killer cage follows the outside corner of the central 3×3 box of the grid. +It is made up of the following three cells: the top left cell of the cage contains a 2, highlighted in red, and a cage sum of 7. +The top right cell of the cage contains a 3. +The bottom right cell of the cage contains a 2, highlighted in red. All other cells are empty.][one-solution-img] ## Example 2: Cage with several combinations @@ -31,7 +41,13 @@ In a 2-digit cage with a sum 10, there are 4 possible combinations: - 37 - 46 -![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled. Each continguous two rows form a killer cage and are marked as grouped together. From top to bottom: first group is a cell with value 1 and a pencil mark indicating a cage sum of 10, cell with value 9. Second group is a cell with value 2 and a pencil mark of 10, cell with value 8. Third group is a cell with value 3 and a pencil mark of 10, cell with value 7. Fourth group is a cell with value 4 and a pencil mark of 10, cell with value 6. The last cell in the column is empty.][four-solutions-img] +![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled. +Each continguous two rows form a killer cage and are marked as grouped together. +From top to bottom: first group is a cell with value 1 and a pencil mark indicating a cage sum of 10, cell with value 9. +Second group is a cell with value 2 and a pencil mark of 10, cell with value 8. +Third group is a cell with value 3 and a pencil mark of 10, cell with value 7. +Fourth group is a cell with value 4 and a pencil mark of 10, cell with value 6. +The last cell in the column is empty.][four-solutions-img] ## Example 3: Cage with several combinations that is restricted @@ -42,7 +58,13 @@ In a 2-digit cage with a sum 10, where the column already contains a 1 and a 4, 19 and 46 are not possible due to the 1 and 4 in the column according to standard Sudoku rules. -![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled. The first row contains a 4, the second is empty, and the third contains a 1. The 1 is highlighted in red to indicate a mistake. The last 6 rows in the column form killer cages of two cells each. From top to bottom: first group is a cell with value 2 and a pencil mark indicating a cage sum of 10, cell with value 8. Second group is a cell with value 3 and a pencil mark of 10, cell with value 7. Third group is a cell with value 1, highlighted in red, and a pencil mark of 10, cell with value 9.][not-possible-img] +![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled. +The first row contains a 4, the second is empty, and the third contains a 1. +The 1 is highlighted in red to indicate a mistake. +The last 6 rows in the column form killer cages of two cells each. +From top to bottom: first group is a cell with value 2 and a pencil mark indicating a cage sum of 10, cell with value 8. +Second group is a cell with value 3 and a pencil mark of 10, cell with value 7. +Third group is a cell with value 1, highlighted in red, and a pencil mark of 10, cell with value 9.][not-possible-img] ## Trying it yourself diff --git a/exercises/practice/matching-brackets/.docs/instructions.md b/exercises/practice/matching-brackets/.docs/instructions.md index 544daa968da..ea170842326 100644 --- a/exercises/practice/matching-brackets/.docs/instructions.md +++ b/exercises/practice/matching-brackets/.docs/instructions.md @@ -1,4 +1,5 @@ # Instructions Given a string containing brackets `[]`, braces `{}`, parentheses `()`, or any combination thereof, verify that any and all pairs are matched and nested correctly. -The string may also contain other characters, which for the purposes of this exercise should be ignored. +Any other characters should be ignored. +For example, `"{what is (42)}?"` is balanced and `"[text}"` is not. diff --git a/exercises/practice/matching-brackets/.docs/introduction.md b/exercises/practice/matching-brackets/.docs/introduction.md new file mode 100644 index 00000000000..0618221b21e --- /dev/null +++ b/exercises/practice/matching-brackets/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +You're given the opportunity to write software for the Bracketeer™, an ancient but powerful mainframe. +The software that runs on it is written in a proprietary language. +Much of its syntax is familiar, but you notice _lots_ of brackets, braces and parentheses. +Despite the Bracketeer™ being powerful, it lacks flexibility. +If the source code has any unbalanced brackets, braces or parentheses, the Bracketeer™ crashes and must be rebooted. +To avoid such a scenario, you start writing code that can verify that brackets, braces, and parentheses are balanced before attempting to run it on the Bracketeer™. From 222986ec167edb4779f476bb94c7c48eb4fcbfc5 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 15:00:29 -0700 Subject: [PATCH 125/277] Synced Pascals-Triangle and Pig-Latin docs. (#3725) --- .../pascals-triangle/.docs/instructions.md | 27 ++++++++++++++++--- .../pascals-triangle/.docs/introduction.md | 22 +++++++++++++++ .../practice/pig-latin/.docs/instructions.md | 4 +-- 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 exercises/practice/pascals-triangle/.docs/introduction.md diff --git a/exercises/practice/pascals-triangle/.docs/instructions.md b/exercises/practice/pascals-triangle/.docs/instructions.md index f556785931c..0f58f006968 100644 --- a/exercises/practice/pascals-triangle/.docs/instructions.md +++ b/exercises/practice/pascals-triangle/.docs/instructions.md @@ -1,8 +1,20 @@ # Instructions -Compute Pascal's triangle up to a given number of rows. +Your task is to output the first N rows of Pascal's triangle. -In Pascal's Triangle each number is computed by adding the numbers to the right and left of the current position in the previous row. +[Pascal's triangle][wikipedia] is a triangular array of positive integers. + +In Pascal's triangle, the number of values in a row is equal to its row number (which starts at one). +Therefore, the first row has one value, the second row has two values, and so on. + +The first (topmost) row has a single value: `1`. +Subsequent rows' values are computed by adding the numbers directly to the right and left of the current position in the previous row. + +If the previous row does _not_ have a value to the left or right of the current position (which only happens for the leftmost and rightmost positions), treat that position's value as zero (effectively "ignoring" it in the summation). + +## Example + +Let's look at the first 5 rows of Pascal's Triangle: ```text 1 @@ -10,5 +22,14 @@ In Pascal's Triangle each number is computed by adding the numbers to the right 1 2 1 1 3 3 1 1 4 6 4 1 -# ... etc ``` + +The topmost row has one value, which is `1`. + +The leftmost and rightmost values have only one preceding position to consider, which is the position to its right respectively to its left. +With the topmost value being `1`, it follows from this that all the leftmost and rightmost values are also `1`. + +The other values all have two positions to consider. +For example, the fifth row's (`1 4 6 4 1`) middle value is `6`, as the values to its left and right in the preceding row are `3` and `3`: + +[wikipedia]: https://en.wikipedia.org/wiki/Pascal%27s_triangle diff --git a/exercises/practice/pascals-triangle/.docs/introduction.md b/exercises/practice/pascals-triangle/.docs/introduction.md new file mode 100644 index 00000000000..60b8ec30dc8 --- /dev/null +++ b/exercises/practice/pascals-triangle/.docs/introduction.md @@ -0,0 +1,22 @@ +# Introduction + +With the weather being great, you're not looking forward to spending an hour in a classroom. +Annoyed, you enter the class room, where you notice a strangely satisfying triangle shape on the blackboard. +Whilst waiting for your math teacher to arrive, you can't help but notice some patterns in the triangle: the outer values are all ones, each subsequent row has one more value than its previous row and the triangle is symmetrical. +Weird! + +Not long after you sit down, your teacher enters the room and explains that this triangle is the famous [Pascal's triangle][wikipedia]. + +Over the next hour, your teacher reveals some amazing things hidden in this triangle: + +- It can be used to compute how many ways you can pick K elements from N values. +- It contains the Fibonacci sequence. +- If you color odd and even numbers differently, you get a beautiful pattern called the [Sierpiński triangle][wikipedia-sierpinski-triangle]. + +The teacher implores you and your classmates to lookup other uses, and assures you that there are lots more! +At that moment, the school bell rings. +You realize that for the past hour, you were completely absorbed in learning about Pascal's triangle. +You quickly grab your laptop from your bag and go outside, ready to enjoy both the sunshine _and_ the wonders of Pascal's triangle. + +[wikipedia]: https://en.wikipedia.org/wiki/Pascal%27s_triangle +[wikipedia-sierpinski-triangle]: https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md index 6c843080d44..a9645ac236f 100644 --- a/exercises/practice/pig-latin/.docs/instructions.md +++ b/exercises/practice/pig-latin/.docs/instructions.md @@ -19,7 +19,7 @@ For example: ## Rule 2 -If a word begins with a one or more consonants, first move those consonants to the end of the word and then add an `"ay"` sound to the end of the word. +If a word begins with one or more consonants, first move those consonants to the end of the word and then add an `"ay"` sound to the end of the word. For example: @@ -33,7 +33,7 @@ If a word starts with zero or more consonants followed by `"qu"`, first move tho For example: -- `"quick"` -> `"ickqu"` -> `"ay"` (starts with `"qu"`, no preceding consonants) +- `"quick"` -> `"ickqu"` -> `"ickquay"` (starts with `"qu"`, no preceding consonants) - `"square"` -> `"aresqu"` -> `"aresquay"` (starts with one consonant followed by `"qu`") ## Rule 4 From de75875626f447d8f488b0f009869855e0bbbdf9 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 15:00:48 -0700 Subject: [PATCH 126/277] Synced docs for poker and Resistor-color-duo. (#3726) --- exercises/practice/poker/.docs/instructions.md | 2 +- exercises/practice/resistor-color-duo/.docs/instructions.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/poker/.docs/instructions.md b/exercises/practice/poker/.docs/instructions.md index 492fc4c9e00..107cd49d66b 100644 --- a/exercises/practice/poker/.docs/instructions.md +++ b/exercises/practice/poker/.docs/instructions.md @@ -2,6 +2,6 @@ Pick the best hand(s) from a list of poker hands. -See [wikipedia][poker-hands] for an overview of poker hands. +See [Wikipedia][poker-hands] for an overview of poker hands. [poker-hands]: https://en.wikipedia.org/wiki/List_of_poker_hands diff --git a/exercises/practice/resistor-color-duo/.docs/instructions.md b/exercises/practice/resistor-color-duo/.docs/instructions.md index bdcd549b1a2..18ee4078bbb 100644 --- a/exercises/practice/resistor-color-duo/.docs/instructions.md +++ b/exercises/practice/resistor-color-duo/.docs/instructions.md @@ -29,5 +29,5 @@ The band colors are encoded as follows: - White: 9 From the example above: -brown-green should return 15 +brown-green should return 15, and brown-green-violet should return 15 too, ignoring the third color. From 5e4dc23cd665b5146f31edad967cab9d1508d3a3 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 15:01:05 -0700 Subject: [PATCH 127/277] Synced docs for Space-Age and Zebra-Puzzle. (#3727) --- .../practice/space-age/.docs/instructions.md | 31 ++++++++++--------- .../practice/space-age/.docs/introduction.md | 20 ++++++++++++ .../zebra-puzzle/.docs/instructions.md | 18 +++++------ .../zebra-puzzle/.docs/introduction.md | 2 +- 4 files changed, 47 insertions(+), 24 deletions(-) create mode 100644 exercises/practice/space-age/.docs/introduction.md diff --git a/exercises/practice/space-age/.docs/instructions.md b/exercises/practice/space-age/.docs/instructions.md index fe938cc09e1..f23b5e2c1fe 100644 --- a/exercises/practice/space-age/.docs/instructions.md +++ b/exercises/practice/space-age/.docs/instructions.md @@ -1,25 +1,28 @@ # Instructions -Given an age in seconds, calculate how old someone would be on: +Given an age in seconds, calculate how old someone would be on a planet in our Solar System. -- Mercury: orbital period 0.2408467 Earth years -- Venus: orbital period 0.61519726 Earth years -- Earth: orbital period 1.0 Earth years, 365.25 Earth days, or 31557600 seconds -- Mars: orbital period 1.8808158 Earth years -- Jupiter: orbital period 11.862615 Earth years -- Saturn: orbital period 29.447498 Earth years -- Uranus: orbital period 84.016846 Earth years -- Neptune: orbital period 164.79132 Earth years +One Earth year equals 365.25 Earth days, or 31,557,600 seconds. +If you were told someone was 1,000,000,000 seconds old, their age would be 31.69 Earth-years. -So if you were told someone were 1,000,000,000 seconds old, you should -be able to say that they're 31.69 Earth-years old. +For the other planets, you have to account for their orbital period in Earth Years: -If you're wondering why Pluto didn't make the cut, go watch [this YouTube video][pluto-video]. +| Planet | Orbital period in Earth Years | +| ------- | ----------------------------- | +| Mercury | 0.2408467 | +| Venus | 0.61519726 | +| Earth | 1.0 | +| Mars | 1.8808158 | +| Jupiter | 11.862615 | +| Saturn | 29.447498 | +| Uranus | 84.016846 | +| Neptune | 164.79132 | -Note: The actual length of one complete orbit of the Earth around the sun is closer to 365.256 days (1 sidereal year). +~~~~exercism/note +The actual length of one complete orbit of the Earth around the sun is closer to 365.256 days (1 sidereal year). The Gregorian calendar has, on average, 365.2425 days. While not entirely accurate, 365.25 is the value used in this exercise. See [Year on Wikipedia][year] for more ways to measure a year. -[pluto-video]: https://www.youtube.com/watch?v=Z_2gbGXzFbs [year]: https://en.wikipedia.org/wiki/Year#Summary +~~~~ diff --git a/exercises/practice/space-age/.docs/introduction.md b/exercises/practice/space-age/.docs/introduction.md new file mode 100644 index 00000000000..014d78857c7 --- /dev/null +++ b/exercises/practice/space-age/.docs/introduction.md @@ -0,0 +1,20 @@ +# Introduction + +The year is 2525 and you've just embarked on a journey to visit all planets in the Solar System (Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus and Neptune). +The first stop is Mercury, where customs require you to fill out a form (bureaucracy is apparently _not_ Earth-specific). +As you hand over the form to the customs officer, they scrutinize it and frown. +"Do you _really_ expect me to believe you're just 50 years old? +You must be closer to 200 years old!" + +Amused, you wait for the customs officer to start laughing, but they appear to be dead serious. +You realize that you've entered your age in _Earth years_, but the officer expected it in _Mercury years_! +As Mercury's orbital period around the sun is significantly shorter than Earth, you're actually a lot older in Mercury years. +After some quick calculations, you're able to provide your age in Mercury Years. +The customs officer smiles, satisfied, and waves you through. +You make a mental note to pre-calculate your planet-specific age _before_ future customs checks, to avoid such mix-ups. + +~~~~exercism/note +If you're wondering why Pluto didn't make the cut, go watch [this YouTube video][pluto-video]. + +[pluto-video]: https://www.youtube.com/watch?v=Z_2gbGXzFbs +~~~~ diff --git a/exercises/practice/zebra-puzzle/.docs/instructions.md b/exercises/practice/zebra-puzzle/.docs/instructions.md index c666e33cb3f..aedce9b25e7 100644 --- a/exercises/practice/zebra-puzzle/.docs/instructions.md +++ b/exercises/practice/zebra-puzzle/.docs/instructions.md @@ -12,20 +12,20 @@ The following 15 statements are all known to be true: 1. There are five houses. 2. The Englishman lives in the red house. 3. The Spaniard owns the dog. -4. Coffee is drunk in the green house. +4. The person in the green house drinks coffee. 5. The Ukrainian drinks tea. 6. The green house is immediately to the right of the ivory house. -7. The Old Gold smoker owns snails. -8. Kools are smoked in the yellow house. -9. Milk is drunk in the middle house. +7. The snail owner likes to go dancing. +8. The person in the yellow house is a painter. +9. The person in the middle house drinks milk. 10. The Norwegian lives in the first house. -11. The man who smokes Chesterfields lives in the house next to the man with the fox. -12. Kools are smoked in the house next to the house where the horse is kept. -13. The Lucky Strike smoker drinks orange juice. -14. The Japanese smokes Parliaments. +11. The person who enjoys reading lives in the house next to the person with the fox. +12. The painter's house is next to the house with the horse. +13. The person who plays football drinks orange juice. +14. The Japanese person plays chess. 15. The Norwegian lives next to the blue house. -Additionally, each of the five houses is painted a different color, and their inhabitants are of different national extractions, own different pets, drink different beverages and smoke different brands of cigarettes. +Additionally, each of the five houses is painted a different color, and their inhabitants are of different national extractions, own different pets, drink different beverages and engage in different hobbies. ~~~~exercism/note There are 24 billion (5!⁵ = 24,883,200,000) possible solutions, so try ruling out as many solutions as possible. diff --git a/exercises/practice/zebra-puzzle/.docs/introduction.md b/exercises/practice/zebra-puzzle/.docs/introduction.md index 33d688fd51b..bbcaa6fd203 100644 --- a/exercises/practice/zebra-puzzle/.docs/introduction.md +++ b/exercises/practice/zebra-puzzle/.docs/introduction.md @@ -1,7 +1,7 @@ # Introduction The Zebra Puzzle is a famous logic puzzle in which there are five houses, each painted a different color. -The houses have different inhabitants, who have different nationalities, own different pets, drink different beverages and smoke different brands of cigarettes. +The houses have different inhabitants, who have different nationalities, own different pets, drink different beverages and enjoy different hobbies. To help you solve the puzzle, you're given 15 statements describing the solution. However, only by combining the information in _all_ statements will you be able to find the solution to the puzzle. From e407e5df7871cdcfa48f48b29e158199fb1f4d4e Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 15:34:05 -0700 Subject: [PATCH 128/277] [Guido's Gorgeous Lasagna] Update introduction.md (#3732) Corrected example/sum on line 107 to 11. --- exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index 6c6812f6d89..c3fe37541ed 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -104,7 +104,7 @@ def add_two_numbers(number_one, number_two): # the variable will also return the value. >>> sum_with_return = add_two_numbers(5, 6) >>> print(sum_with_return) -7 +11 ``` From c7843f557d4f3b3faf622aef7a0e5764bb8dc20b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 15:35:56 -0700 Subject: [PATCH 129/277] [Basics] Update about.md (#3733) Corrected sum in example on line 155. --- concepts/basics/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/basics/about.md b/concepts/basics/about.md index dd636219abd..ef873ce418f 100644 --- a/concepts/basics/about.md +++ b/concepts/basics/about.md @@ -152,7 +152,7 @@ def add_two_numbers(number_one, number_two): # the variable will also return the value. >>> sum_with_return = add_two_numbers(5, 6) >>> print(sum_with_return) -7 +11 ``` Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object. From 47e7837b61d2144bc1e212381b49642e3326ce7e Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 16:05:19 -0700 Subject: [PATCH 130/277] [POV] Update instructions.append.md & Remove Error Message Examples (#3734) --- exercises/practice/pov/.docs/instructions.append.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/exercises/practice/pov/.docs/instructions.append.md b/exercises/practice/pov/.docs/instructions.append.md index 83ab12475c3..024842736aa 100644 --- a/exercises/practice/pov/.docs/instructions.append.md +++ b/exercises/practice/pov/.docs/instructions.append.md @@ -6,12 +6,4 @@ Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tuto This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" multiple `ValueErrors` if the `Tree()` class is passed a tree that cannot be reoriented, or a path cannot be found between a `start node` and an `end node`. The tests will only pass if you both `raise` the `exception` and include a message with it. -To raise a `ValueError` with a message, write the message as an argument to the `exception` type: - -```python -# when a tree cannot be oriented to a new node POV -raise ValueError("Tree could not be reoriented") - -#when a path cannot be found between a start and end node on the tree. -raise ValueError("No path found") -``` \ No newline at end of file +Please check the tests and their expected results carefully. From c3278e145868815f4dd257aab268d1adddbf3167 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 8 Jul 2024 16:20:26 -0700 Subject: [PATCH 131/277] [Update DnD Character] Add Missing Function to Stub (#3735) Per [this discussion](https://forum.exercism.org/t/d-d-character-test-bug/11237/3) on the forum. Decided to amend the stub, as it was the easiest an least confusing solution. --- exercises/practice/dnd-character/dnd_character.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/exercises/practice/dnd-character/dnd_character.py b/exercises/practice/dnd-character/dnd_character.py index f8ecd30bb88..1d310dde81e 100644 --- a/exercises/practice/dnd-character/dnd_character.py +++ b/exercises/practice/dnd-character/dnd_character.py @@ -1,3 +1,6 @@ class Character: def __init__(self): pass + +def modifier(value): + pass From bce0ee289babe55cd3846beb44ce41506c42dfb7 Mon Sep 17 00:00:00 2001 From: qadzek <84473512+qadzek@users.noreply.github.com> Date: Wed, 10 Jul 2024 00:55:36 +0200 Subject: [PATCH 132/277] [Lists] Fix typo (#3737) --- concepts/lists/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/lists/about.md b/concepts/lists/about.md index 657cd43ff6e..f7d4054eef0 100644 --- a/concepts/lists/about.md +++ b/concepts/lists/about.md @@ -296,7 +296,7 @@ Assigning a `list` object to a new variable _name_ **does not copy the `list` ob Any change made to the elements in the `list` under the _new_ name _impact the original_. -Making a `shallow_copy` via `list.copy()` or slice will avoid this first-leve referencing complication. +Making a `shallow_copy` via `list.copy()` or slice will avoid this first-level referencing complication. A `shallow_copy` will create a new `list` object, but **will not** create new objects for the contained list _elements_. This type of copy will usually be enough for you to add or remove items from the two `list` objects independently, and effectively have two "separate" lists. From 030fcca1983e628ff14b9a22c646fd26e220c7fb Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Wed, 10 Jul 2024 19:58:20 +0100 Subject: [PATCH 133/277] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#3736)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/0328994b105cecbf8d5bcab2a7fc5b9791685f87 --- bin/fetch-configlet | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/bin/fetch-configlet b/bin/fetch-configlet index 4800e150849..6bef43ab722 100755 --- a/bin/fetch-configlet +++ b/bin/fetch-configlet @@ -24,10 +24,11 @@ get_download_url() { local latest='https://api.github.com/repos/exercism/configlet/releases/latest' local arch case "$(uname -m)" in - x86_64) arch='x86-64' ;; - *686*) arch='i386' ;; - *386*) arch='i386' ;; - *) arch='x86-64' ;; + aarch64|arm64) arch='arm64' ;; + x86_64) arch='x86-64' ;; + *686*) arch='i386' ;; + *386*) arch='i386' ;; + *) arch='x86-64' ;; esac local suffix="${os}_${arch}.${ext}" curl "${curlopts[@]}" --header 'Accept: application/vnd.github.v3+json' "${latest}" | @@ -47,7 +48,7 @@ main() { fi local os - case "$(uname)" in + case "$(uname -s)" in Darwin*) os='macos' ;; Linux*) os='linux' ;; Windows*) os='windows' ;; @@ -58,8 +59,8 @@ main() { local ext case "${os}" in - windows*) ext='zip' ;; - *) ext='tar.gz' ;; + windows) ext='zip' ;; + *) ext='tar.gz' ;; esac echo "Fetching configlet..." >&2 @@ -69,16 +70,16 @@ main() { curl "${curlopts[@]}" --output "${output_path}" "${download_url}" case "${ext}" in - *zip) unzip "${output_path}" -d "${output_dir}" ;; - *) tar xzf "${output_path}" -C "${output_dir}" ;; + zip) unzip "${output_path}" -d "${output_dir}" ;; + *) tar xzf "${output_path}" -C "${output_dir}" ;; esac rm -f "${output_path}" local executable_ext case "${os}" in - windows*) executable_ext='.exe' ;; - *) executable_ext='' ;; + windows) executable_ext='.exe' ;; + *) executable_ext='' ;; esac local configlet_path="${output_dir}/configlet${executable_ext}" From a5159b5c88b938f73d93f7c54474563e3856b50d Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 11 Jul 2024 22:32:23 +0200 Subject: [PATCH 134/277] Sync the `resistor-color` exercise's docs with the latest data. (#3739) --- .../resistor-color/.docs/instructions.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/exercises/practice/resistor-color/.docs/instructions.md b/exercises/practice/resistor-color/.docs/instructions.md index 646c14398f0..0125e718b45 100644 --- a/exercises/practice/resistor-color/.docs/instructions.md +++ b/exercises/practice/resistor-color/.docs/instructions.md @@ -15,16 +15,16 @@ In this exercise you are going to create a helpful program so that you don't hav These colors are encoded as follows: -- Black: 0 -- Brown: 1 -- Red: 2 -- Orange: 3 -- Yellow: 4 -- Green: 5 -- Blue: 6 -- Violet: 7 -- Grey: 8 -- White: 9 +- black: 0 +- brown: 1 +- red: 2 +- orange: 3 +- yellow: 4 +- green: 5 +- blue: 6 +- violet: 7 +- grey: 8 +- white: 9 The goal of this exercise is to create a way: From 5c9131f9e2ed6e9b7eeb1af633fada9f09882a5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 13:33:04 -0700 Subject: [PATCH 135/277] Bump actions/setup-python from 5.1.0 to 5.1.1 (#3738) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.1.0 to 5.1.1. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/82c7e631bb3cdc910f68e0081d67478d79c6982d...39cd14951b08e74b54015e9e001cdefcf80e669f) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 71dbc4356d2..72be71f7028 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Set up Python - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d + uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d + - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f with: python-version: ${{ matrix.python-version }} From 2529efc02f7ca53c532ccaba8d1ee7041608d462 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 11 Jul 2024 22:33:33 +0200 Subject: [PATCH 136/277] Sync the `resistor-color-duo` exercise's docs with the latest data. (#3740) --- .../resistor-color-duo/.docs/instructions.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/exercises/practice/resistor-color-duo/.docs/instructions.md b/exercises/practice/resistor-color-duo/.docs/instructions.md index 18ee4078bbb..4ae694da025 100644 --- a/exercises/practice/resistor-color-duo/.docs/instructions.md +++ b/exercises/practice/resistor-color-duo/.docs/instructions.md @@ -17,16 +17,16 @@ The program will take color names as input and output a two digit number, even i The band colors are encoded as follows: -- Black: 0 -- Brown: 1 -- Red: 2 -- Orange: 3 -- Yellow: 4 -- Green: 5 -- Blue: 6 -- Violet: 7 -- Grey: 8 -- White: 9 +- black: 0 +- brown: 1 +- red: 2 +- orange: 3 +- yellow: 4 +- green: 5 +- blue: 6 +- violet: 7 +- grey: 8 +- white: 9 From the example above: brown-green should return 15, and From f4e036aa05dd436fe2e897d351ce990f718f9f25 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 11 Jul 2024 22:33:58 +0200 Subject: [PATCH 137/277] Sync the `resistor-color-trio` exercise's docs with the latest data. (#3741) --- .../resistor-color-trio/.docs/instructions.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/exercises/practice/resistor-color-trio/.docs/instructions.md b/exercises/practice/resistor-color-trio/.docs/instructions.md index 59d22783b93..1ac5cf5e9fe 100644 --- a/exercises/practice/resistor-color-trio/.docs/instructions.md +++ b/exercises/practice/resistor-color-trio/.docs/instructions.md @@ -12,16 +12,16 @@ For this exercise, you need to know only three things about them: The program will take 3 colors as input, and outputs the correct value, in ohms. The color bands are encoded as follows: -- Black: 0 -- Brown: 1 -- Red: 2 -- Orange: 3 -- Yellow: 4 -- Green: 5 -- Blue: 6 -- Violet: 7 -- Grey: 8 -- White: 9 +- black: 0 +- brown: 1 +- red: 2 +- orange: 3 +- yellow: 4 +- green: 5 +- blue: 6 +- violet: 7 +- grey: 8 +- white: 9 In Resistor Color Duo you decoded the first two colors. For instance: orange-orange got the main value `33`. From a47255b9bba08739a8752e5f1c88b2cc0f5f49c3 Mon Sep 17 00:00:00 2001 From: qadzek <84473512+qadzek@users.noreply.github.com> Date: Thu, 11 Jul 2024 22:37:56 +0200 Subject: [PATCH 138/277] [Pythagorean Triplet] Fix typos (#3742) --- .../pythagorean-triplet/.approaches/introduction.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/pythagorean-triplet/.approaches/introduction.md b/exercises/practice/pythagorean-triplet/.approaches/introduction.md index ab79ef1afde..0a8cb171416 100644 --- a/exercises/practice/pythagorean-triplet/.approaches/introduction.md +++ b/exercises/practice/pythagorean-triplet/.approaches/introduction.md @@ -6,7 +6,7 @@ The problem space can easily become very large, and 'naive' or more 'brute force There are three reasonably common variations to this problem 1. A [cubic time][approaches-cubic] solution, which uses highly nested loops and is non-performant. 2. A [quadratic time][approaches-quadratic] solution, which uses one nested loop, and is reasonably easy to figure out. -3. A [linear time][approaches-linear] solution, requiring some deeper understanding of the mathematics of finding trplets. +3. A [linear time][approaches-linear] solution, requiring some deeper understanding of the mathematics of finding triplets. If those terms are unclear to you, you might like to read about [time complexity][time-complexity], and how it is described by [asymptotic notation][asymptotic-notation]. @@ -68,7 +68,7 @@ This gives a substantial speed advantage, allowing the tests to run to completio However, the Exercism online test runner will still time out with this solution. -Examining the code, it is clear that the upper bounds on the `loop` variables are far too generous, and too much work is bing done. +Examining the code, it is clear that the upper bounds on the `loop` variables are far too generous, and too much work is being done. The solution below tightens the bounds and pre-calculates `c * c` in the outer `loop`. @@ -171,7 +171,7 @@ def triplets_with_sum(number): If we could be sure that the code only had to handle small values of `n`, a quadratic method would have the advantage of clarity. However, the test suite goes up to 30_000, and the online test runner quickly times out. -We therefor need to accept some less readable code and use a linear-time implementation. +We therefore need to accept some less readable code and use a linear-time implementation. Full details of run-time benchmarking are given in the [Performance article][article-performance]. From c1fd7667575c6d677ddd9b4ecbe28175979e25f4 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 14 Jul 2024 11:37:58 -0700 Subject: [PATCH 139/277] Re-ordered Python docs in prep for new additions. (#3743) --- docs/config.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/config.json b/docs/config.json index ccaea6ac247..8474ac07ad9 100644 --- a/docs/config.json +++ b/docs/config.json @@ -14,20 +14,6 @@ "title": "How to learn Python", "blurb": "An overview of how to get started from scratch with Python." }, - { - "uuid": "7a2e1a4f-1fa8-4327-b700-5af101fcdc89", - "slug": "test-driven-development", - "path": "docs/TDD.md", - "title": "Test Driven Development", - "blurb": "An overview of Test Driven Development." - }, - { - "uuid": "3829fdff-47ac-4283-ae47-a5db1dbce956", - "slug": "traceback-reading", - "path": "docs/TRACEBACKS.md", - "title": "How to read tracebacks", - "blurb": "An overview of how to read Python tracebacks for debugging." - }, { "uuid": "8666f259-de7d-4928-ae6f-15ff6fe6bb74", "slug": "tests", @@ -35,6 +21,13 @@ "title": "Testing on the Python track", "blurb": "Learn how to test your Python exercises on Exercism." }, + { + "uuid": "7a2e1a4f-1fa8-4327-b700-5af101fcdc89", + "slug": "test-driven-development", + "path": "docs/TDD.md", + "title": "Test Driven Development", + "blurb": "An overview of Test Driven Development." + }, { "uuid": "f18d3af2-fb71-41c6-984a-32b3ba86bf02", "slug": "problem-solving", @@ -42,6 +35,13 @@ "title": "Problem Solving Resources", "blurb": "Learn some general problem-solving techniques to help you with programming." }, + { + "uuid": "3829fdff-47ac-4283-ae47-a5db1dbce956", + "slug": "traceback-reading", + "path": "docs/TRACEBACKS.md", + "title": "How to read tracebacks", + "blurb": "An overview of how to read Python tracebacks for debugging." + }, { "uuid": "73ced51d-76d0-45af-952c-8a6d7b5f3f7a", "slug": "resources", From 9d7e0a531a056273947d0d65412c30ab51a37c31 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 14 Jul 2024 11:39:47 -0700 Subject: [PATCH 140/277] Changed Pascals to Pascal's for the Exercise name. (#3744) --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index 99d23d77c31..38dd7520e3c 100644 --- a/config.json +++ b/config.json @@ -1554,7 +1554,7 @@ }, { "slug": "pascals-triangle", - "name": "Pascals Triangle", + "name": "Pascal's Triangle", "uuid": "e1e1c7d7-c1d9-4027-b90d-fad573182419", "practices": ["recursion"], "prerequisites": [ From 20437121e4e991865033fc4da30dc5da84941680 Mon Sep 17 00:00:00 2001 From: Kelly Young Date: Wed, 24 Jul 2024 13:30:42 -0500 Subject: [PATCH 141/277] (fix): update pylint tutorial link and link text (#3748) --- docs/TESTS.md | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 242555371f3..54a10b5782a 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -10,8 +10,7 @@ You should also install the following `pytest` plugins: We also recommend using the code linting program [pylint][pylint], as it is part of our automated feedback on the website and can be a very useful static code analysis tool. For ease-of-use, the [pytest-pylint][pytest-pylint] plugin for `pytest` will allow you to run `pylint` via `pytest` on the command line. -Pylint configuration can be a bit much, so this [tutorial from pycqa.org][tutorial from pycqa.org] can be helpful for getting started, as can this overview of [Code Quality: Tools and Best Practices][Code Quality: Tools and Best Practices] from Real Python. - +Pylint configuration can be a bit much, so this [tutorial from pylint.readthedocs.io][tutorial from pylint.readthedocs.io] can be helpful for getting started, as can this overview of [Code Quality: Tools and Best Practices][Code Quality: Tools and Best Practices] from Real Python. ## Installing pytest @@ -25,7 +24,6 @@ Please adjust the install commands below accordingly. To install `pytest` in a virtual environment, ensure the environment **is activated** prior to executing commands. Otherwise, the `pytest` installation will be global. - #### Windows ```powershell @@ -41,7 +39,6 @@ Successfully installed pytest-7.2.2 ... ``` - To check if installation was successful: ```bash @@ -85,7 +82,6 @@ More information on pytest marks can be found in the `pytest` documentation on [ _More information on customizing pytest configurations can be found in the pytest documentation on [configuration file formats][configuration file formats]_ - ### Test Failures When tests fail, `pytest` prints the text of each failed test, along with the expected and actual `return` values of each to the terminal. @@ -110,13 +106,12 @@ FAILED exercise_test.py::ExerciseTest::name_of_failed_test If you really want to be specific about what pytest returns on your screen, here are some handy command-line arguments that allows you to configure its behavior. - #### Return All Details [`-v`] Adding the `-v` (_verbose_) flag will return both environment information and a test summary in addition to test failures: ```bash -$(my_venv) python3 -m pytest -o markers=task -v exercises// +$(my_venv) python3 -m pytest -o markers=task -v exercises// ======================================== test session starts =========================================== platform darwin -- Python 3.9.0, pytest-6.2.5, -- /usr/local/envs/my_env/bin/python3 @@ -125,7 +120,7 @@ metadata: {'Python': '3.9.0', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Pa rootdir: /Users//exercism/python, configfile: pytest.ini plugins: subtests-0.5.0, pylint-0.18.0 -collected 5 items +collected 5 items exercises/exercise-name/exercise_file_test.py::ExerciseNameTest::test_one FAILED [ 20%] exercises/exercise-name/exercise_file_test.py::ExerciseNameTest::test_two FAILED @@ -149,7 +144,7 @@ Using the `-x` flag will run the tests as normal, but stop the test run upon the This helps when you want to debug a single task or test failure at a time: ```bash -$(my_venv) python3 -m pytest -o markers=task -x exercises// +$(my_venv) python3 -m pytest -o markers=task -x exercises// =================== FAILURES ==================== _______________ example_test_foo ________________ @@ -166,7 +161,6 @@ FAILED example_test.py::ExampleTest::example_test_foo The `pytest-cache` plugin remembers which tests failed last time you ran `pytest`, so using the flag `--ff` will tell `pytest` to run previously failed tests **first**, then continue with the remainder of the tests. This might speed up your testing if you are making a lot of smaller fixes around one particular task or set of inputs. - ```bash $(my_venv) python3 -m pytest -o markers=task --ff ==================== 7 passed in 503s ==================== @@ -192,7 +186,6 @@ This will test your solution. When `pytest` encounters a failed test, the program will stop and tell you which test failed. When you make fixes and run the test again, `pytest` will first run the previous test that failed, then continue with the remaining tests. - ### Using PDB, the Python Debugger, with pytest If you want to "debug like a pro", you can use the `--pdb` argument after the `pytest` command, and drop into the built-in [Python debugger][pdb], `PDB`. @@ -206,13 +199,11 @@ When a test fails, dropping into `PDB` will allow you to step through your code More details on the `PDB` module can be found in the [Python documentation on PDB][pdb]. Additionally, the [pytest docs on PDB][pytest-pdb] and [this guide from Real Python](https://realpython.com/python-debugging-pdb/) are extremely helpful. - ## Extending your IDE If you'd like to extend your IDE with some tools that will help you with testing and improving your code, check the [tools](./tools) page. We explore multiple IDEs, editors and some useful extensions for linting and debugging there. - ## Additional information ### Adding python to your PATH @@ -245,7 +236,6 @@ Then add a new line, as shown in the picture, replacing `{python_directory}` wit ![Add python to path](https://raw.githubusercontent.com/exercism/python/main/docs/img/Windows-AddPythonPath.png) - #### MacOS/Linux The below should work for most Linux and MacOS flavors with a `bash` shell. @@ -264,13 +254,13 @@ export PATH=”$PATH:{python_directory}}” [pip]: https://pip.pypa.io/en/stable/getting-started/ [psf-installer]: https://www.python.org/downloads/ [pylint]: https://pylint.pycqa.org/en/latest/user_guide/ -[pytest-cache]:http://pythonhosted.org/pytest-cache/ +[pytest-cache]: http://pythonhosted.org/pytest-cache/ [pytest-pdb]: https://docs.pytest.org/en/6.2.x/usage.html#dropping-to-pdb-python-debugger-on-failures -[pytest-pylint]:https://github.com/carsongee/pytest-pylint -[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests +[pytest-pylint]: https://github.com/carsongee/pytest-pylint +[pytest-subtests]: https://github.com/pytest-dev/pytest-subtests [pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini [python command line]: https://docs.python.org/3/using/cmdline.html [python-m-pip]: https://snarky.ca/why-you-should-use-python-m-pip/ [quick-and-dirty]: https://snarky.ca/a-quick-and-dirty-guide-on-how-to-install-packages-for-python/ -[tutorial from pycqa.org]: https://pylint.pycqa.org/en/v2.17.2/tutorial.html +[tutorial from pylint.readthedocs.io]: https://pylint.readthedocs.io/en/v2.17.7/tutorial.html [working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers From cf0f7c029f9a5227ffc56dfad56c9b14dfdecead Mon Sep 17 00:00:00 2001 From: Gautzilla <72027971+Gautzilla@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:27:51 +0200 Subject: [PATCH 142/277] remove extra space (#3749) an extra space made the markdown fail displaying _keys_ in italics --- exercises/concept/inventory-management/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/inventory-management/.docs/introduction.md b/exercises/concept/inventory-management/.docs/introduction.md index 2b9ef011e1b..738f36ef754 100644 --- a/exercises/concept/inventory-management/.docs/introduction.md +++ b/exercises/concept/inventory-management/.docs/introduction.md @@ -120,7 +120,7 @@ KeyError: 'name' ## Looping through/Iterating over a Dictionary -Looping through a dictionary using `for item in dict` or `while item` will iterate over only the _keys _ by default. +Looping through a dictionary using `for item in dict` or `while item` will iterate over only the _keys_ by default. You can access the _values_ within the same loop by using _square brackets_: ```python From b189a0895cc03e12ff4188faa5eb331fb482762c Mon Sep 17 00:00:00 2001 From: Gautzilla <72027971+Gautzilla@users.noreply.github.com> Date: Mon, 29 Jul 2024 23:42:05 +0200 Subject: [PATCH 143/277] fix typos (#3750) Remove additional apostrophe in _of that dish's' ingredients._ Remove extra _in_ in _Within in each category_. --- exercises/concept/cater-waiter/.docs/instructions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/cater-waiter/.docs/instructions.md b/exercises/concept/cater-waiter/.docs/instructions.md index 2d54bb0ed2c..696883c3c93 100644 --- a/exercises/concept/cater-waiter/.docs/instructions.md +++ b/exercises/concept/cater-waiter/.docs/instructions.md @@ -42,7 +42,7 @@ Implement the `check_drinks(, )` function that ta The guest list includes diners with different dietary needs, and your staff will need to separate the dishes into Vegan, Vegetarian, Paleo, Keto, and Omnivore. -Implement the `categorize_dish(, )` function that takes a dish name and a `set` of that dish's' ingredients. +Implement the `categorize_dish(, )` function that takes a dish name and a `set` of that dish's ingredients. The function should return a string with the `dish name: ` (_which meal category the dish belongs to_). All dishes will "fit" into one of the categories imported from `sets_categories_data.py` (VEGAN, VEGETARIAN, PALEO, KETO, or OMNIVORE). @@ -125,7 +125,7 @@ appetizers = ['Kingfish Lettuce Cups','Avocado Deviled Eggs','Satay Steak Skewer ## 7. Find Ingredients Used in Only One Recipe -Within in each category (_Vegan, Vegetarian, Paleo, Keto, Omnivore_), you're going to pull out ingredients that appear in only one dish. +Within each category (_Vegan, Vegetarian, Paleo, Keto, Omnivore_), you're going to pull out ingredients that appear in only one dish. These "singleton" ingredients will be assigned a special shopper to ensure they're not forgotten in the rush to get everything else done. Implement the `singleton_ingredients(, )` function that takes a `list` of dishes and a `_INTERSECTIONS` constant for the same category. From e06436a8bd7bdee06ebed8ccc9aecb169f730371 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 31 Jul 2024 10:39:52 -0700 Subject: [PATCH 144/277] [Little Sisters Vocabulary] Update instructions.md (#3752) Rephrased line 7 to omit the usage of `there's`. --- exercises/concept/little-sisters-vocab/.docs/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/little-sisters-vocab/.docs/instructions.md b/exercises/concept/little-sisters-vocab/.docs/instructions.md index 2772ce18d11..2658bb980a4 100644 --- a/exercises/concept/little-sisters-vocab/.docs/instructions.md +++ b/exercises/concept/little-sisters-vocab/.docs/instructions.md @@ -4,7 +4,7 @@ You are helping your younger sister with her English vocabulary homework, which Her class is learning to create new words by adding _prefixes_ and _suffixes_. Given a set of words, the teacher is looking for correctly transformed words with correct spelling by adding the prefix to the beginning or the suffix to the ending. -There's four activities in the assignment, each with a set of text or words to work with. +The assignment has four activities, each with a set of text or words to work with. ## 1. Add a prefix to a word From bcb530035306da95b1118b0cb1cf5f2c9ae625ed Mon Sep 17 00:00:00 2001 From: colinleach Date: Wed, 31 Jul 2024 18:37:03 -0700 Subject: [PATCH 145/277] [Matching Brackets] draft approaches (#3670) * [Matching Brackets] draft approaches * [Matching Brackets] Approaches Review & Edits * Additional grammar and spelling edits * Final Edits Hopefully, the final edits. :smile: * Un crossed left vs right --------- Co-authored-by: BethanyG --- .../matching-brackets/.approaches/config.json | 30 +++ .../.approaches/introduction.md | 78 ++++++++ .../repeated-substitution/content.md | 67 +++++++ .../repeated-substitution/snippet.txt | 5 + .../.approaches/stack-match/content.md | 50 +++++ .../.approaches/stack-match/snippet.txt | 8 + .../matching-brackets/.articles/config.json | 14 ++ .../.articles/performance/code/Benchmark.py | 184 ++++++++++++++++++ .../performance/code/run_times.feather | Bin 0 -> 3018 bytes .../.articles/performance/content.md | 41 ++++ .../.articles/performance/snippet.md | 3 + 11 files changed, 480 insertions(+) create mode 100644 exercises/practice/matching-brackets/.approaches/config.json create mode 100644 exercises/practice/matching-brackets/.approaches/introduction.md create mode 100644 exercises/practice/matching-brackets/.approaches/repeated-substitution/content.md create mode 100644 exercises/practice/matching-brackets/.approaches/repeated-substitution/snippet.txt create mode 100644 exercises/practice/matching-brackets/.approaches/stack-match/content.md create mode 100644 exercises/practice/matching-brackets/.approaches/stack-match/snippet.txt create mode 100644 exercises/practice/matching-brackets/.articles/config.json create mode 100644 exercises/practice/matching-brackets/.articles/performance/code/Benchmark.py create mode 100644 exercises/practice/matching-brackets/.articles/performance/code/run_times.feather create mode 100644 exercises/practice/matching-brackets/.articles/performance/content.md create mode 100644 exercises/practice/matching-brackets/.articles/performance/snippet.md diff --git a/exercises/practice/matching-brackets/.approaches/config.json b/exercises/practice/matching-brackets/.approaches/config.json new file mode 100644 index 00000000000..295cbca6e3f --- /dev/null +++ b/exercises/practice/matching-brackets/.approaches/config.json @@ -0,0 +1,30 @@ +{ + "introduction": { + "authors": [ + "colinleach", + "BethanyG" + ] + }, + "approaches": [ + { + "uuid": "449c828e-ce19-4930-83ab-071eb2821388", + "slug": "stack-match", + "title": "Stack Match", + "blurb": "Maintain context during stream processing by use of a stack.", + "authors": [ + "colinleach", + "BethanyG" + ] + }, + { + "uuid": "b4c42162-751b-42c8-9368-eed9c3f4e4c8", + "slug": "repeated-substitution", + "title": "Repeated Substitution", + "blurb": "Use substring replacement to iteratively simplify the string.", + "authors": [ + "colinleach", + "BethanyG" + ] + } + ] +} diff --git a/exercises/practice/matching-brackets/.approaches/introduction.md b/exercises/practice/matching-brackets/.approaches/introduction.md new file mode 100644 index 00000000000..0096dac45c7 --- /dev/null +++ b/exercises/practice/matching-brackets/.approaches/introduction.md @@ -0,0 +1,78 @@ +# Introduction + +The aim in this exercise is to determine whether opening and closing brackets are properly paired within the input text. + +These brackets may be nested deeply (think Lisp code) and/or dispersed among a lot of other text (think complex LaTeX documents). + +Community solutions fall into two main groups: + +1. Those which make a single pass or loop through the input string, maintaining necessary context for matching. +2. Those which repeatedly make global substitutions within the text for context. + + +## Single-pass approaches + +```python +def is_paired(input_string): + bracket_map = {"]" : "[", "}": "{", ")":"("} + tracking = [] + + for element in input_string: + if element in bracket_map.values(): + tracking.append(element) + if element in bracket_map: + if not tracking or (tracking.pop() != bracket_map[element]): + return False + return not tracking +``` + +The key in this approach is to maintain context by pushing open brackets onto some sort of stack (_in this case appending to a `list`_), then checking if there is a corresponding closing bracket to pair with the top stack item. + +See [stack-match][stack-match] approaches for details. + + +## Repeated-substitution approaches + +```python +def is_paired(text): + text = "".join(item for item in text if item in "()[]{}") + while "()" in text or "[]" in text or "{}" in text: + text = text.replace("()","").replace("[]", "").replace("{}","") + return not text +``` + +In this approach, we first remove any non-bracket characters, then use a loop to repeatedly remove inner bracket pairs. + +See [repeated-substitution][repeated-substitution] approaches for details. + + +## Other approaches + +Languages prizing immutibility are likely to use techniques such as `foldl()` or recursive matching, as discussed on the [Scala track][scala]. + +This is possible in Python, but can read as unidiomatic and will (likely) result in inefficient code if not done carefully. + +For anyone wanting to go down the functional-style path, Python has [`functools.reduce()`][reduce] for folds and added [structural pattern matching][pattern-matching] in Python 3.10. + +Recursion is not highly optimised in Python and there is no tail call optimization, but the default stack depth of 1000 should be more than enough for solving this problem recursively. + + +## Which approach to use + +For short, well-defined input strings such as those currently in the test file, repeated-substitution allows a passing solution in very few lines of code. +But as input grows, this method could become less and less performant, due to the multiple passes and changes needed to determine matches. + +The single-pass strategy of the stack-match approach allows for stream processing, scales linearly (_`O(n)` time complexity_) with text length, and will remain performant for very large inputs. + +Examining the community solutions published for this exercise, it is clear that many programmers prefer the stack-match method which avoids the repeated string copying of the substitution approach. + +Thus it is interesting and perhaps humbling to note that repeated-substitution is **_at least_** as fast in benchmarking, even with large (>30 kB) input strings! + +See the [performance article][article-performance] for more details. + +[article-performance]:https://exercism.org/tracks/python/exercises/matching-brackets/articles/performance +[pattern-matching]: https://docs.python.org/3/whatsnew/3.10.html#pep-634-structural-pattern-matching +[reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[repeated-substitution]: https://exercism.org/tracks/python/exercises/matching-brackets/approaches/repeated-substitution +[scala]: https://exercism.org/tracks/scala/exercises/matching-brackets/dig_deeper +[stack-match]: https://exercism.org/tracks/python/exercises/matching-brackets/approaches/stack-match diff --git a/exercises/practice/matching-brackets/.approaches/repeated-substitution/content.md b/exercises/practice/matching-brackets/.approaches/repeated-substitution/content.md new file mode 100644 index 00000000000..2c8c17d6372 --- /dev/null +++ b/exercises/practice/matching-brackets/.approaches/repeated-substitution/content.md @@ -0,0 +1,67 @@ +# Repeated Substitution + + +```python +def is_paired(text): + text = "".join([element for element in text if element in "()[]{}"]) + while "()" in text or "[]" in text or "{}" in text: + text = text.replace("()","").replace("[]", "").replace("{}","") + return not text +``` + +In this approach, the steps are: + +1. Remove all non-bracket characters from the input string (_as done through the filter clause in the list-comprehension above_). +2. Iteratively remove all remaining bracket pairs: this reduces nesting in the string from the inside outwards. +3. Test for a now empty string, meaning all brackets have been paired. + + +The code above spells out the approach particularly clearly, but there are (of course) several possible variants. + + +## Variation 1: Walrus Operator within a Generator Expression + + +```python +def is_paired(input_string): + symbols = "".join(char for char in input_string if char in "{}[]()") + while (pair := next((pair for pair in ("{}", "[]", "()") if pair in symbols), False)): + symbols = symbols.replace(pair, "") + return not symbols +``` + +The second solution above does essentially the same thing as the initial approach, but uses a generator expression assigned with a [walrus operator][walrus] `:=` (_introduced in Python 3.8_) in the `while-loop` test. + + +## Variation 2: Regex Substitution in a While Loop + +Regex enthusiasts can modify the previous approach, using `re.sub()` instead of `string.replace()` in the `while-loop` test: + +```python +import re + +def is_paired(text: str) -> bool: + text = re.sub(r'[^{}\[\]()]', '', text) + while text != (text := re.sub(r'{\}|\[]|\(\)', '', text)): + continue + return not bool(text) +``` + + +## Variation 3: Regex Substitution and Recursion + + +It is possible to combine `re.sub()` and recursion in the same solution, though not everyone would view this as idiomatic Python: + + +```python +import re + +def is_paired(input_string): + replaced = re.sub(r"[^\[\(\{\}\)\]]|\{\}|\(\)|\[\]", "", input_string) + return not input_string if input_string == replaced else is_paired(replaced) +``` + +Note that solutions using regular expressions ran slightly *slower* than `string.replace()` solutions in benchmarking, so adding this type of complexity brings no benefit to this problem. + +[walrus]: https://martinheinz.dev/blog/79/ diff --git a/exercises/practice/matching-brackets/.approaches/repeated-substitution/snippet.txt b/exercises/practice/matching-brackets/.approaches/repeated-substitution/snippet.txt new file mode 100644 index 00000000000..0fa6d54abdc --- /dev/null +++ b/exercises/practice/matching-brackets/.approaches/repeated-substitution/snippet.txt @@ -0,0 +1,5 @@ +def is_paired(text): + text = "".join(element for element in text if element in "()[]{}") + while "()" in text or "[]" in text or "{}" in text: + text = text.replace("()","").replace("[]", "").replace("{}","") + return not text \ No newline at end of file diff --git a/exercises/practice/matching-brackets/.approaches/stack-match/content.md b/exercises/practice/matching-brackets/.approaches/stack-match/content.md new file mode 100644 index 00000000000..9619e83390f --- /dev/null +++ b/exercises/practice/matching-brackets/.approaches/stack-match/content.md @@ -0,0 +1,50 @@ +# Stack Match + + +```python +def is_paired(input_string): + bracket_map = {"]" : "[", "}": "{", ")":"("} + stack = [] + + for element in input_string: + if element in bracket_map.values(): + stack.append(element) + if element in bracket_map: + if not stack or (stack.pop() != bracket_map[element]): + return False + return not stack +``` + +The point of this approach is to maintain a context of which bracket sets are currently "open": + +- If a left bracket is found, push it onto the stack (_append it to the `list`_). +- If a right bracket is found, **and** it pairs with the last item placed on the stack, pop the bracket off the stack and continue. +- If there is a mismatch, for example `'['` with `'}'` or there is no left bracket on the stack, the code can immediately terminate and return `False`. +- When all the input text is processed, determine if the stack is empty, meaning all left brackets were matched. + +In Python, a [`list`][concept:python/lists]() is a good implementation of a stack: it has [`list.append()`][list-append] (_equivalent to a "push"_) and [`lsit.pop()`][list-pop] methods built in. + +Some solutions use [`collections.deque()`][collections-deque] as an alternative implementation, though this has no clear advantage (_since the code only uses appends to the right-hand side_) and near-identical runtime performance. + +The default iteration for a dictionary is over the _keys_, so the code above uses a plain `bracket_map` to search for right brackets, while `bracket_map.values()` is used to search for left brackets. + +Other solutions created two sets of left and right brackets explicitly, or searched a string representation: + +```python + if element in ']})': +``` + +Such changes made little difference to code length or readability, but ran about 5-fold faster than the dictionary-based solution. + +At the end, success is an empty stack, tested above by using the [False-y quality][falsey] of `[]` (_as Python programmers often do_). + +To be more explicit, we could alternatively use an equality: + +```python + return stack == [] +``` + +[list-append]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists +[list-pop]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists +[collections-deque]: https://docs.python.org/3/library/collections.html#collections.deque +[falsey]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing diff --git a/exercises/practice/matching-brackets/.approaches/stack-match/snippet.txt b/exercises/practice/matching-brackets/.approaches/stack-match/snippet.txt new file mode 100644 index 00000000000..571b6792a6f --- /dev/null +++ b/exercises/practice/matching-brackets/.approaches/stack-match/snippet.txt @@ -0,0 +1,8 @@ + bracket_map = {"]" : "[", "}": "{", ")":"("} + stack = [] + for element in input_string: + if element in bracket_map.values(): tracking.append(element) + if element in bracket_map: + if not stack or (stack.pop() != bracket_map[element]): + return False + return not stack \ No newline at end of file diff --git a/exercises/practice/matching-brackets/.articles/config.json b/exercises/practice/matching-brackets/.articles/config.json new file mode 100644 index 00000000000..0a5a8856a3c --- /dev/null +++ b/exercises/practice/matching-brackets/.articles/config.json @@ -0,0 +1,14 @@ +{ + "articles": [ + { + "uuid": "af7a43b5-c135-4809-9fb8-d84cdd5138d5", + "slug": "performance", + "title": "Performance", + "blurb": "Compare a variety of solutions using benchmarking data.", + "authors": [ + "colinleach", + "BethanyG" + ] + } + ] +} diff --git a/exercises/practice/matching-brackets/.articles/performance/code/Benchmark.py b/exercises/practice/matching-brackets/.articles/performance/code/Benchmark.py new file mode 100644 index 00000000000..1ca6ff0025a --- /dev/null +++ b/exercises/practice/matching-brackets/.articles/performance/code/Benchmark.py @@ -0,0 +1,184 @@ +import timeit + +import pandas as pd +import numpy as np +import requests + + +# ------------ FUNCTIONS TO TIME ------------- # + +def stack_match1(input_string): + bracket_map = {"]" : "[", "}": "{", ")":"("} + tracking = [] + + for element in input_string: + if element in bracket_map.values(): + tracking.append(element) + if element in bracket_map: + if not tracking or (tracking.pop() != bracket_map[element]): + return False + return not tracking + + +def stack_match2(input_string): + opening = {'[', '{', '('} + closing = {']', '}', ')'} + pairs = {('[', ']'), ('{', '}'), ('(', ')')} + stack = list() + + for char in input_string: + if char in opening: + stack.append(char) + elif char in closing: + if not stack or (stack.pop(), char) not in pairs: + return False + return stack == [] + + + +def stack_match3(input_string): + BRACKETS = {'(': ')', '[': ']', '{': '}'} + END_BRACKETS = {')', ']', '}'} + + stack = [] + + def is_valid(char): + return stack and stack.pop() == char + + for char in input_string: + if char in BRACKETS: + stack.append(BRACKETS[char]) + elif char in END_BRACKETS and not is_valid(char): + return False + + return not stack + + +def stack_match4(input_string): + stack = [] + r = {')': '(', ']': '[', '}': '{'} + for c in input_string: + if c in '[{(': + stack.append(c) + if c in ']})': + if not stack: + return False + if stack[-1] == r[c]: + stack.pop() + else: + return False + return not stack + + +from collections import deque +from typing import Deque + + +def stack_match5(text: str) -> bool: + """ + Determine if the given text properly closes any opened brackets. + """ + PUSH = {"[": "]", "{": "}", "(": ")"} + PULL = set(PUSH.values()) + + stack: Deque[str] = deque() + for char in text: + if char in PUSH: + stack.append(PUSH[char]) + elif char in PULL: + if not stack or char != stack.pop(): + return False + return not stack + + +def repeated_substitution1(text): + text = "".join(x for x in text if x in "()[]{}") + while "()" in text or "[]" in text or "{}" in text: + text = text.replace("()","").replace("[]", "").replace("{}","") + return not text + + +def repeated_substitution2(input_string): + symbols = "".join(c for c in input_string if c in "{}[]()") + while (pair := next((pair for pair in ("{}", "[]", "()") if pair in symbols), False)): + symbols = symbols.replace(pair, "") + return not symbols + + +import re + +def repeated_substitution3(str_: str) -> bool: + str_ = re.sub(r'[^{}\[\]()]', '', str_) + while str_ != (str_ := re.sub(r'{\}|\[]|\(\)', '', str_)): + pass + return not bool(str_) + + +def repeated_substitution4(input_string): + replaced = re.sub(r"[^\[\(\{\}\)\]]|\{\}|\(\)|\[\]", "", input_string) + return not input_string if input_string == replaced else repeated_substitution4(replaced) + +## ---------END FUNCTIONS TO BE TIMED-------------------- ## + +## -------- Timing Code Starts Here ---------------------## + +def get_file(url): + resp = requests.get(url) + return resp.text + +short = "\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \\end{array}\\right)" +mars_moons = get_file("https://raw.githubusercontent.com/colinleach/PTYS516/main/term_paper/term_paper.tex") +galaxy_cnn = get_file("https://raw.githubusercontent.com/colinleach/proj502/main/project_report/report.tex") + + +# Input Data Setup +inputs = [short, mars_moons, galaxy_cnn] + +# Ensure the code doesn't terminate early with a mismatch +assert all([stack_match1(txt) for txt in inputs]) + +# #Set up columns and rows for Pandas Data Frame +col_headers = ['short', 'mars_moons', 'galaxy_cnn'] +row_headers = [ + "stack_match1", + "stack_match2", + "stack_match3", + "stack_match4", + "stack_match5", + + "repeated_substitution1", + "repeated_substitution2", + "repeated_substitution3", + "repeated_substitution4" + ] + +# Empty dataframe will be filled in one cell at a time later +df = pd.DataFrame(np.nan, index=row_headers, columns=col_headers) + +# Function List to Call When Timing +functions = [stack_match1, stack_match2, stack_match3, stack_match4, stack_match5, + repeated_substitution1, repeated_substitution2, repeated_substitution3, repeated_substitution4] + +# Run timings using timeit.autorange(). Run Each Set 3 Times. +for function, title in zip(functions, row_headers): + timings = [[ + timeit.Timer(lambda: function(data), globals=globals()).autorange()[1] / + timeit.Timer(lambda: function(data), globals=globals()).autorange()[0] + for data in inputs] for rounds in range(3)] + + # Only the fastest Cycle counts. + timing_result = min(timings) + + print(f'{title}', f'Timings : {timing_result}') + # Insert results into the dataframe + df.loc[title, col_headers[0]:col_headers[-1]] = timing_result + +# Save the data to avoid constantly regenerating it +df.to_feather('run_times.feather') +print("\nDataframe saved to './run_times.feather'") + +# The next bit is useful for `introduction.md` +pd.options.display.float_format = '{:,.2e}'.format +print('\nDataframe in Markdown format:\n') +print(df.to_markdown(floatfmt=".2e")) + diff --git a/exercises/practice/matching-brackets/.articles/performance/code/run_times.feather b/exercises/practice/matching-brackets/.articles/performance/code/run_times.feather new file mode 100644 index 0000000000000000000000000000000000000000..72ef3124185e4bfb6b7e03ba43c83a667d6b1a6d GIT binary patch literal 3018 zcmeGeTWAzl^t#!^RhAIJrlBaVL%@g{vzu)iMCWdZ#we&Eq^Z!z5K9m;?I5IAh^is^s1($cWK2y6+dQ8Ol4Q~pxkpx{8c7lkZT1MJ zgvpY*V6ULEm<&tQq@sXCI4Ub4DW7KAl8jUw1%;#<4Qe4-0BJQDi>DSCT35FmG>{&X z4HjYs1Myx#RzW%qwwl!_37t-mhGx=T!iHN3i6?u+i$u?iYipGpO|6*Tq7&*@1 zCexe2Cu@U3jOhs}rfG0$_RZUP@7VBRrm&+aDX6N84d?UTt>Hq~Tp(EBFl=_;?$-H1 z^GnucW@tJO1SQ&~Gd+cI5Kl2(*N))cdt^Nk)l{TV?BrItM*p=g*3!I z=Ervc20#D+@d)G^WKA*VT9rkEA6W&kC>J>2$=6*q*A?NqHJlC~7?`8kdm!WzmvfNE zuaVCN6Cn>Q`7*K^a<_ym=h<9l<@~Zm_KLwCkA09udCYM_Ah}_Hs`+{-2&*}Ub)f?I zSV`PmD=Bhv-zTtWEXb?@TUa?v89XoCAL>|P$>0x90naa&mc!;#$s%;x7#TdnpRn;W zc!uxE@ef5aIJMSwTKq#6j=w^_*?oNz-fZb}3i~QL14DogjL=u-?tS}I+&_GO=asQ> zF%+2lcgAsBd{umlZGHH?IFL4;e{g3z?Q(qkv~&~GmP z_{WCnI{MYik*U9qe?sS~I({8}(M~sZoSj|Ue$YIb0{Xw)acc0t-ErFAJ??h=u#2{w z9`@DTYoc9c2Ukq~9iS&0ItPcQn&{~4ld&R~pI+bn=iOUZ{WKg7&(-(0(T=lsABm;y zrv9q@^8s`KDcF8004f1$0Ja0P0z?4%04@OVk(pTM+XTdi34;Z{kYdaTM!atFp%0)I z!0#sWu8(y&4pl)8Nr_}v!iXA40}6n*oH*9`;Ip>2oU|atZ|TSQ$0_Eh8zLAp#0?la zh!KL0;T|wnFoarg0wZ9>(}XSVFuRv}Zng<7^<0mTt@YTGY`rh%F_Hh`$ziF8P literal 0 HcmV?d00001 diff --git a/exercises/practice/matching-brackets/.articles/performance/content.md b/exercises/practice/matching-brackets/.articles/performance/content.md new file mode 100644 index 00000000000..0d34786e737 --- /dev/null +++ b/exercises/practice/matching-brackets/.articles/performance/content.md @@ -0,0 +1,41 @@ +# Performance + +All functions were tested on three inputs, a short string from the exercise tests plus two scientific papers in $\LaTeX$ format. + +Python reported these string lengths: + +``` + short: 84 + mars_moons: 34836 + galaxy_cnn: 31468 +``` + +A total of 9 community solutions were tested: 5 variants of stack-match and 4 of repeated-substitution. +Full details are in the [benchmark code][benchmark-code], including URLs for the downloaded papers. +Results are summarized in the table below, with all times in seconds: + + +| | short | mars_moons | galaxy_cnn | +|:-----------------------|:--------:|:------------:|:------------:| +| stack_match4 | 1.77e-06 | 5.92e-04 | 5.18e-04 | +| stack_match2 | 1.71e-06 | 7.38e-04 | 6.64e-04 | +| stack_match3 | 1.79e-06 | 7.72e-04 | 6.95e-04 | +| stack_match5 | 1.70e-06 | 7.79e-04 | 6.97e-04 | +| stack_match1 | 5.64e-06 | 21.9e-04 | 39.7e-04 | +| repeated_substitution1 | 1.20e-06 | 3.50e-04 | 3.06e-04 | +| repeated_substitution2 | 1.86e-06 | 3.58e-04 | 3.15e-04 | +| repeated_substitution3 | 4.27e-06 | 14.0e-04 | 12.5e-04 | +| repeated_substitution4 | 4.96e-06 | 14.9e-04 | 13.5e-04 | + + +Overall, most of these solutions had fairly similar performance, and runtime scaled similarly with input length. + +There is certainly no evidence for either class of solutions being systematically better than the other. + +The slowest was `stack_match1`, which did a lot of lookups in dictionary. +keys and values. Searching instead in sets or strings gave a small but perhaps useful improvement. + +Among the repeated-substitution solutions, the first two used standard Python string operations, running slightly faster than the second two which use regular expressions. + + +[benchmark-code]: https://github.com/exercism/python/blob/main/exercises/practice/matching-brackets/.articles/performance/code/Benchmark.py diff --git a/exercises/practice/matching-brackets/.articles/performance/snippet.md b/exercises/practice/matching-brackets/.articles/performance/snippet.md new file mode 100644 index 00000000000..1479ad508e2 --- /dev/null +++ b/exercises/practice/matching-brackets/.articles/performance/snippet.md @@ -0,0 +1,3 @@ +# Performance + +Compare a variety of solutions using benchmarking data. From 56d158766f61534658d65fa068d9076ccc80146a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 13 Aug 2024 12:01:08 -0700 Subject: [PATCH 146/277] Attempt at fix for issue described in http://forum.exercism.org/t/github-ci-docker-compose-not-found-error-for-python/12487. (#3754) --- .github/workflows/test-runner.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index d57a2d290c2..b9600cc8b9a 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -12,4 +12,4 @@ jobs: steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Run test-runner - run: docker-compose run test-runner + run: docker compose run test-runner From adaa483944c0eb133deab5c1fbb828e15ec8e413 Mon Sep 17 00:00:00 2001 From: Ela Bogucka <9089666+ebogucka@users.noreply.github.com> Date: Tue, 13 Aug 2024 21:09:50 +0200 Subject: [PATCH 147/277] [Collatz Conjecture] Approaches - missing words fix (#3753) * Added missing words in General guidance text * Update exercises/practice/collatz-conjecture/.approaches/introduction.md --------- Co-authored-by: BethanyG --- .../practice/collatz-conjecture/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/collatz-conjecture/.approaches/introduction.md b/exercises/practice/collatz-conjecture/.approaches/introduction.md index 6a6d487f269..58d7a65f38d 100644 --- a/exercises/practice/collatz-conjecture/.approaches/introduction.md +++ b/exercises/practice/collatz-conjecture/.approaches/introduction.md @@ -6,7 +6,7 @@ You can also solve it by using if and else statements or the ternary operator. ## General guidance -The key to this exercise is to check if the number and then do the correct operation. +The key to this exercise is to check if the number is even or odd and then perform the correct operation. Under this process you are supposed to count how many steps it takes to get to one. ## Approach: If/Else From 00d5bcc9de36dcd2300b6b05d08cdcf63d660d9b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 23 Aug 2024 13:48:02 -0700 Subject: [PATCH 148/277] Fixed instructions and tests per https://forum.exercism.org/t/testing-data-in-mecha-munch-management-dict-methods-py-task3/12638. (#3756) [no important files changed] --- .../concept/mecha-munch-management/.docs/instructions.md | 4 ++-- exercises/concept/mecha-munch-management/dict_methods_test.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/concept/mecha-munch-management/.docs/instructions.md b/exercises/concept/mecha-munch-management/.docs/instructions.md index c7cf733276e..13d88f39fa2 100644 --- a/exercises/concept/mecha-munch-management/.docs/instructions.md +++ b/exercises/concept/mecha-munch-management/.docs/instructions.md @@ -59,10 +59,10 @@ The function should return the new/updated "ideas" dictionary. ```python >>> update_recipes({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, -(('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)) +(('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)) ... -{'Banana Bread' : {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, +{'Banana Bread' : {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}} >>> update_recipes({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, diff --git a/exercises/concept/mecha-munch-management/dict_methods_test.py b/exercises/concept/mecha-munch-management/dict_methods_test.py index 622f425d5ac..63fc1874a05 100644 --- a/exercises/concept/mecha-munch-management/dict_methods_test.py +++ b/exercises/concept/mecha-munch-management/dict_methods_test.py @@ -54,7 +54,7 @@ def test_update_recipes(self): input_data = [ ({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, - (('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)), + (('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)), ({'Apple Pie': {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1}, 'Blueberry Pie': {'Blueberries': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, @@ -70,7 +70,7 @@ def test_update_recipes(self): ] output_data = [ - {'Banana Bread': {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, + {'Banana Bread': {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, 'Raspberry Pie': {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, {'Apple Pie': {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1}, 'Blueberry Pie': {'Blueberries': 2, 'Pie Crust': 1, 'Cream Custard': 1}}, From 951d4d35c1d65a77794ba8670a965ece31577f87 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 25 Aug 2024 09:45:40 -0700 Subject: [PATCH 149/277] [Meltdown Mitigation]: Correct Typo in introduction.md (#3758) great --> greater on line 53. --- exercises/concept/meltdown-mitigation/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/meltdown-mitigation/.docs/introduction.md b/exercises/concept/meltdown-mitigation/.docs/introduction.md index 0eb3d88ccde..39d402bc448 100644 --- a/exercises/concept/meltdown-mitigation/.docs/introduction.md +++ b/exercises/concept/meltdown-mitigation/.docs/introduction.md @@ -50,7 +50,7 @@ elif y > z: else: print("z is greater than x and y") ... ->>> z is great than x and y +>>> z is greater than x and y ``` [Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing: From a74dfbd3309bdbe8fc981e7e7bc9678c6828669a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 25 Aug 2024 13:10:05 -0700 Subject: [PATCH 150/277] Msc spelling and grammar fixes for concepts and concept exercises. (#3759) --- concepts/basics/introduction.md | 2 +- concepts/classes/about.md | 4 ++-- concepts/conditionals/about.md | 4 ++-- exercises/concept/black-jack/.docs/instructions.md | 8 ++++---- exercises/concept/ellens-alien-game/.docs/introduction.md | 2 +- .../concept/guidos-gorgeous-lasagna/.docs/introduction.md | 6 +++--- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/concepts/basics/introduction.md b/concepts/basics/introduction.md index dc062bc70da..2a874394ebb 100644 --- a/concepts/basics/introduction.md +++ b/concepts/basics/introduction.md @@ -89,7 +89,7 @@ def add_two_numbers(number_one, number_two): # the variable will also return the value. >>> sum_with_return = add_two_numbers(5, 6) >>> print(sum_with_return) -7 +11 ``` Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object. diff --git a/concepts/classes/about.md b/concepts/classes/about.md index f50af7321d3..f88ce892f3b 100644 --- a/concepts/classes/about.md +++ b/concepts/classes/about.md @@ -314,12 +314,12 @@ class MyClass: # This will compile and run without error, but has no current functionality. def pending_functionality(self): - # Stubbing or placholding the body of this method. + # Stubbing or place-holding the body of this method. pass ``` [class method]: https://stackoverflow.com/questions/17134653/difference-between-class-and-instance-methods -[dunder]: https://www.dataindependent.com/python/python-glossary/python-dunder/ +[dunder]: https://mathspp.com/blog/pydonts/dunder-methods [oop]: https://www.educative.io/blog/object-oriented-programming [dot notation]: https://stackoverflow.com/questions/45179186/understanding-the-dot-notation-in-python [shadowing]: https://oznetnerd.com/2017/07/17/python-shadowing/ diff --git a/concepts/conditionals/about.md b/concepts/conditionals/about.md index 8f0ae8fde08..5a518d3dd6d 100644 --- a/concepts/conditionals/about.md +++ b/concepts/conditionals/about.md @@ -92,13 +92,13 @@ Conditionals can also be nested. >>> driving_status(63, 78) -'Unlicsensed!' +'Unlicensed!' >>> driving_status(16, 81) 'Student driver, needs supervision.' >>> driving_status(23, 80) -'Fully licsensed driver.' +'Fully licensed driver.' ``` ## Conditional expressions or "ternary operators" diff --git a/exercises/concept/black-jack/.docs/instructions.md b/exercises/concept/black-jack/.docs/instructions.md index 21fc971e23b..e95c5fadb9f 100644 --- a/exercises/concept/black-jack/.docs/instructions.md +++ b/exercises/concept/black-jack/.docs/instructions.md @@ -79,15 +79,15 @@ Remember: the value of the hand with the ace needs to be as high as possible _wi ## 4. Determine a "Natural" or "Blackjack" Hand -If the first two cards a player is dealt are an ace (A) and a ten-card (_10, K , Q or J_), then the player has a score of 21. -This is known as a blackjack hand. +If a player is dealt an ace (`A`) and a ten-card (10, `K`, `Q`, or `J`) as their first two cards, then the player has a score of 21. +This is known as a **blackjack** hand. Define the `is_blackjack(, )` function with parameters `card_one` and `card_two`, which are a pair of cards. -Determine if the two-card hand is a `blackjack`, and return the boolean `True` if it is, `False` otherwise. +Determine if the two-card hand is a **blackjack**, and return the boolean `True` if it is, `False` otherwise. **Note** : The score _calculation_ can be done in many ways. -But if possible, we'd like you to check if there is an ace and a ten-card **_in_** the hand (or at a certain position), as opposed to _summing_ the hand values. +But if possible, we'd like you to check if there is an ace and a ten-card **_in_** the hand (_or at a certain position_), as opposed to _summing_ the hand values. ```python >>> is_blackjack('A', 'K') diff --git a/exercises/concept/ellens-alien-game/.docs/introduction.md b/exercises/concept/ellens-alien-game/.docs/introduction.md index ac99054de1d..ea1fc940fa4 100644 --- a/exercises/concept/ellens-alien-game/.docs/introduction.md +++ b/exercises/concept/ellens-alien-game/.docs/introduction.md @@ -261,7 +261,7 @@ class MyClass: [calling]: https://www.pythonmorsels.com/topics/calling-a-function [class method]: https://stackoverflow.com/questions/17134653/difference-between-class-and-instance-methods -[dunder]: https://www.dataindependent.com/python/python-glossary/python-dunder/ +[dunder]: https://mathspp.com/blog/pydonts/dunder-methods [imperative]: https://en.wikipedia.org/wiki/Imperative_programming [declarative]: https://en.wikipedia.org/wiki/Declarative_programming [oop]: https://www.digitalocean.com/community/tutorials/how-to-construct-classes-and-define-objects-in-python-3 diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index c3fe37541ed..ffe2bedd6a3 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -100,8 +100,8 @@ def add_two_numbers(number_one, number_two): >>> add_two_numbers(3, 4) 7 -# Assigning the function call to a variable and printing -# the variable will also return the value. +# Assigning the function call to a variable and printing it +# will also return the value. >>> sum_with_return = add_two_numbers(5, 6) >>> print(sum_with_return) 11 @@ -153,7 +153,7 @@ Dot (`.`) notation is used for calling functions defined inside a class or modul 27 -# A mis-match between the number of parameters and the number of arguments will raise an error. +# A mismatch between the number of parameters and the number of arguments will raise an error. >>> number_to_the_power_of(4,) ... Traceback (most recent call last): From 7ea631f4402bba683fabeba649a248ef946d76fd Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 26 Aug 2024 14:34:27 -0700 Subject: [PATCH 151/277] [Transpose & generate_tests.py]: Added `join` env Function & Changed Template (#3760) [no important files changed] * Added join input function and changed template and test file for transpose exercise. * Regenerated file to replace lines with text. --- bin/generate_tests.py | 3 + .../practice/transpose/.meta/template.j2 | 15 +- exercises/practice/transpose/transpose.py | 2 +- .../practice/transpose/transpose_test.py | 138 +++++++----------- 4 files changed, 64 insertions(+), 94 deletions(-) diff --git a/bin/generate_tests.py b/bin/generate_tests.py index a3089eb8b4b..2ad23a9b5f1 100755 --- a/bin/generate_tests.py +++ b/bin/generate_tests.py @@ -204,6 +204,8 @@ def regex_find(s: str, find: str) -> List[Any]: def regex_split(s: str, find: str) -> List[str]: return re.split(find, s) +def join_test_inputs(test_inputs: list) -> str: + return "\n".join(test_inputs) def filter_test_cases(cases: List[TypeJSON], opts: TestsTOML) -> List[TypeJSON]: """ @@ -409,6 +411,7 @@ def generate( env.filters["regex_replace"] = regex_replace env.filters["regex_find"] = regex_find env.filters["regex_split"] = regex_split + env.filters["join_test_inputs"] = join_test_inputs env.filters["zip"] = zip env.filters["parse_datetime"] = parse_datetime env.filters["escape_invalid_escapes"] = escape_invalid_escapes diff --git a/exercises/practice/transpose/.meta/template.j2 b/exercises/practice/transpose/.meta/template.j2 index e622947b106..debe8fd766d 100644 --- a/exercises/practice/transpose/.meta/template.j2 +++ b/exercises/practice/transpose/.meta/template.j2 @@ -5,9 +5,16 @@ class {{ exercise | camel_case }}Test(unittest.TestCase): {% for case in cases -%} - def test_{{ case["description"] | to_snake }}(self): - lines = {{ case["input"]["lines"] }} - expected = {{ case["expected"] }} - self.assertEqual({{ case["property"] }}("\n".join(lines)), "\n".join(expected)) + def test_{{ case["description"] | to_snake }}(self): + {%- if case["input"]["lines"] | length > 0 %} + text = "{{+ case["input"]["lines"] | join_test_inputs | replace('\n','\\n') }}" + expected = "{{+ case["expected"] | join_test_inputs | replace('\n','\\n') }}" + + {%- else %} + text = "" + expected = "" + {%- endif %} + + self.assertEqual({{ case["property"] }}(text), expected) {% endfor %} diff --git a/exercises/practice/transpose/transpose.py b/exercises/practice/transpose/transpose.py index d6814f837f2..24b60afe187 100644 --- a/exercises/practice/transpose/transpose.py +++ b/exercises/practice/transpose/transpose.py @@ -1,2 +1,2 @@ -def transpose(lines): +def transpose(text): pass diff --git a/exercises/practice/transpose/transpose_test.py b/exercises/practice/transpose/transpose_test.py index d3ab85ff9ff..220c8be4d04 100644 --- a/exercises/practice/transpose/transpose_test.py +++ b/exercises/practice/transpose/transpose_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/transpose/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2024-08-26 import unittest @@ -11,113 +11,73 @@ class TransposeTest(unittest.TestCase): def test_empty_string(self): - lines = [] - expected = [] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "" + expected = "" + + self.assertEqual(transpose(text), expected) def test_two_characters_in_a_row(self): - lines = ["A1"] - expected = ["A", "1"] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "A1" + expected = "A\n1" + + self.assertEqual(transpose(text), expected) def test_two_characters_in_a_column(self): - lines = ["A", "1"] - expected = ["A1"] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "A\n1" + expected = "A1" + + self.assertEqual(transpose(text), expected) def test_simple(self): - lines = ["ABC", "123"] - expected = ["A1", "B2", "C3"] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "ABC\n123" + expected = "A1\nB2\nC3" + + self.assertEqual(transpose(text), expected) def test_single_line(self): - lines = ["Single line."] - expected = ["S", "i", "n", "g", "l", "e", " ", "l", "i", "n", "e", "."] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "Single line." + expected = "S\ni\nn\ng\nl\ne\n \nl\ni\nn\ne\n." + + self.assertEqual(transpose(text), expected) def test_first_line_longer_than_second_line(self): - lines = ["The fourth line.", "The fifth line."] - expected = [ - "TT", - "hh", - "ee", - " ", - "ff", - "oi", - "uf", - "rt", - "th", - "h ", - " l", - "li", - "in", - "ne", - "e.", - ".", - ] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "The fourth line.\nThe fifth line." + expected = "TT\nhh\nee\n \nff\noi\nuf\nrt\nth\nh \n l\nli\nin\nne\ne.\n." + + self.assertEqual(transpose(text), expected) def test_second_line_longer_than_first_line(self): - lines = ["The first line.", "The second line."] - expected = [ - "TT", - "hh", - "ee", - " ", - "fs", - "ie", - "rc", - "so", - "tn", - " d", - "l ", - "il", - "ni", - "en", - ".e", - " .", - ] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "The first line.\nThe second line." + expected = "TT\nhh\nee\n \nfs\nie\nrc\nso\ntn\n d\nl \nil\nni\nen\n.e\n ." + + self.assertEqual(transpose(text), expected) def test_mixed_line_length(self): - lines = ["The longest line.", "A long line.", "A longer line.", "A line."] - expected = [ - "TAAA", - "h ", - "elll", - " ooi", - "lnnn", - "ogge", - "n e.", - "glr", - "ei ", - "snl", - "tei", - " .n", - "l e", - "i .", - "n", - "e", - ".", - ] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "The longest line.\nA long line.\nA longer line.\nA line." + expected = "TAAA\nh \nelll\n ooi\nlnnn\nogge\nn e.\nglr\nei \nsnl\ntei\n .n\nl e\ni .\nn\ne\n." + + self.assertEqual(transpose(text), expected) def test_square(self): - lines = ["HEART", "EMBER", "ABUSE", "RESIN", "TREND"] - expected = ["HEART", "EMBER", "ABUSE", "RESIN", "TREND"] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "HEART\nEMBER\nABUSE\nRESIN\nTREND" + expected = "HEART\nEMBER\nABUSE\nRESIN\nTREND" + + self.assertEqual(transpose(text), expected) def test_rectangle(self): - lines = ["FRACTURE", "OUTLINED", "BLOOMING", "SEPTETTE"] - expected = ["FOBS", "RULE", "ATOP", "CLOT", "TIME", "UNIT", "RENT", "EDGE"] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "FRACTURE\nOUTLINED\nBLOOMING\nSEPTETTE" + expected = "FOBS\nRULE\nATOP\nCLOT\nTIME\nUNIT\nRENT\nEDGE" + + self.assertEqual(transpose(text), expected) def test_triangle(self): - lines = ["T", "EE", "AAA", "SSSS", "EEEEE", "RRRRRR"] - expected = ["TEASER", " EASER", " ASER", " SER", " ER", " R"] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "T\nEE\nAAA\nSSSS\nEEEEE\nRRRRRR" + expected = "TEASER\n EASER\n ASER\n SER\n ER\n R" + + self.assertEqual(transpose(text), expected) def test_jagged_triangle(self): - lines = ["11", "2", "3333", "444", "555555", "66666"] - expected = ["123456", "1 3456", " 3456", " 3 56", " 56", " 5"] - self.assertEqual(transpose("\n".join(lines)), "\n".join(expected)) + text = "11\n2\n3333\n444\n555555\n66666" + expected = "123456\n1 3456\n 3456\n 3 56\n 56\n 5" + + self.assertEqual(transpose(text), expected) From 57f11c2243818269a000b7da2e218cb4f17c8d01 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 26 Aug 2024 14:59:10 -0700 Subject: [PATCH 152/277] [POV]: Update instructions.append.md (#3761) Massaged the phrasing a bit to point students to both raising the specific error type, and including a specific error message. --- exercises/practice/pov/.docs/instructions.append.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/practice/pov/.docs/instructions.append.md b/exercises/practice/pov/.docs/instructions.append.md index 024842736aa..7b16ebbe897 100644 --- a/exercises/practice/pov/.docs/instructions.append.md +++ b/exercises/practice/pov/.docs/instructions.append.md @@ -4,6 +4,7 @@ Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. -This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" multiple `ValueErrors` if the `Tree()` class is passed a tree that cannot be reoriented, or a path cannot be found between a `start node` and an `end node`. The tests will only pass if you both `raise` the `exception` and include a message with it. +This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" multiple `ValueErrors` if the `Tree()` class is passed a tree that cannot be reoriented, or a path cannot be found between a `start node` and an `end node`. +The tests will only pass if you both `raise` the expected `exception` type and include the expected message with it. Please check the tests and their expected results carefully. From e4bb420ff57c23fd41e9d1ed6f08f6d340ab845b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 29 Aug 2024 13:41:05 -0700 Subject: [PATCH 153/277] Adjusted if-elif-else examples and hints. (#3764) --- concepts/conditionals/about.md | 51 ++++++++++++++----- concepts/conditionals/introduction.md | 13 +++-- .../meltdown-mitigation/.docs/hints.md | 6 ++- .../meltdown-mitigation/.docs/introduction.md | 11 ++-- .../meltdown-mitigation/.meta/exemplar.py | 1 - 5 files changed, 55 insertions(+), 27 deletions(-) diff --git a/concepts/conditionals/about.md b/concepts/conditionals/about.md index 5a518d3dd6d..e5a74fb5270 100644 --- a/concepts/conditionals/about.md +++ b/concepts/conditionals/about.md @@ -8,7 +8,6 @@ Python 3.10 introduces a variant case-switch statement called `pattern matching` Conditional statements use expressions that must resolve to `True` or `False` -- either by returning a `bool` directly, or by evaluating ["truthy" or "falsy"][truth value testing]. - ```python x = 10 y = 5 @@ -61,13 +60,15 @@ else: >>> def classic_fizzbuzz(number): if number % 3 == 0 and number % 5 == 0: - return 'FizzBuzz!' + say = 'FizzBuzz!' elif number % 5 == 0: - return 'Buzz!' + say = 'Buzz!' elif number % 3 == 0: - return 'Fizz!' + say = 'Fizz!' else: - return str(number) + say = str(number) + + return say >>> classic_fizzbuzz(15) 'FizzBuzz!' @@ -76,19 +77,44 @@ else: '13' ``` +As an alternative, the example above can be re-written to only use `if` statements with `returns`. +However, re-writing in this way might obscure that the conditions are intended to be [_mutually exclusive_][mutually-exclusive] and could lead to future bugs or maintenance issues. + + +```python +>>> def classic_fizzbuzz(number): + if number % 3 == 0 and number % 5 == 0: + return 'FizzBuzz!' + if number % 5 == 0: + return 'Buzz!' + if number % 3 == 0: + return 'Fizz!' + + return str(number) + +>>> classic_fizzbuzz(15) +'FizzBuzz!' + +>>> classic_fizzbuzz(13) +'13' +``` + + Conditionals can also be nested. ```python >>> def driving_status(driver_age, test_score): if test_score >= 80: if 18 > driver_age >= 16: - return "Student driver, needs supervision." + status = "Student driver, needs supervision." elif driver_age == 18: - return "Permitted driver, on probation." + satus = "Permitted driver, on probation." elif driver_age > 18: - return "Fully licensed driver." + status = "Fully licensed driver." else: - return "Unlicensed!" + status = "Unlicensed!" + + return status >>> driving_status(63, 78) @@ -151,8 +177,9 @@ This is Truthy. Nope. It's Falsey. ``` -[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement -[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools -[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing [boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not [comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons +[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools +[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement +[mutually-exclusive]: https://stackoverflow.com/a/22783232 +[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing diff --git a/concepts/conditionals/introduction.md b/concepts/conditionals/introduction.md index 77bf9608ed8..ee1d4336207 100644 --- a/concepts/conditionals/introduction.md +++ b/concepts/conditionals/introduction.md @@ -45,10 +45,8 @@ z = 20 # The elif statement allows for the checking of more conditions. if x > y > z: - print("x is greater than y and z") elif y > x > z: - print("y is greater than x and z") else: print("z is greater than x and y") @@ -59,16 +57,17 @@ else: [Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing: ```python - >>> def classic_fizzbuzz(number): if number % 3 == 0 and number % 5 == 0: - return 'FizzBuzz!' + say = 'FizzBuzz!' elif number % 5 == 0: - return 'Buzz!' + say = 'Buzz!' elif number % 3 == 0: - return 'Fizz!' + say = 'Fizz!' else: - return str(number) + say = str(number) + + return say >>> classic_fizzbuzz(15) 'FizzBuzz!' diff --git a/exercises/concept/meltdown-mitigation/.docs/hints.md b/exercises/concept/meltdown-mitigation/.docs/hints.md index e500bb4dcdf..e0d1fe0ffb7 100644 --- a/exercises/concept/meltdown-mitigation/.docs/hints.md +++ b/exercises/concept/meltdown-mitigation/.docs/hints.md @@ -30,13 +30,15 @@ - Comparison operators can be combined and used with conditionals. - Any number of `elif` statements can be used as decision "branches". -- Each "branch" can have a separate `return` +- Each "branch" can have a separate `return`, although it might be considered "bad form" by linting tools. +- If the linter complains, consider assigning the output of a branch to a common variable, and then `return`ing that variable. ## 3. Fail Safe Mechanism - Comparison operators can be combined and used with conditionals. - Any number of `elif` statements can be used as decision "branches". -- Each "branch" can have a separate `return` +- Each "branch" can have a separate `return`, although it might be considered "bad form" by linting tools. +- If the linter complains, consider assigning the output of a branch to a common variable, and then `return`ing that variable. [boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not diff --git a/exercises/concept/meltdown-mitigation/.docs/introduction.md b/exercises/concept/meltdown-mitigation/.docs/introduction.md index 39d402bc448..c084236abc8 100644 --- a/exercises/concept/meltdown-mitigation/.docs/introduction.md +++ b/exercises/concept/meltdown-mitigation/.docs/introduction.md @@ -56,16 +56,17 @@ else: [Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing: ```python - >>> def classic_fizzbuzz(number): if number % 3 == 0 and number % 5 == 0: - return 'FizzBuzz!' + say = 'FizzBuzz!' elif number % 5 == 0: - return 'Buzz!' + say = 'Buzz!' elif number % 3 == 0: - return 'Fizz!' + say = 'Fizz!' else: - return str(number) + say = str(number) + + return say >>> classic_fizzbuzz(15) 'FizzBuzz!' diff --git a/exercises/concept/meltdown-mitigation/.meta/exemplar.py b/exercises/concept/meltdown-mitigation/.meta/exemplar.py index 54cb7d5ce70..c2d8ddd7ede 100644 --- a/exercises/concept/meltdown-mitigation/.meta/exemplar.py +++ b/exercises/concept/meltdown-mitigation/.meta/exemplar.py @@ -45,7 +45,6 @@ def reactor_efficiency(voltage, current, theoretical_max_power): generated_power = voltage * current percentage_range = (generated_power / theoretical_max_power) * 100 - efficiency_level = 'unknown' if 80 <= percentage_range <= 100: efficiency_level = 'green' From ee643b03e31a3e2e064990c7eefc926a85d4cad0 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 29 Aug 2024 14:45:18 -0700 Subject: [PATCH 154/277] [Conditionals Concept] Update about.md (#3765) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TYPO! 😡 --- concepts/conditionals/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/conditionals/about.md b/concepts/conditionals/about.md index e5a74fb5270..d0b91f26ce6 100644 --- a/concepts/conditionals/about.md +++ b/concepts/conditionals/about.md @@ -108,7 +108,7 @@ Conditionals can also be nested. if 18 > driver_age >= 16: status = "Student driver, needs supervision." elif driver_age == 18: - satus = "Permitted driver, on probation." + status = "Permitted driver, on probation." elif driver_age > 18: status = "Fully licensed driver." else: From 6b8660a7a3b266eaeb65d54686127942e4a53ce8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Sep 2024 05:49:20 -0700 Subject: [PATCH 155/277] Bump actions/setup-python from 5.1.1 to 5.2.0 (#3766) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.1.1 to 5.2.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/39cd14951b08e74b54015e9e001cdefcf80e669f...f677139bbe7f9c59b41e40162b753c062f5d49a3) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 72be71f7028..a2257ad1734 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - name: Set up Python - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: python-version: ${{ matrix.python-version }} From 63fffc04e92999faddb4eba0168f91968ac940cc Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Wed, 4 Sep 2024 15:03:30 +0100 Subject: [PATCH 156/277] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#3770)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/fc1613760f6670850e29a593bbb5c9669edc23bd --- .../ping-cross-track-maintainers-team.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/ping-cross-track-maintainers-team.yml diff --git a/.github/workflows/ping-cross-track-maintainers-team.yml b/.github/workflows/ping-cross-track-maintainers-team.yml new file mode 100644 index 00000000000..b6ec9c5662f --- /dev/null +++ b/.github/workflows/ping-cross-track-maintainers-team.yml @@ -0,0 +1,16 @@ +name: Ping cross-track maintainers team + +on: + pull_request_target: + types: + - opened + +permissions: + pull-requests: write + +jobs: + ping: + if: github.repository_owner == 'exercism' # Stops this job from running on forks + uses: exercism/github-actions/.github/workflows/ping-cross-track-maintainers-team.yml@main + secrets: + github_membership_token: ${{ secrets.COMMUNITY_CONTRIBUTIONS_WORKFLOW_TOKEN }} From b061dd047fcd0cf851d90cf6888868323aea4db4 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 4 Sep 2024 16:05:04 +0200 Subject: [PATCH 157/277] Change the interval for dependabot updates to monthly (#3769) --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ed8f4a432bc..234b07e766c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,4 +6,4 @@ updates: - package-ecosystem: 'github-actions' directory: '/' schedule: - interval: 'daily' + interval: 'monthly' From b4391bcb77d28f261e45807f3b7adb4b2e086a2f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 14 Sep 2024 16:11:14 -0700 Subject: [PATCH 158/277] Fixed broken linkes in hints file. (#3773) --- exercises/concept/cater-waiter/.docs/hints.md | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/exercises/concept/cater-waiter/.docs/hints.md b/exercises/concept/cater-waiter/.docs/hints.md index 89f51753bca..c8b5c0badef 100644 --- a/exercises/concept/cater-waiter/.docs/hints.md +++ b/exercises/concept/cater-waiter/.docs/hints.md @@ -10,52 +10,52 @@ ## 1. Clean up Dish Ingredients -- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable. -- Remember: [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can be formed using `(, )` or via the `tuple()` constructor. +- The `set()` constructor can take any [iterable][iterable] as an argument. [concept: lists](/tracks/python/concepts/lists) are iterable. +- Remember: [concept: tuples](/tracks/python/concepts/tuples) can be formed using `(, )` or via the `tuple()` constructor. ## 2. Cocktails and Mocktails - A `set` is _disjoint_ from another set if the two sets share no elements. -- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable. -- In Python, [strings:python/strings](https://exercism.lol/tracks/python/concepts/strings) can be concatenated with the `+` sign. +- The `set()` constructor can take any [iterable][iterable] as an argument. [concept: lists](/tracks/python/concepts/lists) are iterable. +- In Python, [concept: strings](/tracks/python/concepts/strings) can be concatenated with the `+` sign. ## 3. Categorize Dishes -- Using [loops:python/loops](https://exercism.lol/tracks/python/concepts/loops) to iterate through the available meal categories might be useful here. +- Using [concept: loops](/tracks/python/concepts/loops) to iterate through the available meal categories might be useful here. - If all the elements of `` are contained within ``, then ` <= `. - The method equivalent of `<=` is `.issubset()` -- [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can contain any data type, including other tuples. Tuples can be formed using `(, )` or via the `tuple()` constructor. -- Elements within [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can be accessed from the left using a 0-based index number, or from the right using a -1-based index number. -- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable. -- [strings:python/strings](https://exercism.lol/tracks/python/concepts/strings) can be concatenated with the `+` sign. +- [concept: tuples](/tracks/python/concepts/tuples) can contain any data type, including other tuples. Tuples can be formed using `(, )` or via the `tuple()` constructor. +- Elements within [concept: tuples](/tracks/python/concepts/tuples) can be accessed from the left using a 0-based index number, or from the right using a -1-based index number. +- The `set()` constructor can take any [iterable][iterable] as an argument. [concept: lists](/tracks/python/concepts/lists) are iterable. +- [concept: strings](/tracks/python/concepts/strings) can be concatenated with the `+` sign. ## 4. Label Allergens and Restricted Foods - A set _intersection_ are the elements shared between `` and ``. - The set method equivalent of `&` is `.intersection()` -- Elements within [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can be accessed from the left using a 0-based index number, or from the right using a -1-based index number. -- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable. -- [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can be formed using `(, )` or via the `tuple()` constructor. +- Elements within [concept: tuples](/tracks/python/concepts/tuples) can be accessed from the left using a 0-based index number, or from the right using a -1-based index number. +- The `set()` constructor can take any [iterable][iterable] as an argument. [concept: lists](/tracks/python/concepts/lists) are iterable. +- [concept: tuples](/tracks/python/concepts/tuples) can be formed using `(, )` or via the `tuple()` constructor. ## 5. Compile a "Master List" of Ingredients - A set _union_ is where ` and `` are combined into a single `set` - The set method equivalent of `|` is `.union()` -- Using [loops:python/loops](https://exercism.lol/tracks/python/concepts/loops) to iterate through the various dishes might be useful here. +- Using [concept: loops](/tracks/python/concepts/loops) to iterate through the various dishes might be useful here. ## 6. Pull out Appetizers for Passing on Trays - A set _difference_ is where the elements of `` are removed from ``, e.g. ` - `. - The set method equivalent of `-` is `.difference()` -- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable. -- The [list:python/lists](https://exercism.lol/tracks/python/concepts/lists) constructor can take any [iterable][iterable] as an argument. Sets are iterable. +- The `set()` constructor can take any [iterable][iterable] as an argument. [concept: lists](/tracks/python/concepts/lists) are iterable. +- The [concept: list](/tracks/python/concepts/lists) constructor can take any [iterable][iterable] as an argument. Sets are iterable. ## 7. Find Ingredients Used in Only One Recipe - A set _symmetric difference_ is where elements appear in `` or ``, but not **_both_** sets. - A set _symmetric difference_ is the same as subtracting the `set` _intersection_ from the `set` _union_, e.g. `( | ) - ( & )` - A _symmetric difference_ of more than two `sets` will include elements that are repeated more than two times across the input `sets`. To remove these cross-set repeated elements, the _intersections_ between set pairs needs to be subtracted from the symmetric difference. -- Using [loops:python/loops](https://exercism.lol/tracks/python/concepts/loops) to iterate through the various dishes might be useful here. +- Using [concept: loops](/tracks/python/concepts/loops) to iterate through the various dishes might be useful here. [hashable]: https://docs.python.org/3.7/glossary.html#term-hashable From c4925ea95235d0e968e80675878948f3676e6dc8 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 17 Sep 2024 14:05:28 -0700 Subject: [PATCH 159/277] Modified instructions to use add_item instead of add_items. (#3774) --- .../concept/mecha-munch-management/.docs/instructions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/concept/mecha-munch-management/.docs/instructions.md b/exercises/concept/mecha-munch-management/.docs/instructions.md index 13d88f39fa2..802366aab45 100644 --- a/exercises/concept/mecha-munch-management/.docs/instructions.md +++ b/exercises/concept/mecha-munch-management/.docs/instructions.md @@ -13,15 +13,15 @@ If a user wants to add 2 Oranges, 'Oranges' will appear twice in the input itera If the user already has the item in their cart, the cart quantity should be increased by 1. If the item is _new_ to the cart, it should be added with a quantity of 1. -Create the function `add_items(, )` that takes a cart dictionary and any list-like iterable of items to add as arguments. +Create the function `add_item(, )` that takes a cart dictionary and any list-like iterable of items to add as arguments. It should return a new/updated shopping cart dictionary for the user. ```python ->>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, +>>> add_item({'Banana': 3, 'Apple': 2, 'Orange': 1}, ('Apple', 'Apple', 'Orange', 'Apple', 'Banana')) {'Banana': 4, 'Apple': 5, 'Orange': 2} ->>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, +>>> add_item({'Banana': 3, 'Apple': 2, 'Orange': 1}, ['Banana', 'Orange', 'Blueberries', 'Banana']) {'Banana': 5, 'Apple': 2, 'Orange': 2, 'Blueberries': 1} ``` From 6c78db5cc71e319862daed33460006f44199d53c Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 24 Sep 2024 14:50:37 -0700 Subject: [PATCH 160/277] Add variable defs to cater-waiter introduction. (#3775) --- .../cater-waiter/.docs/introduction.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index 94a0b4c7669..02addaab967 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -126,6 +126,27 @@ There is no operator equivalent: ```python +# Both mammals and additional_animals are lists. +>>> mammals = ['squirrel','dog','cat','cow', 'tiger', 'elephant'] +>>> additional_animals = ['pangolin', 'panda', 'parrot', + 'lemur', 'tiger', 'pangolin'] + +# Animals is a dict. +>>> animals = {'chicken': 'white', + 'sparrow': 'grey', + 'eagle': 'brown and white', + 'albatross': 'grey and white', + 'crow': 'black', + 'elephant': 'grey', + 'dog': 'rust', + 'cow': 'black and white', + 'tiger': 'orange and black', + 'cat': 'grey', + 'squirrel': 'black'} + +# Birds is a set. +>>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} + # Mammals and birds don't share any elements. >>> birds.isdisjoint(mammals) True From 2a09812b26577fd37fc4664630a78282af8b5a60 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 27 Sep 2024 06:29:41 -0700 Subject: [PATCH 161/277] corrected refrence to python internal sort method from timsort to powersort. (#3776) --- concepts/list-methods/about.md | 23 +++++++++++++++---- .../.docs/introduction.md | 13 +++++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/concepts/list-methods/about.md b/concepts/list-methods/about.md index 3a5132fd0ad..f0eb704808d 100644 --- a/concepts/list-methods/about.md +++ b/concepts/list-methods/about.md @@ -136,10 +136,22 @@ The order of list elements can be reversed _**in place**_ with `.reverse( [3, 2, 1] ``` -List elements can be sorted _**in place**_ using `.sort()`. - Internally, Python uses [`Timsort`][timsort] to arrange the elements. - The default order is _ascending_. - The Python docs have [additional tips and techniques for sorting][sorting how to] `lists` effectively. +A list can be re-ordered _**in place**_ with the help of `.sort()`. +Default sort order is _ascending_ from the left. +The Python docs offer [additional tips and techniques for sorting][sorting how to] lists effectively. + + +~~~~exercism/note + From 2002 to 2022, Python used an algorithm called [`Timsort`][timsort] internally to arrange lists, but switched to [`Powersort`][powersort] from `Python 3.11` onward. +You can read more details and discussion on the change from the core Python team in the GitHub [issue 78742][78742]. + +For technical details on the algorithm, see the J. Ian Munro and Sebastian Wild paper [Nearly-Optimal Mergesorts: Fast, Practical Sorting Methods That Optimally Adapt to Existing Runs][nearly-optimal-mergesorts] + +[78742]: https://github.com/python/cpython/issues/78742 +[nearly-optimal-mergesorts]: https://arxiv.org/abs/1805.04154 +[powersort]: https://www.wild-inter.net/publications/munro-wild-2018 +[timsort]: https://en.wikipedia.org/wiki/Timsort +~~~~ ```python @@ -256,5 +268,6 @@ For a detailed explanation of names, values, list, and nested list behavior, tak [slice notation]: https://docs.python.org/3/reference/expressions.html#slicings [sorted]: https://docs.python.org/3/library/functions.html#sorted [sorting how to]: https://docs.python.org/3/howto/sorting.html -[timsort]: https://en.wikipedia.org/wiki/Timsort [tuple]: https://docs.python.org/3/library/stdtypes.html#tuple + + diff --git a/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md b/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md index 764de5fc54d..ce593407a57 100644 --- a/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md +++ b/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md @@ -151,9 +151,15 @@ The `.reverse()` method will reverse the order of elements **in-place**. A list can be re-ordered _**in place**_ with the help of `.sort()`. - Internally, Python uses [`Timsort`][timsort] to arrange the list. - Default order is _ascending_ from the left. - The Python docs offer [additional tips and techniques for sorting][sorting how to] lists effectively. +Default sort order is _ascending_ from the left. +The Python docs offer [additional tips and techniques for sorting][sorting how to] lists effectively. + +~~~~exercism/note + From 2002 to 2022, Python used an algorithm called [`Timsort`][timsort] internally to arrange lists, but switched to [`Powersort`][powersort] from `Python 3.11` onward. + +[powersort]: https://www.wild-inter.net/publications/munro-wild-2018 +[timsort]: https://en.wikipedia.org/wiki/Timsort +~~~~ ```python @@ -244,5 +250,4 @@ ValueError: 10 is not in list [slice notation]: https://docs.python.org/3/reference/expressions.html#slicings [sorted]: https://docs.python.org/3/library/functions.html#sorted [sorting how to]: https://docs.python.org/3/howto/sorting.html -[timsort]: https://en.wikipedia.org/wiki/Timsort [tuple]: https://docs.python.org/3/library/stdtypes.html#tuple From 7e38ff11718198bd95d758b2b2bba11db32eef4f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 27 Sep 2024 09:17:15 -0700 Subject: [PATCH 162/277] [Chaitanas Colossal Coaster]: Removed "effectively" from HOWTO sentence. (#3777) * Removed effectively from HOWTO sentence. * Resorted reflinks. --- concepts/list-methods/about.md | 7 +++---- .../chaitanas-colossal-coaster/.docs/introduction.md | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/concepts/list-methods/about.md b/concepts/list-methods/about.md index f0eb704808d..1c9686360d4 100644 --- a/concepts/list-methods/about.md +++ b/concepts/list-methods/about.md @@ -136,9 +136,9 @@ The order of list elements can be reversed _**in place**_ with `.reverse( [3, 2, 1] ``` -A list can be re-ordered _**in place**_ with the help of `.sort()`. +A list can be re-ordered _**in place**_ with the help of [`.sort()`][sort]. Default sort order is _ascending_ from the left. -The Python docs offer [additional tips and techniques for sorting][sorting how to] lists effectively. +The Python docs offer [additional tips and techniques for sorting][sorting how to]. ~~~~exercism/note @@ -266,8 +266,7 @@ For a detailed explanation of names, values, list, and nested list behavior, tak [set]: https://docs.python.org/3/library/stdtypes.html#set [shallow vs deep]: https://realpython.com/copying-python-objects/ [slice notation]: https://docs.python.org/3/reference/expressions.html#slicings +[sort]: https://docs.python.org/3/library/stdtypes.html#list.sort [sorted]: https://docs.python.org/3/library/functions.html#sorted [sorting how to]: https://docs.python.org/3/howto/sorting.html [tuple]: https://docs.python.org/3/library/stdtypes.html#tuple - - diff --git a/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md b/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md index ce593407a57..bfbf0537522 100644 --- a/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md +++ b/exercises/concept/chaitanas-colossal-coaster/.docs/introduction.md @@ -150,9 +150,9 @@ The `.reverse()` method will reverse the order of elements **in-place**. ``` -A list can be re-ordered _**in place**_ with the help of `.sort()`. +A list can be re-ordered _**in place**_ with the help of [`.sort()`][sort]. Default sort order is _ascending_ from the left. -The Python docs offer [additional tips and techniques for sorting][sorting how to] lists effectively. +The Python docs offer [additional tips and techniques for sorting][sorting how to]. ~~~~exercism/note From 2002 to 2022, Python used an algorithm called [`Timsort`][timsort] internally to arrange lists, but switched to [`Powersort`][powersort] from `Python 3.11` onward. @@ -239,7 +239,6 @@ ValueError: 10 is not in list 3 ``` - [common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations [dict]: https://docs.python.org/3/library/stdtypes.html#dict [list-methods]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists @@ -248,6 +247,7 @@ ValueError: 10 is not in list [sequence type]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range [set]: https://docs.python.org/3/library/stdtypes.html#set [slice notation]: https://docs.python.org/3/reference/expressions.html#slicings +[sort]: https://docs.python.org/3/library/stdtypes.html#list.sort [sorted]: https://docs.python.org/3/library/functions.html#sorted [sorting how to]: https://docs.python.org/3/howto/sorting.html [tuple]: https://docs.python.org/3/library/stdtypes.html#tuple From b6b3c4b64928901699ed5f87008614a1307b0231 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:08:47 -0700 Subject: [PATCH 163/277] Bump actions/checkout from 4.1.7 to 4.2.0 (#3778) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.7 to 4.2.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/692973e3d937129bcbf40652eb9f2f61becf3332...d632683dd7b4114ad314bca15554477dd762a938) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index a2257ad1734..c895b2ae2a2 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Set up Python uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index f8d9227f3c0..d4434c808b0 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index b9600cc8b9a..9faae798e25 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 - name: Run test-runner run: docker compose run test-runner From 6a2ddfaae786156329599c56e2ccccf9e077bc98 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 7 Oct 2024 16:45:59 -0700 Subject: [PATCH 164/277] Typos and emdash changes. (#3780) --- concepts/sets/about.md | 6 +++--- concepts/sets/introduction.md | 4 ++-- exercises/concept/cater-waiter/.docs/instructions.md | 4 ++-- exercises/concept/cater-waiter/.docs/introduction.md | 8 ++++---- exercises/concept/cater-waiter/sets.py | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/concepts/sets/about.md b/concepts/sets/about.md index 321c5116c9a..058be5c7def 100644 --- a/concepts/sets/about.md +++ b/concepts/sets/about.md @@ -1,8 +1,8 @@ # Sets A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. -Set members must be distinct -- duplicate items are not allowed. -They can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. +Set members must be distinct — duplicate items are not allowed. +They can hold multiple different data types and even nested structures like a `tuple` of `tuples` — as long as all elements can be _hashed_. Sets also come in an immutable [`frozensets`][type-frozenset] flavor. Sets are most commonly used to quickly remove duplicates from other data structures or item groupings. @@ -360,7 +360,7 @@ The operator version of this method is ` - - - 'Goose Berries', 'Strawberries'} # Operators require sets. ->>> berries_and_veggies - just_berries +>>> berries_and_veggies - berries {'Artichokes','Asparagus','Broccoli','Kale', 'Ramps','Rhubarb','Walking Onions','Watercress'} ``` diff --git a/concepts/sets/introduction.md b/concepts/sets/introduction.md index 4c264f0903e..5d66b6a8ad8 100644 --- a/concepts/sets/introduction.md +++ b/concepts/sets/introduction.md @@ -1,8 +1,8 @@ # Sets A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. -Set members must be distinct -- duplicate items are not allowed. -They can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. +Set members must be distinct — duplicate items are not allowed. +They can hold multiple different data types and even nested structures like a `tuple` of `tuples` — as long as all elements can be _hashed_. Sets also come in an immutable [`frozensets`][type-frozenset] flavor. Sets are most commonly used to quickly remove duplicates from other data structures or item groupings. diff --git a/exercises/concept/cater-waiter/.docs/instructions.md b/exercises/concept/cater-waiter/.docs/instructions.md index 696883c3c93..b0a9e1f855e 100644 --- a/exercises/concept/cater-waiter/.docs/instructions.md +++ b/exercises/concept/cater-waiter/.docs/instructions.md @@ -4,7 +4,7 @@ You and your business partners operate a small catering company. You've just agr ## 1. Clean up Dish Ingredients -The event recipes were added from various sources and their ingredients appear to have duplicate (_or more_) entries -- you don't want to end up purchasing excess items! +The event recipes were added from various sources and their ingredients appear to have duplicate (_or more_) entries — you don't want to end up purchasing excess items! Before the shopping and cooking can commence, each dish's ingredient list needs to be "cleaned". Implement the `clean_ingredients(, )` function that takes the name of a dish and a `list` of ingredients. @@ -138,5 +138,5 @@ from sets_categories_data import example_dishes, EXAMPLE_INTERSECTION >>> singleton_ingredients(example_dishes, EXAMPLE_INTERSECTION) ... -{'vegetable oil', 'vegetable stock', 'barley malt', 'tofu', 'fresh basil', 'lemon', 'ginger', 'honey', 'spaghetti', 'cornstarch', 'yeast', 'red onion', 'breadcrumbs', 'mixed herbs', 'garlic powder', 'celeriac', 'lemon zest', 'sunflower oil', 'mushrooms', 'silken tofu', 'smoked tofu', 'bell pepper', 'cashews', 'oregano', 'tomatoes', 'parsley', 'red pepper flakes', 'rosemary'} +{'garlic powder', 'sunflower oil', 'mixed herbs', 'cornstarch', 'celeriac', 'honey', 'mushrooms', 'bell pepper', 'rosemary', 'parsley', 'lemon', 'yeast', 'vegetable oil', 'vegetable stock', 'silken tofu', 'tofu', 'cashews', 'lemon zest', 'smoked tofu', 'spaghetti', 'ginger', 'breadcrumbs', 'tomatoes', 'barley malt', 'red pepper flakes', 'oregano', 'red onion', 'fresh basil'} ``` diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index 02addaab967..6a7993bd8a6 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -1,8 +1,8 @@ # Sets -A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. -Set members must be distinct -- duplicate items are not allowed. -They can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. +A [`set`][type-set] is a _mutable_ and unordered_ collection of [_hashable_][hashable] objects. +Set members must be distinct — duplicate items are not allowed. +They can hold multiple different data types and even nested structures like a `tuple` of `tuples` — as long as all elements can be _hashed_. Sets also come in an immutable [`frozensets`][type-frozenset] flavor. Sets are most commonly used to quickly remove duplicates from other data structures or item groupings. @@ -332,7 +332,7 @@ The operator version of this method is ` - - - 'Goose Berries', 'Strawberries'} # Operators require sets. ->>> berries_and_veggies - just_berries +>>> berries_and_veggies - berries {'Artichokes','Asparagus','Broccoli','Kale', 'Ramps','Rhubarb','Walking Onions','Watercress'} ``` diff --git a/exercises/concept/cater-waiter/sets.py b/exercises/concept/cater-waiter/sets.py index b0202e6a5fb..e726e3d6646 100644 --- a/exercises/concept/cater-waiter/sets.py +++ b/exercises/concept/cater-waiter/sets.py @@ -43,7 +43,7 @@ def categorize_dish(dish_name, dish_ingredients): """Categorize `dish_name` based on `dish_ingredients`. :param dish_name: str - dish to be categorized. - :param dish_ingredients: list - ingredients for the dish. + :param dish_ingredients: set - ingredients for the dish. :return: str - the dish name appended with ": ". This function should return a string with the `dish name: ` (which meal category the dish belongs to). From b13c61ea36aac201be76c0ec6279b44370e74071 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 8 Oct 2024 00:50:36 -0700 Subject: [PATCH 165/277] Added missing italics underscore back into file. (#3781) --- exercises/concept/cater-waiter/.docs/introduction.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index 6a7993bd8a6..044432b5c2a 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -1,6 +1,7 @@ # Sets -A [`set`][type-set] is a _mutable_ and unordered_ collection of [_hashable_][hashable] objects. + +A [set][type-set] is a _mutable_ and _unordered_ collection of [_hasable_][hashable] objects. Set members must be distinct — duplicate items are not allowed. They can hold multiple different data types and even nested structures like a `tuple` of `tuples` — as long as all elements can be _hashed_. Sets also come in an immutable [`frozensets`][type-frozenset] flavor. From 3dcbf4cb7cb73af4eef54a07fa03f951d38fa16b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 8 Oct 2024 12:02:33 -0700 Subject: [PATCH 166/277] Fixed dict.items refrences and text. (#3782) --- .../about.md | 17 ++++++------- .../locomotive-engineer/.docs/introduction.md | 24 +++++++++++-------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index 1cec2f92ec9..787e2ef08e8 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -129,9 +129,9 @@ ValueError: too many values to unpack (expected 1) ### Unpacking a list/tuple with `*` -When [unpacking a `list`/`tuple`][packing and unpacking] you can use the `*` operator to capture the "leftover" values. +When [unpacking a `list`/`tuple`][packing and unpacking] you can use the `*` operator to capture "leftover" values. This is clearer than slicing the `list`/`tuple` (_which in some situations is less readable_). -For example, we can extract the first element and then assign the remaining values into a new `list` without the first element: +For example, we can extract the first element and pack the remaining values into a new `list` without the first element: ```python >>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"] @@ -169,7 +169,7 @@ We can also use `*` in deep unpacking: ### Unpacking a dictionary -[Unpacking a dictionary][packing and unpacking] is a bit different than unpacking a `list`/`tuple`. +[Unpacking a dictionary][packing and unpacking] is a bit different from unpacking a `list`/`tuple`. Iteration over dictionaries defaults to the **keys**. So when unpacking a `dict`, you can only unpack the **keys** and not the **values**: @@ -180,7 +180,7 @@ So when unpacking a `dict`, you can only unpack the **keys** and not the **value "apple" ``` -If you want to unpack the values then you can use the `values()` method: +If you want to unpack the values then you can use the `.values()` method: ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} @@ -189,9 +189,9 @@ If you want to unpack the values then you can use the `values()` method: 6 ``` -If both **keys** and **values** are needed, use the `items()` method. -Using `items()` will generate tuples with **key-value** pairs. -This is because of [`dict.items()` generates an iterable with key-value `tuples`][items]. +If both **keys** and **values** are needed, use the [`.items()`][items] method. +`.items()` generates an [iterable view][view-objects] containing **key-value** pairs. +These can be unpacked into a `tuple`: ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} @@ -367,8 +367,9 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the ``` [args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ -[items]: https://www.geeksforgeeks.org/python-dictionary-items-method/ +[items]: https://docs.python.org/3/library/stdtypes.html#dict.items [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ [packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/ [sorting algorithms]: https://realpython.com/sorting-algorithms-python/ [unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp +[view-objects]: https://docs.python.org/3/library/stdtypes.html#dict-views diff --git a/exercises/concept/locomotive-engineer/.docs/introduction.md b/exercises/concept/locomotive-engineer/.docs/introduction.md index 66d9ba15810..798a334aeb9 100644 --- a/exercises/concept/locomotive-engineer/.docs/introduction.md +++ b/exercises/concept/locomotive-engineer/.docs/introduction.md @@ -9,6 +9,7 @@ The special operators `*` and `**` are often used in unpacking contexts and with `*` and `**` should not be confused with `*` and `**`. While `*` and `**` are used for multiplication and exponentiation respectively, `*` and `**` are used as packing and unpacking operators. ~~~~ + ## Multiple assignment In multiple assignment, the number of variables on the left side of the assignment operator (`=`) must match the number of values on the right side. @@ -55,6 +56,7 @@ For example: Since `tuples` are immutable, you can't swap elements in a `tuple`. + ## Unpacking ~~~~exercism/note @@ -80,9 +82,10 @@ If there are values that are not needed then you can use `_` to flag them: "cherry" ``` + ### Deep unpacking -Unpacking and assigning values from a `list`/`tuple` inside of a `list` or `tuple` (_also known as nested lists/tuples_), works in the same way a shallow unpacking does, but often needs qualifiers to clarify the values context or position: +Unpacking and assigning values from a `list`/`tuple` enclosed inside a `list` or `tuple` (_also known as nested lists/tuples_) works in the same way a shallow unpacking does — but often needs qualifiers to clarify the context or position: ```python >>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]] @@ -119,7 +122,7 @@ ValueError: too many values to unpack (expected 1) When [unpacking a `list`/`tuple`][packing and unpacking] you can use the `*` operator to capture the "leftover" values. This is clearer than slicing the `list`/`tuple` (_which in some situations is less readable_). -For example, we can extract the first element and then assign the remaining values into a new `list` without the first element: +For example, the first element can be extracted and then the remaining values can be placed into a new `list` without the first element: ```python >>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"] @@ -157,7 +160,7 @@ We can also use `*` in deep unpacking: ### Unpacking a dictionary -[Unpacking a dictionary][packing and unpacking] is a bit different than unpacking a `list`/`tuple`. +[Unpacking a dictionary][packing and unpacking] is a bit different from unpacking a `list`/`tuple`. Iteration over dictionaries defaults to the **keys**. So when unpacking a `dict`, you can only unpack the **keys** and not the **values**: @@ -168,7 +171,7 @@ So when unpacking a `dict`, you can only unpack the **keys** and not the **value "apple" ``` -If you want to unpack the values then you can use the `values()` method: +If you want to unpack the values then you can use the `.values()` method: ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} @@ -177,9 +180,9 @@ If you want to unpack the values then you can use the `values()` method: 6 ``` -If both **keys** and **values** are needed, use the `items()` method. -Using `items()` will generate tuples with **key-value** pairs. -This is because of [`dict.items()` generates an iterable with key-value `tuples`][items]. +If both **keys** and **values** are needed, use the [`.items()`][items] method. +`.items()` generates an [iterable view][view-objects] containing **key-value** pairs. +These can be unpacked into a `tuple`: ```python >>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3} @@ -238,8 +241,8 @@ This will pack all **key**-**value** pairs from one dictionary into another dict ### Packing with function parameters When you create a function that accepts an arbitrary number of arguments, you can use [`*args` or `**kwargs`][args and kwargs] in the function definition. -`*args` is used to pack an arbitrary number of positional (non-keyworded) arguments and -`**kwargs` is used to pack an arbitrary number of keyword arguments. +`*args` is used to pack an arbitrary number of positional (_non-keyword_) arguments as a `tuple` and +`**kwargs` is used to pack an arbitrary number of keyword arguments as a dictionary. Usage of `*args`: @@ -355,8 +358,9 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the ``` [args and kwargs]: https://www.geeksforgeeks.org/args-kwargs-python/ -[items]: https://www.geeksforgeeks.org/python-dictionary-items-method/ +[items]: https://docs.python.org/3/library/stdtypes.html#dict.items [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ [packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/ [sorting algorithms]: https://realpython.com/sorting-algorithms-python/ [unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp +[view-objects]: https://docs.python.org/3/library/stdtypes.html#dict-views From fb1cb444ad75bfcf8c0d5087f4144856e439fcfc Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 11 Oct 2024 17:41:40 -0700 Subject: [PATCH 167/277] [Wordy & Wordy Approaches]: Added 6 Additional Approaches & Modified the Instruction Append for Wordy. (#3783) * Added 6 additional appraches and extended introduction for Wordy. * Corrected slug for regex with operator module approach. --- .../practice/wordy/.approaches/config.json | 46 +- .../dunder-getattribute/content.md | 78 ++-- .../.approaches/functools-reduce/content.md | 126 ++++++ .../.approaches/functools-reduce/snippet.txt | 7 + .../import-callables-from-operator/content.md | 93 ++++ .../snippet.txt | 8 + .../wordy/.approaches/introduction.md | 416 +++++++++++++++++- .../lambdas-in-a-dictionary/content.md | 100 +++++ .../lambdas-in-a-dictionary/snippet.txt | 6 + .../wordy/.approaches/recursion/content.md | 266 +++++++++++ .../wordy/.approaches/recursion/snippet.txt | 8 + .../regex-with-operator-module/content.md | 98 +++++ .../regex-with-operator-module/snippet.txt | 8 + .../string-list-and-dict-methods/content.md | 161 +++++++ .../string-list-and-dict-methods/snippet.txt | 8 + .../wordy/.docs/instructions.append.md | 26 +- 16 files changed, 1392 insertions(+), 63 deletions(-) create mode 100644 exercises/practice/wordy/.approaches/functools-reduce/content.md create mode 100644 exercises/practice/wordy/.approaches/functools-reduce/snippet.txt create mode 100644 exercises/practice/wordy/.approaches/import-callables-from-operator/content.md create mode 100644 exercises/practice/wordy/.approaches/import-callables-from-operator/snippet.txt create mode 100644 exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md create mode 100644 exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/snippet.txt create mode 100644 exercises/practice/wordy/.approaches/recursion/content.md create mode 100644 exercises/practice/wordy/.approaches/recursion/snippet.txt create mode 100644 exercises/practice/wordy/.approaches/regex-with-operator-module/content.md create mode 100644 exercises/practice/wordy/.approaches/regex-with-operator-module/snippet.txt create mode 100644 exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md create mode 100644 exercises/practice/wordy/.approaches/string-list-and-dict-methods/snippet.txt diff --git a/exercises/practice/wordy/.approaches/config.json b/exercises/practice/wordy/.approaches/config.json index ed71a84650f..79a3a114baa 100644 --- a/exercises/practice/wordy/.approaches/config.json +++ b/exercises/practice/wordy/.approaches/config.json @@ -1,10 +1,52 @@ { "introduction": { - "authors": ["bobahop"], - "contributors": [] + "authors": ["BethanyG"], + "contributors": ["bobahop"] }, "approaches": [ { + "uuid": "4eeb0638-671a-4289-a83c-583b616dc698", + "slug": "string-list-and-dict-methods", + "title": "String, List, and Dictionary Methods", + "blurb": "Use Core Python Features to Solve Word Problems.", + "authors": ["BethanyG"] + }, + { + "uuid": "d3ff485a-defe-42d9-b9c6-c38019221ffa", + "slug": "import-callables-from-operator", + "title": "Import Callables from the Operator Module", + "blurb": "Use Operator Module Methods to Solve Word Problems.", + "authors": ["BethanyG"] + }, + { + "uuid": "61f44943-8a12-471b-ab15-d0d10fa4f72f", + "slug": "regex-with-operator-module", + "title": "Regex with the Operator Module", + "blurb": "Use Regex with the Callables from Operator to solve word problems.", + "authors": ["BethanyG"] + }, + { + "uuid": "46bd15dd-cae4-4eb3-ac63-a8b631a508d1", + "slug": "lambdas-in-a-dictionary", + "title": "Lambdas in a Dictionary to Return Functions", + "blurb": "Use lambdas in a dictionary to return functions for solving word problems.", + "authors": ["BethanyG"] + }, + { + "uuid": "2e643b88-9b76-45a1-98f4-b211919af061", + "slug": "recursion", + "title": "Recursion for iteration.", + "blurb": "Use recursion with other strategies to solve word problems.", + "authors": ["BethanyG"] + }, + { + "uuid": "1e136304-959c-4ad1-bc4a-450d13e5f668", + "slug": "functools-reduce", + "title": "Functools.reduce for Calculation", + "blurb": "Use functools.reduce with other strategies to calculate solutions.", + "authors": ["BethanyG"] + }, + { "uuid": "d643e2b4-daee-422d-b8d3-2cad2f439db5", "slug": "dunder-getattribute", "title": "dunder with __getattribute__", diff --git a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md index 76280615bc5..31c42e7a2aa 100644 --- a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md +++ b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md @@ -1,5 +1,6 @@ # Dunder methods with `__getattribute__` + ```python OPS = { "plus": "__add__", @@ -33,70 +34,61 @@ def answer(question): ``` -This approach begins by defining a [dictionary][dictionaries] of the word keys with their related [dunder][dunder] methods. +This approach begins by defining a [dictionary][dictionaries] of the word keys with their related [`dunder-methods`][dunder] methods. +Since only whole numbers are involved, the available `dunder-methods` are those for the [`int`][int] class/namespace. +The supported methods for the `int()` namespace can be found by using `print(dir(int))` or `print(int.__dict__)` in a Python terminal. +See [SO: Difference between dir() and __dict__][dir-vs-__dict__] for differences between the two. ~~~~exercism/note -They are called "dunder" methods because they have **d**ouble **under**scores at the beginning and end of the method name. -They are also called magic methods. +The built-in [`dir`](https://docs.python.org/3/library/functions.html?#dir) function returns a list of all valid attributes for an object. +The `dunder-method` [`.__dict__`](https://docs.python.org/3/reference/datamodel.html#object.__dict__) is a mapping of an objects writable attributes. ~~~~ -Since only whole numbers are involved, the dunder methods are those for [`int`][int]. -The supported methods for `int` can be found by using `print(dir(int))`. - -~~~~exercism/note -The built-in [`dir`](https://docs.python.org/3/library/functions.html?#dir) function returns a list of valid attributes for an object. -~~~~ +The `OPS` dictionary is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. +It indicates that the value should not be changed. -Python doesn't _enforce_ having real constant values, -but the `OPS` dictionary is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. -It indicates that the value is not intended to be changed. - -The input question to the `answer` function is cleaned using the [`removeprefix`][removeprefix], [`removesuffix`][removesuffix], and [`strip`][strip] methods. +The input question to the `answer()` function is cleaned using the [`removeprefix`][removeprefix], [`removesuffix`][removesuffix], and [`strip`][strip] string methods. The method calls are [chained][method-chaining], so that the output from one call is the input for the next call. If the input has no characters left, -it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return the [`ValueError`][value-error] for having a syntax error. +it uses the [falsiness][falsiness] of an empty string with the [`not`][not] operator to return a `ValueError("syntax error")`. -Next, the [`isdigit`][isdigit] method is used to see if all of the remaining characters in the input are digits. +Next, the [`isdigit`][isdigit] method is used to see if the remaining characters in the input are digits. If so, it uses the [`int()`][int-constructor] constructor to return the string as an integer. -Next, the elements in the `OPS` dictionary are iterated. -If the key name is in the input, then the [`replace()`][replace] method is used to replace the name in the input with the dunder method value. -If none of the key names are found in the input, then a `ValueError` is returned for having an unknown operation. - -At this point the input question is [`split()`][split] into a list of its words, which is then iterated while its [`len()`][len] is greater than 1. +Next, the elements in the `OPS` dictionary are iterated over. +If the key name is in the input, then the [`str.replace`][replace] method is used to replace the name in the input with the `dunder-method` value. +If none of the key names are found in the input, a `ValueError("unknown operation")` is returned. -Within a [try][exception-handling], the list is [destructured][destructure] into `x, op, y, *tail`. -If `op` is not in the supported dunder methods, it raises `ValueError("syntax error")`. -If there are any other exceptions raised in the try, `except` raises `ValueError("syntax error")` +At this point the input question is [`split()`][split] into a `list` of its words, which is then iterated over while its [`len()`][len] is greater than 1. -Next, it converts `x` to an `int` and calls the [`__getattribute__`][getattribute] for its dunder method and calls it, -passing it `y` converted to an `int`. +Within a [try-except][exception-handling] block, the list is [unpacked][unpacking] (_see also [Concept: unpacking][unpacking-and-multiple-assignment]_) into the variables `x, op, y, and *tail`. +If `op` is not in the supported `dunder-methods` dictionary, a `ValueError("syntax error")` is raised. +If there are any other exceptions raised within the `try` block, they are "caught"/ handled in the `except` clause by raising a `ValueError("syntax error")`. -It sets the list to the result of the dunder method plus the remaining elements in `*tail`. +Next, `x` is converted to an `int` and [`__getattribute__`][getattribute] is called for the `dunder-method` (`op`) to apply to `x`. +`y` is then converted to an `int` and passed as the second arguemnt to `op`. -~~~~exercism/note -The `*` prefix in `*tail` [unpacks](https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/) the `tail` list back into its elements. -This concept is also a part of [unpacking-and-multiple-assignment](https://exercism.org/tracks/python/concepts/unpacking-and-multiple-assignment) concept in the syllabus. -~~~~ +Then `ret` is redefined to a `list` containing the result of the dunder method plus the remaining elements in `*tail`. When the loop exhausts, the first element of the list is selected as the function return value. +[const]: https://realpython.com/python-constants/ [dictionaries]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries +[dir-vs-__dict__]: https://stackoverflow.com/a/14361362 [dunder]: https://www.tutorialsteacher.com/python/magic-methods-in-python +[exception-handling]: https://docs.python.org/3/tutorial/errors.html#handling-exceptions +[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ +[getattribute]: https://docs.python.org/3/reference/datamodel.html?#object.__getattribute__ +[int-constructor]: https://docs.python.org/3/library/functions.html?#int [int]: https://docs.python.org/3/library/stdtypes.html#typesnumeric -[const]: https://realpython.com/python-constants/ -[removeprefix]: https://docs.python.org/3/library/stdtypes.html#str.removeprefix -[removesuffix]: https://docs.python.org/3/library/stdtypes.html#str.removesuffix -[strip]: https://docs.python.org/3/library/stdtypes.html#str.strip +[isdigit]: https://docs.python.org/3/library/stdtypes.html?#str.isdigit +[len]: https://docs.python.org/3/library/functions.html?#len [method-chaining]: https://www.tutorialspoint.com/Explain-Python-class-method-chaining [not]: https://docs.python.org/3/library/operator.html?#operator.__not__ -[falsiness]: https://www.pythontutorial.net/python-basics/python-boolean/ -[value-error]: https://docs.python.org/3/library/exceptions.html?#ValueError -[isdigit]: https://docs.python.org/3/library/stdtypes.html?#str.isdigit -[int-constructor]: https://docs.python.org/3/library/functions.html?#int +[removeprefix]: https://docs.python.org/3/library/stdtypes.html#str.removeprefix +[removesuffix]: https://docs.python.org/3/library/stdtypes.html#str.removesuffix [replace]: https://docs.python.org/3/library/stdtypes.html?#str.replace [split]: https://docs.python.org/3/library/stdtypes.html?#str.split -[len]: https://docs.python.org/3/library/functions.html?#len -[exception-handling]: https://docs.python.org/3/tutorial/errors.html#handling-exceptions -[destructure]: https://riptutorial.com/python/example/14981/destructuring-assignment -[getattribute]: https://docs.python.org/3/reference/datamodel.html?#object.__getattribute__ +[strip]: https://docs.python.org/3/library/stdtypes.html#str.strip +[unpacking]: https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/ +[unpacking-and-multiple-assignment]: https://exercism.org/tracks/python/concepts/unpacking-and-multiple-assignment diff --git a/exercises/practice/wordy/.approaches/functools-reduce/content.md b/exercises/practice/wordy/.approaches/functools-reduce/content.md new file mode 100644 index 00000000000..08f21a8ad20 --- /dev/null +++ b/exercises/practice/wordy/.approaches/functools-reduce/content.md @@ -0,0 +1,126 @@ +# Functools.reduce for Calculation + + +```python +from operator import add, mul, sub +from operator import floordiv as div +from functools import reduce + + +# Define a lookup table for mathematical operations +OPERATORS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} + +def answer(question): + # Check for basic validity right away, and fail out with error if not valid. + if not question.startswith( "What is") or "cubed" in question: + raise ValueError("unknown operation") + + # Using the built-in filter() to clean & split the question.. + list(filter(lambda x: + x not in ("What", "is", "by"), + question.strip("?").split())) + + # Separate candidate operators and numbers into two lists. + operations = question[1::2] + + # Convert candidate elements to int(), checking for "-". + # All other values are replaced with None. + digits = [int(element) if (element.isdigit() or element[1:].isdigit()) + else None for element in question[::2]] + + # If there is a mis-match between operators and numbers, toss error. + if len(digits)-1 != len(operations) or None in digits: + raise ValueError("syntax error") + + # Evaluate the expression from left to right using functools.reduce(). + # Look up each operation in the operation dictionary. + return reduce(lambda x, y: OPERATORS[operations.pop(0)](x, y), digits) +``` + +This approach replaces the `while-loop` or `recursion` used in many solutions with a call to [`functools.reduce`][functools-reduce]. +It requires that the question be separated into candidate digits and candidate operators, which is accomplished here via [list-slicing][sequence-operations] (_for some additional information on working with `lists`, see [concept: lists](/tracks/python/concepts/lists)_). + +A nested call to `filter()` and `split()` within a `list` constructor is used to clean and process the question into an initial `list` of digit and operator strings. +However, this could easily be accomplished by either using [chained][method-chaining] string methods or a `list-comprehension`: + + +```python + # Alternative 1 is chaining various string methods together. + # The wrapping () invoke implicit concatenation for the chained functions + return (question.removeprefix("What is") + .removesuffix("?") + .replace("by", "") + .strip()).split() # <-- this split() turns the string into a list. + + + # Alternative 2 to the nested calls to filter and split is to use a list-comprehension: + return [item for item in + question.strip("?").split() + if item not in ("What", "is", "by")] #<-- The [] of the comprehension invokes implicit concatenation. +``` + + +Since "valid" questions are all in the form of `digit-operator-digit` (_and so on_), it is safe to assume that every other element beginning at index 0 is a "number", and every other element beginning at index 1 is an operator. +By that definition, the operators `list` is 1 shorter in `len()` than the digits list. +Anything else (_or having None/an unknown operation in the operations list_) is a `ValueError("syntax error")`. + + +The final call to `functools.reduce` essentially performs the same steps as the `while-loop` implementation, with the `lambda-expression` passing successive items of the digits `list` to the popped and looked-up operation from the operations `list` (_made [callable][callable] by adding `()`_), until it is reduced to one number and returned. +A `try-except` is not needed here because the error scenarios are already filtered out in the `if` check right before the call to `reduce()`. + +`functools.reduce` is certainly convenient, and makes the solution much shorter. +But it is also hard to understand what is happening if you have not worked with a reduce or foldl function in the past. +It could be argued that writing the code as a `while-loop` or recursive function is easier to reason about for non-functional programmers. + + +## Variation: 1: Use a Dictionary of `lambdas` instead of importing from operator + + +The imports from operator can be swapped out for a dictionary of `lambda-expressions` (or calls to `dunder-methods`), if so desired. +The same cautions apply here as were discussed in the [lambdas in a dictionary][approach-lambdas-in-a-dictionary] approach: + + +```python +from functools import reduce + +# Define a lookup table for mathematical operations +OPERATORS = {"plus": lambda x, y: x + y, + "minus": lambda x, y: x - y, + "multiplied": lambda x, y: x * y, + "divided": lambda x, y: x / y} + +def answer(question): + + # Check for basic validity right away, and fail out with error if not valid. + if not question.startswith( "What is") or "cubed" in question: + raise ValueError("unknown operation") + + # Clean and split the question into a list for processing. + question = [item for item in + question.strip("?").split() if + item not in ("What", "is", "by")] + + # Separate candidate operators and numbers into two lists. + operations = question[1::2] + + # Convert candidate elements to int(), checking for "-". + # All other values are replaced with None. + digits = [int(element) if (element.isdigit() or element[1:].isdigit()) + else None for element in question[::2]] + + # If there is a mis-match between operators and numbers, toss error. + if len(digits)-1 != len(operations) or None in digits: + raise ValueError("syntax error") + + # Evaluate the expression from left to right using functools.reduce(). + # Look up each operation in the operation dictionary. + result = reduce(lambda x, y: OPERATORS[operations.pop(0)](x, y), digits) + + return result +``` + +[approach-lambdas-in-a-dictionary]: https://exercise.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary +[callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ +[functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[method-chaining]: https://www.tutorialspoint.com/Explain-Python-class-method-chaining +[sequence-operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations diff --git a/exercises/practice/wordy/.approaches/functools-reduce/snippet.txt b/exercises/practice/wordy/.approaches/functools-reduce/snippet.txt new file mode 100644 index 00000000000..f8d5a294195 --- /dev/null +++ b/exercises/practice/wordy/.approaches/functools-reduce/snippet.txt @@ -0,0 +1,7 @@ +OPERATORS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} + +operations = question[1::2] +digits = [int(element) if (element.isdigit() or element[1:].isdigit()) + else None for element in question[::2]] +... +return reduce(lambda x, y: OPERATORS[operations.pop(0)](x, y), digits) \ No newline at end of file diff --git a/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md b/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md new file mode 100644 index 00000000000..0c2683760ac --- /dev/null +++ b/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md @@ -0,0 +1,93 @@ +# Import Callables from the Operator Module + + +```python +from operator import add, mul, sub +from operator import floordiv as div + +OPERATIONS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} + +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").strip() + + if question.isdigit(): + return int(question) + + if not question: + raise ValueError("syntax error") + + equation = [word for word in question.split() if word != 'by'] + + while len(equation) > 1: + try: + x_value, operation, y_value, *rest = equation + equation = [OPERATIONS[operation](int(x_value), int(y_value)), + *rest] + except: + raise ValueError("syntax error") + + return equation[0] +``` + + +This approach is nearly identical to the [string, list, and dict methods][approach-string-list-and-dict-methods] approach, so it is recommended to review that before going over this one. +The two major differences are the `operator` module, and the elimination of the `if-elif-else` block. + + +The solution begins by importing basic mathematical operations as methods from the [`operator`][operator] module. +These functions (_floordiv is [aliased][aliasing] to "div"_) are stored in a dictionary that serves as a lookup table when the problems are processed. +These operations are later made [callable][callable] by using `()` after the name, and supplying arguments. + + +In `answer()`, the question is first checked for validity, cleaned, and finally split into a `list` using [`str.startswith`][startswith], [`str.removeprefix`][removeprefix]/[`str.removesuffix`][removesuffix], [strip][strip], and [split][split]. +Checks for digits and an empty string are done, and the word "by" is filtered from the equation `list` using a [`list-comprehension`][list-comprehension]. + + +The equation `list` is then processed in a `while-loop` within a [try-except][handling-exceptions] block. +The `list` is [unpacked][unpacking] (_see also [concept: unpacking and multiple assignment](/tracks/python/concepts/unpacking-and-multiple-assignment)_) into `x_value`, `operation`, `y_value`, and `*rest`, and reduced by looking up and calling the mathematical function in the OPERATIONS dictionary and passing in `int(x_value)` and `int(y_value)` as arguments. + + +The processing of the equation `list` continues until it is of `len()` 1, at which point the single element is returned as the answer. + + +To walk through this step-by-step, you can interact with this code on [`pythontutor.com`][pythontutor]. + + +Using a `list-comprehension` to filter out "by" can be replaced with the [`str.replace`][str-replace] method during question cleaning. +[Implicit concatenation][implicit-concatenation] can be used to improve the readability of the [chained][chaining-method-calls] method calls: + + +```python +question = (question.removeprefix("What is") + .removesuffix("?") + .replace("by", "") + .strip()) #<-- Enclosing () means these lines are automatically joined by the interpreter. +``` + + +The call to `str.replace` could instead be chained to the call to `split` when creating the equation `list`: + + +```python +equation = question.replace("by", "").split() +``` + +[aliasing]: https://mimo.org/glossary/python +[approach-string-list-and-dict-methods]: https://exercise.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods +[callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ +[chaining-method-calls]: https://nikhilakki.in/understanding-method-chaining-in-python +[handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions +[implicit-concatenation]: https://docs.python.org/3/reference/lexical_analysis.html#implicit-line-joining +[list-comprehension]: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions +[operator]: https://docs.python.org/3/library/operator.html#module-operator +[pythontutor]: https://pythontutor.com/render.html#code=from%20operator%20import%20add,%20mul,%20sub%0Afrom%20operator%20import%20floordiv%20as%20div%0A%0AOPERATIONS%20%3D%20%7B%22plus%22%3A%20add,%20%22minus%22%3A%20sub,%20%22multiplied%22%3A%20mul,%20%22divided%22%3A%20div%7D%0A%0Adef%20answer%28question%29%3A%0A%20%20%20%20if%20not%20question.startswith%28%22What%20is%22%29%20or%20%22cubed%22%20in%20question%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22unknown%20operation%22%29%0A%20%20%20%20%0A%20%20%20%20question%20%3D%20question.removeprefix%28%22What%20is%22%29.removesuffix%28%22%3F%22%29.strip%28%29%0A%0A%20%20%20%20if%20question.isdigit%28%29%3A%20%0A%20%20%20%20%20%20%20%20return%20int%28question%29%0A%20%20%20%20%0A%20%20%20%20if%20not%20question%3A%20%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22syntax%20error%22%29%0A%20%20%20%20%0A%20%20%20%20equation%20%3D%20%5Bword%20for%20word%20in%20question.split%28%29%20if%20word%20!%3D%20'by'%5D%0A%20%20%20%20%0A%20%20%20%20while%20len%28equation%29%20%3E%201%3A%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20x_value,%20operation,%20y_value,%20*rest%20%3D%20equation%0A%20%20%20%20%20%20%20%20%20%20%20%20equation%20%3D%20%5BOPERATIONS%5Boperation%5D%28int%28x_value%29,%20int%28y_value%29%29,%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20*rest%5D%0A%20%20%20%20%20%20%20%20except%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%22syntax%20error%22%29%0A%20%20%20%20%0A%20%20%20%20return%20equation%5B0%5D%0A%20%20%20%20%0Aprint%28answer%28%22What%20is%202%20plus%202%20plus%203%3F%22%29%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false +[removeprefix]: https://docs.python.org/3.9/library/stdtypes.html#str.removeprefix +[removesuffix]: https://docs.python.org/3.9/library/stdtypes.html#str.removesuffix +[split]: https://docs.python.org/3.9/library/stdtypes.html#str.split +[startswith]: https://docs.python.org/3.9/library/stdtypes.html#str.startswith +[str-replace]: https://docs.python.org/3/library/stdtypes.html#str.replace +[strip]: https://docs.python.org/3.9/library/stdtypes.html#str.strip +[unpacking]: https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/ \ No newline at end of file diff --git a/exercises/practice/wordy/.approaches/import-callables-from-operator/snippet.txt b/exercises/practice/wordy/.approaches/import-callables-from-operator/snippet.txt new file mode 100644 index 00000000000..d5cb5a13547 --- /dev/null +++ b/exercises/practice/wordy/.approaches/import-callables-from-operator/snippet.txt @@ -0,0 +1,8 @@ +OPERATIONS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} +while len(equation) > 1: + try: + x_value, operation, y_value, *rest = equation + equation = [OPERATIONS[operation](int(x_value), int(y_value)),*rest] + except: + raise ValueError("syntax error") +return equation[0] \ No newline at end of file diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index 9faba81cbcd..ae8900e08f1 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -1,22 +1,367 @@ # Introduction -There are various ways to solve Wordy. -Using [`eval`][eval] is a [convenient but potentially dangerous][eval-danger] approach. -Another approach could replace the operation words with [dunder][dunder] methods. +The objective of the Wordy exercise is to parse and evaluate small/simple mathematical word problems, returning the result as an integer. +These problems do not require complex or [PEMDAS][PEMDAS]-based evaluation and are instead processed from left-to-right _in sequence_. +This means that for some of the test cases, the solution will not be the same as if the word problem was evaluated like a traditional math problem. -~~~~exercism/note -They are called "dunder" methods because they have **d**ouble **under**scores at the beginning and end of the method name. -They are also called magic methods. -~~~~ -The dunder methods can be called by using the [`__getattribute__`][getattribute] method for [`int`][int]. +## General Guidance -## General guidance +The key to a Wordy solution is to remove the "question" portion of the sentence (_"What is", "?"_) and process the remaining words between numbers as [operators][mathematical operators]. +If a single number remains after removing the "question", it should be converted to an [`int`][int] and returned as the answer. +Any words or word-number combinations that do not fall into the simple mathematical evaluation pattern (_number-operator-number_) should [`raise`][raise-statement] a [`ValueError`][value-error] with a message. +This includes any "extra" spaces between numbers. + +One way to reduce the number of `raise` statements/ `ValueError`s needed in the code is to determine if a problem is a "valid" question _before_ proceeding to parsing and calculation. +As shown in various approaches, there are multiple strategies for validating questions, with no one "canonical" solution. +One very effective approach is to check if a question starts with "What is", ends with "?", and includes only valid operations. +That could lead to future maintenance issues if the definition of a question ever changes or operations are added, but for the purposes of passing the current Wordy tests, it works well. + +There are various Pythonic ways to go about the cleaning, parsing, and calculation steps of Wordy. +For cleaning the "question" portion of the problem, [`str.removeprefix`][removeprefix] and +[`str.removesuffix`][removesuffix] introduced in `Python 3.9` can be very useful: + + +```python +>>> 'Supercalifragilisticexpialidocious'.removeprefix('Super') +'califragilisticexpialidocious' + +>>> 'Supercalifragilisticexpialidocious'.removesuffix('expialidocious') +'Supercalifragilistic' + + +#The two methods can be chained to remove both a suffix and prefix in one line. +>>> 'Supercalifragilisticexpialidocious'.removesuffix('expialidocious').removeprefix('Super') +'califragilistic' +``` + + +You can also use [`str.startswith`][startswith] and [`str.endswith`][endswith] in conjunction with [string slicing][sequence-operations] for cleaning: + + +```python +>>> if 'Supercalifragilisticexpialidocious'.startswith('Super'): + new_string = 'Supercalifragilisticexpialidocious'[5:] +>>> new_string +'califragilisticexpialidocious' + + +>>> if new_string.endswith('expialidocious'): + new_string = new_string[:15] +>>> new_string +'califragilistic' +``` + + +Different combinations of [`str.find`][find], [`str.rfind`][rfind], or [`str.index`][index] with string slicing could be used to clean up the initial word problem. +A [regex][regex] could also be used to process the question, but might be considered overkill given the fixed nature of the prefix/suffix and operations. +Finally, [`str.strip`][strip] and its variants are very useful for cleaning up any leftover leading or trailing whitespace. + +Many solutions then use [`str.split`][split] to process the remaining "cleaned" question into a `list` for convenient iteration, although other strategies are also used. + +For math operations, many solutions involve importing and using methods from the [operator][operator] module in combination with different looping, parsing, and substitution strategies. +Some solutions use either [lambda][lambdas] expressions or [dunder/"special" methods][dunder-methods] to replace words with arithmetic operations. +However, the exercise can be solved without using `operator`, `lambdas`, or `dunder-methods`. + +Using [`eval`][eval] for the operations might seem convenient, but it is a [dangerous][eval-danger] and possibly [destructive][eval-destructive] approach. +It is also entirely unnecessary, as the other methods described here are safer and equally performant. + + +## Approach: String, List, and Dictionary Methods + + +```python +OPERATIONS = {"plus": '+', "minus": '-', "multiplied": '*', "divided": '/'} + + +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").strip() + + if not question: + raise ValueError("syntax error") + + if question.isdigit(): + return int(question) + + formula = [] + for operation in question.split(): + if operation == 'by': + continue + else: + formula.append(OPERATIONS.get(operation, operation)) + + while len(formula) > 1: + try: + x_value = int(formula[0]) + symbol = formula[1] + y_value = int(formula[2]) + remainder = formula[3:] + + if symbol == "+": + formula = [x_value + y_value] + remainder + elif symbol == "-": + formula = [x_value - y_value] + remainder + elif symbol == "*": + formula = [x_value * y_value] + remainder + elif symbol == "/": + formula = [x_value / y_value] + remainder + else: + raise ValueError("syntax error") + except: + raise ValueError("syntax error") + + return formula[0] +``` + +This approach uses only data structures and methods (_[dict][dict], [dict.get()][dict-get] and [list()][list]_) from core Python, and does not import any extra modules. +It may have more lines of code than average, but it is clear to follow and fairly straightforward to reason about. +It does use a [try-except][handling-exceptions] block for handling unknown operators. +As an alternative to the `formula` loop-append, a [list-comprehension][list-comprehension] can be used to create the initial parsed formula. + +For more details and variations, read the [String, List and Dictionary Methods][approach-string-list-and-dict-methods] approach. + + +## Approach: Import Callables from the Operator Module + + +```python +from operator import add, mul, sub +from operator import floordiv as div + +OPERATIONS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} + +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").strip() + + if question.isdigit(): + return int(question) + + if not question: + raise ValueError("syntax error") + + equation = [word for word in question.split() if word != 'by'] + + while len(equation) > 1: + try: + x_value, operation, y_value, *rest = equation + equation = [OPERATIONS[operation](int(x_value), int(y_value)), + *rest] + except: + raise ValueError("syntax error") + + return equation[0] +``` + +This solution imports methods from the `operator` module, and uses them in a dictionary/lookup map. +Like the first approach, it uses a [try-except][handling-exceptions] block for handling unknown operators. + It also uses a [list-comprehension][list-comprehension] to create the parsed "formula" and employs [concept: unpacking and multiple assignment](/tracks/python/concepts/unpacking-and-multiple-assignment). + +For more details and options, take a look at the [Import Callables from the Operator Module][approach-import-callables-from-operator] approach. + + +## Approach: Regex and the Operator Module + + +```python +import re +from operator import add, mul, sub +from operator import floordiv as div + +OPERATIONS = {"plus": add, "minus": sub, "multiplied by": mul, "divided by": div} +REGEX = { + 'number': re.compile(r'-?\d+'), + 'operator': re.compile(f'(?:{"|".join(OPERATIONS)})\\b') +} + + +def get_number(question): + pattern = REGEX['number'].match(question) + if not pattern: + raise ValueError("syntax error") + return [question.removeprefix(pattern.group(0)).lstrip(), + int(pattern.group(0))] + +def get_operation(question): + pattern = REGEX['operator'].match(question) + if not pattern: + raise ValueError("unknown operation") + return [question.removeprefix(pattern.group(0)).lstrip(), + OPERATIONS[pattern.group(0)]] + +def answer(question): + prefix = "What is" + if not question.startswith(prefix): + raise ValueError("unknown operation") + + question = question.removesuffix("?").removeprefix(prefix).lstrip() + question, result = get_number(question) + + while len(question) > 0: + if REGEX['number'].match(question): + raise ValueError("syntax error") + + question, operation = get_operation(question) + question, num = get_number(question) + + result = operation(result, num) + + return result +``` + + +This approach uses a dictionary of regex patterns for matching numbers and operators, paired with a dictionary of operations imported from the `operator` module. +It pulls number and operator processing out into separate functions and uses a while loop in `answer()` to evaluate the word problem. +It also uses multiple assignment for various variables. +It is longer than some solutions, but clearer and potentially easier to maintain due to the separate `get_operation()` and `get_number()` functions. + +For more details, take a look at the [regex-with-operator-module][approach-regex-with-operator-module] approach. + + +## Approach: Lambdas in a Dictionary to return Functions + + +```python +OPERATIONS = { + 'minus': lambda a, b: a - b, + 'plus': lambda a, b: a + b, + 'multiplied': lambda a, b: a * b, + 'divided': lambda a, b: a / b + } + + +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").strip() + + if question.isdigit(): + return int(question) + + if not question: + raise ValueError("syntax error") + + equation = [word for word in question.split() if word != 'by'] + + while len(equation) > 1: + try: + x_value, operation, y_value, *rest = equation + equation = [OPERATIONS[operation](int(x_value), int(y_value)), + *rest] + except: + raise ValueError("syntax error") + + return equation[0] +``` + + +Rather than import methods from the `operator` module, this approach defines a series of [`lambda expressions`][lambdas] in the OPERATIONS dictionary. +These `lambdas` then return a function that takes two numbers as arguments, returning the result. + +One drawback of this strategy over using named functions or methods from `operator` is the lack of debugging information should something go wrong with evaluation. +Lambda expressions are all named `"lambda"` in stack traces, so it becomes less clear where an error is coming from if you have a number of lambda expressions within a large program. +Since this is not a large program, debugging these `lambdas` is fairly straightforward. +These "hand-crafted" `lambdas` could also introduce a mathematical error, although for the simple problems in Wordy, this is a fairly small consideration. + +For more details, take a look at the [Lambdas in a Dictionary][approach-lambdas-in-a-dictionary] approach. + + +## Approach: Recursion + + +```python +from operator import add, mul, sub +from operator import floordiv as div + +OPERATIONS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} + +def answer(question): + return calculate(clean(question)) + +def clean(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + return (question.removeprefix("What is") + .removesuffix("?") + .replace("by", "") + .strip()).split() + +def calculate(equation): + if len(equation) == 1: + return int(equation[0]) + else: + try: + x_value, operation, y_value, *rest = equation + equation = [OPERATIONS[operation](int(x_value), + int(y_value)), *rest] + except: + raise ValueError("syntax error") + + return calculate(equation) +``` + + +Like previous approaches that substitute methods from `operator` for `lambdas` or `list-comprehensions` for `loops` that append to a `list` -- `recursion` can be substituted for the `while-loop` that many solutions use to process a parsed word problem. +Depending on who is reading the code, `recursion` may or may not be easier to reason about. +It may also be more (_or less!_) performant than using a `while-loop` or `functools.reduce` (_see below_), depending on how the various cleaning and error-checking actions are performed. + +The dictionary in this example could use functions from `operator`, `lambdas`, `dunder-methods`, or other strategies -- as long as they can be applied in the `calculate()` function. + +For more details, take a look at the [recursion][approach-recursion] approach. + + +## Approach: functools.reduce() + + +```python +from operator import add, mul, sub +from operator import floordiv as div +from functools import reduce + + +OPERATORS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} + +def answer(question): + if not question.startswith( "What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = list(filter(lambda x: + x not in ("What", "is", "by"), + question.strip("?").split())) + + operations = question[1::2] + digits = [int(element) if (element.isdigit() or + element[1:].isdigit()) else None for + element in question[::2]] + + if len(digits)-1 != len(operations) or None in digits: + raise ValueError("syntax error") + + result = reduce(lambda x, y: OPERATORS[operations.pop(0)](x, y), digits) + + return result +``` + + +This approach replaces the `while-loop` used in many solutions (_or the `recursion` strategy outlined in the approach above_) with a call to [`functools.reduce`][functools-reduce]. +It also employs a lookup dictionary for methods imported from the `operator` module, as well as a `list-comprehension`, the built-in [`filter`][filter] function, and multiple string [slices][sequence-operations]. +If desired, the `operator` imports can be replaced with a dictionary of `lambda` expressions or `dunder-methods`. + +This solution may be a little less clear to follow or reason about due to the slicing syntax and the particular syntax of both `filter` and `fuctools.reduce`. + +For more details and variations, take a look at the [functools.reduce for Calculation][approach-functools-reduce] approach. -Parsing should verify that the expression in words can be translated to a valid mathematical expression. ## Approach: Dunder methods with `__getattribute__` + ```python OPS = { "plus": "__add__", @@ -50,11 +395,52 @@ def answer(question): ``` -For more information, check the [dunder method with `__getattribute__` approach][approach-dunder-getattribute]. +This approach uses the [`dunder methods`][dunder-methods] / ["special methods"][special-methods] / "magic methods" associated with the `int()` class, using the `dunder-method` called [`.__getattribute__`][getattribute] to find the [callable][callable] operation in the `int()` class [namespace][namespace] / dictionary. +This works because the operators for basic math (_"+, -, *, /, //, %, **"_) have been implemented as callable methods for all integers (_as well as floats and other number types_) and are automatically loaded when the Python interpreter is loaded. + +As described in the first link, it is considered bad form to directly call a `dunder method` (_there are some exceptions_), as they are intended mostly for internal Python use, user-defined class customization, and operator overloading (_a specific form of class-customization_). + +This is why the `operator` module exists - as a vehicle for providing callable methods for basic math when **not** overloading or customizing class functionality. +For more detail on this solution, take a look at the [dunder method with `__getattribute__` approach][approach-dunder-getattribute]. + + +[PEMDAS]: https://www.mathnasium.com/math-centers/eagan/news/what-pemdas-e +[approach-dunder-getattribute]: https://exercism.org/tracks/python/exercises/wordy/approaches/dunder-getattribute +[approach-functools-reduce]: https://exercism.org/tracks/python/exercises/wordy/approaches/functools-reduce +[approach-import-callables-from-operator]: https://exercism.org/tracks/python/exercises/wordy/approaches/import-callables-from-operator +[approach-lambdas-in-a-dictionary]: https://exercise.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary +[approach-recursion]: https://exercise.org/tracks/python/exercises/wordy/approaches/recursion +[approach-regex-with-operator-module]: https://exercise.org/tracks/python/exercises/wordy/approaches/regex-with-operator-module +[approach-string-list-and-dict-methods]: https://exercise.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods +[callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ +[dict-get]: https://docs.python.org/3/library/stdtypes.html#dict.get +[dict]: https://docs.python.org/3/library/stdtypes.html#dict +[dunder-methods]: https://www.pythonmorsels.com/what-are-dunder-methods/?watch +[endswith]: https://docs.python.org/3.9/library/stdtypes.html#str.endswith +[eval-danger]: https://softwareengineering.stackexchange.com/questions/311507/why-are-eval-like-features-considered-evil-in-contrast-to-other-possibly-harmfu +[eval-destructive]: https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html [eval]: https://docs.python.org/3/library/functions.html?#eval -[eval-danger]: https://diveintopython3.net/advanced-iterators.html#eval -[dunder]: https://www.tutorialsteacher.com/python/magic-methods-in-python -[getattribute]: https://docs.python.org/3/reference/datamodel.html?#object.__getattribute__ +[find]: https://docs.python.org/3.9/library/stdtypes.html#str.find +[functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[getattribute]: https://docs.python.org/3/reference/datamodel.html#object.__getattribute__ +[handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions +[index]: https://docs.python.org/3.9/library/stdtypes.html#str.index [int]: https://docs.python.org/3/library/stdtypes.html#typesnumeric -[approach-dunder-getattribute]: https://exercism.org/tracks/python/exercises/wordy/approaches/dunder-getattribute +[lambdas]: https://docs.python.org/3/howto/functional.html#small-functions-and-the-lambda-expression +[list-comprehension]: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions +[list]: https://docs.python.org/3/library/stdtypes.html#list +[mathematical operators]: https://www.w3schools.com/python/gloss_python_arithmetic_operators.asp +[namespace]: https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces +[operator]: https://docs.python.org/3/library/operator.html#module-operator +[raise-statement]: https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement +[regex]: https://docs.python.org/3/library/re.html#module-re +[removeprefix]: https://docs.python.org/3.9/library/stdtypes.html#str.removeprefix +[removesuffix]: https://docs.python.org/3.9/library/stdtypes.html#str.removesuffix +[rfind]: https://docs.python.org/3.9/library/stdtypes.html#str.rfind +[sequence-operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[special-methods]: https://docs.python.org/3/reference/datamodel.html#specialnames +[split]: https://docs.python.org/3.9/library/stdtypes.html#str.split +[startswith]: https://docs.python.org/3.9/library/stdtypes.html#str.startswith +[strip]: https://docs.python.org/3.9/library/stdtypes.html#str.strip +[value-error]: https://docs.python.org/3.11/library/exceptions.html#ValueError diff --git a/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md new file mode 100644 index 00000000000..6836e7b4686 --- /dev/null +++ b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md @@ -0,0 +1,100 @@ +# Lambdas in a Dictionary to Return Functions + + +```python +OPERATIONS = { + 'minus': lambda a, b: a - b, + 'plus': lambda a, b: a + b, + 'multiplied': lambda a, b: a * b, + 'divided': lambda a, b: a / b + } + + +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").strip() + + if question.isdigit(): + return int(question) + + if not question: + raise ValueError("syntax error") + + equation = question.replace("by", "").split() + + while len(equation) > 1: + try: + x_value, operation, y_value, *rest = equation + equation = [OPERATIONS[operation](int(x_value), int(y_value)), + *rest] + except: + raise ValueError("syntax error") + + return equation[0] +``` + +This approach is nearly identical to the [string, list, and dict methods][approach-string-list-and-dict-methods] and the [import callables from the operator][approach-import-callables-from-operator] approaches, so it is recommended that you review those before going over this one. +The major difference here is the use of [`lambda expressions`][lambdas] in place of `operator` methods or string representations in the OPERATIONS dictionary. + +`lambda expressions` are small "throwaway" expressions that are simple enough to not require a formal function definition or name. +They are most commonly used in [`key functions`][key-functions], the built-ins [`map`][map] and [`filter`][filter], and in [`functools.reduce`][functools-reduce]. + `lambdas` are also often defined in areas where a function is needed for one-time use or callback but it would be onerous or confusing to create a full function definition. +The two forms are parsed identically (_they are both function definitions_), but in the case of [`lambdas`][lambda], the function name is always "lambda" and the expression cannot contain statements or annotations. + +For example, the code above could be re-written to include user-defined functions as opposed to `lambda expressions`: + + +```python +def add_(x, y): + return x + y + +def mul_(x, y): + return x * y + +def div_(x, y): + return x//y + +def sub_(x, y): + return x - y + +def answer(question): + operations = {'minus': sub_,'plus': add_,'multiplied': mul_,'divided': div_} + + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").strip() + + if question.isdigit(): + return int(question) + + if not question: + raise ValueError("syntax error") + + equation = question.replace("by", "").split() + + while len(equation) > 1: + try: + x_value, operation, y_value, *rest = equation + equation = [operations[operation](int(x_value), int(y_value)), + *rest] + except: + raise ValueError("syntax error") + + return equation[0] +``` + +However, this makes the code more verbose and does not improve readability. +In addition, the functions need to carry a trailing underscore to avoid potential shadowing or name conflict. +It is better and cleaner in this circumstance to use `lambda expressions` for the functions - although it could be argued that importing and using the methods from `operator` is even better and clearer. + +[approach-import-callables-from-operator]: https://exercism.org/tracks/python/exercises/wordy/approaches/import-callables-from-operator +[approach-string-list-and-dict-methods]: https://exercise.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods +[filter]: https://docs.python.org/3/library/functions.html#filter +[functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[key-functions]: https://docs.python.org/3/howto/sorting.html#key-functions +[lambda]: https://docs.python.org/3/reference/expressions.html#lambda +[lambdas]: https://docs.python.org/3/howto/functional.html#small-functions-and-the-lambda-expression +[map]: https://docs.python.org/3/library/functions.html#map diff --git a/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/snippet.txt b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/snippet.txt new file mode 100644 index 00000000000..3769bef8c5c --- /dev/null +++ b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/snippet.txt @@ -0,0 +1,6 @@ +OPERATIONS = { + 'minus': lambda a, b: a - b, + 'plus': lambda a, b: a + b, + 'multiplied': lambda a, b: a * b, + 'divided': lambda a, b: a / b + } \ No newline at end of file diff --git a/exercises/practice/wordy/.approaches/recursion/content.md b/exercises/practice/wordy/.approaches/recursion/content.md new file mode 100644 index 00000000000..5dd0a7ba376 --- /dev/null +++ b/exercises/practice/wordy/.approaches/recursion/content.md @@ -0,0 +1,266 @@ +# Recursion + + +[Any function that can be written iteratively (_with loops_) can be written using recursion][recursion-and-iteration], and [vice-versa][recursion-is-not-a-superpower]. +A recursive strategy [may not always be obvious][looping-vs-recursion] or easy — but it is always possible. +So the `while-loop`s used in other approaches to Wordy can be re-written to use recursive calls. + +That being said, Pyton famously does not perform [tail-call optimization][tail-call-optimization], and limits recursive calls on the stack to a depth of 1000 frames, so it is important to only use recursion where you are confident that it can complete within the limit (_or something close to it_). +[Memoization][memoization] and other strategies in [dynamic programming][dynamic-programming] can help to make recursion more efficient and "shorter" in Python, but it's always good to give it careful consideration. + +Recursion works best with problem spaces that resemble trees, include [backtracking][backtracking], or become progressively smaller. + Some examples include financial processes like calculating [amortization][amortization] and [depreciation][depreciation], tracking [radiation reduction through nuclei decay][nuclei-decay], and algorithms like [biscetion search][bisection-search], [depth-firs search][dfs], and [merge sort][merge-sort]_). + +Other algorithms such as [breadth-first search][bfs], [Dijkstra's algorithm][dijkstra], and [Bellman-Ford Algorithm][bellman-ford] lend themselves better to iteration. + + +```python +from operator import add, mul, sub +from operator import floordiv as div + +# Define a lookup table for mathematical operations +OPERATIONS = {"plus": add, "minus": sub, "multiplied": mul, "divided": div} + + +def answer(question): + # Call clean() and feed it to calculate() + return calculate(clean(question)) + +def clean(question): + # It's not a question unless it starts with 'What is'. + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + # Remove the unnecessary parts of the question and + # parse the cleaned question into a list of items to process. + # The wrapping () invoke implicit concatenation for the chained functions + return (question.removeprefix("What is") + .removesuffix("?") + .replace("by", "") + .strip()).split() # <-- this split() turns the string into a list. + +# Recursively calculate the first piece of the equation, calling +# calculate() on the product + the remainder. +# Return the solution when len(equation) is one. +def calculate(equation): + if len(equation) == 1: + return int(equation[0]) + else: + try: + # Unpack the equation into first int, operator, and second int. + # Stuff the remainder into *rest + x_value, operation, y_value, *rest = equation + + # Redefine the equation list as the product of the first three + # variables concatenated with the unpacked remainder. + equation = [OPERATIONS[operation](int(x_value), + int(y_value)), *rest] + except: + raise ValueError("syntax error") + + # Call calculate() with the redefined/partially reduced equation. + return calculate(equation) +``` + +This approach separates the solution into three functions: + +1. `answer()`, which takes the question and calls `calculate(clean())`, returning the answer to the question. +2. `clean()`, which takes a question string and returns a `list` of parsed words and numbers to calculate from. +3. `calculate()`, which performs the calculations on the `list` recursively, until a single number (_the base case check_) is returned as the answer — or an error is thrown. + +The cleaning logic is separate from the processing logic so that the cleaning steps aren't repeated over and over with each recursive `calculate()` call. +This separation also makes it easier to make changes in processing or calculating without creating conflict or confusion. + +Note that `calculate()` performs the same steps as the `while-loop` from [Import Callables from the Operator Module][approach-import-callables-from-operator] and others. +The difference being that the `while-loop` test for `len()` 1 now occurs as an `if` condition in the function (_the base case_), and the "looping" is now a call to `calculate()` in the `else` condition. +`calculate()` can also use many of the strategies detailed in other approaches, as long as they work with the recursion. + + +`clean()` can also use any of the strategies detailed in other approaches, two of which are below: + +```python + # Alternative 1 to the chained calls is to use a list-comprehension: + return [item for item in + question.strip("?").split() + if item not in ("What", "is", "by")] #<-- The [] of the comprehension invokes implicit concatenation. + + + # Alternative 2 is the built-in filter(), but it can be somewhat hard to read. + return list(filter(lambda x: + x not in ("What", "is", "by"), + question.strip("?").split())) #<-- The () in list() also invokes implicit concatenation. +``` + + +## Variation 1: Use Regex for matching, cleaning, and calculating + + +```python + +import re +from operator import add, mul, sub +from operator import floordiv as div + +# This regex looks for any number 0-9 that may or may not have a - in front of it. +DIGITS = re.compile(r"-?\d+") + +# These regex look for a number (x or y) before and after a phrase or word. +OPERATORS = { + mul: re.compile(r"(?P-?\d+) multiplied by (?P-?\d+)"), + div: re.compile(r"(?P-?\d+) divided by (?P-?\d+)"), + add: re.compile(r"(?P-?\d+) plus (?P-?\d+)"), + sub: re.compile(r"(?P-?\d+) minus (?P-?\d+)"), + } + +# This regex looks for any digit 0-9 (optionally negative) followed by any valid operation, +# ending in any digit (optionally negative). +VALIDATE = re.compile(r"(?P-?\d+) (multiplied by|divided by|plus|minus) (?P-?\d+)") + + +def answer(question): + if (not question.startswith( "What is") or "cubed" in question): + raise ValueError("unknown operation") + + question = question.removeprefix( "What is").removesuffix("?").strip() + + # If after cleaning, there is only one number, return it as an int(). + if DIGITS.fullmatch(question): + return int(question) + + # If after cleaning, there isn't anything, toss an error. + if not question: + raise ValueError("syntax error") + + # Call the recursive calculate() function. + return calculate(question) + +# Recursively calculate the first piece of the equation, calling +# calculate() on the product + the remainder. +# Return the solution when len(equation) is one. +def calculate(question): + new_question = "" + + for symbol, pattern in OPERATORS.items(): + # Declare match variable and assign the pattern match as a value + if match := pattern.match(question): + + # Attempt to calculate the first num symbol num trio. + # Convert strings to ints where needed. + first_calc = f"{symbol(int(match['x']), int(match['y']))}" + + # Strip the pattern from the question + remainder = question.removeprefix(match.group()) + + # Create new question with first calculation + the remainder + new_question = first_calc + remainder + + # Check if there is just a single number, so that it can be returned. + # This is the "base case" of this recursive function. + if DIGITS.fullmatch(new_question): + return int(new_question) + + # Check if the new question is still a "valid" question. + # Error out if not. + elif not VALIDATE.match(new_question): + raise ValueError("syntax error") + + # Otherwise, call yourself to process the new question. + else: + return calculate(new_question) +``` + + +This variation shows how the dictionary of operators from `operator` can be augmented with [regex][re] to perform string matching for a question. +Regex are also used here to check that a question is a valid and to ensure that the base case (_nothing but digits are left in the question_) is met for the recursive call in `calculate()`. +The regex patterns use [named groups][named-groups] for easy reference, but it's not necessary to do so. + + +Interestingly, `calculate()` loops through `dict.items()` to find symbols, using a [walrus operator][walrus] to complete successive regex matches and composing an [f-string][f-string] to perform the calculation. +The question remains a `str` throughout the process, so `question.removeprefix(match.group())` is used to "reduce" the original question to form a remainder that is then concatenated with the `f-string` to form a new question. + + +Because each new iteration of the question needs to be validated, there is an `if-elif-else` block at the end that returns the answer, throws a `ValueError("syntax error")`, or makes the recursive call. + + +Note that the `for-loop` and VALIDATE use [`re.match`][re-match], but DIGITS validation uses [`re.fullmatch`][re-fullmatch]. + + +## Variation 2: Use Regex, Recurse within the For-loop + + +```python +import re +from operator import add, mul, sub +from operator import floordiv as div + +DIGITS = re.compile(r"-?\d+") +OPERATORS = ( + (mul, re.compile(r"(?P.*) multiplied by (?P.*)")), + (div, re.compile(r"(?P.*) divided by (?P.*)")), + (add, re.compile(r"(?P.*) plus (?P.*)")), + (sub, re.compile(r"(?P.*) minus (?P.*)")), + ) + +def answer(question): + if not question.startswith( "What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix( "What is").removesuffix("?").strip() + + if not question: + raise ValueError("syntax error") + + return calculate(question) + +def calculate(question): + if DIGITS.fullmatch(question): + return int(question) + + for operation, pattern in OPERATORS: + if match := pattern.match(question): + return operation(calculate(match['x']), calculate(match['y'])) #<-- the loop is paused here to make the two recursive calls. + raise ValueError("syntax error") +``` + +This solution uses a `tuple` of nested `tuples` containing the operators from `operator` and regex in place of the dictionaries that have been used in the previous approaches. +This saves some space, but requires that the nested `tuples` be unpacked as the main `tuple` is iterated over (_note the `for operation, pattern in OPERATORS:` in the `for-loop`_ ) so that operations can be matched to strings in the question. + The regex is also more generic than the example above (_anything before and after the operation words is allowed_). + +Recursion is used a bit differently here from the previous variations — the calls are placed [within the `for-loop`][recursion-within-loops]. +Because the regex are more generic, they will match a `digit-operation-digit` trio in a longer question, so the line `return operation(calculate(match['x']), calculate(match['y']))` is effectively splitting a question into parts that can then be worked on in their own stack frames. + +For example: + +1. "1 plus -10 multiplied by 13 divided by 2" would match on "1 plus -10" (_group x_) **multiplied by** "13 divided by 2" (_group y_). +2. This would then be re-arranged to `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` +3. At this point the loop would pause as the two recursive calls to `calculate()` spawn +4. The loops would run again — and so would the calls to `calculate()`, until there wasn't any match that caused a split of the question or an error. +5. One at a time, the numbers would then be returned, until the main `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` could be solved, at which point the answer would be returned. + +For a more visual picture, you can step through the code on [pythontutor.com][recursion-in-loop-pythontutor]. + +[amortization]: https://www.investopedia.com/terms/a/amortization.asp +[approach-import-callables-from-operator]: https://exercism.org/tracks/python/exercises/wordy/approaches/import-callables-from-operator +[backtracking]: https://en.wikipedia.org/wiki/Backtracking +[bellman-ford]: https://www.programiz.com/dsa/bellman-ford-algorithm +[bfs]: https://en.wikipedia.org/wiki/Breadth-first_search +[bisection-search]: https://en.wikipedia.org/wiki/Bisection_method +[depreciation]: https://www.investopedia.com/terms/d/depreciation.asp +[dfs]: https://en.wikipedia.org/wiki/Depth-first_search +[dijkstra]: https://www.programiz.com/dsa/dijkstra-algorithm +[dynamic-programming]: https://algo.monster/problems/dynamic_programming_intro +[f-string]: https://docs.python.org/3.11/reference/lexical_analysis.html#formatted-string-literals +[looping-vs-recursion]: https://softwareengineering.stackexchange.com/questions/303242/is-there-anything-that-can-be-done-with-recursion-that-cant-be-done-with-loops +[memoization]: https://inventwithpython.com/recursion/chapter7.html +[merge-sort]: https://www.digitalocean.com/community/tutorials/merge-sort-algorithm-java-c-python +[named-groups]: https://docs.python.org/3/howto/regex.html#non-capturing-and-named-groups +[nuclei-decay]: https://courses.lumenlearning.com/suny-physics/chapter/31-5-half-life-and-activity/ +[re-fullmatch]: https://docs.python.org/3/library/re.html#re.full-match +[re-match]: https://docs.python.org/3/library/re.html#re.match +[re]: https://docs.python.org/3/library/re.html +[recursion-and-iteration]: https://web.mit.edu/6.102/www/sp23/classes/11-recursive-data-types/recursion-and-iteration-review.html#:~:text=The%20converse%20is%20also%20true,we%20are%20trying%20to%20solve. +[recursion-in-loop-pythontutor]: https://pythontutor.com/render.html#code=import%20re%0Afrom%20operator%20import%20add,%20mul,%20sub%0Afrom%20operator%20import%20floordiv%20as%20div%0A%0ADIGITS%20%3D%20re.compile%28r%22-%3F%5Cd%2B%22%29%0AOPERATORS%20%3D%20%28%0A%20%20%20%20%28mul,%20re.compile%28r%22%28%3FP%3Cx%3E.*%29%20multiplied%20by%20%28%3FP%3Cy%3E.*%29%22%29%29,%0A%20%20%20%20%28div,%20re.compile%28r%22%28%3FP%3Cx%3E.*%29%20divided%20by%20%28%3FP%3Cy%3E.*%29%22%29%29,%0A%20%20%20%20%28add,%20re.compile%28r%22%28%3FP%3Cx%3E.*%29%20plus%20%28%3FP%3Cy%3E.*%29%22%29%29,%0A%20%20%20%20%28sub,%20re.compile%28r%22%28%3FP%3Cx%3E.*%29%20minus%20%28%3FP%3Cy%3E.*%29%22%29%29,%0A%20%20%20%20%29%0A%0Adef%20answer%28question%29%3A%0A%20%20%20%20if%20not%20question.startswith%28%20%22What%20is%22%29%20or%20%22cubed%22%20in%20question%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22unknown%20operation%22%29%0A%20%20%20%20%0A%20%20%20%20question%20%3D%20question.removeprefix%28%20%22What%20is%22%29.removesuffix%28%22%3F%22%29.strip%28%29%0A%0A%20%20%20%20if%20not%20question%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22syntax%20error%22%29%0A%20%20%20%20%0A%20%20%20%20return%20calculate%28question%29%0A%0Adef%20calculate%28question%29%3A%0A%20%20%20%20if%20DIGITS.fullmatch%28question%29%3A%0A%20%20%20%20%20%20%20%20return%20int%28question%29%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20for%20operation,%20pattern%20in%20OPERATORS%3A%0A%20%20%20%20%20%20%20%20if%20match%20%3A%3D%20pattern.match%28question%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20operation%28calculate%28match%5B'x'%5D%29,%20calculate%28match%5B'y'%5D%29%29%20%23%3C--%20the%20loop%20is%20paused%20here%20to%20make%20the%20two%20recursive%20calls.%0A%20%20%20%20raise%20ValueError%28%22syntax%20error%22%29%0A%0Aprint%28answer%28%22What%20is%201%20plus%20-10%20multiplied%20by%2013%20divided%20by%202%3F%22%29%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false +[recursion-is-not-a-superpower]: https://inventwithpython.com/blog/2021/09/05/recursion-is-not-a-superpower-an-iterative-ackermann/ +[recursion-within-loops]: https://stackoverflow.com/questions/4795527/how-recursion-works-inside-a-for-loop +[tail-call-optimization]: https://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html +[walrus]: https://docs.python.org/3/reference/expressions.html#grammar-token-python-grammar-assignment_expression diff --git a/exercises/practice/wordy/.approaches/recursion/snippet.txt b/exercises/practice/wordy/.approaches/recursion/snippet.txt new file mode 100644 index 00000000000..373481f8f4b --- /dev/null +++ b/exercises/practice/wordy/.approaches/recursion/snippet.txt @@ -0,0 +1,8 @@ +def calculate(equation): + if len(equation) == 1: return int(equation[0]) + else: + try: + x_value, operation, y_value, *rest = equation + equation = [OPERATIONS[operation](int(x_value), int(y_value)), *rest] + except: raise ValueError("syntax error") + return calculate(equation) \ No newline at end of file diff --git a/exercises/practice/wordy/.approaches/regex-with-operator-module/content.md b/exercises/practice/wordy/.approaches/regex-with-operator-module/content.md new file mode 100644 index 00000000000..d3d5c21430d --- /dev/null +++ b/exercises/practice/wordy/.approaches/regex-with-operator-module/content.md @@ -0,0 +1,98 @@ +# Regex and the Operator Module + + +```python +import re +from operator import add, mul, sub +from operator import floordiv as div + +OPERATIONS = {"plus": add, "minus": sub, "multiplied by": mul, "divided by": div} + +REGEX = { + 'number': re.compile(r'-?\d+'), + 'operator': re.compile(f'(?:{"|".join(OPERATIONS)})\\b') +} + +# Helper function to extract a number from the question. +def get_number(question): + # Match a number. + pattern = REGEX['number'].match(question) + + # Toss an error if there is no match. + if not pattern: + raise ValueError("syntax error") + + # Remove the matched pattern from the question, and convert + # that same pattern to an int. Return the modified question and the int. + return [question.removeprefix(pattern.group(0)).lstrip(), + int(pattern.group(0))] + +# Helper function to extract an operation from the question. +def get_operation(question): + # Match an operation word + pattern = REGEX['operator'].match(question) + + # Toss an error if there is no match. + if not pattern: + raise ValueError("unknown operation") + + # Remove the matched pattern from the question, and look up + # that same pattern in OPERATIONS. Return the modified question and the operator. + return [question.removeprefix(pattern.group(0)).lstrip(), + OPERATIONS[pattern.group(0)]] + +def answer(question): + prefix = "What is" + + # Toss an error right away if the question isn't valid. + if not question.startswith(prefix): + raise ValueError("unknown operation") + + # Clean the question by removing the suffix and prefix and whitespace. + question = question.removesuffix("?").removeprefix(prefix).lstrip() + + # the question should start with a number + question, result = get_number(question) + + # While there are portions of the question left, continue to process. + while len(question) > 0: + # can't have a number followed by a number + if REGEX['number'].match(question): + raise ValueError("syntax error") + + # Call get_operation and unpack the result + # into question and operation. + question, operation = get_operation(question) + + # Call get_number and unpack the result + # into question and num + question, num = get_number(question) + + # Perform the calculation, using result and num as + # arguments to operation. + result = operation(result, num) + + return result +``` + +This approach uses two dictionaries: one of operations imported from `operators`, and another that holds regex for matching digits and matching operations in the text of a question. + +It defines two "helper" functions, `get_number()` and `get_operation`, that take a question and use the regex patterns to remove, convert, and return a number (_`get_number()`_) or an operation (_`get_operation()`_), along with a modified "new question". + +In the `answer()` function, the question is checked for validity (_does it start with "What is"_), and a `ValueError("unknown operation")` it raised if it is not a valid question. +Next, the question is cleaned with [`str.removeprefix`][removeprefix] & [`str.removesuffix`][removesuffix], removing "What is" and "?". +Left-trailing white space is stripped with the help of [`lstrip()`][lstrip]. +After that, the variable `result` is declared with an initial value from `get_number()`. + +The question is then iterated over via a `while-loop`, which calls `get_operation()` and `get_number()` — "reducing" the question by removing the leading numbers and operator. +The return values from each call are [unpacked][unpacking] into a "leftover" question portion, and the number or operator. +The returned operation is then made [callable][callable] using `()`, with result and the "new" number (_returned from `get_number()`_) passed as arguments. +The `loop` then proceeds with processing of the "new question", until the `len()` is 0. + +Once there is no more question to process, `result` is returned as the answer. + +[callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ +[lstrip]: https://docs.python.org/3/library/stdtypes.html#str.lstrip +[removeprefix]: https://docs.python.org/3.9/library/stdtypes.html#str.removeprefix +[removesuffix]: https://docs.python.org/3.9/library/stdtypes.html#str.removesuffix +[unpacking]: https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/ diff --git a/exercises/practice/wordy/.approaches/regex-with-operator-module/snippet.txt b/exercises/practice/wordy/.approaches/regex-with-operator-module/snippet.txt new file mode 100644 index 00000000000..4d89edb5377 --- /dev/null +++ b/exercises/practice/wordy/.approaches/regex-with-operator-module/snippet.txt @@ -0,0 +1,8 @@ +while len(question) > 0: + if REGEX['number'].match(question): + raise ValueError("syntax error") + + question, operation = get_operation(question) + question, num = get_number(question) + + result = operation(result, num) \ No newline at end of file diff --git a/exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md b/exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md new file mode 100644 index 00000000000..3c3dcfe8ee9 --- /dev/null +++ b/exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md @@ -0,0 +1,161 @@ +# String, List, and Dictionary Methods + + +```python +OPERATIONS = {"plus": '+', "minus": '-', "multiplied": '*', "divided": '/'} + + +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").strip() + + if not question: + raise ValueError("syntax error") + + formula = [] + for operation in question.split(): + if operation == 'by': + continue + else: + formula.append(OPERATIONS.get(operation, operation)) + + while len(formula) > 1: + try: + x_value, y_value = int(formula[0]), int(formula[2]) + symbol = formula[1] + remainder = formula[3:] + + if symbol == "+": + formula = [x_value + y_value] + remainder + elif symbol == "-": + formula = [x_value - y_value] + remainder + elif symbol == "*": + formula = [x_value * y_value] + remainder + elif symbol == "/": + formula = [x_value / y_value] + remainder + else: + raise ValueError("syntax error") + except: + raise ValueError("syntax error") + + return int(formula[0]) +``` + +Within the `answer()` function, the question is first checked for "unknown operations" by validating that it starts with "What is" ([`str.startswith`][startswith], [`str.endswith`][endswith]) and does not include the word "cubed" (_which is an invalid operation_). +This eliminates all the [current cases][unknown-operation-tests] where a [`ValueError("unknown operation")`][value-error] needs to be [raised][raise-statement]. +Should the definition of a question expand or change, this strategy would need to be revised. + + +The question is then "cleaned" by removing the prefix "What is" and the suffix "?" ([`str.removeprefix`][removeprefix], [`str.removesuffix`][removesuffix]) and [stripping][strip] any leading or trailing whitespaces. + + +If the question is now an empty string, a `ValueError("syntax error")` is raised. + + +Next, the question is [split][split] into a `list` and iterated over, with each element looked up and replaced from the OPERATIONS dictionary. +The [`dict.get`][dict-get] method is used for this, as it takes a default argument for when a [`KeyError`][keyerror] is thrown. +Here the default for `dict.get` is set to the element being iterated over, which is effectively _"if not found, skip it"_. + This avoids error handling, extra logic, or interruption when an element is not found. + One exception here is the word "by", which is explicitly skipped within the `for-loop`, so that it doesn't appear in the formula to be processed. + This filtering out could also be accomplished by using [`str.replace`][str-replace] in the cleaning step or during the `split` step. + The results of iterating through the question are then appended to a new formula `list`. + + + +````exercism/note +There are a couple of common alternatives to the `loop-append`: + +1. [`list-comprehensions`][list-comprehension] duplicate the same process in a more succinct and declarative fashion: + ```python + + formula = [OPERATIONS.get(operation, operation) for + operation in question.split() if operation != 'by'] + ``` + +2. The built-in [`filter()`][filter] and [`map()`][map] functions used with a [`lambda`][lambdas] to process the elements of the list. + This is identical in process to both the `loop-append` and the `list-comprehension`, but might be easier to reason about for those coming from a more functional programming language: + + ```python + formula = list(map(lambda x : OPERATIONS.get(x, x), + filter(lambda x: x != "by", question.split()))) + ``` + +[list-comprehension]: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions +[lambdas]: https://docs.python.org/3/howto/functional.html#small-functions-and-the-lambda-expression +[filter]: https://docs.python.org/3/library/functions.html#filter +[map]: https://docs.python.org/3/library/functions.html#map +```` + + +After the formula `list` is composed, it is processed in a `while-loop`. + +The processing within the `loop` is wrapped in a [try-except][handling-exceptions] block to trap any errors and raise them as `ValueError("syntax error")`. +While each type of error could be checked for individually, it is not necessary since only `ValueError("syntax error")` is required here. + +1. `x_value` and `y_value` are assigned to the first element and third element of the list using [bracket notation][bracket-notation], and converted to integers. + - Rather than indexing and slicing, [concept: unpacking and multiple assignment](/tracks/python/concepts/unpacking-and-multiple-assignment) can be used to assign the variables. + This does require a modification to the returned formula `list`: + ```python + x_value, operation, y_value, *remainder = formula # <-- Unpacking won't allow conversion to int() here. + + ... + if symbol == "+": + formula = [int(x_value) + int(y_value)] + remainder # <-- Instead, conversion to int() must happen here. + ... + + return int(formula[0]) + ``` + +2. `symbol` is assigned to the second element of the list. +3. `remainder` is assigned to a [slice][list-slice] of everything else in the `list`. + +The `symbol` is then tested in the `if-elif-else` block and the formula `list` is modified by calculating the operation on `x_value` and `y_value` and then appending whatever part of the question remains. + +Once the formula `list` is calculated down to a number, that number is converted to an `int` and returned as the answer. + + +````exercism/note +Introduced in Python 3.10, [structural pattern matching][structural-pattern-matching] can be used to replace the `if-elif-else` chain in the `while-loop`. +In some circumstances, this could be easier to read and/or reason about: + + +```python + while len(formula) > 1: + try: + x_value, symbol, y_value, *remainder = formula + + match symbol: + case "+": + formula = [int(x_value) + int(y_value)] + remainder + case "-": + formula = [int(x_value) - int(y_value)] + remainder + case "*": + formula = [int(x_value) * int(y_value)] + remainder + case "/": + formula = [int(x_value) / int(y_value)] + remainder + case _: + raise ValueError("syntax error") + except: + raise ValueError("syntax error") +``` + +[structural-pattern-matching]: https://peps.python.org/pep-0636/ +```` + +[bracket-notation]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[dict-get]: https://docs.python.org/3/library/stdtypes.html#dict.get +[endswith]: https://docs.python.org/3.9/library/stdtypes.html#str.endswith +[handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions +[keyerror]: https://docs.python.org/3/library/exceptions.html#KeyError +[list-slice]: https://www.pythonmorsels.com/slicing/ +[raise-statement]: https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement +[removeprefix]: https://docs.python.org/3.9/library/stdtypes.html#str.removeprefix +[removesuffix]: https://docs.python.org/3.9/library/stdtypes.html#str.removesuffix +[split]: https://docs.python.org/3.9/library/stdtypes.html#str.split +[startswith]: https://docs.python.org/3.9/library/stdtypes.html#str.startswith +[str-replace]: https://docs.python.org/3/library/stdtypes.html#str.replace +[strip]: https://docs.python.org/3.9/library/stdtypes.html#str.strip +[unknown-operation-tests]: https://github.com/exercism/python/blob/main/exercises/practice/wordy/wordy_test.py#L58-L68 +[value-error]: https://docs.python.org/3.11/library/exceptions.html#ValueError diff --git a/exercises/practice/wordy/.approaches/string-list-and-dict-methods/snippet.txt b/exercises/practice/wordy/.approaches/string-list-and-dict-methods/snippet.txt new file mode 100644 index 00000000000..700804b6d18 --- /dev/null +++ b/exercises/practice/wordy/.approaches/string-list-and-dict-methods/snippet.txt @@ -0,0 +1,8 @@ +try: + x_value, y_value, symbol, remainder = int(formula[0]), int(formula[2]), formula[1], formula[3:] + if symbol == "+": formula = [x_value + y_value] + remainder + elif symbol == "-": formula = [x_value - y_value] + remainder + elif symbol == "*": formula = [x_value * y_value] + remainder + elif symbol == "/": formula = [x_value / y_value] + remainder + else: raise ValueError("syntax error") +except: raise ValueError("syntax error") \ No newline at end of file diff --git a/exercises/practice/wordy/.docs/instructions.append.md b/exercises/practice/wordy/.docs/instructions.append.md index 0a0d309f7e6..ca646b7bd9b 100644 --- a/exercises/practice/wordy/.docs/instructions.append.md +++ b/exercises/practice/wordy/.docs/instructions.append.md @@ -2,11 +2,12 @@ ## Exception messages -Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. +Sometimes it is necessary to [raise an exception][raise-an-exception]. When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types][built-in-errors], but should still include a meaningful message. -This particular exercise requires that you use the [raise statement](https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement) to "throw" a `ValueError` if the question passed to `answer()` is malformed/invalid, or contains an unknown operation. The tests will only pass if you both `raise` the `exception` and include a message with it. +This particular exercise requires that you use the [raise statement][raise-statement] to "throw" a `ValueError` if the question passed to `answer()` is malformed/invalid, or contains an unknown operation. The tests will only pass if you both `raise` the `exception` and include a message with it. + +To raise a [`ValueError`][value-error] with a message, write the message as an argument to the `exception` type: -To raise a `ValueError` with a message, write the message as an argument to the `exception` type: ```python # if the question contains an unknown operation. @@ -15,3 +16,22 @@ raise ValueError("unknown operation") # if the question is malformed or invalid. raise ValueError("syntax error") ``` + +To _handle_ a raised error within a particular code block, one can use a [try-except][handling-exceptions] : + +```python +while len(equation) > 1: + try: + x_value, operation, y_value, *rest = equation + + ... + + except: + raise ValueError("syntax error") +``` + +[built-in-errors]: https://docs.python.org/3.11/library/exceptions.html#built-in-exceptions +[handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions +[raise-an-exception]: https://docs.python.org/3/tutorial/errors.html#raising-exceptions +[raise-statement]: https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement +[value-error]: https://docs.python.org/3.11/library/exceptions.html#ValueError From dd39c7176da4645c2d1ecd965ebf638f32473d01 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 11 Oct 2024 18:25:46 -0700 Subject: [PATCH 168/277] [Wordy]: Link Corrections and Typos (#3784) * Added 6 additional appraches and extended introduction for Wordy. * Corrected slug for regex with operator module approach. * Fixed link typos for other approaches and the filter function. * One. Moar. Typo. --- .../wordy/.approaches/functools-reduce/content.md | 3 ++- .../import-callables-from-operator/content.md | 2 +- exercises/practice/wordy/.approaches/introduction.md | 9 +++++---- .../wordy/.approaches/lambdas-in-a-dictionary/content.md | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/exercises/practice/wordy/.approaches/functools-reduce/content.md b/exercises/practice/wordy/.approaches/functools-reduce/content.md index 08f21a8ad20..0a2532d4425 100644 --- a/exercises/practice/wordy/.approaches/functools-reduce/content.md +++ b/exercises/practice/wordy/.approaches/functools-reduce/content.md @@ -119,7 +119,8 @@ def answer(question): return result ``` -[approach-lambdas-in-a-dictionary]: https://exercise.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary + +[approach-lambdas-in-a-dictionary]: https://exercism.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary [callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ [functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce [method-chaining]: https://www.tutorialspoint.com/Explain-Python-class-method-chaining diff --git a/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md b/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md index 0c2683760ac..9f57c1c9c6b 100644 --- a/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md +++ b/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md @@ -76,7 +76,7 @@ equation = question.replace("by", "").split() ``` [aliasing]: https://mimo.org/glossary/python -[approach-string-list-and-dict-methods]: https://exercise.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods +[approach-string-list-and-dict-methods]: https://exercism.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods [callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ [chaining-method-calls]: https://nikhilakki.in/understanding-method-chaining-in-python [handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index ae8900e08f1..0ea75927049 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -409,10 +409,10 @@ For more detail on this solution, take a look at the [dunder method with `__geta [approach-dunder-getattribute]: https://exercism.org/tracks/python/exercises/wordy/approaches/dunder-getattribute [approach-functools-reduce]: https://exercism.org/tracks/python/exercises/wordy/approaches/functools-reduce [approach-import-callables-from-operator]: https://exercism.org/tracks/python/exercises/wordy/approaches/import-callables-from-operator -[approach-lambdas-in-a-dictionary]: https://exercise.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary -[approach-recursion]: https://exercise.org/tracks/python/exercises/wordy/approaches/recursion -[approach-regex-with-operator-module]: https://exercise.org/tracks/python/exercises/wordy/approaches/regex-with-operator-module -[approach-string-list-and-dict-methods]: https://exercise.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods +[approach-lambdas-in-a-dictionary]: https://exercsim.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary +[approach-recursion]: https://exercism.org/tracks/python/exercises/wordy/approaches/recursion +[approach-regex-with-operator-module]: https://exercism.org/tracks/python/exercises/wordy/approaches/regex-with-operator-module +[approach-string-list-and-dict-methods]: https://exercism.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods [callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ [dict-get]: https://docs.python.org/3/library/stdtypes.html#dict.get [dict]: https://docs.python.org/3/library/stdtypes.html#dict @@ -421,6 +421,7 @@ For more detail on this solution, take a look at the [dunder method with `__geta [eval-danger]: https://softwareengineering.stackexchange.com/questions/311507/why-are-eval-like-features-considered-evil-in-contrast-to-other-possibly-harmfu [eval-destructive]: https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html [eval]: https://docs.python.org/3/library/functions.html?#eval +[filter]: https://docs.python.org/3/library/functions.html#filter [find]: https://docs.python.org/3.9/library/stdtypes.html#str.find [functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce [getattribute]: https://docs.python.org/3/reference/datamodel.html#object.__getattribute__ diff --git a/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md index 6836e7b4686..df06d3b1716 100644 --- a/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md +++ b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md @@ -91,7 +91,7 @@ In addition, the functions need to carry a trailing underscore to avoid potentia It is better and cleaner in this circumstance to use `lambda expressions` for the functions - although it could be argued that importing and using the methods from `operator` is even better and clearer. [approach-import-callables-from-operator]: https://exercism.org/tracks/python/exercises/wordy/approaches/import-callables-from-operator -[approach-string-list-and-dict-methods]: https://exercise.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods +[approach-string-list-and-dict-methods]: https://exercism.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods [filter]: https://docs.python.org/3/library/functions.html#filter [functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce [key-functions]: https://docs.python.org/3/howto/sorting.html#key-functions From 6800ddb6a2faf1dadcf66213e53b6ef41c372ace Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 11 Oct 2024 21:14:00 -0700 Subject: [PATCH 169/277] Fixed even more typos in recursion approach. (#3785) --- exercises/practice/wordy/.approaches/config.json | 2 +- .../practice/wordy/.approaches/recursion/content.md | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/exercises/practice/wordy/.approaches/config.json b/exercises/practice/wordy/.approaches/config.json index 79a3a114baa..670284d4715 100644 --- a/exercises/practice/wordy/.approaches/config.json +++ b/exercises/practice/wordy/.approaches/config.json @@ -35,7 +35,7 @@ { "uuid": "2e643b88-9b76-45a1-98f4-b211919af061", "slug": "recursion", - "title": "Recursion for iteration.", + "title": "Recursion for Iteration.", "blurb": "Use recursion with other strategies to solve word problems.", "authors": ["BethanyG"] }, diff --git a/exercises/practice/wordy/.approaches/recursion/content.md b/exercises/practice/wordy/.approaches/recursion/content.md index 5dd0a7ba376..8131cbb7c95 100644 --- a/exercises/practice/wordy/.approaches/recursion/content.md +++ b/exercises/practice/wordy/.approaches/recursion/content.md @@ -1,17 +1,17 @@ -# Recursion +# Recursion for Iteration [Any function that can be written iteratively (_with loops_) can be written using recursion][recursion-and-iteration], and [vice-versa][recursion-is-not-a-superpower]. A recursive strategy [may not always be obvious][looping-vs-recursion] or easy — but it is always possible. So the `while-loop`s used in other approaches to Wordy can be re-written to use recursive calls. -That being said, Pyton famously does not perform [tail-call optimization][tail-call-optimization], and limits recursive calls on the stack to a depth of 1000 frames, so it is important to only use recursion where you are confident that it can complete within the limit (_or something close to it_). +That being said, Python famously does not perform [tail-call optimization][tail-call-optimization], and limits recursive calls on the stack to a depth of 1000 frames, so it is important to only use recursion where you are confident that it can complete within the limit (_or something close to it_). [Memoization][memoization] and other strategies in [dynamic programming][dynamic-programming] can help to make recursion more efficient and "shorter" in Python, but it's always good to give it careful consideration. Recursion works best with problem spaces that resemble trees, include [backtracking][backtracking], or become progressively smaller. - Some examples include financial processes like calculating [amortization][amortization] and [depreciation][depreciation], tracking [radiation reduction through nuclei decay][nuclei-decay], and algorithms like [biscetion search][bisection-search], [depth-firs search][dfs], and [merge sort][merge-sort]_). + Some examples include financial processes like calculating [amortization][amortization] and [depreciation][depreciation], tracking [radiation reduction through nuclei decay][nuclei-decay], and algorithms like [biscetion search][bisection-search], [depth-firs search][dfs], and [merge sort][merge-sort]. -Other algorithms such as [breadth-first search][bfs], [Dijkstra's algorithm][dijkstra], and [Bellman-Ford Algorithm][bellman-ford] lend themselves better to iteration. +Other algorithms such as [breadth-first search][bfs], [Dijkstra's algorithm][dijkstra], and the [Bellman-Ford Algorithm][bellman-ford] lend themselves better to iteration. ```python @@ -170,7 +170,7 @@ def calculate(question): ``` -This variation shows how the dictionary of operators from `operator` can be augmented with [regex][re] to perform string matching for a question. +This variation shows how the dictionary of operators from `operator` can be augmented with [regex][re] to perform string matching for a question. Regex are also used here to check that a question is a valid and to ensure that the base case (_nothing but digits are left in the question_) is met for the recursive call in `calculate()`. The regex patterns use [named groups][named-groups] for easy reference, but it's not necessary to do so. From d2cf34b90a1efe5af5c27cdc30c7ff26050be4b6 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 12 Oct 2024 11:35:12 -0700 Subject: [PATCH 170/277] Changed Wordy difficulty to 3 and reorderd config.json. (#3786) --- config.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/config.json b/config.json index 38dd7520e3c..09bd642c985 100644 --- a/config.json +++ b/config.json @@ -363,21 +363,6 @@ ], "difficulty": 1 }, - { - "slug": "wordy", - "name": "Wordy", - "uuid": "af50bb9a-e400-49ce-966f-016c31720be1", - "practices": ["string-methods"], - "prerequisites": [ - "basics", - "lists", - "loops", - "strings", - "string-methods", - "numbers" - ], - "difficulty": 1 - }, { "slug": "resistor-color", "name": "Resistor Color", @@ -1210,6 +1195,21 @@ ], "difficulty": 3 }, + { + "slug": "wordy", + "name": "Wordy", + "uuid": "af50bb9a-e400-49ce-966f-016c31720be1", + "practices": ["string-methods"], + "prerequisites": [ + "basics", + "lists", + "loops", + "strings", + "string-methods", + "numbers" + ], + "difficulty": 3 + }, { "slug": "crypto-square", "name": "Crypto Square", From 63cc10b06f425edb3a85c404d15d360c333bde0b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 14 Oct 2024 16:29:08 -0700 Subject: [PATCH 171/277] Further refinements to approaches. Added hits.md file. (#3788) --- .../.approaches/functools-reduce/content.md | 2 +- .../wordy/.approaches/introduction.md | 89 +++++---- .../string-list-and-dict-methods/content.md | 183 ++++++++++++------ .../wordy/.docs/instructions.append.md | 16 +- 4 files changed, 191 insertions(+), 99 deletions(-) diff --git a/exercises/practice/wordy/.approaches/functools-reduce/content.md b/exercises/practice/wordy/.approaches/functools-reduce/content.md index 0a2532d4425..602a678ffd5 100644 --- a/exercises/practice/wordy/.approaches/functools-reduce/content.md +++ b/exercises/practice/wordy/.approaches/functools-reduce/content.md @@ -33,7 +33,7 @@ def answer(question): raise ValueError("syntax error") # Evaluate the expression from left to right using functools.reduce(). - # Look up each operation in the operation dictionary. + # Look up each operation in the OPERATORS dictionary. return reduce(lambda x, y: OPERATORS[operations.pop(0)](x, y), digits) ``` diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index 0ea75927049..6f23f040bac 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -8,17 +8,39 @@ This means that for some of the test cases, the solution will not be the same as ## General Guidance The key to a Wordy solution is to remove the "question" portion of the sentence (_"What is", "?"_) and process the remaining words between numbers as [operators][mathematical operators]. -If a single number remains after removing the "question", it should be converted to an [`int`][int] and returned as the answer. + + +If a single number remains after removing the "question" pieces, it should be converted to an [`int`][int] and returned as the answer. + + Any words or word-number combinations that do not fall into the simple mathematical evaluation pattern (_number-operator-number_) should [`raise`][raise-statement] a [`ValueError`][value-error] with a message. This includes any "extra" spaces between numbers. + One way to reduce the number of `raise` statements/ `ValueError`s needed in the code is to determine if a problem is a "valid" question _before_ proceeding to parsing and calculation. As shown in various approaches, there are multiple strategies for validating questions, with no one "canonical" solution. -One very effective approach is to check if a question starts with "What is", ends with "?", and includes only valid operations. -That could lead to future maintenance issues if the definition of a question ever changes or operations are added, but for the purposes of passing the current Wordy tests, it works well. -There are various Pythonic ways to go about the cleaning, parsing, and calculation steps of Wordy. -For cleaning the "question" portion of the problem, [`str.removeprefix`][removeprefix] and + +One very effective validation approach is to check if a question starts with "What is", ends with "?", and does not include the word "cubed". +Any other question formulation becomes a `ValueError("unknown operation")`. +This very restrictive approach could lead to future maintenance issues if the definition of a question ever changes or operations are added, but for the purposes of passing the current Wordy tests, it works well. + + +Proceeding from validation, there are many Pythonic ways to go about the cleaning, parsing, and calculation steps of Wordy. +However, they all follow these general steps: + +1. Remove the parts of the question string that do not apply to calculating the answer. +2. Iterate over the question, determining which words are numbers, and which are meant to be mathematical operations. + - _Converting the question string into a `list` of words is hugely helpful here, but not absolutely necessary._ +3. **_Starting from the left_**, take the first three elements and convert number strings to `int` and operations words to +, -, *, /. +4. Apply the operation to the numbers, which should result in a single number. + - _Employing a `try-except` block around the conversion and operator application steps can trap any errors thrown and make the code both "safer" and less complex._ +5. Use the calculated number from step 4 as the start for the next "trio" (_number, operation, number_) in the question. The calculated number + the remainder of the question becomes the question being worked on in the next iteration. + - _Using a `while-loop` with a test on the length of the question to do calculation is a very common strategy._ +6. Once the question is calculated down to a single number, that is the answer. Anything else that happens in the loop/iteration or within the accumulated result is a `ValueError("syntax error")`. + + +For cleaning the question, [`str.removeprefix`][removeprefix] and [`str.removesuffix`][removesuffix] introduced in `Python 3.9` can be very useful: @@ -53,73 +75,70 @@ You can also use [`str.startswith`][startswith] and [`str.endswith`][endswith] i ``` -Different combinations of [`str.find`][find], [`str.rfind`][rfind], or [`str.index`][index] with string slicing could be used to clean up the initial word problem. -A [regex][regex] could also be used to process the question, but might be considered overkill given the fixed nature of the prefix/suffix and operations. +Different combinations of [`str.find`][find], [`str.rfind`][rfind], or [`str.index`][index] with string slicing could also be used to clean up the initial question. +A [regex][regex] could be used to process the question as well, but might be considered overkill given the fixed nature of the prefix/suffix and operations. Finally, [`str.strip`][strip] and its variants are very useful for cleaning up any leftover leading or trailing whitespace. -Many solutions then use [`str.split`][split] to process the remaining "cleaned" question into a `list` for convenient iteration, although other strategies are also used. +Many solutions then use [`str.split`][split] to process the remaining "cleaned" question into a `list` for convenient looping/iteration, although other strategies can also be used. + For math operations, many solutions involve importing and using methods from the [operator][operator] module in combination with different looping, parsing, and substitution strategies. -Some solutions use either [lambda][lambdas] expressions or [dunder/"special" methods][dunder-methods] to replace words with arithmetic operations. -However, the exercise can be solved without using `operator`, `lambdas`, or `dunder-methods`. +Some solutions use either [lambda][lambdas] expressions, [dunder/"special" methods][dunder-methods], or even `eval()` to replace words with arithmetic operations. +However, the exercise can be solved **without** using `operator`, `lambdas`, `dunder-methods` or `eval`. + It is recommended that you first start by solving it _without_ "advanced" strategies, and then refine your solution into something more compact or complex as you learn and practice. + +~~~~exercism/caution Using [`eval`][eval] for the operations might seem convenient, but it is a [dangerous][eval-danger] and possibly [destructive][eval-destructive] approach. It is also entirely unnecessary, as the other methods described here are safer and equally performant. +~~~~ ## Approach: String, List, and Dictionary Methods ```python -OPERATIONS = {"plus": '+', "minus": '-', "multiplied": '*', "divided": '/'} - - def answer(question): if not question.startswith("What is") or "cubed" in question: raise ValueError("unknown operation") - question = question.removeprefix("What is").removesuffix("?").strip() + question = question.removeprefix("What is") + question = question.removesuffix("?") + question = question.replace("by", "") + question = question.strip() if not question: raise ValueError("syntax error") - - if question.isdigit(): - return int(question) - - formula = [] - for operation in question.split(): - if operation == 'by': - continue - else: - formula.append(OPERATIONS.get(operation, operation)) + formula = question.split() while len(formula) > 1: try: x_value = int(formula[0]) - symbol = formula[1] y_value = int(formula[2]) + symbol = formula[1] remainder = formula[3:] - if symbol == "+": + if symbol == "plus": formula = [x_value + y_value] + remainder - elif symbol == "-": + elif symbol == "minus": formula = [x_value - y_value] + remainder - elif symbol == "*": + elif symbol == "multiplied": formula = [x_value * y_value] + remainder - elif symbol == "/": + elif symbol == "divided": formula = [x_value / y_value] + remainder else: raise ValueError("syntax error") except: raise ValueError("syntax error") - return formula[0] + return int(formula[0]) ``` -This approach uses only data structures and methods (_[dict][dict], [dict.get()][dict-get] and [list()][list]_) from core Python, and does not import any extra modules. +This approach uses only data structures and methods (_[str methods][str-methods], [list()][list], loops, etc._) from core Python, and does not import any extra modules. It may have more lines of code than average, but it is clear to follow and fairly straightforward to reason about. It does use a [try-except][handling-exceptions] block for handling unknown operators. -As an alternative to the `formula` loop-append, a [list-comprehension][list-comprehension] can be used to create the initial parsed formula. + +Alternatives could use a [dictionary][dict] to store word --> operator mappings that could be looked up in the `while-loop` using [`.get()`][dict-get], among other strategies. For more details and variations, read the [String, List and Dictionary Methods][approach-string-list-and-dict-methods] approach. @@ -350,7 +369,7 @@ def answer(question): ``` -This approach replaces the `while-loop` used in many solutions (_or the `recursion` strategy outlined in the approach above_) with a call to [`functools.reduce`][functools-reduce]. +This approach replaces the `while-loop` used in many solutions (_or the `recursion` strategy outlined in the approach above_) with a call to [`functools.reduce`][functools-reduce]. It also employs a lookup dictionary for methods imported from the `operator` module, as well as a `list-comprehension`, the built-in [`filter`][filter] function, and multiple string [slices][sequence-operations]. If desired, the `operator` imports can be replaced with a dictionary of `lambda` expressions or `dunder-methods`. @@ -418,9 +437,6 @@ For more detail on this solution, take a look at the [dunder method with `__geta [dict]: https://docs.python.org/3/library/stdtypes.html#dict [dunder-methods]: https://www.pythonmorsels.com/what-are-dunder-methods/?watch [endswith]: https://docs.python.org/3.9/library/stdtypes.html#str.endswith -[eval-danger]: https://softwareengineering.stackexchange.com/questions/311507/why-are-eval-like-features-considered-evil-in-contrast-to-other-possibly-harmfu -[eval-destructive]: https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html -[eval]: https://docs.python.org/3/library/functions.html?#eval [filter]: https://docs.python.org/3/library/functions.html#filter [find]: https://docs.python.org/3.9/library/stdtypes.html#str.find [functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce @@ -444,4 +460,5 @@ For more detail on this solution, take a look at the [dunder method with `__geta [split]: https://docs.python.org/3.9/library/stdtypes.html#str.split [startswith]: https://docs.python.org/3.9/library/stdtypes.html#str.startswith [strip]: https://docs.python.org/3.9/library/stdtypes.html#str.strip +[str-methods]: https://docs.python.org/3/library/stdtypes.html#string-methods [value-error]: https://docs.python.org/3.11/library/exceptions.html#ValueError diff --git a/exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md b/exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md index 3c3dcfe8ee9..cce88a4bb06 100644 --- a/exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md +++ b/exercises/practice/wordy/.approaches/string-list-and-dict-methods/content.md @@ -1,6 +1,73 @@ # String, List, and Dictionary Methods +```python +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is") + question = question.removesuffix("?") + question = question.replace("by", "") + question = question.strip() + + if not question: + raise ValueError("syntax error") + + formula = question.split() + while len(formula) > 1: + try: + x_value = int(formula[0]) + y_value = int(formula[2]) + symbol = formula[1] + remainder = formula[3:] + + if symbol == "plus": + formula = [x_value + y_value] + remainder + elif symbol == "minus": + formula = [x_value - y_value] + remainder + elif symbol == "multiplied": + formula = [x_value * y_value] + remainder + elif symbol == "divided": + formula = [x_value / y_value] + remainder + else: + raise ValueError("syntax error") + except: + raise ValueError("syntax error") + + return int(formula[0]) +``` + +Within the `answer()` function, the question is first checked for "unknown operations" by validating that it starts with "What is" ([`str.startswith`][startswith], [`str.endswith`][endswith]) and does not include the word "cubed" (_which is an invalid operation_). +This eliminates all the [current cases][unknown-operation-tests] where a [`ValueError("unknown operation")`][value-error] needs to be [raised][raise-statement]. +Should the definition of a question expand or change, this strategy would need to be revised. + + +The question is then "cleaned" by removing the prefix "What is" and the suffix "?" ([`str.removeprefix`][removeprefix], [`str.removesuffix`][removesuffix]), replacing "by" with "" ([`str.replace`][str-replace]), and [stripping][strip] any leading or trailing whitespaces. + + +If the question is now an empty string, a `ValueError("syntax error")` is raised. + + +The remaining question string is then converted into a `list` of elements via [`str.split`][split], and that `list` is iterated over using a `while-loop` with a `len()` > 1 condition. + +Within a [`try-except`][handling-exceptions] block to trap/handle any errors (_which will all map to `ValueError("syntax error")`_), the question `list` is divided up among 4 variables using [bracket notation][bracket-notation]: + +1. The first element, `x_value`. This is assumed to be a number, so it is converted to an `int()` +2. The third element, `y_value`. This is also assumed to be a number and converted to an `int()`. +3. The second element, `symbol`. This is assumed to be an operator, and is left as-is. +4. The `remainder` of the question, if there is any. This is a [slice][list-slice] starting at index 3, and going to the end. + + +`symbol` is then tested for "plus, minus, multiplied, or divided", and the `formula` list is modified by applying the given operation, and creating a new `formula` `list` by concatenating a `list` of the first product with the `remainder` list. +If `symbol` doesn't match any known operators, a `ValueError("syntax error")` is raised. + +Once `len(formula) == 1`, the first element (`formula[0]`) is converted to an `int()` and returned as the answer. + + +## Variation 1: Use a Dictionary for Lookup/Replace + + ```python OPERATIONS = {"plus": '+', "minus": '-', "multiplied": '*', "divided": '/'} @@ -9,21 +76,19 @@ def answer(question): if not question.startswith("What is") or "cubed" in question: raise ValueError("unknown operation") - question = question.removeprefix("What is").removesuffix("?").strip() + question = question.removeprefix("What is").removesuffix("?").replace("by", "").strip() if not question: raise ValueError("syntax error") formula = [] for operation in question.split(): - if operation == 'by': - continue - else: - formula.append(OPERATIONS.get(operation, operation)) + formula.append(OPERATIONS.get(operation, operation)) while len(formula) > 1: try: - x_value, y_value = int(formula[0]), int(formula[2]) + x_value = int(formula[0]) + y_value = int(formula[2]) symbol = formula[1] remainder = formula[3:] @@ -43,31 +108,31 @@ def answer(question): return int(formula[0]) ``` -Within the `answer()` function, the question is first checked for "unknown operations" by validating that it starts with "What is" ([`str.startswith`][startswith], [`str.endswith`][endswith]) and does not include the word "cubed" (_which is an invalid operation_). -This eliminates all the [current cases][unknown-operation-tests] where a [`ValueError("unknown operation")`][value-error] needs to be [raised][raise-statement]. -Should the definition of a question expand or change, this strategy would need to be revised. - -The question is then "cleaned" by removing the prefix "What is" and the suffix "?" ([`str.removeprefix`][removeprefix], [`str.removesuffix`][removesuffix]) and [stripping][strip] any leading or trailing whitespaces. +````exercism/note +[chaining][method-chaining] is used in the clean step for this variation, and is the equivalent of assigning and re-assigning `question` as is done in the initial approach. + This is because `str.startswith`, `str.endswith`, and `str.replace` all return strings, so the output of one can be used as the input to the next. + + [method-chaining]: https://www.tutorialspoint.com/Explain-Python-class-method-chaining +```` -If the question is now an empty string, a `ValueError("syntax error")` is raised. +This variation creates a dictionary to map operation words to symbols. +It pre-processes the question string into a `formula` list by looking up the operation words and replacing them with the symbols via the [`.get`][dict-get] method, which takes a [default argument][default-argument] for when a [`KeyError`][keyerror] is thrown. +Here the default for `dict.get()` is set to the element being iterated over, which is effectively _"if not found, skip it"_. +This means the number strings will be passed through, even though they would otherwise toss an error. + The results of iterating through the question are appended to `formula` via [`list.append`][list-append]. -Next, the question is [split][split] into a `list` and iterated over, with each element looked up and replaced from the OPERATIONS dictionary. -The [`dict.get`][dict-get] method is used for this, as it takes a default argument for when a [`KeyError`][keyerror] is thrown. -Here the default for `dict.get` is set to the element being iterated over, which is effectively _"if not found, skip it"_. - This avoids error handling, extra logic, or interruption when an element is not found. - One exception here is the word "by", which is explicitly skipped within the `for-loop`, so that it doesn't appear in the formula to be processed. - This filtering out could also be accomplished by using [`str.replace`][str-replace] in the cleaning step or during the `split` step. - The results of iterating through the question are then appended to a new formula `list`. +This dictionary is not necessary, but does potentially make adding/tracking future operations easier, although the `if-elif-else` block in the `while-loop` is equally awkward for maintenance (_see the [import callables from operator][approach-import-callables-from-operator] for a way to replace the block_). +The `while-loop`, `if-elif-else` block, and the `try-except` block are then the same as in the initial approach. ````exercism/note -There are a couple of common alternatives to the `loop-append`: +There are a couple of common alternatives to the `loop-append` used here: -1. [`list-comprehensions`][list-comprehension] duplicate the same process in a more succinct and declarative fashion: +1. [`list-comprehensions`][list-comprehension] duplicate the same process in a more succinct and declarative fashion. This one also includes filtering out "by": ```python formula = [OPERATIONS.get(operation, operation) for @@ -88,67 +153,68 @@ There are a couple of common alternatives to the `loop-append`: [map]: https://docs.python.org/3/library/functions.html#map ```` + Rather than indexing and slicing, [concept: unpacking and multiple assignment](/tracks/python/concepts/unpacking-and-multiple-assignment) can be used to assign the variables. + However, this does require a modification to the returned formula `list`: -After the formula `list` is composed, it is processed in a `while-loop`. - -The processing within the `loop` is wrapped in a [try-except][handling-exceptions] block to trap any errors and raise them as `ValueError("syntax error")`. -While each type of error could be checked for individually, it is not necessary since only `ValueError("syntax error")` is required here. - -1. `x_value` and `y_value` are assigned to the first element and third element of the list using [bracket notation][bracket-notation], and converted to integers. - - Rather than indexing and slicing, [concept: unpacking and multiple assignment](/tracks/python/concepts/unpacking-and-multiple-assignment) can be used to assign the variables. - This does require a modification to the returned formula `list`: - ```python - x_value, operation, y_value, *remainder = formula # <-- Unpacking won't allow conversion to int() here. - - ... - if symbol == "+": - formula = [int(x_value) + int(y_value)] + remainder # <-- Instead, conversion to int() must happen here. - ... - - return int(formula[0]) - ``` -2. `symbol` is assigned to the second element of the list. -3. `remainder` is assigned to a [slice][list-slice] of everything else in the `list`. + ```python + x_value, operation, y_value, *remainder = formula # <-- Unpacking won't allow conversion to int() here. + + ... + if symbol == "+": + formula = [int(x_value) + int(y_value)] + remainder # <-- Instead, conversion to int() must happen here. + ... + + return int(formula[0]) + ``` -The `symbol` is then tested in the `if-elif-else` block and the formula `list` is modified by calculating the operation on `x_value` and `y_value` and then appending whatever part of the question remains. -Once the formula `list` is calculated down to a number, that number is converted to an `int` and returned as the answer. +## Variation 2: Structural Pattern Matching to Replace `if-elif-else` -````exercism/note -Introduced in Python 3.10, [structural pattern matching][structural-pattern-matching] can be used to replace the `if-elif-else` chain in the `while-loop`. +Introduced in Python 3.10, [structural pattern matching][structural-pattern-matching] can be used to replace the `if-elif-else` chain in the `while-loop` used in the two approaches above. In some circumstances, this could be easier to read and/or reason about: ```python +def answer(question): + if not question.startswith("What is") or "cubed" in question: + raise ValueError("unknown operation") + + question = question.removeprefix("What is").removesuffix("?").replace("by", "").strip() + + if not question: + raise ValueError("syntax error") + + formula = question.split() while len(formula) > 1: try: - x_value, symbol, y_value, *remainder = formula + x_value, symbol, y_value, *remainder = formula #<-- unpacking and multiple assignment. - match symbol: - case "+": + match symbol: + case "plus": formula = [int(x_value) + int(y_value)] + remainder - case "-": + case "minus": formula = [int(x_value) - int(y_value)] + remainder - case "*": + case "multiplied": formula = [int(x_value) * int(y_value)] + remainder - case "/": + case "divided": formula = [int(x_value) / int(y_value)] + remainder - case _: - raise ValueError("syntax error") - except: - raise ValueError("syntax error") + case _: + raise ValueError("syntax error") #<-- "fall through case for no match." + except: raise ValueError("syntax error") # <-- error handling for anything else that goes wrong. + + return int(formula[0]) ``` -[structural-pattern-matching]: https://peps.python.org/pep-0636/ -```` - +[approach-import-callables-from-operator]: https://exercism.org/tracks/python/exercises/wordy/approaches/import-callables-from-operator [bracket-notation]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[default-argument]: https://docs.python.org/3/tutorial/controlflow.html#default-argument-values [dict-get]: https://docs.python.org/3/library/stdtypes.html#dict.get [endswith]: https://docs.python.org/3.9/library/stdtypes.html#str.endswith [handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions [keyerror]: https://docs.python.org/3/library/exceptions.html#KeyError +[list-append]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists [list-slice]: https://www.pythonmorsels.com/slicing/ [raise-statement]: https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement [removeprefix]: https://docs.python.org/3.9/library/stdtypes.html#str.removeprefix @@ -157,5 +223,6 @@ In some circumstances, this could be easier to read and/or reason about: [startswith]: https://docs.python.org/3.9/library/stdtypes.html#str.startswith [str-replace]: https://docs.python.org/3/library/stdtypes.html#str.replace [strip]: https://docs.python.org/3.9/library/stdtypes.html#str.strip +[structural-pattern-matching]: https://peps.python.org/pep-0636/ [unknown-operation-tests]: https://github.com/exercism/python/blob/main/exercises/practice/wordy/wordy_test.py#L58-L68 [value-error]: https://docs.python.org/3.11/library/exceptions.html#ValueError diff --git a/exercises/practice/wordy/.docs/instructions.append.md b/exercises/practice/wordy/.docs/instructions.append.md index ca646b7bd9b..d26afab5fff 100644 --- a/exercises/practice/wordy/.docs/instructions.append.md +++ b/exercises/practice/wordy/.docs/instructions.append.md @@ -5,6 +5,8 @@ Sometimes it is necessary to [raise an exception][raise-an-exception]. When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types][built-in-errors], but should still include a meaningful message. This particular exercise requires that you use the [raise statement][raise-statement] to "throw" a `ValueError` if the question passed to `answer()` is malformed/invalid, or contains an unknown operation. The tests will only pass if you both `raise` the `exception` and include a message with it. +**Please note**: The message needed is different for each scenario, even though the same _error type_ is being raised. +Check the tests carefully. To raise a [`ValueError`][value-error] with a message, write the message as an argument to the `exception` type: @@ -17,16 +19,22 @@ raise ValueError("unknown operation") raise ValueError("syntax error") ``` -To _handle_ a raised error within a particular code block, one can use a [try-except][handling-exceptions] : +To _handle_ a raised error within a particular code block, one can use a [try-except][handling-exceptions]. + `try-except` blocks "wrap" the code that could potentially cause an error, mapping all the exceptions to one error, multiple errors, or other pieces of code to deal with the problem: + ```python while len(equation) > 1: - try: + try: + # The questionable/error-prone code goes here,in an indented block + # It can contain statements, loops, if-else blocks, or other executable code. x_value, operation, y_value, *rest = equation - ... - + ... + except: + # Code for what to do when an error gets thrown in the code above. + # This could be one error, or more complicated logging, error checking and messaging. raise ValueError("syntax error") ``` From c5922804a889978ec08dcd9d08b2604b68bc3bf6 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 14 Oct 2024 17:26:36 -0700 Subject: [PATCH 172/277] [Wordy]: Moar Typos & Missing Links. (#3789) * Moar typos and missing links. Hopefully, this is the last round. * Added in missing hints file. * Further touchup on intro md. --- .../wordy/.approaches/introduction.md | 54 ++++++++++++------- exercises/practice/wordy/.docs/hints.md | 40 ++++++++++++++ 2 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 exercises/practice/wordy/.docs/hints.md diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index 6f23f040bac..8af7b35835c 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -1,46 +1,45 @@ # Introduction The objective of the Wordy exercise is to parse and evaluate small/simple mathematical word problems, returning the result as an integer. -These problems do not require complex or [PEMDAS][PEMDAS]-based evaluation and are instead processed from left-to-right _in sequence_. +These questions do not require complex or [PEMDAS][PEMDAS]-based evaluation and are instead processed from left-to-right _in sequence_. This means that for some of the test cases, the solution will not be the same as if the word problem was evaluated like a traditional math problem. +
+ ## General Guidance The key to a Wordy solution is to remove the "question" portion of the sentence (_"What is", "?"_) and process the remaining words between numbers as [operators][mathematical operators]. - - If a single number remains after removing the "question" pieces, it should be converted to an [`int`][int] and returned as the answer. -Any words or word-number combinations that do not fall into the simple mathematical evaluation pattern (_number-operator-number_) should [`raise`][raise-statement] a [`ValueError`][value-error] with a message. +Any words or word-number combinations that do not fall into the simple mathematical evaluation pattern (_number-operator-number_) should [`raise`][raise-statement] a ["ValueError('syntax error")`][value-error] with a message. This includes any "extra" spaces between numbers. - - -One way to reduce the number of `raise` statements/ `ValueError`s needed in the code is to determine if a problem is a "valid" question _before_ proceeding to parsing and calculation. As shown in various approaches, there are multiple strategies for validating questions, with no one "canonical" solution. -One very effective validation approach is to check if a question starts with "What is", ends with "?", and does not include the word "cubed". +A whole class of error can be eliminated up front by checking if a question starts with "What is", ends with "?", and does not include the word "cubed". Any other question formulation becomes a `ValueError("unknown operation")`. -This very restrictive approach could lead to future maintenance issues if the definition of a question ever changes or operations are added, but for the purposes of passing the current Wordy tests, it works well. +This could lead to future maintenance issues if the definition of a question ever changes or operations are added, but for the purposes of passing the current Wordy tests, it works well. -Proceeding from validation, there are many Pythonic ways to go about the cleaning, parsing, and calculation steps of Wordy. -However, they all follow these general steps: +~~~~exercism/note +There are many Pythonic ways to go about the cleaning, parsing, and calculation steps of Wordy. +However, the solutions all follow these general steps: 1. Remove the parts of the question string that do not apply to calculating the answer. 2. Iterate over the question, determining which words are numbers, and which are meant to be mathematical operations. - - _Converting the question string into a `list` of words is hugely helpful here, but not absolutely necessary._ -3. **_Starting from the left_**, take the first three elements and convert number strings to `int` and operations words to +, -, *, /. + -- _Converting the question string into a `list` of words is hugely helpful here._ +3. **_Starting from the left_**, take the first three elements and convert number strings to `int` and operations words to the mathematical operations +, -, *, and /. 4. Apply the operation to the numbers, which should result in a single number. - - _Employing a `try-except` block around the conversion and operator application steps can trap any errors thrown and make the code both "safer" and less complex._ + -- _Employing a `try-except` block can trap any errors thrown and make the code both "safer" and less complex._ 5. Use the calculated number from step 4 as the start for the next "trio" (_number, operation, number_) in the question. The calculated number + the remainder of the question becomes the question being worked on in the next iteration. - - _Using a `while-loop` with a test on the length of the question to do calculation is a very common strategy._ + -- _Using a `while-loop` with a test on the length of the question to do calculation is a very common strategy._ 6. Once the question is calculated down to a single number, that is the answer. Anything else that happens in the loop/iteration or within the accumulated result is a `ValueError("syntax error")`. +~~~~ -For cleaning the question, [`str.removeprefix`][removeprefix] and +For question cleaning, [`str.removeprefix`][removeprefix] and [`str.removesuffix`][removesuffix] introduced in `Python 3.9` can be very useful: @@ -79,20 +78,37 @@ Different combinations of [`str.find`][find], [`str.rfind`][rfind], or [`str.ind A [regex][regex] could be used to process the question as well, but might be considered overkill given the fixed nature of the prefix/suffix and operations. Finally, [`str.strip`][strip] and its variants are very useful for cleaning up any leftover leading or trailing whitespace. -Many solutions then use [`str.split`][split] to process the remaining "cleaned" question into a `list` for convenient looping/iteration, although other strategies can also be used. +Many solutions then use [`str.split`][split] to process the remaining "cleaned" question into a `list` for convenient looping/iteration, although other strategies can also be used: -For math operations, many solutions involve importing and using methods from the [operator][operator] module in combination with different looping, parsing, and substitution strategies. +```python +>>> sentence = "The quick brown fox jumped over the lazy dog 10 times" +>>> sentence.split() +['The', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog', '10', 'times'] +``` + + +For math operations, many solutions involve importing and using methods from the [operator][operator] module. Some solutions use either [lambda][lambdas] expressions, [dunder/"special" methods][dunder-methods], or even `eval()` to replace words with arithmetic operations. -However, the exercise can be solved **without** using `operator`, `lambdas`, `dunder-methods` or `eval`. + + +However, the exercise can be solved without using `operator`, `lambdas`, `dunder-methods` or `eval`. It is recommended that you first start by solving it _without_ "advanced" strategies, and then refine your solution into something more compact or complex as you learn and practice. ~~~~exercism/caution Using [`eval`][eval] for the operations might seem convenient, but it is a [dangerous][eval-danger] and possibly [destructive][eval-destructive] approach. It is also entirely unnecessary, as the other methods described here are safer and equally performant. + +[eval-danger]: https://softwareengineering.stackexchange.com/questions/311507/why-are-eval-like-features-considered-evil-in-contrast-to-other-possibly-harmfu +[eval-destructive]: https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html +[eval]: https://docs.python.org/3/library/functions.html?#eval ~~~~ +
+ +_____________ + ## Approach: String, List, and Dictionary Methods diff --git a/exercises/practice/wordy/.docs/hints.md b/exercises/practice/wordy/.docs/hints.md new file mode 100644 index 00000000000..95e798f7dbc --- /dev/null +++ b/exercises/practice/wordy/.docs/hints.md @@ -0,0 +1,40 @@ +# Hints + +## General + +- This challenge is all about validating, "cleaning up", and processing the given question in the **_correct order_**. +- If you read the directions and tests carefully, you will find there are three conditions that need to be met for a question to be "valid". +If a question is not valid, you will need to [raise ][raise-statement] a [ValueError][value-error] with a message (_`ValueError("unknown operation")`_). + It is best if you get this particular check out of the way before doing anything else. +- Processing a question before calculating the answer is all about utilizing [string methods][str-methods]. +A few popular ones to investigate include `str.removeprefix`, `str.removesuffix`, `str.replace`, and `str.strip`. +Others you might want to check out are `str.startswith`, `str.endswith`, and `in` (_which can apply to more than just strings_). + +- It is possible to iterate over a string. However, it is **much** easier to iterate over a list of _words_ if the string you are processing is a sentence or fragment with spaces. [`str.split`][split] can break apart a string and return a list of "words". +- A [`while-loop`][while-loop] is very useful for iterating over the question to process items. +- For fewer error checks and cleaner error-handling, a [`try-except`][handling-exceptions] is recommended as you process the question. +- **Remember**: the question is processed **_left-to-right_**. That means "1 plus 12 minus 3 multiplied by 4" gets processed by: + - Calculating "1 plus 12" (13), + - Calculating "13 minus 3" (10), + - Calculating "10 multiplied by 4" (40). + - The result of the first calculation is _concatenated with the remainder of the question to form a new question for the next step_. + - This technique is sometimes called [the accumulator pattern][accumulator-pattern], or [fold][fold] / [foldl][foldl] in functional programming languages like [Haskell][haskell-folds] or Lisp. + - Python includes two methods that are purpose-built to apply the `accumulator-pattern` to iterable data structures like `lists`, `tuples`, and `strings`. + [`functools.reduce`][reduce] and [`itertools.accumulate`][accumulate] could be interesting to investigate here. + +- This exercise has many potential solutions and many paths you can take along the way. + No path is manifestly "better" than another, although a particular path may be more interesting or better suited to what you want to learn or explore right now. + + +[accumulate]: https://docs.python.org/3/library/itertools.html#itertools.accumulate +[accumulator-pattern]: https://muzny.github.io/csci1200-notes/08/2/accumulator.html +[fold]: https://en.wikipedia.org/wiki/Fold_(higher-order_function) +[foldl]: https://slim.computer/eecs-111-ta-guide/material/higher-order/Fold.html +[handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions +[haskell-folds]: https://www.ashwinnarayan.com/post/a-study-on-haskell-folds/ +[raise-statement]: https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement +[reduce]: https://docs.python.org/3/library/functools.html#functools.reduce +[split]: https://docs.python.org/3.9/library/stdtypes.html#str.split +[str-methods]: https://docs.python.org/3/library/stdtypes.html#string-methods +[value-error]: https://docs.python.org/3.11/library/exceptions.html#ValueError +[while-loop]: https://python-practice.com/learn/loops/while_loop/ From 7875b774154e9fe0fe9a830e41d0935ad083b2e7 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 15 Oct 2024 15:18:06 -0700 Subject: [PATCH 173/277] Fixed links and other lingering typos. (#3790) --- .../dunder-getattribute/content.md | 6 ++++- .../.approaches/functools-reduce/content.md | 4 +-- .../wordy/.approaches/introduction.md | 14 ++++++++-- .../wordy/.approaches/recursion/content.md | 27 ++++++++++++++----- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md index 31c42e7a2aa..3f82c0ee518 100644 --- a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md +++ b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md @@ -37,13 +37,17 @@ def answer(question): This approach begins by defining a [dictionary][dictionaries] of the word keys with their related [`dunder-methods`][dunder] methods. Since only whole numbers are involved, the available `dunder-methods` are those for the [`int`][int] class/namespace. The supported methods for the `int()` namespace can be found by using `print(dir(int))` or `print(int.__dict__)` in a Python terminal. -See [SO: Difference between dir() and __dict__][dir-vs-__dict__] for differences between the two. +See [`SO: Difference between dir() and __dict__`][dir-vs-__dict__] for more details. + +
~~~~exercism/note The built-in [`dir`](https://docs.python.org/3/library/functions.html?#dir) function returns a list of all valid attributes for an object. The `dunder-method` [`.__dict__`](https://docs.python.org/3/reference/datamodel.html#object.__dict__) is a mapping of an objects writable attributes. ~~~~ +
+ The `OPS` dictionary is defined with all uppercase letters, which is the naming convention for a Python [constant][const]. It indicates that the value should not be changed. diff --git a/exercises/practice/wordy/.approaches/functools-reduce/content.md b/exercises/practice/wordy/.approaches/functools-reduce/content.md index 602a678ffd5..8bb7de25f30 100644 --- a/exercises/practice/wordy/.approaches/functools-reduce/content.md +++ b/exercises/practice/wordy/.approaches/functools-reduce/content.md @@ -72,8 +72,9 @@ A `try-except` is not needed here because the error scenarios are already filter But it is also hard to understand what is happening if you have not worked with a reduce or foldl function in the past. It could be argued that writing the code as a `while-loop` or recursive function is easier to reason about for non-functional programmers. +
-## Variation: 1: Use a Dictionary of `lambdas` instead of importing from operator +## Variation 1: Use a Dictionary of `lambdas` instead of importing from operator The imports from operator can be swapped out for a dictionary of `lambda-expressions` (or calls to `dunder-methods`), if so desired. @@ -119,7 +120,6 @@ def answer(question): return result ``` - [approach-lambdas-in-a-dictionary]: https://exercism.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary [callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ [functools-reduce]: https://docs.python.org/3/library/functools.html#functools.reduce diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index 8af7b35835c..135af33b59e 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -22,10 +22,12 @@ A whole class of error can be eliminated up front by checking if a question star Any other question formulation becomes a `ValueError("unknown operation")`. This could lead to future maintenance issues if the definition of a question ever changes or operations are added, but for the purposes of passing the current Wordy tests, it works well. +
~~~~exercism/note There are many Pythonic ways to go about the cleaning, parsing, and calculation steps of Wordy. -However, the solutions all follow these general steps: +However, solutions all follow the same general steps: + 1. Remove the parts of the question string that do not apply to calculating the answer. 2. Iterate over the question, determining which words are numbers, and which are meant to be mathematical operations. @@ -38,6 +40,7 @@ However, the solutions all follow these general steps: 6. Once the question is calculated down to a single number, that is the answer. Anything else that happens in the loop/iteration or within the accumulated result is a `ValueError("syntax error")`. ~~~~ +
For question cleaning, [`str.removeprefix`][removeprefix] and [`str.removesuffix`][removesuffix] introduced in `Python 3.9` can be very useful: @@ -95,6 +98,7 @@ Some solutions use either [lambda][lambdas] expressions, [dunder/"special" metho However, the exercise can be solved without using `operator`, `lambdas`, `dunder-methods` or `eval`. It is recommended that you first start by solving it _without_ "advanced" strategies, and then refine your solution into something more compact or complex as you learn and practice. +
~~~~exercism/caution Using [`eval`][eval] for the operations might seem convenient, but it is a [dangerous][eval-danger] and possibly [destructive][eval-destructive] approach. @@ -158,6 +162,7 @@ Alternatives could use a [dictionary][dict] to store word --> operator mappings For more details and variations, read the [String, List and Dictionary Methods][approach-string-list-and-dict-methods] approach. +
## Approach: Import Callables from the Operator Module @@ -199,6 +204,7 @@ Like the first approach, it uses a [try-except][handling-exceptions] block for h For more details and options, take a look at the [Import Callables from the Operator Module][approach-import-callables-from-operator] approach. +
## Approach: Regex and the Operator Module @@ -257,6 +263,7 @@ It is longer than some solutions, but clearer and potentially easier to maintain For more details, take a look at the [regex-with-operator-module][approach-regex-with-operator-module] approach. +
## Approach: Lambdas in a Dictionary to return Functions @@ -306,6 +313,7 @@ These "hand-crafted" `lambdas` could also introduce a mathematical error, althou For more details, take a look at the [Lambdas in a Dictionary][approach-lambdas-in-a-dictionary] approach. +
## Approach: Recursion @@ -351,6 +359,7 @@ The dictionary in this example could use functions from `operator`, `lambdas`, ` For more details, take a look at the [recursion][approach-recursion] approach. +
## Approach: functools.reduce() @@ -393,6 +402,7 @@ This solution may be a little less clear to follow or reason about due to the sl For more details and variations, take a look at the [functools.reduce for Calculation][approach-functools-reduce] approach. +
## Approach: Dunder methods with `__getattribute__` @@ -444,7 +454,7 @@ For more detail on this solution, take a look at the [dunder method with `__geta [approach-dunder-getattribute]: https://exercism.org/tracks/python/exercises/wordy/approaches/dunder-getattribute [approach-functools-reduce]: https://exercism.org/tracks/python/exercises/wordy/approaches/functools-reduce [approach-import-callables-from-operator]: https://exercism.org/tracks/python/exercises/wordy/approaches/import-callables-from-operator -[approach-lambdas-in-a-dictionary]: https://exercsim.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary +[approach-lambdas-in-a-dictionary]: https://exercism.org/tracks/python/exercises/wordy/approaches/lambdas-in-a-dictionary [approach-recursion]: https://exercism.org/tracks/python/exercises/wordy/approaches/recursion [approach-regex-with-operator-module]: https://exercism.org/tracks/python/exercises/wordy/approaches/regex-with-operator-module [approach-string-list-and-dict-methods]: https://exercism.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods diff --git a/exercises/practice/wordy/.approaches/recursion/content.md b/exercises/practice/wordy/.approaches/recursion/content.md index 8131cbb7c95..0ba69fca502 100644 --- a/exercises/practice/wordy/.approaches/recursion/content.md +++ b/exercises/practice/wordy/.approaches/recursion/content.md @@ -5,14 +5,21 @@ A recursive strategy [may not always be obvious][looping-vs-recursion] or easy — but it is always possible. So the `while-loop`s used in other approaches to Wordy can be re-written to use recursive calls. +
+ That being said, Python famously does not perform [tail-call optimization][tail-call-optimization], and limits recursive calls on the stack to a depth of 1000 frames, so it is important to only use recursion where you are confident that it can complete within the limit (_or something close to it_). [Memoization][memoization] and other strategies in [dynamic programming][dynamic-programming] can help to make recursion more efficient and "shorter" in Python, but it's always good to give it careful consideration. +
+ Recursion works best with problem spaces that resemble trees, include [backtracking][backtracking], or become progressively smaller. - Some examples include financial processes like calculating [amortization][amortization] and [depreciation][depreciation], tracking [radiation reduction through nuclei decay][nuclei-decay], and algorithms like [biscetion search][bisection-search], [depth-firs search][dfs], and [merge sort][merge-sort]. + Some examples include financial processes like calculating [amortization][amortization] and [depreciation][depreciation], tracking [radiation reduction through nuclei decay][nuclei-decay], and algorithms like [biscetion search][bisection-search], [depth-first search][dfs], and [merge sort][merge-sort]. + +
-Other algorithms such as [breadth-first search][bfs], [Dijkstra's algorithm][dijkstra], and the [Bellman-Ford Algorithm][bellman-ford] lend themselves better to iteration. +Other algorithms such as [breadth-first search][bfs], [Dijkstra's algorithm][dijkstra], and the [Bellman-Ford Algorithm][bellman-ford] lend themselves better to loops. +
```python from operator import add, mul, sub @@ -68,13 +75,16 @@ This approach separates the solution into three functions: 2. `clean()`, which takes a question string and returns a `list` of parsed words and numbers to calculate from. 3. `calculate()`, which performs the calculations on the `list` recursively, until a single number (_the base case check_) is returned as the answer — or an error is thrown. +
+ The cleaning logic is separate from the processing logic so that the cleaning steps aren't repeated over and over with each recursive `calculate()` call. -This separation also makes it easier to make changes in processing or calculating without creating conflict or confusion. +This separation also makes it easier to make changes without creating conflict or confusion. -Note that `calculate()` performs the same steps as the `while-loop` from [Import Callables from the Operator Module][approach-import-callables-from-operator] and others. +`calculate()` performs the same steps as the `while-loop` from [Import Callables from the Operator Module][approach-import-callables-from-operator] and others. The difference being that the `while-loop` test for `len()` 1 now occurs as an `if` condition in the function (_the base case_), and the "looping" is now a call to `calculate()` in the `else` condition. `calculate()` can also use many of the strategies detailed in other approaches, as long as they work with the recursion. +
`clean()` can also use any of the strategies detailed in other approaches, two of which are below: @@ -91,6 +101,7 @@ The difference being that the `while-loop` test for `len()` 1 now occurs as an ` question.strip("?").split())) #<-- The () in list() also invokes implicit concatenation. ``` +
## Variation 1: Use Regex for matching, cleaning, and calculating @@ -184,6 +195,7 @@ Because each new iteration of the question needs to be validated, there is an `i Note that the `for-loop` and VALIDATE use [`re.match`][re-match], but DIGITS validation uses [`re.fullmatch`][re-fullmatch]. +
## Variation 2: Use Regex, Recurse within the For-loop @@ -229,13 +241,14 @@ This saves some space, but requires that the nested `tuples` be unpacked as the Recursion is used a bit differently here from the previous variations — the calls are placed [within the `for-loop`][recursion-within-loops]. Because the regex are more generic, they will match a `digit-operation-digit` trio in a longer question, so the line `return operation(calculate(match['x']), calculate(match['y']))` is effectively splitting a question into parts that can then be worked on in their own stack frames. + For example: 1. "1 plus -10 multiplied by 13 divided by 2" would match on "1 plus -10" (_group x_) **multiplied by** "13 divided by 2" (_group y_). -2. This would then be re-arranged to `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` +2. This is re-arranged to `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` 3. At this point the loop would pause as the two recursive calls to `calculate()` spawn -4. The loops would run again — and so would the calls to `calculate()`, until there wasn't any match that caused a split of the question or an error. -5. One at a time, the numbers would then be returned, until the main `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` could be solved, at which point the answer would be returned. +4. The loops then run again — and so would the calls to `calculate()`, until there wasn't any match that caused a split of the question or an error. +5. One at a time, the numbers would then be returned, until the main `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` could be solved, at which point the answer is returned. For a more visual picture, you can step through the code on [pythontutor.com][recursion-in-loop-pythontutor]. From df381d86f49a7bee83e42cd467acbd0665b8752d Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 15 Oct 2024 21:43:01 -0700 Subject: [PATCH 174/277] Touch ups and edits for clarity in instructions, introductions, hints and stub file. (#3791) --- concepts/basics/introduction.md | 23 +++++++--- .../guidos-gorgeous-lasagna/.docs/hints.md | 5 +- .../.docs/instructions.md | 46 ++++++++++++++----- .../.docs/introduction.md | 16 ++++--- .../guidos-gorgeous-lasagna/lasagna.py | 12 +++-- 5 files changed, 74 insertions(+), 28 deletions(-) diff --git a/concepts/basics/introduction.md b/concepts/basics/introduction.md index 2a874394ebb..818dd47deac 100644 --- a/concepts/basics/introduction.md +++ b/concepts/basics/introduction.md @@ -2,18 +2,26 @@ Python is a [dynamic and strongly typed][dynamic typing in python] programming language. It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints]. +Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions. + +Python was created by Guido van Rossum and first released in 1991. Imperative, declarative (e.g., functional), and object-oriented programming _styles_ are all supported, but internally **[everything in Python is an object][everythings an object]**. -Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions. +We'll dig more into what all of that means as we continue through the Python track concepts. -Python was created by Guido van Rossum and first released in 1991. +This first concept (`basics`) introduces 4 major Python language features: +1. Name Assignment (_variables and constants_), +2. Functions (_the `def` keyword and the `return` keyword_), +3. Comments, and +4. Docstrings. +
## Name Assignment (Variables & Constants) Programmers can bind [_names_][facts-and-myths-about-python-names] (also called _variables_) to any type of object using the assignment `=` operator: ` = `. -A name can be reassigned (or re-bound) to different values (different object types) over its lifetime. +A name can be reassigned (or re-bound) to different values (different object types) over its lifetime: ```python @@ -37,9 +45,10 @@ A name can be reassigned (or re-bound) to different values (different object typ ### Constants -Constants are names meant to be assigned only once in a program. -They should be defined at a [module][module] (file) level, and are typically visible to all functions and classes in the program. -Using `SCREAMING_SNAKE_CASE` signals that the name should not be re-assigned, or its value mutated. +Constants are names meant to be assigned only once in a program — although Python will not prevent re-assignment. +Using `SCREAMING_SNAKE_CASE` signals to anyone reading the code that the name should **not** be re-assigned, or its value mutated. +Constants should be defined at a [module][module] (file) level, and are typically visible to all functions and classes in a program. + ## Functions @@ -92,7 +101,9 @@ def add_two_numbers(number_one, number_two): 11 ``` + Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object. +This means that if you do not use `return` in a function, Python will return the `None` object for you. The details of `None` will be covered in a later exercise. For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null: diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md index 90065248520..f2c07d50b6e 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/hints.md @@ -10,7 +10,9 @@ ## 1. Define expected bake time in minutes -- You need to [name][naming] a constant, and [assign][assignment] it an [integer][numbers] value. +- You need to [name][naming] a [constant][constants], and [assign][assignment] it an [integer][numbers] value. + This constant should be the first thing after the docstring that is at the top of the file. + Remember to remove the #TODO comment after defining the constant. ## 2. Calculate remaining bake time in minutes @@ -38,6 +40,7 @@ [assignment]: https://docs.python.org/3/reference/simple_stmts.html#grammar-token-assignment-stmt [comments]: https://realpython.com/python-comments-guide/ +[constants]: https://stackoverflow.com/a/2682752 [defining functions]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions [docstrings]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings [naming]: https://realpython.com/python-variables/ diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md index 2d61cec837d..fce16c46795 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/instructions.md @@ -4,52 +4,70 @@ You're going to write some code to help you cook a gorgeous lasagna from your fa You have five tasks, all related to cooking your recipe. -## 1. Define expected bake time in minutes +
-Define an `EXPECTED_BAKE_TIME` constant that returns how many minutes the lasagna should bake in the oven. +~~~~exercism/note +We have started the first function definition for you in the stub file, but you will need to write the remaining function definitions yourself. +You will also need to define any constants yourself. +Read the #TODO comment lines in the stub file carefully. +Once you are done with a task, remove the TODO comment. +~~~~ + +
+ +## 1. Define expected bake time in minutes as a constant + +Define the `EXPECTED_BAKE_TIME` [constant][constants] that represents how many minutes the lasagna should bake in the oven. According to your cookbook, the Lasagna should be in the oven for 40 minutes: ```python ->>> import lasagna ->>> lasagna.EXPECTED_BAKE_TIME +>>> print(EXPECTED_BAKE_TIME) 40 ``` ## 2. Calculate remaining bake time in minutes -Implement the `bake_time_remaining()` function that takes the actual minutes the lasagna has been in the oven as an argument and returns how many minutes the lasagna still needs to bake based on the `EXPECTED_BAKE_TIME`. +Complete the `bake_time_remaining()` function that takes the actual minutes the lasagna has been in the oven as an argument and returns how many minutes the lasagna still needs to bake based on the `EXPECTED_BAKE_TIME` constant. ```python ->>> from lasagna import bake_time_remaining >>> bake_time_remaining(30) 10 ``` + ## 3. Calculate preparation time in minutes -Implement the `preparation_time_in_minutes(number_of_layers)` function that takes the number of layers you want to add to the lasagna as an argument and returns how many minutes you would spend making them. +Define the `preparation_time_in_minutes()` [function][functions] that takes the `number_of_layers` you want to add to the lasagna as an argument and returns how many minutes you would spend making them. Assume each layer takes 2 minutes to prepare. ```python ->>> from lasagna import preparation_time_in_minutes +>>> def preparation_time_in_minutes(number_of_layers): + ... + ... + >>> preparation_time_in_minutes(2) 4 ``` + ## 4. Calculate total elapsed cooking time (prep + bake) in minutes -Implement the `elapsed_time_in_minutes(number_of_layers, elapsed_bake_time)` function that has two parameters: `number_of_layers` (_the number of layers added to the lasagna_) and `elapsed_bake_time` (_the number of minutes the lasagna has been baking in the oven_). -This function should return the total number of minutes you've been cooking, or the sum of your preparation time and the time the lasagna has already spent baking in the oven. +Define the `elapsed_time_in_minutes()` function that takes two parameters as arguments: `number_of_layers` (_the number of layers added to the lasagna_) and `elapsed_bake_time` (_the number of minutes the lasagna has been baking in the oven_). +This function should return the total number of minutes you have been cooking, or the sum of your preparation time and the time the lasagna has already spent baking in the oven. ```python ->>> from lasagna import elapsed_time_in_minutes +>>> def elapsed_time_in_minutes(number_of_layers, elapsed_bake_time): + ... + ... + >>> elapsed_time_in_minutes(3, 20) 26 ``` + ## 5. Update the recipe with notes -Go back through the recipe, adding "notes" in the form of function docstrings. +Go back through the recipe, adding "notes" in the form of [function docstrings][function-docstrings]. ```python def elapsed_time_in_minutes(number_of_layers, elapsed_bake_time): @@ -64,3 +82,7 @@ def elapsed_time_in_minutes(number_of_layers, elapsed_bake_time): lasagna. """ ``` + +[constants]: https://stackoverflow.com/a/2682752 +[functions]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions +[function-docstrings]: https://docs.python.org/3/tutorial/controlflow.html#documentation-strings diff --git a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md index ffe2bedd6a3..20321da5303 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md +++ b/exercises/concept/guidos-gorgeous-lasagna/.docs/introduction.md @@ -14,6 +14,7 @@ This first exercise introduces 4 major Python language features: 3. Comments, and 4. Docstrings. +
~~~~exercism/note @@ -25,6 +26,7 @@ On the Python track, [variables][variables] are always written in [`snake_case`] [snake case]: https://en.wikipedia.org/wiki/Snake_case ~~~~ +
## Name Assignment (Variables & Constants) @@ -33,8 +35,8 @@ A name can be reassigned (or re-bound) to different values (different object typ ```python ->>> my_first_variable = 1 # my_first_variable bound to an integer object of value one. ->>> my_first_variable = 2 # my_first_variable re-assigned to integer value 2. +>>> my_first_variable = 1 #<-- my_first_variable bound to an integer object of value one. +>>> my_first_variable = 2 #<-- my_first_variable re-assigned to integer value 2. >>> print(type(my_first_variable)) @@ -42,12 +44,12 @@ A name can be reassigned (or re-bound) to different values (different object typ >>> print(my_first_variable) 2 ->>> my_first_variable = "Now, I'm a string." # You may re-bind a name to a different object type and value. +>>> my_first_variable = "Now, I'm a string." #<-- You may re-bind a name to a different object type and value. >>> print(type(my_first_variable)) >>> print(my_first_variable) -"Now, I'm a string." # Strings can be declared using single or double quote marks. +"Now, I'm a string." #<-- Strings can be declared using single or double quote marks. ``` @@ -90,10 +92,11 @@ IndentationError: unindent does not match any outer indentation level Functions _explicitly_ return a value or object via the [`return`][return] keyword: + ```python # Function definition on first line, explicit return used on final line. -def add_two_numbers(number_one, number_two): - return number_one + number_two +>>> def add_two_numbers(number_one, number_two): + return number_one + number_two # Calling the function in the Python terminal returns the sum of the numbers. @@ -109,6 +112,7 @@ def add_two_numbers(number_one, number_two): Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object. +This means that if you do not use `return` in a function, Python will return the `None` object for you. The details of `None` will be covered in a later exercise. For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null: diff --git a/exercises/concept/guidos-gorgeous-lasagna/lasagna.py b/exercises/concept/guidos-gorgeous-lasagna/lasagna.py index 90d0102584c..a3df0ab2da0 100644 --- a/exercises/concept/guidos-gorgeous-lasagna/lasagna.py +++ b/exercises/concept/guidos-gorgeous-lasagna/lasagna.py @@ -8,7 +8,7 @@ """ -#TODO: define the 'EXPECTED_BAKE_TIME' constant. +#TODO: define the 'EXPECTED_BAKE_TIME' constant below. #TODO: Remove 'pass' and complete the 'bake_time_remaining()' function below. @@ -27,9 +27,15 @@ def bake_time_remaining(): #TODO: Define the 'preparation_time_in_minutes()' function below. -# You might also consider using 'PREPARATION_TIME' here, if you have it defined. +# You might also consider defining a 'PREPARATION_TIME' constant. +# You can do that on the line below the 'EXPECTED_BAKE_TIME' constant. +# This will make it easier to do calculations. #TODO: define the 'elapsed_time_in_minutes()' function below. -# Remember to add a docstring (you can copy and then alter the one from bake_time_remaining.) + + + +# TODO: Remember to go back and add docstrings to all your functions +# (you can copy and then alter the one from bake_time_remaining.) From c230448e3f9a2aefd3b0198c9fad8d6efc3b09c2 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 16 Oct 2024 00:13:48 -0700 Subject: [PATCH 175/277] Fixed final test and error message. Sometimes, I am an idiot. (#3792) [no important files changed] The test has changed, but this will allow more passing code without need for retest. --- exercises/concept/plane-tickets/generators_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/exercises/concept/plane-tickets/generators_test.py b/exercises/concept/plane-tickets/generators_test.py index 12a3532aa5a..1596d424ed4 100644 --- a/exercises/concept/plane-tickets/generators_test.py +++ b/exercises/concept/plane-tickets/generators_test.py @@ -134,4 +134,7 @@ def test_generate_codes(self): f'The function returned {actual_result}, but the tests ' f'expected {expected} when generating ticket numbers.') - self.assertEqual(list(generate_codes(seat_numbers, flight_id)), expected, msg=error_message) + # Note: DO NOT call the function here again, in case the student is using list.pop() + # to process the input. If another call is done with that condition, + # the test will fail with a terrible error message. + self.assertEqual(actual_result, expected, msg=error_message) From 1d2c981fc0e80632bcf64269f994f89c9fdab581 Mon Sep 17 00:00:00 2001 From: Wolfgang <53096914+wolmoe@users.noreply.github.com> Date: Sun, 20 Oct 2024 22:28:40 +0200 Subject: [PATCH 176/277] Fixed typo.md (#3794) Fixed minor typo hasable -> hashable --- exercises/concept/cater-waiter/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index 044432b5c2a..926aea3d906 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -1,7 +1,7 @@ # Sets -A [set][type-set] is a _mutable_ and _unordered_ collection of [_hasable_][hashable] objects. +A [set][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. Set members must be distinct — duplicate items are not allowed. They can hold multiple different data types and even nested structures like a `tuple` of `tuples` — as long as all elements can be _hashed_. Sets also come in an immutable [`frozensets`][type-frozenset] flavor. From 83e71ed61e2110e62e0f7a9e959bfd8cdff3ec7a Mon Sep 17 00:00:00 2001 From: Andrew Pam Date: Wed, 23 Oct 2024 19:50:00 +1100 Subject: [PATCH 177/277] Update resistor_color_expert_test.py (#3797) Add additional tests for some edge cases. Note that "1 ohms", "1 kiloohms" and "1 megaohms" should really be "ohm" singular but that's out of scope of the requirements for this exercise. --- .../resistor_color_expert_test.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/exercises/practice/resistor-color-expert/resistor_color_expert_test.py b/exercises/practice/resistor-color-expert/resistor_color_expert_test.py index 2ba4b877d9d..47e7fc63440 100644 --- a/exercises/practice/resistor-color-expert/resistor_color_expert_test.py +++ b/exercises/practice/resistor-color-expert/resistor_color_expert_test.py @@ -49,3 +49,13 @@ def test_brown_red_orange_green_and_blue(self): self.assertEqual( resistor_label(["brown", "red", "orange", "green", "blue"]), "12.3 megaohms ±0.25%" ) + + def test_brown_black_brown_yellow_and_violet(self): + self.assertEqual( + resistor_label(["brown", "black", "brown", "yellow", "violet"]), "1.01 megaohms ±0.1%" + ) + + def test_brown_black_red_and_red(self): + self.assertEqual( + resistor_label(["brown", "black", "red", "red"]), "1 kiloohms ±2%" + ) From 6a7a35111da194b5e9f90903897fd70267211152 Mon Sep 17 00:00:00 2001 From: Heitor de Bittencourt Date: Wed, 23 Oct 2024 16:24:16 -0300 Subject: [PATCH 178/277] [Mecha Munch & Dict Methods Concept]: Fix Typo in Introduction & About Files (#3796) * Fix typo in dict methods introduction * Fix typo in dict-methods concept --- concepts/dict-methods/about.md | 2 +- exercises/concept/mecha-munch-management/.docs/introduction.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/concepts/dict-methods/about.md b/concepts/dict-methods/about.md index d910d3e9168..6dcf9b4ae7a 100644 --- a/concepts/dict-methods/about.md +++ b/concepts/dict-methods/about.md @@ -214,7 +214,7 @@ When both dictionaries share keys, `dict_two` values take precedence. 'Purple baseline': '#161748'} ``` -`dict_one |= other` behaves similar to `.update()`, but in this case, `other` can be either a `dict` or an iterable of (`key`, `value`) pairs: +`dict_one |= other` behaves similar to `.update()`, but in this case, `other` can be either a `dict` or an iterable of (`key`, `value`) pairs: ```python >>> palette_III = {'Grassy Green': (155, 196, 0), diff --git a/exercises/concept/mecha-munch-management/.docs/introduction.md b/exercises/concept/mecha-munch-management/.docs/introduction.md index 983b905c276..17d67487715 100644 --- a/exercises/concept/mecha-munch-management/.docs/introduction.md +++ b/exercises/concept/mecha-munch-management/.docs/introduction.md @@ -191,7 +191,7 @@ When both dictionaries share keys, `dict_two` values take precedence. 'Purple baseline': '#161748'} ``` -`dict_one |= other` behaves similar to `.update()`, but in this case, `other` can be either a `dict` or an iterable of (`key`, `value`) pairs: +`dict_one |= other` behaves similar to `.update()`, but in this case, `other` can be either a `dict` or an iterable of (`key`, `value`) pairs: ```python >>> palette_III = {'Grassy Green': (155, 196, 0), From 5f70c9d4ee61faed488e789bc6449fa347f9f778 Mon Sep 17 00:00:00 2001 From: Luke McGuire Date: Wed, 23 Oct 2024 19:30:28 -0500 Subject: [PATCH 179/277] test: Update Chaitana's tests to verify queue has been updated (#3799) --- .../list_methods_test.py | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py b/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py index c519ff255cd..5df2af327b7 100644 --- a/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py +++ b/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py @@ -140,14 +140,39 @@ def test_how_many_namefellows(self): @pytest.mark.task(taskno=6) def test_remove_the_last_person(self): - test_data = ['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'] - actual_result = remove_the_last_person(test_data) - expected = 'Rocket' - error_message = (f'Called remove_the_last_person({test_data}).' - f'The function returned {actual_result}, but the tests ' - f'expected {expected} as the person who was removed.') + test_data = [ + (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], 'Rocket'), + (['Wanda', 'Natasha', 'Steve', 'Rocket', 'Ultron'], 'Ultron'), + (['Steve', 'Wanda', 'Rocket', 'Ultron', 'Natasha'], 'Natasha') + ] + for variant, (input, expected) in enumerate(test_data, start=1): + with self.subTest(f'variation #{variant}', input=input, expected=expected): + actual_result = remove_the_last_person(input) + expected_result = expected + + error_message = (f'Called remove_the_last_person({input}).' + f'The function returned {actual_result}, but the tests expected {expected_result}.') + + self.assertEqual(actual_result, expected_result, msg=error_message) + + @pytest.mark.task(taskno=6) + def test_remove_the_last_person_validate_queue(self): + test_data = [ + (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Steve', 'Ultron', 'Natasha']), + (['Wanda', 'Natasha', 'Steve', 'Rocket', 'Ultron'], ['Wanda', 'Natasha', 'Steve', 'Rocket']), + (['Steve', 'Wanda', 'Rocket', 'Ultron', 'Natasha'], ['Steve', 'Wanda', 'Rocket', 'Ultron']) + ] + for variant, (input, modified) in enumerate(test_data, start=1): + with self.subTest(f'variation #{variant}', input=input, modified=modified): + unmodified = deepcopy(input) + actual_result = remove_the_last_person(input) + expected_queue = modified + + error_message = (f'\nCalled remove_the_last_person({unmodified}).\n' + f'The function was expected to change the queue to {expected_queue},\n' + f'but the queue looks like {input} instead.') - self.assertIs(actual_result, expected, msg=error_message) + self.assertEqual(input, expected_queue, msg=error_message) @pytest.mark.task(taskno=7) def test_sorted_names(self): From 7993df720af847fb12b8051de9739a0dba70dbab Mon Sep 17 00:00:00 2001 From: Ryan Souza <76923948+Ryrden@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:39:18 -0300 Subject: [PATCH 180/277] fix: wrong reference (#3800) --- exercises/practice/acronym/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/acronym/.approaches/introduction.md b/exercises/practice/acronym/.approaches/introduction.md index 38b606b4a26..9aaac23d6fa 100644 --- a/exercises/practice/acronym/.approaches/introduction.md +++ b/exercises/practice/acronym/.approaches/introduction.md @@ -157,4 +157,4 @@ To compare performance of the approaches, take a look at the [Performance articl [approach-map-function]: https://exercism.org/tracks/python/exercises/acronym/approaches/map-function [approach-regex-join]: https://exercism.org/tracks/python/exercises/acronym/approaches/regex-join [approach-regex-sub]: https://exercism.org/tracks/python/exercises/acronym/approaches/regex-sub -[article-performance]: https://exercism.org/tracks/python/exercises/isogram/articles/performance +[article-performance]: https://exercism.org/tracks/python/exercises/acronym/articles/performance From 694a421e0276970e14e6b4ce306f51770fd84f56 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 31 Oct 2024 14:13:39 -0700 Subject: [PATCH 181/277] what I hope is the final pass of the wordy typo monsters. (#3803) --- .../wordy/.approaches/functools-reduce/content.md | 12 +++++++----- exercises/practice/wordy/.approaches/introduction.md | 6 +++--- .../practice/wordy/.approaches/recursion/content.md | 6 +++--- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/exercises/practice/wordy/.approaches/functools-reduce/content.md b/exercises/practice/wordy/.approaches/functools-reduce/content.md index 8bb7de25f30..8bc42449fa0 100644 --- a/exercises/practice/wordy/.approaches/functools-reduce/content.md +++ b/exercises/practice/wordy/.approaches/functools-reduce/content.md @@ -16,16 +16,17 @@ def answer(question): raise ValueError("unknown operation") # Using the built-in filter() to clean & split the question.. - list(filter(lambda x: - x not in ("What", "is", "by"), - question.strip("?").split())) + question = list(filter(lambda x: + x not in ("What", "is", "by"), + question.strip("?").split())) # Separate candidate operators and numbers into two lists. operations = question[1::2] # Convert candidate elements to int(), checking for "-". # All other values are replaced with None. - digits = [int(element) if (element.isdigit() or element[1:].isdigit()) + digits = [int(element) if + (element.isdigit() or element[1:].isdigit()) else None for element in question[::2]] # If there is a mis-match between operators and numbers, toss error. @@ -106,7 +107,8 @@ def answer(question): # Convert candidate elements to int(), checking for "-". # All other values are replaced with None. - digits = [int(element) if (element.isdigit() or element[1:].isdigit()) + digits = [int(element) if + (element.isdigit() or element[1:].isdigit()) else None for element in question[::2]] # If there is a mis-match between operators and numbers, toss error. diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md index 135af33b59e..c6d7ed89279 100644 --- a/exercises/practice/wordy/.approaches/introduction.md +++ b/exercises/practice/wordy/.approaches/introduction.md @@ -31,12 +31,12 @@ However, solutions all follow the same general steps: 1. Remove the parts of the question string that do not apply to calculating the answer. 2. Iterate over the question, determining which words are numbers, and which are meant to be mathematical operations. - -- _Converting the question string into a `list` of words is hugely helpful here._ + — _Converting the question string into a `list` of words is hugely helpful here._ 3. **_Starting from the left_**, take the first three elements and convert number strings to `int` and operations words to the mathematical operations +, -, *, and /. 4. Apply the operation to the numbers, which should result in a single number. - -- _Employing a `try-except` block can trap any errors thrown and make the code both "safer" and less complex._ + — _Employing a `try-except` block can trap any errors thrown and make the code both "safer" and less complex._ 5. Use the calculated number from step 4 as the start for the next "trio" (_number, operation, number_) in the question. The calculated number + the remainder of the question becomes the question being worked on in the next iteration. - -- _Using a `while-loop` with a test on the length of the question to do calculation is a very common strategy._ + — _Using a `while-loop` with a test on the length of the question to do calculation is a very common strategy._ 6. Once the question is calculated down to a single number, that is the answer. Anything else that happens in the loop/iteration or within the accumulated result is a `ValueError("syntax error")`. ~~~~ diff --git a/exercises/practice/wordy/.approaches/recursion/content.md b/exercises/practice/wordy/.approaches/recursion/content.md index 0ba69fca502..794f1b41c19 100644 --- a/exercises/practice/wordy/.approaches/recursion/content.md +++ b/exercises/practice/wordy/.approaches/recursion/content.md @@ -246,9 +246,9 @@ For example: 1. "1 plus -10 multiplied by 13 divided by 2" would match on "1 plus -10" (_group x_) **multiplied by** "13 divided by 2" (_group y_). 2. This is re-arranged to `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` -3. At this point the loop would pause as the two recursive calls to `calculate()` spawn -4. The loops then run again — and so would the calls to `calculate()`, until there wasn't any match that caused a split of the question or an error. -5. One at a time, the numbers would then be returned, until the main `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` could be solved, at which point the answer is returned. +3. At this point, the loop pauses as the two recursive calls to `calculate()` spawn +4. The loop runs again — and so do the calls to `calculate()` — until there isn't any match that splits the question or any errors. +5. One at a time, the numbers are returned from the `calculate()` calls on the stack, until the main `mul(calculate("1 plus -10"), calculate("13 divided by 2"))` is solved, at which point the answer is returned. For a more visual picture, you can step through the code on [pythontutor.com][recursion-in-loop-pythontutor]. From dc66e5964c60d70259bfbc4040dcbbcfa1655fa3 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 31 Oct 2024 14:34:29 -0700 Subject: [PATCH 182/277] Fixed spelling errors in approaches docs. (#3804) --- .../acronym/.approaches/generator-expression/content.md | 2 +- .../practice/acronym/.approaches/list-comprehension/content.md | 2 +- exercises/practice/acronym/.approaches/regex-join/content.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/acronym/.approaches/generator-expression/content.md b/exercises/practice/acronym/.approaches/generator-expression/content.md index f5b590ccaa6..47ec9aa8f89 100644 --- a/exercises/practice/acronym/.approaches/generator-expression/content.md +++ b/exercises/practice/acronym/.approaches/generator-expression/content.md @@ -30,7 +30,7 @@ A [`generator-expression`][generator-expression] is then used to iterate through Generator expressions are short-form [generators][generators] - lazy iterators that produce their values _on demand_, instead of saving them to memory. This generator expression is consumed by [`str.join()`][str-join], which joins the generated letters together using an empty string. -Other "seperator" strings can be used with `str.join()` - see [concept:python/string-methods]() for some additional examples. +Other "separator" strings can be used with `str.join()` - see [concept:python/string-methods]() for some additional examples. Since the generator expression and `join()` are fairly succinct, they are put directly on the `return` line rather than assigning and returning an intermediate variable for the acronym. diff --git a/exercises/practice/acronym/.approaches/list-comprehension/content.md b/exercises/practice/acronym/.approaches/list-comprehension/content.md index 3a7b0cd40fb..7e98f45c74f 100644 --- a/exercises/practice/acronym/.approaches/list-comprehension/content.md +++ b/exercises/practice/acronym/.approaches/list-comprehension/content.md @@ -25,7 +25,7 @@ As of this writing, both of these methods benchmark slower than using `str.repla A [`list comprehension`][list comprehension] is then used to iterate through the phrase and select the first letters of each word via [`bracket notation`][subscript notation]. This comprehension is passed into [`str.join()`][str-join], which unpacks the `list` of first letters and joins them together using an empty string - the acronym. -Other "seperator" strings besides an empty string can be used with `str.join()` - see [concept:python/string-methods]() for some additional examples. +Other "separator" strings besides an empty string can be used with `str.join()` - see [concept:python/string-methods]() for some additional examples. Since the comprehension and `join()` are fairly succinct, they are put directly on the `return` line rather than assigning and returning an intermediate variable for the acronym. diff --git a/exercises/practice/acronym/.approaches/regex-join/content.md b/exercises/practice/acronym/.approaches/regex-join/content.md index f6ca2f4844d..227ba06d5ea 100644 --- a/exercises/practice/acronym/.approaches/regex-join/content.md +++ b/exercises/practice/acronym/.approaches/regex-join/content.md @@ -74,7 +74,7 @@ Note that when using `finditer()`, the `Match object` has to be unpacked via `ma Generator expressions are short-form [generators][generators] - lazy iterators that produce their values _on demand_, instead of saving them to memory. This generator expression is consumed by [`str.join()`][str-join], which joins the generated letters together using an empty string. -Other "seperator" strings can be used with `str.join()` - see [concept:python/string-methods]() for some additional examples. +Other "separator" strings can be used with `str.join()` - see [concept:python/string-methods]() for some additional examples. Finally, the result of `.join()` is capitalized using the [chained][chaining] [`.upper()`][str-upper]. From abd31ba96e0822d35fe67ba965bbe457f9df2437 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 31 Oct 2024 14:41:21 -0700 Subject: [PATCH 183/277] Initialzed - initialized spelling corrections. (#3805) --- exercises/practice/luhn/.approaches/recursion/content.md | 2 +- .../luhn/.approaches/replace-reverse-enumerate/content.md | 2 +- exercises/practice/luhn/.approaches/reversed-for/content.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/luhn/.approaches/recursion/content.md b/exercises/practice/luhn/.approaches/recursion/content.md index 6bb89e7932b..a84c5478510 100644 --- a/exercises/practice/luhn/.approaches/recursion/content.md +++ b/exercises/practice/luhn/.approaches/recursion/content.md @@ -29,7 +29,7 @@ class Luhn: ``` -The `Luhn` object is initialzed with the `card_num` value, which is the number to be validated with the Luhn algorithm. +The `Luhn` object is initialized with the `card_num` value, which is the number to be validated with the Luhn algorithm. The result of the validation is returned from `Luhn`'s `valid()` method. In this approach, a member variable is set to the result of running the Luhn algorithm. That variable is returned from the `valid()` method. diff --git a/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md b/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md index fd4e49b12c0..fe0b697b315 100644 --- a/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md +++ b/exercises/practice/luhn/.approaches/replace-reverse-enumerate/content.md @@ -29,7 +29,7 @@ class Luhn: ``` -The `Luhn` object is initialzed with the `card_num` value, which is the number to be validated with the Luhn algorithm. +The `Luhn` object is initialized with the `card_num` value, which is the number to be validated with the Luhn algorithm. The result of the validation is returned from `Luhn`'s `valid()` method. In this approach, a member variable is set to the result of running the Luhn algorithm. That variable is returned from the `valid()` method. diff --git a/exercises/practice/luhn/.approaches/reversed-for/content.md b/exercises/practice/luhn/.approaches/reversed-for/content.md index abf5a591ca4..683fca30486 100644 --- a/exercises/practice/luhn/.approaches/reversed-for/content.md +++ b/exercises/practice/luhn/.approaches/reversed-for/content.md @@ -30,7 +30,7 @@ class Luhn: ``` -The `Luhn` object is initialzed with the `card_num` value, which is the number to be validated with the Luhn algorithm. +The `Luhn` object is initialized with the `card_num` value, which is the number to be validated with the Luhn algorithm. The result of the validation is returned from `Luhn`'s `valid()` method. In this approach, a member variable is set to the result of running the Luhn algorithm. That variable is returned from the `valid()` method. From f93b0ddcc6b92bce8f1a2aa8b70f899bb09d91ea Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 31 Oct 2024 14:45:58 -0700 Subject: [PATCH 184/277] Seperate to separate typo fix. (#3806) --- concepts/classes/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/classes/about.md b/concepts/classes/about.md index f88ce892f3b..11b03643543 100644 --- a/concepts/classes/about.md +++ b/concepts/classes/about.md @@ -118,7 +118,7 @@ class MyClass: def __init__(self, location): # This is an instance or object property, attribute, or variable. - # Note that we are unpacking the tuple argument into two seperate instance variables. + # Note that we are unpacking the tuple argument into two separate instance variables. self.location_x = location[0] self.location_y = location[1] From 5ad6668ac2e667e23342084cf34cfbcd17d68fd8 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 31 Oct 2024 14:52:28 -0700 Subject: [PATCH 185/277] Fix spelling error and heading in instruction.append file. (#3807) --- exercises/practice/linked-list/.docs/instructions.append.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/linked-list/.docs/instructions.append.md b/exercises/practice/linked-list/.docs/instructions.append.md index 00032862c2a..25f30a19934 100644 --- a/exercises/practice/linked-list/.docs/instructions.append.md +++ b/exercises/practice/linked-list/.docs/instructions.append.md @@ -5,7 +5,7 @@ While linked lists can be implemented in a variety of ways with a variety of underlying data structures, we ask here that you implement your linked list in an OOP fashion. In the stub file, you will see the start of a `Node` class, as well as a `LinkedList` class. -Your `Node` class should keep track of its value, as well as which other nodes preceed or follow. +Your `Node` class should keep track of its value, as well as which nodes precede or follow. Your `push`, `pop`, `shift`, `unshift`, and the special method for `len` should be implemented in the `LinkedList` class. You might also find it useful to implement a special `iter` method for iteration. @@ -17,7 +17,7 @@ If the value appears more than once, only the **first** occurrence should be rem
-## Exception messages +## Exception Messages Sometimes it is necessary to [raise an exception][raising]. When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. From bfc578ecdd4369ccd798135780fec6be5cf3bf15 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 31 Oct 2024 15:02:34 -0700 Subject: [PATCH 186/277] fixed typo in itertools compress approach. (#3808) --- .../raindrops/.approaches/itertools-compress/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/raindrops/.approaches/itertools-compress/content.md b/exercises/practice/raindrops/.approaches/itertools-compress/content.md index b5da4768622..42e02c188e0 100644 --- a/exercises/practice/raindrops/.approaches/itertools-compress/content.md +++ b/exercises/practice/raindrops/.approaches/itertools-compress/content.md @@ -19,7 +19,7 @@ If the 'sounds' string is empty, a string version of the number is returned inst This is very succinct code that avoids string concatenation. However, it does require the overhead of importing `compress()` from the [itertools][itertools] module. The code is also harder to maintain should there be additional factors/sounds needed. -Because the factors and sounds are seperated, there is a chance mistakes could be made like forgetting a number or swapping which factor is paired with which sound. +Because the factors and sounds are separated, there is a chance mistakes could be made like forgetting a number or swapping which factor is paired with which sound. A better approach for maintenance might be to turn the 'sounds' `tuple` into a dictionary where the factors and sounds can be stored separate from the logic that does the calculations and string creation: From 42bbbfd2af2c2eb8ea999110a4138e99f6347a70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:06:20 -0700 Subject: [PATCH 187/277] Bump actions/checkout from 4.2.0 to 4.2.2 (#3810) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.0 to 4.2.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/d632683dd7b4114ad314bca15554477dd762a938...11bd71901bbe5b1630ceea73d27597364c9af683) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- .github/workflows/issue-commenter.yml | 2 +- .github/workflows/test-runner.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index c895b2ae2a2..af069321ce5 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -14,7 +14,7 @@ jobs: housekeeping: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Set up Python uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 @@ -55,7 +55,7 @@ jobs: matrix: python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2] steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml index d4434c808b0..46319fa611c 100644 --- a/.github/workflows/issue-commenter.yml +++ b/.github/workflows/issue-commenter.yml @@ -9,7 +9,7 @@ jobs: name: Comments for every NEW issue. steps: - name: Checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Read issue-comment.md id: issue-comment diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml index 9faae798e25..88c348a3662 100644 --- a/.github/workflows/test-runner.yml +++ b/.github/workflows/test-runner.yml @@ -10,6 +10,6 @@ jobs: test-runner: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Run test-runner run: docker compose run test-runner From da94511af8ab41cabbd4bf1222d6df714219204d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:06:54 -0700 Subject: [PATCH 188/277] Bump actions/setup-python from 5.2.0 to 5.3.0 (#3809) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.2.0 to 5.3.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/f677139bbe7f9c59b41e40162b753c062f5d49a3...0b93645e9fea7318ecaed2b359559ac225c90a2b) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index af069321ce5..8c9d9f7fb60 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Set up Python - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b with: python-version: ${{ matrix.python-version }} From e8b71922430e0d675956087cb482665f1025ca00 Mon Sep 17 00:00:00 2001 From: Andrew Pam Date: Tue, 5 Nov 2024 06:38:14 +1100 Subject: [PATCH 189/277] Update instructions.append.md (#3812) * Update instructions.append.md Clarify that the built-in exponentiation operator ** is equivalent to pow() and therefore doesn't demonstrate an implementation of a square root algorithm, and also note that for the domain of this exercise there are available solutions using only positive integers. * Update exercises/practice/square-root/.docs/instructions.append.md Adhere to one sentence per line convention. Co-authored-by: BethanyG --------- Co-authored-by: BethanyG --- exercises/practice/square-root/.docs/instructions.append.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/exercises/practice/square-root/.docs/instructions.append.md b/exercises/practice/square-root/.docs/instructions.append.md index 84b4cf8ee70..eab4f0ac659 100644 --- a/exercises/practice/square-root/.docs/instructions.append.md +++ b/exercises/practice/square-root/.docs/instructions.append.md @@ -3,10 +3,11 @@ ## How this Exercise is Structured in Python -Python offers a wealth of mathematical functions in the form of the [math module][math-module] and built-ins such as [`pow()`][pow] and [`sum()`][sum]. +Python offers a wealth of mathematical functions in the form of the [math module][math-module] and built-ins such as the exponentiation operator `**`, [`pow()`][pow] and [`sum()`][sum]. However, we'd like you to consider the challenge of solving this exercise without those built-ins or modules. -While there is a mathematical formula that will find the square root of _any_ number, we have gone the route of having only [natural numbers][nautral-number] (positive integers) as solutions. +While there is a mathematical formula that will find the square root of _any_ number, we have gone the route of having only [natural numbers][nautral-number] (positive integers) as solutions. +It is possible to compute the square root of any natural number using only natural numbers in the computation. [math-module]: https://docs.python.org/3/library/math.html From d7a8e5b8d130c6a27376458344b92be0bb26639b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 4 Nov 2024 15:51:06 -0800 Subject: [PATCH 190/277] Updated metadata and instructions from problem specs Nov maintenence. (#3813) --- exercises/practice/hamming/.docs/instructions.md | 10 +++++----- exercises/practice/hamming/.meta/config.json | 2 +- exercises/practice/luhn/.docs/instructions.md | 3 ++- exercises/practice/pov/.meta/config.json | 2 +- .../practice/rna-transcription/.docs/instructions.md | 6 +++--- exercises/practice/sublist/.docs/instructions.md | 4 ++-- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/exercises/practice/hamming/.docs/instructions.md b/exercises/practice/hamming/.docs/instructions.md index 020fdd02d4e..b9ae6efc516 100644 --- a/exercises/practice/hamming/.docs/instructions.md +++ b/exercises/practice/hamming/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Calculate the Hamming Distance between two DNA strands. +Calculate the Hamming distance between two DNA strands. Your body is made up of cells that contain DNA. Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. @@ -9,18 +9,18 @@ In fact, the average human body experiences about 10 quadrillion cell divisions When cells divide, their DNA replicates too. Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. If we compare two strands of DNA and count the differences between them we can see how many mistakes occurred. -This is known as the "Hamming Distance". +This is known as the "Hamming distance". -We read DNA using the letters C,A,G and T. +We read DNA using the letters C, A, G and T. Two strands might look like this: GAGCCTACTAACGGGAT CATCGTAATGACGGCCT ^ ^ ^ ^ ^ ^^ -They have 7 differences, and therefore the Hamming Distance is 7. +They have 7 differences, and therefore the Hamming distance is 7. -The Hamming Distance is useful for lots of things in science, not just biology, so it's a nice phrase to be familiar with :) +The Hamming distance is useful for lots of things in science, not just biology, so it's a nice phrase to be familiar with :) ## Implementation notes diff --git a/exercises/practice/hamming/.meta/config.json b/exercises/practice/hamming/.meta/config.json index 1280ded9127..117a2d954d0 100644 --- a/exercises/practice/hamming/.meta/config.json +++ b/exercises/practice/hamming/.meta/config.json @@ -31,7 +31,7 @@ ".meta/example.py" ] }, - "blurb": "Calculate the Hamming difference between two DNA strands.", + "blurb": "Calculate the Hamming distance between two DNA strands.", "source": "The Calculating Point Mutations problem at Rosalind", "source_url": "https://rosalind.info/problems/hamm/" } diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md index 8cbe791fc23..49934c10646 100644 --- a/exercises/practice/luhn/.docs/instructions.md +++ b/exercises/practice/luhn/.docs/instructions.md @@ -22,7 +22,8 @@ The first step of the Luhn algorithm is to double every second digit, starting f We will be doubling ```text -4_3_ 3_9_ 0_4_ 6_6_ +4539 3195 0343 6467 +↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ (double these) ``` If doubling the number results in a number greater than 9 then subtract 9 from the product. diff --git a/exercises/practice/pov/.meta/config.json b/exercises/practice/pov/.meta/config.json index cdc96ff934a..44527ba940d 100644 --- a/exercises/practice/pov/.meta/config.json +++ b/exercises/practice/pov/.meta/config.json @@ -22,5 +22,5 @@ }, "blurb": "Reparent a graph on a selected node.", "source": "Adaptation of exercise from 4clojure", - "source_url": "https://www.4clojure.com/" + "source_url": "https://github.com/oxalorg/4ever-clojure" } diff --git a/exercises/practice/rna-transcription/.docs/instructions.md b/exercises/practice/rna-transcription/.docs/instructions.md index 36da381f5a7..4dbfd3a2719 100644 --- a/exercises/practice/rna-transcription/.docs/instructions.md +++ b/exercises/practice/rna-transcription/.docs/instructions.md @@ -1,12 +1,12 @@ # Instructions -Your task is determine the RNA complement of a given DNA sequence. +Your task is to determine the RNA complement of a given DNA sequence. Both DNA and RNA strands are a sequence of nucleotides. -The four nucleotides found in DNA are adenine (**A**), cytosine (**C**), guanine (**G**) and thymine (**T**). +The four nucleotides found in DNA are adenine (**A**), cytosine (**C**), guanine (**G**), and thymine (**T**). -The four nucleotides found in RNA are adenine (**A**), cytosine (**C**), guanine (**G**) and uracil (**U**). +The four nucleotides found in RNA are adenine (**A**), cytosine (**C**), guanine (**G**), and uracil (**U**). Given a DNA strand, its transcribed RNA strand is formed by replacing each nucleotide with its complement: diff --git a/exercises/practice/sublist/.docs/instructions.md b/exercises/practice/sublist/.docs/instructions.md index 7535931afa8..8228edc6ce2 100644 --- a/exercises/practice/sublist/.docs/instructions.md +++ b/exercises/practice/sublist/.docs/instructions.md @@ -8,8 +8,8 @@ Given any two lists `A` and `B`, determine if: - None of the above is true, thus lists `A` and `B` are unequal Specifically, list `A` is equal to list `B` if both lists have the same values in the same order. -List `A` is a superlist of `B` if `A` contains a sub-sequence of values equal to `B`. -List `A` is a sublist of `B` if `B` contains a sub-sequence of values equal to `A`. +List `A` is a superlist of `B` if `A` contains a contiguous sub-sequence of values equal to `B`. +List `A` is a sublist of `B` if `B` contains a contiguous sub-sequence of values equal to `A`. Examples: From dcc052c730b178c53b1170039e56b6fbed941390 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 4 Nov 2024 16:09:36 -0800 Subject: [PATCH 191/277] Updtated tests for Forth from problem specs. (#3814) --- exercises/practice/forth/.meta/tests.toml | 18 ++++++++++++++++++ exercises/practice/forth/forth_test.py | 20 +++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/exercises/practice/forth/.meta/tests.toml b/exercises/practice/forth/.meta/tests.toml index 16e0ffd9a6e..5b5c09e240f 100644 --- a/exercises/practice/forth/.meta/tests.toml +++ b/exercises/practice/forth/.meta/tests.toml @@ -24,6 +24,9 @@ description = "addition -> errors if there is nothing on the stack" [06efb9a4-817a-435e-b509-06166993c1b8] description = "addition -> errors if there is only one value on the stack" +[1e07a098-c5fa-4c66-97b2-3c81205dbc2f] +description = "addition -> more than two values on the stack" + [09687c99-7bbc-44af-8526-e402f997ccbf] description = "subtraction -> can subtract two numbers" @@ -33,6 +36,9 @@ description = "subtraction -> errors if there is nothing on the stack" [b3cee1b2-9159-418a-b00d-a1bb3765c23b] description = "subtraction -> errors if there is only one value on the stack" +[2c8cc5ed-da97-4cb1-8b98-fa7b526644f4] +description = "subtraction -> more than two values on the stack" + [5df0ceb5-922e-401f-974d-8287427dbf21] description = "multiplication -> can multiply two numbers" @@ -42,6 +48,9 @@ description = "multiplication -> errors if there is nothing on the stack" [8ba4b432-9f94-41e0-8fae-3b3712bd51b3] description = "multiplication -> errors if there is only one value on the stack" +[5cd085b5-deb1-43cc-9c17-6b1c38bc9970] +description = "multiplication -> more than two values on the stack" + [e74c2204-b057-4cff-9aa9-31c7c97a93f5] description = "division -> can divide two numbers" @@ -57,12 +66,21 @@ description = "division -> errors if there is nothing on the stack" [d5547f43-c2ff-4d5c-9cb0-2a4f6684c20d] description = "division -> errors if there is only one value on the stack" +[f224f3e0-b6b6-4864-81de-9769ecefa03f] +description = "division -> more than two values on the stack" + [ee28d729-6692-4a30-b9be-0d830c52a68c] description = "combined arithmetic -> addition and subtraction" [40b197da-fa4b-4aca-a50b-f000d19422c1] description = "combined arithmetic -> multiplication and division" +[f749b540-53aa-458e-87ec-a70797eddbcb] +description = "combined arithmetic -> multiplication and addition" + +[c8e5a4c2-f9bf-4805-9a35-3c3314e4989a] +description = "combined arithmetic -> addition and multiplication" + [c5758235-6eef-4bf6-ab62-c878e50b9957] description = "dup -> copies a value on the stack" diff --git a/exercises/practice/forth/forth_test.py b/exercises/practice/forth/forth_test.py index f8402bdd64c..1489bbd7df0 100644 --- a/exercises/practice/forth/forth_test.py +++ b/exercises/practice/forth/forth_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/forth/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2024-11-04 import unittest @@ -36,6 +36,9 @@ def test_addition_errors_if_there_is_only_one_value_on_the_stack(self): str(err.exception.args[0]), "Insufficient number of items in stack" ) + def test_addition_more_than_two_values_on_the_stack(self): + self.assertEqual(evaluate(["1 2 3 +"]), [1, 5]) + def test_subtraction_can_subtract_two_numbers(self): self.assertEqual(evaluate(["3 4 -"]), [-1]) @@ -55,6 +58,9 @@ def test_subtraction_errors_if_there_is_only_one_value_on_the_stack(self): str(err.exception.args[0]), "Insufficient number of items in stack" ) + def test_subtraction_more_than_two_values_on_the_stack(self): + self.assertEqual(evaluate(["1 12 3 -"]), [1, 9]) + def test_multiplication_can_multiply_two_numbers(self): self.assertEqual(evaluate(["2 4 *"]), [8]) @@ -74,6 +80,9 @@ def test_multiplication_errors_if_there_is_only_one_value_on_the_stack(self): str(err.exception.args[0]), "Insufficient number of items in stack" ) + def test_multiplication_more_than_two_values_on_the_stack(self): + self.assertEqual(evaluate(["1 2 3 *"]), [1, 6]) + def test_division_can_divide_two_numbers(self): self.assertEqual(evaluate(["12 3 /"]), [4]) @@ -103,12 +112,21 @@ def test_division_errors_if_there_is_only_one_value_on_the_stack(self): str(err.exception.args[0]), "Insufficient number of items in stack" ) + def test_division_more_than_two_values_on_the_stack(self): + self.assertEqual(evaluate(["1 12 3 /"]), [1, 4]) + def test_combined_arithmetic_addition_and_subtraction(self): self.assertEqual(evaluate(["1 2 + 4 -"]), [-1]) def test_combined_arithmetic_multiplication_and_division(self): self.assertEqual(evaluate(["2 4 * 3 /"]), [2]) + def test_combined_arithmetic_multiplication_and_addition(self): + self.assertEqual(evaluate(["1 3 4 * +"]), [13]) + + def test_combined_arithmetic_addition_and_multiplication(self): + self.assertEqual(evaluate(["1 3 4 + *"]), [7]) + def test_dup_copies_a_value_on_the_stack(self): self.assertEqual(evaluate(["1 dup"]), [1, 1]) From 27931ea05d893fdb125264602701a658a049770a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 6 Nov 2024 14:35:18 -0800 Subject: [PATCH 192/277] Spellchecking in ref floder. Hopefully for the only time. (#3816) --- reference/concepts/boolean_values.md | 2 +- reference/concepts/builtin_types/dict.md | 2 +- reference/concepts/builtin_types/frozenset.md | 2 +- reference/concepts/builtin_types/list.md | 2 +- reference/concepts/constructor.md | 2 +- reference/concepts/default_arguments.md | 2 +- reference/concepts/dunder_methods.md | 2 +- reference/concepts/initialization.md | 2 +- reference/concepts/instance_attributes.md | 2 +- reference/concepts/instance_methods.md | 2 +- reference/concepts/instance_properties.md | 2 +- reference/concepts/regular_expressions.md | 2 +- reference/concepts/return_value.md | 4 ++-- reference/concepts/slicing.md | 2 +- reference/concepts/string_splitting.md | 2 +- reference/concepts/string_translation.md | 2 +- reference/concepts/type_hinting.md | 2 +- reference/exercise-concepts/binary-search-tree.md | 4 ++-- reference/exercise-concepts/leap.md | 2 +- reference/exercise-concepts/markdown.md | 6 +++--- reference/exercise-concepts/matrix.md | 12 ++++++------ reference/exercise-concepts/phone-number.md | 2 +- reference/exercise-concepts/reverse-string.md | 4 ++-- reference/exercise-concepts/rna-transcription.md | 4 ++-- reference/exercise-concepts/robot-simulator.md | 10 +++++----- 25 files changed, 40 insertions(+), 40 deletions(-) diff --git a/reference/concepts/boolean_values.md b/reference/concepts/boolean_values.md index 40bce78e7cb..a3ec6c0b28a 100644 --- a/reference/concepts/boolean_values.md +++ b/reference/concepts/boolean_values.md @@ -3,4 +3,4 @@ TODO: ADD MORE - this solution uses Boolean values (`True` / `False`) [hamming](../exercise-concepts/hamming.md) -- True and False of type `bopl`. The example solution uses `True` and `False` as return values from functions that test membership in a list of values. [markdown](../exercise-concepts/markdown.md) +- True and False of type `bool`. The example solution uses `True` and `False` as return values from functions that test membership in a list of values. [markdown](../exercise-concepts/markdown.md) diff --git a/reference/concepts/builtin_types/dict.md b/reference/concepts/builtin_types/dict.md index 90e7ec62d5e..f636cb27c2e 100644 --- a/reference/concepts/builtin_types/dict.md +++ b/reference/concepts/builtin_types/dict.md @@ -1,6 +1,6 @@ # `dict` -Python's primary [mapping type][docs-mapping-type] that associatess keys with values in a [hash map][hash-map]. +Python's primary [mapping type][docs-mapping-type] that associates keys with values in a [hash map][hash-map]. See examples of usage in [markdown][markdown], [rna-transcription][rna-transcription], and [robot-simulator][robot-simulator]. diff --git a/reference/concepts/builtin_types/frozenset.md b/reference/concepts/builtin_types/frozenset.md index 03d582e3833..021a657eaa7 100644 --- a/reference/concepts/builtin_types/frozenset.md +++ b/reference/concepts/builtin_types/frozenset.md @@ -2,7 +2,7 @@ TODO: ADD MORE DETAIL -See the Python documentation entries for the [`set`][docs-set] collection, the [immutable][set type][docs-set-type]; essentially a [hash map][hash-map] in which only the key is relevant, and which disallows [mutaion][mutation] of keys after intialization. +See the Python documentation entries for the [`set`][docs-set] collection, the [immutable][set type][docs-set-type]; essentially a [hash map][hash-map] in which only the key is relevant, and which disallows [mutation][mutation] of keys after initialization. [immutable]: https://github.com/exercism/v3/blob/main/reference/concepts/immutability.md [mutation]: https://github.com/exercism/v3/blob/main/reference/concepts/mutation.md diff --git a/reference/concepts/builtin_types/list.md b/reference/concepts/builtin_types/list.md index 364df7b9f5b..c7d3231a0fd 100644 --- a/reference/concepts/builtin_types/list.md +++ b/reference/concepts/builtin_types/list.md @@ -8,7 +8,7 @@ A multi-dimensional list-with-a-list is used as a simple (but not very efficient TODO: ADD MORE DETAIL -See the Python documentation entries for the [mutable][mutation] [`list` type][docs-list-type] and it's [constructor][list-as-function]. This is Python's most commonly used [sequential collection][docs-sequence-types], and as it allows _heterogenous data_ it's quite different from the [fixed array][general-concept-array] and [singly-linked list][general-concept-list] types you may have encountered in other, less flexible, languages. +See the Python documentation entries for the [mutable][mutation] [`list` type][docs-list-type] and it's [constructor][list-as-function]. This is Python's most commonly used [sequential collection][docs-sequence-types], and as it allows _heterogeneous data_ it's quite different from the [fixed array][general-concept-array] and [singly-linked list][general-concept-list] types you may have encountered in other, less flexible, languages. [variable-length-quantity]: ../../exercise-concepts/variable-length-quantity.md [markdown]: ../../exercise-concepts/markdown.md diff --git a/reference/concepts/constructor.md b/reference/concepts/constructor.md index 292e436fa1e..b3d14c459c5 100644 --- a/reference/concepts/constructor.md +++ b/reference/concepts/constructor.md @@ -3,4 +3,4 @@ TODO: ADD MORE - student needs to know how to build an object using its constructor [binary-search-tree](../exercise-concepts/binary-search-tree.md) -- customizing object initalization with actions and persisting data. The example uses a constructor to process the passed in data into a list of lists assigned to an instance property [matrix](../exercise-concepts/matrix.md) +- customizing object initialization with actions and persisting data. The example uses a constructor to process the passed in data into a list of lists assigned to an instance property [matrix](../exercise-concepts/matrix.md) diff --git a/reference/concepts/default_arguments.md b/reference/concepts/default_arguments.md index 987eaee82fb..0131934be40 100644 --- a/reference/concepts/default_arguments.md +++ b/reference/concepts/default_arguments.md @@ -2,4 +2,4 @@ TODO: ADD MORE -- pre-setting function arguments to protect against them not being passed by a caller. The example uses `direction = NORTH` and `x=0, y=0` to ensure those values for a `robot` even if they are not initally passed. [robot-simulator](../exercise-concepts/robot-simulator.md) +- pre-setting function arguments to protect against them not being passed by a caller. The example uses `direction = NORTH` and `x=0, y=0` to ensure those values for a `robot` even if they are not initially passed. [robot-simulator](../exercise-concepts/robot-simulator.md) diff --git a/reference/concepts/dunder_methods.md b/reference/concepts/dunder_methods.md index 751eebea35c..c52459ac4c4 100644 --- a/reference/concepts/dunder_methods.md +++ b/reference/concepts/dunder_methods.md @@ -7,4 +7,4 @@ TODO: ADD MORE - "dunder" -> "double under", referring to the names of these methods being prefixed with two underscores, e.g. `__init__`. There is no formal privacy in Python, but conventionally a single underscore indicates a private method, or one that the programmer should assume may change at any time; methods without an underscore are considered part of an object's public API. Double underscores are even more special - they are used by Python's builtin functions like `len()`, for example, to allow objects to implement various interfaces and functionality. They can also be used for operator overloading. If you have a custom class that you would like to be able to compare to other instances of the same class, implementing `__lt__`, `__gt__`, `__eq__` etc. allow programmers to use the `>`, `<`, `=` operators. Dunder methods allow programmers to build useful objects with simple interfaces, i.e. you can add two instances together using `+` instead of writing something like `instance1.add(instance2)`. [hamming](../exercise-concepts/hamming.md) - the example uses the `__init__` magic method as its constructor for the class [matrix](../exercise-concepts/matrix.md) - User defined classes can (and generally do) overload the `__init__` method, whose first argument is `self`, because the result of `__init__` is a class _instance_. [phone-number](../exercise-concepts/phone-number.md) -- The example uses `__init__` as a constructor for the class, which also calls `__new__`. In addition, the example uses `__call__()` via the appending of `()` to instance method names, and `__eq__()` (_rich compairison_) via the use of `==` [robot-simulator](../exercise-concepts/robot-simulator.md) +- The example uses `__init__` as a constructor for the class, which also calls `__new__`. In addition, the example uses `__call__()` via the appending of `()` to instance method names, and `__eq__()` (_rich_comparison_) via the use of `==` [robot-simulator](../exercise-concepts/robot-simulator.md) diff --git a/reference/concepts/initialization.md b/reference/concepts/initialization.md index 20979b99265..c0880200d53 100644 --- a/reference/concepts/initialization.md +++ b/reference/concepts/initialization.md @@ -2,4 +2,4 @@ TODO: ADD MORE -- customizing object instatiation with actions and persisting data. The example uses `__init__` to persist a `compass` object and x, y coordinates assigned to instance attributes. [robot-simulator](../exercise-concepts/robot-simulator.md) +- customizing object instantiation with actions and persisting data. The example uses `__init__` to persist a `compass` object and x, y coordinates assigned to instance attributes. [robot-simulator](../exercise-concepts/robot-simulator.md) diff --git a/reference/concepts/instance_attributes.md b/reference/concepts/instance_attributes.md index 1ee02d76d99..7251ef962f1 100644 --- a/reference/concepts/instance_attributes.md +++ b/reference/concepts/instance_attributes.md @@ -2,4 +2,4 @@ TODO: ADD MORE -- this exercise rquires one or more instance attributes to persist passed in data. [robot-simulator](../exercise-concepts/robot-simulator.md) +- this exercise requires one or more instance attributes to persist passed in data. [robot-simulator](../exercise-concepts/robot-simulator.md) diff --git a/reference/concepts/instance_methods.md b/reference/concepts/instance_methods.md index 5b61f32c57c..25ec8caa1c7 100644 --- a/reference/concepts/instance_methods.md +++ b/reference/concepts/instance_methods.md @@ -4,6 +4,6 @@ TODO: ADD MORE - the exercise relies on the `def` statement to create an instance method [allergies](../exercise-concepts/allergies.md) - use of `def` to define a class's methods [clock](../exercise-concepts/clock.md) -- classes can have instance _methods_ which are called from an instance of the class (as opposed to class methods, called from the Class itself). The first parameter of an instance method is always `self`, which is provided when calling from the instance (i.e. the programmer does not need to pass it as an argument explicitly). Static methods are methods called from the class itself, and are not connected to an instance of the class. They have access to class attributes (those defined on the class, not connected to the `self`), and do not require an instance of the class to exist. Classes can also define a `property` by using the `@property` decorator (not shown here); a `property` can be "lazily evaluated" to avoid uneeded computation [phone-number](../exercise-concepts/phone-number.md) +- classes can have instance _methods_ which are called from an instance of the class (as opposed to class methods, called from the Class itself). The first parameter of an instance method is always `self`, which is provided when calling from the instance (i.e. the programmer does not need to pass it as an argument explicitly). Static methods are methods called from the class itself, and are not connected to an instance of the class. They have access to class attributes (those defined on the class, not connected to the `self`), and do not require an instance of the class to exist. Classes can also define a `property` by using the `@property` decorator (not shown here); a `property` can be "lazily evaluated" to avoid unneeded computation [phone-number](../exercise-concepts/phone-number.md) - tests for this exercises require one or more instance methods that will return a specified row or column list of the `matrix`. [matrix](../exercise-concepts/matrix.md) - tests for this exercises require one or more instance methods that will take in a set of starting coordinates and a bearing and then accept a series of instructions that "move" the instance to a new set of coordinates and bearing. [robot-simulator](../exercise-concepts/robot-simulator.md) diff --git a/reference/concepts/instance_properties.md b/reference/concepts/instance_properties.md index f4a05ee9c93..39c49a98e23 100644 --- a/reference/concepts/instance_properties.md +++ b/reference/concepts/instance_properties.md @@ -2,4 +2,4 @@ TODO: ADD MORE -- this exercise rquires one or more instance properties to persist passed in data. [matrix](../exercise-concepts/matrix.md) +- this exercise requires one or more instance properties to persist passed in data. [matrix](../exercise-concepts/matrix.md) diff --git a/reference/concepts/regular_expressions.md b/reference/concepts/regular_expressions.md index 821210074c0..8721003a248 100644 --- a/reference/concepts/regular_expressions.md +++ b/reference/concepts/regular_expressions.md @@ -4,7 +4,7 @@ TODO: ADD MORE - the `re.sub()` function of the `re` module that replaces a `regular expression` match with a new value. The example solutions use this function in various places to substitute _markdown_ syntax for _HTML_ syntax in the passed in markdown text. [markdown](../exercise-concepts/markdown.md) - Both the original code to be refactored for this exercise and the example solution import and use the `re` module for Regular Expressions in python. [markdown](../exercise-concepts/markdown.md) -- the `re.match()` function from the `re` module returns a `match` object with any matched values from a specified Regular Expression or pre-compliled Regular Expression. The example uses `re.match()` in multiple places to search for text patterns that need re-formatting or subsitituting. [markdown](../exercise-concepts/markdown.md) +- the `re.match()` function from the `re` module returns a `match` object with any matched values from a specified Regular Expression or pre-compiled Regular Expression. The example uses `re.match()` in multiple places to search for text patterns that need re-formatting or substituting. [markdown](../exercise-concepts/markdown.md) - Various functions in the re module return a `re.Match` _instance_ which in turn has a `Match.group` method. `Match.group` exists even if there are no groups specified in the pattern. See the [Match.group docs](https://docs.python.org/3/library/re.html#re.Match.group) for more detail. [markdown](../exercise-concepts/markdown.md) - regular expressions is a language of sorts that can detect substrings and extract groups from a string, as well as replace them with something else [phone-number](../exercise-concepts/phone-number.md) - A Domain Specific Language (DSL) for text processing. Like many other programming languages in use, python supports a quasi-dialect of PCRE (_Perl compatible regular expressions_). `Regular expressions` can be used via the core python `re` module, or the third-party `regex` module. Both the original code to be refactored for this exercise and the example solutions use the core `re` module to access `regular expressions` functionality. [markdown](../exercise-concepts/markdown.md) diff --git a/reference/concepts/return_value.md b/reference/concepts/return_value.md index 5cd84d303eb..e2074391c51 100644 --- a/reference/concepts/return_value.md +++ b/reference/concepts/return_value.md @@ -7,7 +7,7 @@ TODO: ADD MORE - Most of the functions in the example solution specify a _return_ value using the `return` keyword. [markdown](../exercise-concepts/markdown.md) - the exercise must use a `return` statement to return a value to the caller [leap](../exercise-concepts/leap.md) - this function return a string by this line: `return text[::-1]` [reverse-string](../exercise-concepts/reverse-string.md) -- the `return` keyword is used in a _return statement_ at the end of a function. Exits a function and may or may not pass data or an expression back to calling code. Functions in python without an expicit `return` keyword and statment will return (pass back) the singleton object `none`. The example code _returns_ a copy of the passed-in argument (assumed to be a string) that has been mapped through `str.translate()`, using the table made from `str.maketrans()` [rna-transcription](../exercise-concepts/rna-transcription.md) -- knowing that functions need not have _explicit_ return statements or values but will return `None` if `return` is not specified. Except for the two `@property`-decorated functions, all of the functions in the example omit an explicit `return` statment and all return `None`. [robot-simulator](../exercise-concepts/robot-simulator.md) +- the `return` keyword is used in a _return statement_ at the end of a function. Exits a function and may or may not pass data or an expression back to calling code. Functions in python without an explicit `return` keyword and statement will return (pass back) the singleton object `none`. The example code _returns_ a copy of the passed-in argument (assumed to be a string) that has been mapped through `str.translate()`, using the table made from `str.maketrans()` [rna-transcription](../exercise-concepts/rna-transcription.md) +- knowing that functions need not have _explicit_ return statements or values but will return `None` if `return` is not specified. Except for the two `@property`-decorated functions, all functions in the example omit an explicit `return` statement and all return `None`. [robot-simulator](../exercise-concepts/robot-simulator.md) - the knowledge of `return` statement could be a useful concept in this exercise [variable-length-quantity](../exercise-concepts/variable-length-quantity.md) - "row" and "column" list values are expected from defined instance method(s) [matrix](../exercise-concepts/matrix.md) diff --git a/reference/concepts/slicing.md b/reference/concepts/slicing.md index dbfe4915619..2694063c0f6 100644 --- a/reference/concepts/slicing.md +++ b/reference/concepts/slicing.md @@ -4,4 +4,4 @@ TODO: ADD MORE - the extended solution to this exercise can employ a slice (returns a copy) instead of calling `.copy()`. [matrix](../exercise-concepts/matrix.md) - a slice within an iterable, i.e. the slice of items from `[x]` to `[y]`, can be accessed via `[x:y]` notation; a third parameter allows "skipping" by `z`, i.e. `stringname[x:y:z]` [phone-number](../exercise-concepts/phone-number.md) -- becase `str` in Python is a sequence type, [slicing](https://docs.python.org/3/reference/expressions.html#slicings) syntax can be used here. Specifically: for syntax `string[start:stop:stride]`: [reverse-string](../exercise-concepts/reverse-string.md) +- because `str` in Python is a sequence type, [slicing](https://docs.python.org/3/reference/expressions.html#slicings) syntax can be used here. Specifically: for syntax `string[start:stop:stride]`: [reverse-string](../exercise-concepts/reverse-string.md) diff --git a/reference/concepts/string_splitting.md b/reference/concepts/string_splitting.md index 778ca9ad7ed..bb6f502c166 100644 --- a/reference/concepts/string_splitting.md +++ b/reference/concepts/string_splitting.md @@ -3,4 +3,4 @@ TODO: ADD MORE - The example solution uses `str.split()` to break the passed in markdown string into a list of lines broken up by the `\n` character. The alternate Python example solution uses `str.splitlines()` for the same effect across all line end characters. [markdown](../exercise-concepts/markdown.md) -- the example uses `str.split` with and without seperators to break the passed in string into "rows" and then "elements" [matrix](../exercise-concepts/matrix.md) +- the example uses `str.split` with and without separators to break the passed in string into "rows" and then "elements" [matrix](../exercise-concepts/matrix.md) diff --git a/reference/concepts/string_translation.md b/reference/concepts/string_translation.md index 2b0c5e9fdcc..456b21abde0 100644 --- a/reference/concepts/string_translation.md +++ b/reference/concepts/string_translation.md @@ -2,4 +2,4 @@ TODO: ADD MORE -- the `str.translate()` _instance method_ is called on an object from the `str` class (e.g. ``.translate()). Returns a copy of the inital string with each character re-mapped through the given _translation table_. The _translation table_ is typically a mapping or sequence type that implements indexing via the magic method `__getitem__()`. [rna-transcription](../exercise-concepts/rna-transcription.md) +- the `str.translate()` _instance method_ is called on an object from the `str` class (e.g. ``.translate()). Returns a copy of the initial string with each character re-mapped through the given _translation table_. The _translation table_ is typically a mapping or sequence type that implements indexing via the magic method `__getitem__()`. [rna-transcription](../exercise-concepts/rna-transcription.md) diff --git a/reference/concepts/type_hinting.md b/reference/concepts/type_hinting.md index cc18920d4ea..18cf3b11a67 100644 --- a/reference/concepts/type_hinting.md +++ b/reference/concepts/type_hinting.md @@ -2,4 +2,4 @@ TODO: ADD MORE -- In modern Python it's possibly to type hint annotations to parameters and variables, see [typing](https://docs.python.org/3/library/typing.html#module-typing). While not neccessary in Python such annotations can help your code be easier to read, understand, and check automatically using tools like `mypy`. [reverse-string](../exercise-concepts/reverse-string.md) +- In modern Python it's possibly to type hint annotations to parameters and variables, see [typing](https://docs.python.org/3/library/typing.html#module-typing). While not necessary in Python such annotations can help your code be easier to read, understand, and check automatically using tools like `mypy`. [reverse-string](../exercise-concepts/reverse-string.md) diff --git a/reference/exercise-concepts/binary-search-tree.md b/reference/exercise-concepts/binary-search-tree.md index a44e11c99de..ae047fc9d56 100644 --- a/reference/exercise-concepts/binary-search-tree.md +++ b/reference/exercise-concepts/binary-search-tree.md @@ -60,7 +60,7 @@ class BinarySearchTree: ## Concepts -- [class][class]: a general comprehension of class concept and and how it works is required, `class` statement +- [class][class]: a general comprehension of class concept and how it works is required, `class` statement - [Implied Argument][implied-argument]: student needs to know how to use statement `self` in a class - [class members][class-members]: student must know how members of a class work - [class methods][class-methods]: student must know how methods of a class work inside and outside the class, the use and meaning of `def` statement @@ -78,5 +78,5 @@ class BinarySearchTree: - [Integer comparison][integer-comparison]: concept required to solve the exercise - [Recursion][recursion]: recursion is a core concept in this exercise - [Lists][lists]: knowledge of lists and iteration on lists is required for this exercise -- [Conditional structures][conditional-structures]: knowledge of conditional conceptis and `if...else` statements are required +- [Conditional structures][conditional-structures]: knowledge of conditional concepts and `if...else` statements are required - [Methods of list][methods-of-list]: the use of methods of list could be useful in this exercise. Methods like `append`, `pop`... diff --git a/reference/exercise-concepts/leap.md b/reference/exercise-concepts/leap.md index deb14475f95..6846d3b097f 100644 --- a/reference/exercise-concepts/leap.md +++ b/reference/exercise-concepts/leap.md @@ -18,7 +18,7 @@ def leap(year): - [Modular Division][modular-division]: the exercise relies on the `%` operator to check if one number is evenly divisible by another - [Boolean Operators][boolean-operators]: the exercise relies on `and`, `or`, and (optionally) `not` to form Boolean predicates - [Boolean Logic][boolean-logic]: the exercise relies on `and` and `or` to combine Boolean predicates into a single logical answer -- [Comparision][comparision]: the exercise relies on the `==` and `!=` operators to make binary comparisons between values +- [Comparison][comparison]: the exercise relies on the `==` and `!=` operators to make binary comparisons between values - [Equivalence][equivalence]: the exercise relies on the `==` and `!=` operators to check that two values are equivalent (or not) - [Order of Evaluation][order-of-evaluation]: the exercise relies on parentheses to explicitly modify the normal order of evaluation of an expression - [Operator Precedence][operator-precedence]: the exercise is most simply stated when the student understands the operator precedence binding rules of Python diff --git a/reference/exercise-concepts/markdown.md b/reference/exercise-concepts/markdown.md index 34c896b883c..137f1a81799 100644 --- a/reference/exercise-concepts/markdown.md +++ b/reference/exercise-concepts/markdown.md @@ -161,14 +161,14 @@ def parse(markdown: str) -> str: - [Regular Expressions][regular-expressions]: Both the original code to be refactored for this exercise and the example solution import and use the `re` module for Regular Expressions in python. - [Importing][importing]: Both the original code to be refactored for the exercise and the example solution use the `import` keyword to import the `re` module in support of Regular Expressions in python. - [String Splitting][string-splitting]: The example solution uses `str.split()` to break the passed in markdown string into a list of lines broken up by the `\n` character. The alternate Python example solution uses `str.splitlines()` for the same effect across all line end characters. -- [Regular Expressions][regular-expressions]: the `re.match()` function from the `re` module returns a `match` object with any matched values from a specified Regular Expression or pre-compliled Regular Expression. The example uses `re.match()` in multiple places to search for text patterns that need re-formatting or subsitituting. +- [Regular Expressions][regular-expressions]: the `re.match()` function from the `re` module returns a `match` object with any matched values from a specified Regular Expression or pre-compiled Regular Expression. The example uses `re.match()` in multiple places to search for text patterns that need re-formatting or substituting. - [Regular expressions][regular-expressions]: A Domain Specific Language (DSL) for text processing. Like many other programming languages in use, python supports a quasi-dialect of PCRE (_Perl compatible regular expressions_). `Regular expressions` can be used via the core python `re` module, or the third-party `regex` module. Both the original code to be refactored for this exercise and the example solutions use the core `re` module to access `regular expressions` functionality. - [Return value][return-value]: Most of the functions in the example solution specify a _return_ value using the `return` keyword. - [None][none]: Pythons null type, referred to when a null or "placeholder" is needed. It is in and of itself a singleton in any given python program. -- [Booleans][booleans]: True and False of type `bopl`. The example solution uses `True` and `False` as return values from functions that test membership in a list of values. +- [Booleans][booleans]: True and False of type `bool`. The example solution uses `True` and `False` as return values from functions that test membership in a list of values. - [Assignment][assignment]: The example solution uses assignment for variables and other values. - [Regular Expressions][regular-expression]: the `re.sub()` function of the `re` module that replaces a `regular expression` match with a new value. The example solutions use this function in various places to substitute _markdown_ syntax for _HTML_ syntax in the passed in markdown text. -- [Dictionaries][dictionaries]: Mapping type. The example solution employes a dictionary to return values from the `parse_line()` function. +- [Dictionaries][dictionaries]: Mapping type. The example solution employs a dictionary to return values from the `parse_line()` function. - [For loops][for-loops]: The example solution uses `for` loops to iterate over various function inputs. - [Iteration][iterable]: The example solution uses the `for _ in _` syntax to iterate over a list of lines. This is possible because a list is an `iterable`. - [Conditionals][conditionals]: The example solution uses `if` to check for pattern matching and membership conditions in different functions for processing different markdown patterns. diff --git a/reference/exercise-concepts/matrix.md b/reference/exercise-concepts/matrix.md index 1b5b2da6c94..37fe76175d2 100644 --- a/reference/exercise-concepts/matrix.md +++ b/reference/exercise-concepts/matrix.md @@ -53,17 +53,17 @@ class Matrix(object): - [Classes][classes]: the exercise objective is to define a `matrix` type. Tested methods are linked to a `matrix` class - [Objects][objects]: creating different instances with different data representing different `matrices` is tested -- [Constructor][constructor]: customizing object initalization with actions and persisting data. The example uses a constructor to process the passed in data into a list of lists assigned to an instance property +- [Constructor][constructor]: customizing object initialization with actions and persisting data. The example uses a constructor to process the passed in data into a list of lists assigned to an instance property - [Dunder Methods][dunder-methods]: the example uses the `__init__` magic method as its constructor for the class - [Return Values][return-value]: "row" and "column" list values are expected from defined instance method(s) - [Implicit Argument][implicit-argument]: the example uses the `self` implicit argument for methods and properties linked to a specific instance of the class - [Namespaces][namespaces]: knowing to use `self`.`` for instance properties and `self` as first argument to instance methods in a class - [Instance Methods][instance-methods]: tests for this exercises require one or more instance methods that will return a specified row or column list of the `matrix`. -- [Instance Properties][instance-properties]: this exercise rquires one or more instance properties to persist passed in data. +- [Instance Properties][instance-properties]: this exercise requires one or more instance properties to persist passed in data. - [Mutability][mutability]: in the extended example, knowing there are no protected or private properties in python and adjusting coding patterns - [Assignment][assignment]: instance properties need to be assigned passed in data - [Method Arguments][method-arguments]: the methods returning "row" and "column" need to take both `self` and an integer as arguments -- [Lists][lists]: this exercise requires "row" or "column" be returnd as a `list`. A `list` of `lists` is also the reccommended way to process and store the passed-in data. +- [Lists][lists]: this exercise requires "row" or "column" be returned as a `list`. A `list` of `lists` is also the recommended way to process and store the passed-in data. - [Indexing][indexing]: the "rows" and "columns" of this exercise need to be retrieved from a list of lists via index - [Bracket Notation][bracket-notation]: knowing that `[]` should be used to refer to a value at a specific index in a list - [Slicing][slicing]: the extended solution to this exercise can employ a slice (returns a copy) instead of calling `.copy()`. @@ -71,9 +71,9 @@ class Matrix(object): - [Iterables][iterables]: understanding that strings, lists, and other data structures can be iterated over in the same fashion - [Iterators][iterators]: the example solution for this exercise uses `zip()`, which returns an _iterator_. - [For Loop][for-loop]: iterating over the passed in `matrix` string using a `for` loop to extract "rows" and "columns" that are appended to a list -- [Comprehension Syntax][comprehension-syntax]: knowing that this is equivelent to a `for loop` - putting the row or column creation code _inside_ the list literal instead of using loop + append. -- [Zip][zip]: the example solution for this exercise uses this function to aggregage the column-wise elements of each rown list to form the matrix "columns". +- [Comprehension Syntax][comprehension-syntax]: knowing that this is equivalent to a `for loop` - putting the row or column creation code _inside_ the list literal instead of using loop + append. +- [Zip][zip]: the example solution for this exercise uses this function to aggregate the column-wise elements of each row list to form the matrix "columns". - [Argument Unpacking][argument unpacking]: the example solution for this exercise uses `splat` (`*`) to unpack rows for the `zip()` function. -- [String Splitting][string-splitting]: the example uses `str.split` with and without seperators to break the passed in string into "rows" and then "elements" +- [String Splitting][string-splitting]: the example uses `str.split` with and without separators to break the passed in string into "rows" and then "elements" - [Type Conversion][type-conversion]: the passed in data is in `str` format but the output is expected as a list of type `int`. - [Int][int]: the example converts the parsed `str` elements into `int` diff --git a/reference/exercise-concepts/phone-number.md b/reference/exercise-concepts/phone-number.md index b7b631f4ab0..d56201ccdcf 100644 --- a/reference/exercise-concepts/phone-number.md +++ b/reference/exercise-concepts/phone-number.md @@ -40,7 +40,7 @@ class PhoneNumber: - [Class][class]: classes are defined with the `class :` syntax - [Dunder Methods][dunder-methods]: User defined classes can (and generally do) overload the `__init__` method, whose first argument is `self`, because the result of `__init__` is a class _instance_. - [Inheritance][inheritance]: The default `__str___` method is inherited from `Object`, which every class in Python inherits from. (See: inheritance) -- [Methods][methods]: classes can have instance _methods_ which are called from an instance of the class (as opposed to class methods, called from the Class itself). The first parameter of an instance method is always `self`, which is provided when calling from the instance (i.e. the programmer does not need to pass it as an argument explicitly). Static methods are methods called from the class itself, and are not connected to an instance of the class. They have access to class attributes (those defined on the class, not connected to the `self`), and do not require an instance of the class to exist. Classes can also define a `property` by using the `@property` decorator (not shown here); a `property` can be "lazily evaluated" to avoid uneeded computation +- [Methods][methods]: classes can have instance _methods_ which are called from an instance of the class (as opposed to class methods, called from the Class itself). The first parameter of an instance method is always `self`, which is provided when calling from the instance (i.e. the programmer does not need to pass it as an argument explicitly). Static methods are methods called from the class itself, and are not connected to an instance of the class. They have access to class attributes (those defined on the class, not connected to the `self`), and do not require an instance of the class to exist. Classes can also define a `property` by using the `@property` decorator (not shown here); a `property` can be "lazily evaluated" to avoid unneeded computation - [Non-Public Methods][non-public-methods]: Methods or attributes (including those of an imported module) prefixed with an underscore, `_`, are conventionally treated as "non-public" methods. Python does not support data privacy in the way a language like Java does. Instead convention dictates that methods and attributes that are not prefixed with a single underscore can be expected to remain stable along with semver, i.e. a public method will be backwards compatible with minor version updates, and can change with major version updates. Generally, importing non-public functions or using non-public methods is discouraged, though Python will not explicitly stop the programmer from doing so. - [Implied Argument][implied-argument]: within the class definition, methods and properties can be accessed via the `self.` notation - [Inheritance][inheritance]: a "subclass" will inherit all methods, attributes from it's parent class, and can then override methods as needed. Overriding means the logic in the parent class is not used. The `super` builtin function (not shown here) exists to allow the programmer to defer logic up the inheritance chain to the parent class when needed. diff --git a/reference/exercise-concepts/reverse-string.md b/reference/exercise-concepts/reverse-string.md index 52b5fd4e2f5..b9a9944e9ec 100644 --- a/reference/exercise-concepts/reverse-string.md +++ b/reference/exercise-concepts/reverse-string.md @@ -16,7 +16,7 @@ def reverse(text: str = "") -> str: - [Immutability][immutability]: `text` str in Python is [immutable](https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str). In this exercise, you return a new string, the old string `text` is not changed. - [Return Value][return-value]: this function return a string by this line: `return text[::-1]` -- [Slicing][slicing]: becase `str` in Python is a sequence type, [slicing](https://docs.python.org/3/reference/expressions.html#slicings) syntax can be used here. Specifically: for syntax `string[start:stop:stride]`: +- [Slicing][slicing]: because `str` in Python is a sequence type, [slicing](https://docs.python.org/3/reference/expressions.html#slicings) syntax can be used here. Specifically: for syntax `string[start:stop:stride]`: - `start`: 0-index of the start position, `start=0` by default (i.e., not specified) (start from the beginning) - `stop`: 0-index of the stop position, `stop=-1` by default (i.e., not specified) (stop at the end) @@ -31,4 +31,4 @@ def reverse(text: str = "") -> str: [Extra material for string slicing.](https://www.digitalocean.com/community/tutorials/how-to-index-and-slice-strings-in-python-3) - [Docstrings][docstrings]: used to document the function, normally situated right below `def func():` -- [Type hinting][type-hinting]: In modern Python it's possibly to type hint annotations to parameters and variables, see [typing](https://docs.python.org/3/library/typing.html#module-typing). While not neccessary in Python such annotations can help your code be easier to read, understand, and check automatically using tools like `mypy`. +- [Type hinting][type-hinting]: In modern Python it's possibly to type hint annotations to parameters and variables, see [typing](https://docs.python.org/3/library/typing.html#module-typing). While not necessary in Python such annotations can help your code be easier to read, understand, and check automatically using tools like `mypy`. diff --git a/reference/exercise-concepts/rna-transcription.md b/reference/exercise-concepts/rna-transcription.md index 004ded95110..3eb5f63b6f6 100644 --- a/reference/exercise-concepts/rna-transcription.md +++ b/reference/exercise-concepts/rna-transcription.md @@ -16,7 +16,7 @@ def to_rna(dna_strand): - [Static Methods][static-methods]: Distinct from built-in functions, instance methods, and class methods, these are methods that are bound to a class, rather than an instance, and called _without_ explicitly or implicitly passing in an object of the class. The example solution for this exercise uses the `static` `str` method `maketrans`. - [String Methods][string-methods]: this exercise uses `str.maketrans()` (a static method of `str` that returns a dictionary to create a _translation table_ as required by the `str.translate()` instance method. This method is unusual in that it takes either a single dictionary or two strings of equal length. The example solution for this exercise uses `str.maketrans()` with a two-string argument. - [Dictionary][dictionary]: mapping type that has key-value pairs. Returned by `str.maketrans` in the example code. Also one of the argument types accepted by `str.maketrans()`. -- [String Translation][string-translation]: the `str.translate()` _instance method_ is called on an object from the `str` class (e.g. ``.translate()). Returns a copy of the inital string with each character re-mapped through the given _translation table_. The _translation table_ is typically a mapping or sequence type that implements indexing via the magic method `__getitem__()`. +- [String Translation][string-translation]: the `str.translate()` _instance method_ is called on an object from the `str` class (e.g. ``.translate()). Returns a copy of the initial string with each character re-mapped through the given _translation table_. The _translation table_ is typically a mapping or sequence type that implements indexing via the magic method `__getitem__()`. - [Function][function]: A named (_and often reusable_) section of code that performs a specific task. It may or may not have _arguments_ passed in, and may or may not _return_ data. Created using the `def` keyword. - [Function Arguments][function-arguments]: Parameters passed into a function. In python, these are noted in the `()` following a function name. The example code uses a function named `to_rna()` with an argument of `dna_strand`. -- [Return Value][return-value]: the `return` keyword is used in a _return statement_ at the end of a function. Exits a function and may or may not pass data or an expression back to calling code. Functions in python without an expicit `return` keyword and statment will return (pass back) the singleton object `none`. The example code _returns_ a copy of the passed-in argument (assumed to be a string) that has been mapped through `str.translate()`, using the table made from `str.maketrans()` +- [Return Value][return-value]: the `return` keyword is used in a _return statement_ at the end of a function. Exits a function and may or may not pass data or an expression back to calling code. Functions in python without an explicit `return` keyword and statement will return (pass back) the singleton object `none`. The example code _returns_ a copy of the passed-in argument (assumed to be a string) that has been mapped through `str.translate()`, using the table made from `str.maketrans()` diff --git a/reference/exercise-concepts/robot-simulator.md b/reference/exercise-concepts/robot-simulator.md index 8f80f67c1c3..702ada899aa 100644 --- a/reference/exercise-concepts/robot-simulator.md +++ b/reference/exercise-concepts/robot-simulator.md @@ -67,8 +67,8 @@ class Robot: - [Range][range]: the `range()` built-in type represents an immutable sequence of numbers (or any object that implements the `__index__` dunder method). Used in the example to represent the values from zero to 3 as assigned to NORTH, EAST, SOUTH, WEST. - [Class][class]: the exercise objective is to define a `robot` type. Tested methods are linked to a `robot` class. - [Instantiation][instantiation]: creating different instances of the `robot` class with different data representing different starting positions and bearing are tested. -- [Initialization][initialization]: customizing object instatiation with actions and persisting data. The example uses `__init__` to persist a `compass` object and x, y coordinates assigned to instance attributes. -- [Return Value][return-value]: knowing that functions need not have _explicit_ return statements or values but will return `None` if `return` is not specified. Except for the two `@property`-decorated functions, all of the functions in the example omit an explicit `return` statment and all return `None`. +- [Initialization][initialization]: customizing object instantiation with actions and persisting data. The example uses `__init__` to persist a `compass` object and x, y coordinates assigned to instance attributes. +- [Return Value][return-value]: knowing that functions need not have _explicit_ return statements or values but will return `None` if `return` is not specified. Except for the two `@property`-decorated functions, all of the functions in the example omit an explicit `return` statement and all return `None`. - [Implicit Argument][implicit-argument]: the example uses `self` for methods and properties linked to a specific instance of the class. - [Namespaces][namespaces]: knowing to use `self.` for instance attributes and `self` as first argument to instance methods in a class. Additionally, the example uses `self.()` to call a previously stored method name. - [Instance Methods][instance-methods]: tests for this exercises require one or more instance methods that will take in a set of starting coordinates and a bearing and then accept a series of instructions that "move" the instance to a new set of coordinates and bearing. @@ -76,11 +76,11 @@ class Robot: - [Higher-Order Function][higher-order-function]: a function that takes one or more other functions as arguments, _returning_ a function as its return value. The example uses the built-in `property()` as a higher-order function through `@property`. - [Property][property]: the `property()` built-in is a function that returns a property attribute. When used as a decorator, this transforms the passed-in method into a _getter_ method for read-only attribute with the same name and docstring. - [Assignment][assignment]: the example uses assignment for all the instance properties and `instructions` dictionary. -- [Instance Attributes][instance-attributes]: this exercise rquires one or more instance attributes to persist passed in data. +- [Instance Attributes][instance-attributes]: this exercise requires one or more instance attributes to persist passed in data. - [Mutability][mutability]: in the example, knowing there are no protected or private properties in python and so consciously mutating `self.x`, `self.y` and `self.compass` through the called instance methods. - [Method Parameters][method-parameters]: the example `__init__` method has `self`, direction, x, and y (coordinates) as parameters. It also uses `self` and `commands` (a string) for parameters of the `move()` method. -- [Default Arguments][default-arguments]: pre-setting function arguments to protect against them not being passed by a caller. The example uses `direction = NORTH` and `x=0, y=0` to ensure those values for a `robot` even if they are not initally passed. -- [Dictionary][dictionary]: the example uses a dictionary to map paassed in move arguments to methods that perform the moves. The example also uses a dictionary/mapping created by calling `str.maketrans()`. +- [Default Arguments][default-arguments]: pre-setting function arguments to protect against them not being passed by a caller. The example uses `direction = NORTH` and `x=0, y=0` to ensure those values for a `robot` even if they are not initially passed. +- [Dictionary][dictionary]: the example uses a dictionary to map passed in move arguments to methods that perform the moves. The example also uses a dictionary/mapping created by calling `str.maketrans()`. - [Indexing][indexing]: finding a value by key in a dictionary using `[]` The example uses passed in move arguments as `keys` to look up corresponding `values` (_method names_) for moving the robot in the _instructions_ dictionary. - [Iteration][iteration]: the example uses a `for loop` to iterate through the letters of the passed-in `commands` string and looks up the corresponding values in a dictionary, so that the appropriate methods can be called to move the `robot`. - [Composition][composition]: adding functionality from a class by incorporating an instance of that class in a class you are creating. The example creates a `robot` by instantiating a `compass` and assigning it to the `self`.compass attribute of `robot`. From 102bfb78f28a3a0b5142b6199b1d6571df807487 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 6 Nov 2024 15:17:13 -0800 Subject: [PATCH 193/277] Hopefully, the last typos of this round. (#3817) --- concepts/bitwise-operators/about.md | 2 +- exercises/practice/bob/.approaches/introduction.md | 2 +- .../practice/nth-prime/.approaches/generator-fun/content.md | 6 ++++-- .../practice/roman-numerals/.approaches/introduction.md | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/concepts/bitwise-operators/about.md b/concepts/bitwise-operators/about.md index a4ddb509c11..a68e5378f12 100644 --- a/concepts/bitwise-operators/about.md +++ b/concepts/bitwise-operators/about.md @@ -117,7 +117,7 @@ This means that all bits are inverted and a number is _**interpreted as negative Positive numbers have an MSB of `0`. This representation has the advantage of only having one version of zero, so that the programmer doesn't have to manage `-0` and `+0`. -This way of representing negative and positive numbers adds a complication for Python: there are no finite-integer concepts like `int32` or `int64` internally in the core langauge. +This way of representing negative and positive numbers adds a complication for Python: there are no finite-integer concepts like `int32` or `int64` internally in the core language. In 'modern' Python, `int`s are of unlimited size (_limited only by hardware capacity_), and a negative or bit-inverted number has a (_theoretically_) infinite number of `1`'s to the left, just as a positive number has unlimited `0`'s. This makes it difficult to give a useful example of `bitwise not`: diff --git a/exercises/practice/bob/.approaches/introduction.md b/exercises/practice/bob/.approaches/introduction.md index 07d68d1a1e7..b9a54b9f570 100644 --- a/exercises/practice/bob/.approaches/introduction.md +++ b/exercises/practice/bob/.approaches/introduction.md @@ -13,7 +13,7 @@ Regardless of the approach used, some things you could look out for include - Use the [`endswith`][endswith] method instead of checking the last character by index for `?`. -- Don't copy/paste the logic for determining a shout and for determing a question into determing a shouted question. +- Don't copy/paste the logic for determining a shout and for determining a question into determining a shouted question. Combine the two determinations instead of copying them. Not duplicating the code will keep the code [DRY][dry]. diff --git a/exercises/practice/nth-prime/.approaches/generator-fun/content.md b/exercises/practice/nth-prime/.approaches/generator-fun/content.md index 39290335a04..b7fc867ab77 100644 --- a/exercises/practice/nth-prime/.approaches/generator-fun/content.md +++ b/exercises/practice/nth-prime/.approaches/generator-fun/content.md @@ -19,11 +19,13 @@ Using a lambda expression, we `filter` out any numbers above two that are prime. Doesn't this result in an infinite loop? No - `filter` _also_ returns a generator object (which are [evaluated lazily][generator]), so while it's too will produce values infinitely if evaluated, it doesn't hang to program at the time of instantiation. -`itertools.islice` takes in a generator object and an end count, returning a generator object which _only evalutes until that end count_. +`itertools.islice` takes in a generator object and an end count, returning a generator object which _only evaluates until that end count_. The next line exhausts all the values in the generator except the end, and we finally return the last element. -We can utilize the `functools.cache` decorator for greater speeds at higher values of `number`, so we take it out. The added bonus is that a very long line of code is cleant up. +We can utilize the `functools.cache` decorator for greater speeds at higher values of `number`, so we take it out. +The added bonus is that a very long line of code is cleaned up. + ```python from itertools import islice, count diff --git a/exercises/practice/roman-numerals/.approaches/introduction.md b/exercises/practice/roman-numerals/.approaches/introduction.md index 35f73e3acee..49060c2029c 100644 --- a/exercises/practice/roman-numerals/.approaches/introduction.md +++ b/exercises/practice/roman-numerals/.approaches/introduction.md @@ -186,11 +186,11 @@ As the textbooks say, further analysis of this approach is left as an exercise f ## Which approach to use? In production, it would make sense to use the `roman` package. -It is debugged and supports Roman-to-Arabic conversions in addtion to the Arabic-to-Roman approaches discussed here. +It is debugged and supports Roman-to-Arabic conversions in addition to the Arabic-to-Roman approaches discussed here. Most submissions, like the `roman` package implementation, use some variant of [`loop-over-romans`][loop-over-romans]. -Using a [2-D lookup table][table-lookup] takes a bit more initialization, but then everthing can be done in a list comprehension instead of nested loops. +Using a [2-D lookup table][table-lookup] takes a bit more initialization, but then everything can be done in a list comprehension instead of nested loops. Python is relatively unusual in supporting both tuples-of-tuples and relatively fast list comprehensions, so the approach seems a good fit for this language. No performance article is currently included for this exercise. From da351aea14672f5ebaab2629b44fc89bf3c28031 Mon Sep 17 00:00:00 2001 From: JD DeLaune <102549713+jddelaune@users.noreply.github.com> Date: Sat, 16 Nov 2024 22:41:04 -0600 Subject: [PATCH 194/277] Fixed broken link in grains performance doc (#3822) --- exercises/practice/grains/.articles/performance/content.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/grains/.articles/performance/content.md b/exercises/practice/grains/.articles/performance/content.md index b1150018314..7ab12a14781 100644 --- a/exercises/practice/grains/.articles/performance/content.md +++ b/exercises/practice/grains/.articles/performance/content.md @@ -37,7 +37,7 @@ pow square 64: 5.738279999932274e-07 Using `if number not in range(1, 65):` was over `125` nanoseconds longer than using `if number < 1 or number > 64:` for all approaches. [approaches]: https://exercism.org/tracks/python/exercises/grains/approaches -[approach-bit-shifting]: https://exercism.org/python/csharp/exercises/grains/approaches/bit-shifting +[approach-bit-shifting]: https://exercism.org/tracks/python/exercises/grains/approaches/bit-shifting [approach-pow]: https://exercism.org/tracks/python/exercises/grains/approaches/pow [approach-exponentiation]: https://exercism.org/tracks/python/exercises/grains/approaches/exponentiation [benchmark-application]: https://github.com/exercism/python/blob/main/exercises/practice/grains/.articles/performance/code/Benchmark.py From 8dde6fd43646a1e071aca2d5a1a324b503dc47a6 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 17 Nov 2024 22:29:03 -0800 Subject: [PATCH 195/277] Testing fix for no-important-files workflow. (#3824) --- .github/workflows/no-important-files-changed.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml index 812e9129668..cfcd6db4d44 100644 --- a/.github/workflows/no-important-files-changed.yml +++ b/.github/workflows/no-important-files-changed.yml @@ -17,7 +17,7 @@ permissions: jobs: check: - uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main + uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@fix-no-important-files with: repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} ref: ${{ github.head_ref }} From bba3d8010d767508050bd09833f0f6672dab7d92 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 19 Nov 2024 10:41:40 -0800 Subject: [PATCH 196/277] Changed list method tests to validate the queue. Also ajdusted instructions. (#3825) [no important files changed] While these are new tests, the majority of existing solutions should pass them, so no need to do a mass re-test. --- .../.docs/instructions.md | 2 + .../list_methods_test.py | 310 +++++++++++++----- 2 files changed, 222 insertions(+), 90 deletions(-) diff --git a/exercises/concept/chaitanas-colossal-coaster/.docs/instructions.md b/exercises/concept/chaitanas-colossal-coaster/.docs/instructions.md index 40280179c8e..0a5bf25ff0d 100644 --- a/exercises/concept/chaitanas-colossal-coaster/.docs/instructions.md +++ b/exercises/concept/chaitanas-colossal-coaster/.docs/instructions.md @@ -12,6 +12,8 @@ There are two queues for this ride, each represented as a `list`: You have been asked to write some code to better manage the guests at the park. You need to implement the following functions as soon as possible before the guests (and your boss, Chaitana!) get cranky. + Make sure you read carefully. + Some tasks ask that you change or update the existing queue, while others ask you to make a copy of it. ## 1. Add me to the queue diff --git a/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py b/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py index 5df2af327b7..7a754b78e12 100644 --- a/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py +++ b/exercises/concept/chaitanas-colossal-coaster/list_methods_test.py @@ -1,6 +1,7 @@ import unittest -import pytest from copy import deepcopy +import pytest + from list_methods import ( add_me_to_the_queue, @@ -16,108 +17,215 @@ class ListMethodsTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_add_me_to_the_queue(self): - test_data = [(['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), - (['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich')] - result_data = [['RobotGuy', 'WW', 'HawkEye'], - ['Tony', 'Bruce', 'RichieRich']] + test_data = [ + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), ['RobotGuy', 'WW', 'HawkEye']), + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich'), ['Tony', 'Bruce', 'RichieRich']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 1, 'Okoye'), ['Agatha', 'Pepper', 'Valkyrie', 'Okoye']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 0, 'Gamora'), ['Drax', 'Nebula', 'Gamora']), + ] - for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + for variant, (params, expected) in enumerate(test_data, start=1): + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + express_queue, normal_queue, ticket_type, person_name = deepcopy(params) + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = add_me_to_the_queue(*params) + + error_message = ( + f'\nCalled add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}.\n' + f'The function returned {actual_result},\n' + f' but the tests expected {expected} after {person_name} was added.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=1) + def test_add_me_to_the_queue_validate_queue(self): + test_data = [ + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 0, 'HawkEye'), ['RobotGuy', 'WW', 'HawkEye']), + ((['Tony', 'Bruce'], ['RobotGuy', 'WW'], 1, 'RichieRich'), ['Tony', 'Bruce', 'RichieRich']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 1, 'Okoye'), ['Agatha', 'Pepper', 'Valkyrie', 'Okoye']), + ((['Agatha', 'Pepper', 'Valkyrie'], ['Drax', 'Nebula'], 0, 'Gamora'), ['Drax', 'Nebula', 'Gamora']), + ] + + for variant, (params, expected) in enumerate(test_data, start=1): # Deepcopy() is needed here because the task expects the input lists to be mutated. # That mutation wrecks havoc with the verification and error messaging. express_queue, normal_queue, ticket_type, person_name = deepcopy(params) + express, normal, ticket, name = params with self.subTest(f'variation #{variant}', - express_queue=express_queue, - normal_queue=normal_queue, - ticket_type=ticket_type, - person_name=person_name, - expected=expected): + express=express, normal=normal, + ticket=ticket, name=name, expected=expected): - actual_result = add_me_to_the_queue(*params) - error_message = (f'Called add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}. ' - f'The function returned {actual_result}, but ' - f'The tests expected {expected} when adding ' - f'{person_name} to the queue.') + actual_result = add_me_to_the_queue(express, normal, ticket, name) + + if type == 1: + error_message = ( + f'\nCalled add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}.\n' + f'The queue == {express}, but the tests expected\n' + f'queue == {expected} after {person_name} was added.' + ) - self.assertListEqual(actual_result, expected, msg=error_message) + self.assertIs(actual_result, express, msg=error_message) + + if type == 0: + error_message = ( + f'\nCalled add_me_to_the_queue{express_queue, normal_queue, ticket_type, person_name}.\n' + f'The queue == {normal}, but the tests expected \n' + f'queue == {expected} after {person_name} was added.' + ) + + self.assertIs(actual_result, normal, msg=error_message) @pytest.mark.task(taskno=2) def test_find_my_friend(self): - test_data = [(['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Natasha'), - (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Steve'), - (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Rocket')] + test_data = [ + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Natasha'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Steve'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 'Rocket'), + ] + result_data = (0,1,4) for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): with self.subTest(f'variation #{variant}', params=params, expected=expected): actual_result = find_my_friend(*params) - error_message = (f'Called find_my_friend{params}. ' - f'The function returned {actual_result}, but ' - f'The tests expected {expected} when looking for ' - f'{params[-1]} in the queue.') + error_message = ( + f'\nCalled find_my_friend{params}.\n' + f'The function returned {actual_result}, but\n' + f'the tests expected {expected} when looking for\n' + f'{params[-1]} in the queue.' + ) self.assertIs(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=3) def test_add_me_with_my_friends(self): - test_data = [(['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 0, 'Bucky'), + test_data = [ + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 0, 'Bucky'), (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 1, 'Bucky'), - (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 5, 'Bucky')] + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 5, 'Bucky'), + ] - result_data = [['Bucky', 'Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], - ['Natasha', 'Bucky', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], - ['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket', 'Bucky']] + result_data = [ + ['Bucky', 'Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], + ['Natasha', 'Bucky', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], + ['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket', 'Bucky'], + ] for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): - # Deepcopy() is needed here because the task expects the input lists to be mutated. # That mutation wrecks havoc with the verification and error messaging. queue, index, person_name = deepcopy(params) - with self.subTest(f'variation #{variant}', - queue=queue, - index=index, - person_name=person_name, - expected=expected): + with self.subTest(f'variation #{variant}', params=params, expected=expected): actual_result = add_me_with_my_friends(*params) - error_message = (f'Called add_me_with_my_friends{queue, index, person_name}. ' - f'The function returned {actual_result}, but ' - f'The tests expected {expected} when adding ' - f'{person_name} to position {index} in the queue.') + error_message = ( + f'\nCalled add_me_with_my_friends{queue, index, person_name}.\n' + f'The function returned {actual_result}, but\n' + f'the tests expected {expected} when adding\n' + f'{person_name} to position {index} in the queue.' + ) + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=3) + def test_add_me_with_my_friends_validate_queue(self): + test_data = [ + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 0, 'Bucky'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 1, 'Bucky'), + (['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], 5, 'Bucky'), + ] + + result_data = [ + ['Bucky', 'Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], + ['Natasha', 'Bucky', 'Steve', 'Tchalla', 'Wanda', 'Rocket'], + ['Natasha', 'Steve', 'Tchalla', 'Wanda', 'Rocket', 'Bucky'], + ] - self.assertListEqual(actual_result, expected, msg=error_message) + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + start_queue, add_index, person_name = deepcopy(params) + queue, _, _ = params + + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = add_me_with_my_friends(*params) + error_message = ( + f'\nCalled add_me_with_my_friends{start_queue, add_index, person_name}.\n' + f'The function returned {actual_result},\n' + 'but the original queue was unmodified. The tests expected the \n' + f'*original* queue to be modified by adding "{person_name}".' + ) + + self.assertIs(actual_result, queue, msg=error_message) @pytest.mark.task(taskno=4) def test_remove_the_mean_person(self): - test_data = [(['Natasha', 'Steve', 'Ultron', 'Wanda', 'Rocket'], 'Ultron'), - (['Natasha', 'Steve', 'Wanda', 'Rocket', 'Ultron'], 'Ultron'), - (['Ultron', 'Natasha', 'Steve', 'Wanda', 'Rocket'], 'Ultron'), + test_data = [ + (['Natasha', 'Steve', 'Ultron', 'Wanda', 'Rocket'], 'Ultron'), + (['Natasha', 'Steve', 'Wanda', 'Rocket', 'Ultron'], 'Rocket'), + (['Ultron', 'Natasha', 'Steve', 'Wanda', 'Rocket'], 'Steve'), + ] + result_data = [ + ['Natasha', 'Steve', 'Wanda', 'Rocket'], + ['Natasha', 'Steve', 'Wanda', 'Ultron'], + ['Ultron', 'Natasha', 'Wanda', 'Rocket'], ] - result_data = [['Natasha', 'Steve', 'Wanda', 'Rocket'], - ['Natasha', 'Steve', 'Wanda', 'Rocket'], - ['Natasha', 'Steve', 'Wanda', 'Rocket']] for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): # Deepcopy() is needed here because the task expects the input lists to be mutated. # That mutation wrecks havoc with the verification and error messaging. - queue, person_name = deepcopy(params) + start_queue, person_name = deepcopy(params) - with self.subTest(f'variation #{variant}', - queue=queue, - person_name=person_name, - expected=expected): + with self.subTest(f'variation #{variant}', params=params, expected=expected): + actual_result = remove_the_mean_person(*params) + error_message = ( + f'\nCalled remove_the_mean_person{start_queue, person_name}.\n' + f'The function returned {actual_result}, but\n' + f'the tests expected {expected} when removing\n' + f'{person_name} from the queue.' + ) + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=4) + def test_remove_the_mean_person_validate_queue(self): + test_data = [ + (['Natasha', 'Steve', 'Ultron', 'Wanda', 'Rocket'], 'Ultron'), + (['Natasha', 'Steve', 'Wanda', 'Rocket', 'Ultron'], 'Rocket'), + (['Ultron', 'Natasha', 'Steve', 'Wanda', 'Rocket'], 'Steve'), + ] + + result_data = [ + ['Natasha', 'Steve', 'Wanda', 'Rocket'], + ['Natasha', 'Steve', 'Wanda', 'Ultron'], + ['Ultron', 'Natasha', 'Wanda', 'Rocket'], + ] + + + for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1): + + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + start_queue, person_name = deepcopy(params) + queue, _ = params + with self.subTest(f'variation #{variant}', params=params, expected=expected): actual_result = remove_the_mean_person(*params) - error_message = (f'Called remove_the_mean_person{queue, person_name}. ' - f'The function returned {actual_result}, but ' - f'The tests expected {expected} when removing ' - f'{person_name} from the queue.') + error_message = ( + f'\nCalled remove_the_mean_person{start_queue, person_name}.\n' + f'The function returned {actual_result}, queue == {queue}.\n' + f'But the tests expected queue == {expected} when removing\n' + f'{person_name}.' + ) + + self.assertIs(actual_result, queue, msg=error_message) - self.assertListEqual(actual_result, expected, msg=error_message) @pytest.mark.task(taskno=5) def test_how_many_namefellows(self): @@ -141,46 +249,68 @@ def test_how_many_namefellows(self): @pytest.mark.task(taskno=6) def test_remove_the_last_person(self): test_data = [ - (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], 'Rocket'), - (['Wanda', 'Natasha', 'Steve', 'Rocket', 'Ultron'], 'Ultron'), - (['Steve', 'Wanda', 'Rocket', 'Ultron', 'Natasha'], 'Natasha') + (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Steve', 'Ultron', 'Natasha'], 'Rocket'), + (['Wanda', 'Natasha', 'Steve', 'Rocket', 'Ultron'], ['Wanda', 'Natasha', 'Steve', 'Rocket'], 'Ultron'), + (['Steve', 'Wanda', 'Rocket', 'Ultron', 'Natasha'], ['Steve', 'Wanda', 'Rocket', 'Ultron'], 'Natasha') ] - for variant, (input, expected) in enumerate(test_data, start=1): - with self.subTest(f'variation #{variant}', input=input, expected=expected): - actual_result = remove_the_last_person(input) + for variant, (queue, modified, expected) in enumerate(test_data, start=1): + with self.subTest(f'variation #{variant}', queue=queue, modified=modified, expected=expected): + + # Deepcopy() is needed here because the task expects the input lists to be mutated. + # That mutation wrecks havoc with the verification and error messaging. + unmodified_queue = deepcopy(queue) expected_result = expected + actual_result = remove_the_last_person(queue) + expected_queue = modified - error_message = (f'Called remove_the_last_person({input}).' - f'The function returned {actual_result}, but the tests expected {expected_result}.') + error_message = (f'\nCalled remove_the_last_person({unmodified_queue}).\n' + f'The function was expected to remove and return the name "{expected_result}" ' + f'and change the queue to {expected_queue},\n' + f'but the name "{actual_result}" was returned and the queue == {queue}.') - self.assertEqual(actual_result, expected_result, msg=error_message) + self.assertEqual((actual_result, queue), (expected_result, expected_queue), msg=error_message) - @pytest.mark.task(taskno=6) - def test_remove_the_last_person_validate_queue(self): - test_data = [ - (['Natasha', 'Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Steve', 'Ultron', 'Natasha']), - (['Wanda', 'Natasha', 'Steve', 'Rocket', 'Ultron'], ['Wanda', 'Natasha', 'Steve', 'Rocket']), - (['Steve', 'Wanda', 'Rocket', 'Ultron', 'Natasha'], ['Steve', 'Wanda', 'Rocket', 'Ultron']) - ] - for variant, (input, modified) in enumerate(test_data, start=1): - with self.subTest(f'variation #{variant}', input=input, modified=modified): - unmodified = deepcopy(input) - actual_result = remove_the_last_person(input) - expected_queue = modified - error_message = (f'\nCalled remove_the_last_person({unmodified}).\n' - f'The function was expected to change the queue to {expected_queue},\n' - f'but the queue looks like {input} instead.') + @pytest.mark.task(taskno=7) + def test_sorted_names(self): + test_data =( + (['Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Rocket', 'Steve', 'Ultron']), + (['Agatha', 'Pepper', 'Valkyrie', 'Drax', 'Nebula'], ['Agatha', 'Drax', 'Nebula', 'Pepper', 'Valkyrie']), + (['Gamora', 'Loki', 'Tony', 'Peggy', 'Okoye'], ['Gamora', 'Loki', 'Okoye', 'Peggy', 'Tony']), + ) + + for variant, (queue, expected) in enumerate(test_data, start=1): + with self.subTest(f'variation #{variant}', queue=queue, expected=expected): + actual_result = sorted_names(queue) + expected_result = expected + + error_message = (f'\nCalled sorted_names({queue}).\n' + f'The function returned {actual_result}, but \n' + f'the tests expect {expected_result}.') - self.assertEqual(input, expected_queue, msg=error_message) + self.assertEqual(actual_result, expected_result, msg=error_message) @pytest.mark.task(taskno=7) - def test_sorted_names(self): - test_data = ['Steve', 'Ultron', 'Natasha', 'Rocket'] - expected = ['Natasha', 'Rocket', 'Steve', 'Ultron'] - actual_result = sorted_names(test_data) - error_message = (f'Called sorted_names({test_data}).' - f'The function returned {actual_result}, but the tests ' - f'expected {expected}.') - - self.assertListEqual(actual_result, expected, msg=error_message) + def test_sorted_names_validate_queue(self): + test_data = ( + (['Steve', 'Ultron', 'Natasha', 'Rocket'], ['Natasha', 'Rocket', 'Steve', 'Ultron']), + (['Agatha', 'Pepper', 'Valkyrie', 'Drax', 'Nebula'], ['Agatha', 'Drax', 'Nebula', 'Pepper', 'Valkyrie']), + (['Gamora', 'Loki', 'Tony', 'Peggy', 'Okoye'], ['Gamora', 'Loki', 'Okoye', 'Peggy', 'Tony']), + ) + + for variant, (queue, expected) in enumerate(test_data, start=1): + with self.subTest(f'variation #{variant}', queue=queue, expected=expected): + + # Deepcopy() is needed here because the input lists might be mutated. + # That mutation wrecks havoc with the verification and error messaging. + original_queue = deepcopy(queue) + actual_result = sorted_names(queue) + expected_result = expected + + error_message = (f'\nCalled sorted_names({original_queue}).\n' + f'The function returned {actual_result}, \n' + f'with a queue == {queue}.\n' + f'The tests expect {expected_result}, \n' + f'with a queue == {original_queue}.') + + self.assertIsNot(actual_result, queue, msg=error_message) From be191488d58b022ece632ee7064716c4fd1d033c Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 19 Nov 2024 10:42:05 -0800 Subject: [PATCH 197/277] Added some notes on using * on the left-hand side for unpacking. (#3826) --- concepts/unpacking-and-multiple-assignment/about.md | 12 +++++++++++- .../locomotive-engineer/.docs/introduction.md | 7 ++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/concepts/unpacking-and-multiple-assignment/about.md b/concepts/unpacking-and-multiple-assignment/about.md index 787e2ef08e8..a24e2c6d1a5 100644 --- a/concepts/unpacking-and-multiple-assignment/about.md +++ b/concepts/unpacking-and-multiple-assignment/about.md @@ -222,12 +222,20 @@ This will pack all the values into a `list`/`tuple`. >>> combined_fruits ("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango") -# If the * operator is used on the left side of "=" the result is a list +# If the * operator is used on the left side of "=" the result is a list. +# Note the trailing comma. >>> *combined_fruits_too, = *fruits, *more_fruits >>> combined_fruits_too ['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango'] + +# A list literal can be used instead, but might not be as readable. +>>> [*combined_fruits_too] = *fruits, *more_fruits +>>> combined_fruits_too +['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango'] ``` +For more details on the use of `*` and `**`, check out [PEP 3132][pep-3132] and [PEP 448][pep-448]. + ### Packing a dictionary with `**` Packing a dictionary is done by using the `**` operator. @@ -370,6 +378,8 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the [items]: https://docs.python.org/3/library/stdtypes.html#dict.items [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ [packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/ +[pep-448]: https://peps.python.org/pep-0448/ +[pep-3132]: https://peps.python.org/pep-3132/ [sorting algorithms]: https://realpython.com/sorting-algorithms-python/ [unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp [view-objects]: https://docs.python.org/3/library/stdtypes.html#dict-views diff --git a/exercises/concept/locomotive-engineer/.docs/introduction.md b/exercises/concept/locomotive-engineer/.docs/introduction.md index 798a334aeb9..39ba5b49090 100644 --- a/exercises/concept/locomotive-engineer/.docs/introduction.md +++ b/exercises/concept/locomotive-engineer/.docs/introduction.md @@ -213,12 +213,16 @@ This will pack all the values into a `list`/`tuple`. >>> combined_fruits ("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango") -# If the * operator is used on the left side of "=" the result is a list +# If the * operator is used on the left side of "=" the result is a list. +# Note the trailing comma. >>> *combined_fruits_too, = *fruits, *more_fruits >>> combined_fruits_too ['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango'] ``` +For more background on using `*` on the left-hand side, see [PEP 3132][pep-3132]. + + ### Packing a dictionary with `**` Packing a dictionary is done by using the `**` operator. @@ -361,6 +365,7 @@ Since `zip()` takes multiple iterables and returns a `list` of `tuples` with the [items]: https://docs.python.org/3/library/stdtypes.html#dict.items [multiple assignment]: https://www.geeksforgeeks.org/assigning-multiple-variables-in-one-line-in-python/ [packing and unpacking]: https://www.geeksforgeeks.org/packing-and-unpacking-arguments-in-python/ +[pep-3132]: https://peps.python.org/pep-3132/ [sorting algorithms]: https://realpython.com/sorting-algorithms-python/ [unpacking]: https://www.geeksforgeeks.org/unpacking-arguments-in-python/?ref=rp [view-objects]: https://docs.python.org/3/library/stdtypes.html#dict-views From 2779c2749f3bf58c44635442f30c9d416214243e Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 22 Nov 2024 09:55:30 -0800 Subject: [PATCH 198/277] [Roman Numeral Approaches]: Fix Broken Link in Approaches Introduction (#3828) https://exercism.org/tracks/python/exercises/roman-numerals/approaches/loop-over-roman --> https://exercism.org/tracks/python/exercises/roman-numerals/approaches/loop-over-romans --- exercises/practice/roman-numerals/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/roman-numerals/.approaches/introduction.md b/exercises/practice/roman-numerals/.approaches/introduction.md index 49060c2029c..3358c23f40e 100644 --- a/exercises/practice/roman-numerals/.approaches/introduction.md +++ b/exercises/practice/roman-numerals/.approaches/introduction.md @@ -200,6 +200,6 @@ The problem is inherently limited in scope by the design of Roman numerals, so a [if-else]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/if-else [table-lookup]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/table-lookup -[loop-over-romans]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/loop-over-roman +[loop-over-romans]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/loop-over-romans [recurse-match]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/recurse-match [roman-module]: https://github.com/zopefoundation/roman From c38e94b94091f253dd4c65a7e33045493d29d884 Mon Sep 17 00:00:00 2001 From: Andrew Pam Date: Mon, 25 Nov 2024 06:14:11 +1100 Subject: [PATCH 199/277] Update content.md (#3829) Add missing URI for "dictionary approach" --- exercises/practice/scrabble-score/.approaches/enum/content.md | 1 + .../practice/scrabble-score/.approaches/nested-tuple/content.md | 1 + 2 files changed, 2 insertions(+) diff --git a/exercises/practice/scrabble-score/.approaches/enum/content.md b/exercises/practice/scrabble-score/.approaches/enum/content.md index 5c2ad3a18ae..f5845a50918 100644 --- a/exercises/practice/scrabble-score/.approaches/enum/content.md +++ b/exercises/practice/scrabble-score/.approaches/enum/content.md @@ -48,6 +48,7 @@ The `score` function uses the same [generator expression][generator-expression] Instead of looking up the value in a _dictionary_, it looks up the `InEnum` class member value. [classes]: https://docs.python.org/3/tutorial/classes.html +[dictionary-approach]: https://exercism.org/tracks/python/exercises/scrabble-score/approaches/dictionary [enum]: https://docs.python.org/3/library/enum.html [generator-expression]: https://peps.python.org/pep-0289/ [int-enum]: https://docs.python.org/3/library/enum.html#enum.IntEnum diff --git a/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md b/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md index 70dc860a0a4..6bbd28a6bc2 100644 --- a/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md +++ b/exercises/practice/scrabble-score/.approaches/nested-tuple/content.md @@ -30,6 +30,7 @@ You can read more about unpacking in the [concept:python/unpacking-and-multiple- Then the code checks if the character is in the unpacked letters and if it is we return its score. +[dictionary-approach]: https://exercism.org/tracks/python/exercises/scrabble-score/approaches/dictionary [generator-expression]: https://peps.python.org/pep-0289/ [for-loop]: https://realpython.com/python-for-loop/ [tuple]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences From 01fb2575e8a75775d3e0f5cf198120953afa02f5 Mon Sep 17 00:00:00 2001 From: glaxxie <86179463+glaxxie@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:47:14 -0600 Subject: [PATCH 200/277] Update TESTS.md (#3830) * Update TESTS.md - Change placeholder brackets from `{}` to `<>` - Add a note explain placeholder value - Change pytest version number to reflect the latest one * Update TOOLS.md Small change to fit with the version in `INSTALLATION.md` * Update docs/TESTS.md Using exercism note block and clearer example Co-authored-by: BethanyG * Update docs/TOOLS.md Add more details about tooling Co-authored-by: BethanyG --------- Co-authored-by: BethanyG --- docs/TESTS.md | 45 +++++++++++++++++++++++++++++---------------- docs/TOOLS.md | 5 ++++- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 54a10b5782a..5a38cf4c55d 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -28,14 +28,14 @@ Otherwise, the `pytest` installation will be global. ```powershell PS C:\Users\foobar> py -m pip install pytest pytest-cache pytest-subtests pytest-pylint -Successfully installed pytest-7.2.2 ... +Successfully installed pytest-8.3.3 ... ``` #### Linux / MacOS ```bash $ python3 -m pip install pytest pytest-cache pytest-subtests pytest-pylint -Successfully installed pytest-7.2.2 ... +Successfully installed pytest-8.3.3 ... ``` @@ -43,23 +43,36 @@ To check if installation was successful: ```bash $ python3 -m pytest --version -pytest 7.2.2 +pytest 8.3.3 ``` ## Running the tests -To run the tests, go to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_). +To run the tests, go to the folder where the exercise is stored using `cd` in your terminal (_replace `` below with your path_). ```bash -$ cd {exercise-folder-location} +$ cd ``` +
+ +~~~~exercism/note + `` or most things inside angle brackets denote a **_placeholder value_**. +A normal path or file name should be written _without_ any brackets. + + +For example: `/Users/janedoe/exercism/python/exercises/concept/chaitanas-colossal-coaster` (on *nix systems), `C:\Users\janedoe\exercism\python\exercises\practice\hello-world\` (on Windows), `myFolder` or `my_file.py`. +~~~~ + +
+ + The file you will want to run usually ends in `_test.py`. This file contains the tests for the exercise solution, and are the same tests that run on the website when a solution is uploaded. -Next, run the following command in your terminal, replacing `{exercise_test.py}` with the location/name of the test file: +Next, run the following command in your terminal, replacing `` with the location/name of the test file: ```bash -$ python3 -m pytest -o markers=task {exercise_test.py} +$ python3 -m pytest -o markers=task ==================== 7 passed in 0.08s ==================== ``` @@ -88,15 +101,15 @@ When tests fail, `pytest` prints the text of each failed test, along with the ex Below is an generic example of a failed test: ```bash -$(my_venv) python3 -m pytest -o markers=task {exercise_test.py} +$(my_venv) python3 -m pytest -o markers=task =================== FAILURES ==================== ______________ name_of_failed_test ______________ -# Test code inside of {exercise_test.py} that failed. +# Test code inside of that failed. ... E TypeOfError: ReturnedValue != ExpectedValue -exercise_test.py:{line_of_failed_test}: TypeOfError +exercise_test.py:: TypeOfError ============ short test summary info ============ FAILED exercise_test.py::ExerciseTest::name_of_failed_test ========== 1 failed, 2 passed in 0.13s ========== @@ -216,10 +229,10 @@ If you do not know where you have installed Python, run the following command in ```bash $ python3 -c "import os, sys; print(os.path.dirname(sys.executable))" -{python_directory} + ``` -The _returned_ directory is where your current active Python version is installed, in this section it is referred to as `{python_directory}`. +The _returned_ directory is where your current active Python version is installed, in this section it is referred to as ``. #### Windows @@ -232,7 +245,7 @@ Then find the `Path` variable in your _User variables_, select it, and click `Ed ![Selecting the path variable](https://raw.githubusercontent.com/exercism/python/main/docs/img/Windows-EnvironmentVariables.png) -Then add a new line, as shown in the picture, replacing `{python_directory}` with your Python installation's directory: +Then add a new line, as shown in the picture, replacing `` with your Python installation's directory: ![Add python to path](https://raw.githubusercontent.com/exercism/python/main/docs/img/Windows-AddPythonPath.png) @@ -240,17 +253,17 @@ Then add a new line, as shown in the picture, replacing `{python_directory}` wit The below should work for most Linux and MacOS flavors with a `bash` shell. Commands may vary by Linux distro, and whether a `fish` or `zsh` shell is used. -Replace `{python_directory}` with the output of `python3 -c "import os, sys; print(os.path.dirname(sys.executable))"` +Replace `` with the output of `python3 -c "import os, sys; print(os.path.dirname(sys.executable))"` ```bash -export PATH=”$PATH:{python_directory}}” +export PATH=”$PATH:” ``` [Code Quality: Tools and Best Practices]: https://realpython.com/python-code-quality/ [Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html [configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats [marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks -[pdb]: https://docs.python.org/3.9/library/pdb.html +[pdb]: https://docs.python.org/3.11/library/pdb.html [pip]: https://pip.pypa.io/en/stable/getting-started/ [psf-installer]: https://www.python.org/downloads/ [pylint]: https://pylint.pycqa.org/en/latest/user_guide/ diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 20ce04ded09..bacb8626aaa 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -30,7 +30,10 @@ If you have an editor, IDE, tool, or plugin recommendation, we encourage you to Before you start exploring, make sure that you have a recent version of Python installed. -The Exercism platform currently supports `Python 3.7 - 3.11.2` (_exercises and tests_) and `Python 3.11.2` (_tooling_). +The Exercism web platform currently supports `Python 3.7 - 3.11.5` (_exercises and tests_) and `Python 3.11.5` (_tooling_). +Our online test runner currently uses `pytest 7.2.2` and `pytest-subtests 0.11.0`. +Our online analyzer uses `pylint 2.17.7`. +Using different versions of `Python`, `pytest`, or `pylint` locally might give you different results than the website. For more information, please refer to [Installing Python locally][Installing Python locally].
From db94cb030748e5de605f0e78688fdb8675e5a53b Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 30 Nov 2024 11:37:26 -0800 Subject: [PATCH 201/277] [Darts] : Add Approaches (#3386) * Initial docs for darts approaches. * Initial rough draft of darts approaches. * Removing .articles for now. * Wrapped up approaches content details and removed performance info. * Cleaned up typos and changed toss to throw. --- .../.approaches/booleans-as-ints/content.md | 36 +++++ .../.approaches/booleans-as-ints/snippet.txt | 3 + .../practice/darts/.approaches/config.json | 50 ++++++ .../.approaches/dict-and-dict-get/content.md | 72 +++++++++ .../.approaches/dict-and-dict-get/snippet.txt | 5 + .../.approaches/dict-and-generator/content.md | 69 +++++++++ .../dict-and-generator/snippet.txt | 8 + .../.approaches/if-statements/content.md | 73 +++++++++ .../.approaches/if-statements/snippet.txt | 8 + .../darts/.approaches/introduction.md | 146 ++++++++++++++++++ .../darts/.approaches/match-case/content.md | 85 ++++++++++ .../darts/.approaches/match-case/snippet.txt | 8 + .../.approaches/tuple-and-loop/content.md | 55 +++++++ .../.approaches/tuple-and-loop/snippet.txt | 7 + 14 files changed, 625 insertions(+) create mode 100644 exercises/practice/darts/.approaches/booleans-as-ints/content.md create mode 100644 exercises/practice/darts/.approaches/booleans-as-ints/snippet.txt create mode 100644 exercises/practice/darts/.approaches/config.json create mode 100644 exercises/practice/darts/.approaches/dict-and-dict-get/content.md create mode 100644 exercises/practice/darts/.approaches/dict-and-dict-get/snippet.txt create mode 100644 exercises/practice/darts/.approaches/dict-and-generator/content.md create mode 100644 exercises/practice/darts/.approaches/dict-and-generator/snippet.txt create mode 100644 exercises/practice/darts/.approaches/if-statements/content.md create mode 100644 exercises/practice/darts/.approaches/if-statements/snippet.txt create mode 100644 exercises/practice/darts/.approaches/introduction.md create mode 100644 exercises/practice/darts/.approaches/match-case/content.md create mode 100644 exercises/practice/darts/.approaches/match-case/snippet.txt create mode 100644 exercises/practice/darts/.approaches/tuple-and-loop/content.md create mode 100644 exercises/practice/darts/.approaches/tuple-and-loop/snippet.txt diff --git a/exercises/practice/darts/.approaches/booleans-as-ints/content.md b/exercises/practice/darts/.approaches/booleans-as-ints/content.md new file mode 100644 index 00000000000..d3c1541d2a6 --- /dev/null +++ b/exercises/practice/darts/.approaches/booleans-as-ints/content.md @@ -0,0 +1,36 @@ +# Using Boolean Values as Integers + + +```python +def score(x_coord, y_coord): + radius = (x_coord**2 + y_coord**2) + return (radius<=1)*5 + (radius<=25)*4 + (radius<=100)*1 +``` + + +In Python, the [Boolean values `True` and `False` are _subclasses_ of `int`][bools-as-ints] and can be interpreted as `0` (False) and `1` (True) in a mathematical context. +This approach leverages that interpretation by checking which areas the throw falls into and multiplying each Boolean `int` by a scoring multiple. +For example, a throw that lands on the 25 (_or 5 if using `math.sqrt(x**2 + y**2)`_) circle should have a score of 5: + +```python +>>> (False)*5 + (True)*4 + (True)*1 +5 +``` + + +This makes for very compact code and has the added boost of not requiring any `loops` or additional data structures. +However, it is considered bad form to rely on Boolean interpretation. +Instead, the Python documentation recommends an explicit conversion to `int`: + + +```python +def score(x_coord, y_coord): + radius = (x_coord**2 + y_coord**2) + return int(radius<=1)*5 + int(radius<=25)*4 + int(radius<=100)*1 +``` + +Beyond that recommendation, the terseness of this approach might be harder to reason about or decode — especially if a programmer is coming from a programming langauge that does not treat Boolean values as `ints`. +Despite the "radius" variable name, it is also more difficult to relate the scoring "rings" of the Dartboard to the values being checked and calculated in the `return` statement. +If using this code in a larger program, it would be strongly recommended that a docstring be provided to explain the Dartboard rings, scoring rules, and the corresponding scores. + +[bools-as-ints]: https://docs.python.org/3/library/stdtypes.html#boolean-type-bool \ No newline at end of file diff --git a/exercises/practice/darts/.approaches/booleans-as-ints/snippet.txt b/exercises/practice/darts/.approaches/booleans-as-ints/snippet.txt new file mode 100644 index 00000000000..ec7dcfabbfc --- /dev/null +++ b/exercises/practice/darts/.approaches/booleans-as-ints/snippet.txt @@ -0,0 +1,3 @@ +def score(x_coord, y_coord): + radius = (x_coord**2 + y_coord**2) + return (radius<=1)*5 + (radius<=25)*4 +(radius<=100)*1 \ No newline at end of file diff --git a/exercises/practice/darts/.approaches/config.json b/exercises/practice/darts/.approaches/config.json new file mode 100644 index 00000000000..77f331bfce0 --- /dev/null +++ b/exercises/practice/darts/.approaches/config.json @@ -0,0 +1,50 @@ +{ + "introduction": { + "authors": ["bethanyg"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "7d78f598-8b4c-4f7f-89e1-e8644e934a4c", + "slug": "if-statements", + "title": "Use If Statements", + "blurb": "Use if-statements to check scoring boundaries for a dart throw.", + "authors": ["bethanyg"] + }, + { + "uuid": "f8f5533a-09d2-4b7b-9dec-90f268bfc03b", + "slug": "tuple-and-loop", + "title": "Use a Tuple & Loop through Scores", + "blurb": "Score the Dart throw by looping through a tuple of scores.", + "authors": ["bethanyg"] + }, + { + "uuid": "a324f99e-15bb-43e0-9181-c1652094bc4f", + "slug": "match-case", + "title": "Use Structural Pattern Matching ('Match-Case')", + "blurb": "Use a Match-Case (Structural Pattern Matching) to score the dart throw.)", + "authors": ["bethanyg"] + }, + { + "uuid": "966bd2dd-c4fd-430b-ad77-3a304dedd82e", + "slug": "dict-and-generator", + "title": "Use a Dictionary with a Generator Expression", + "blurb": "Use a generator expression looping over a scoring dictionary, getting the max score for the dart throw.", + "authors": ["bethanyg"] + }, + { + "uuid": "5b087f50-31c5-4b84-9116-baafd3a30ed6", + "slug": "booleans-as-ints", + "title": "Use Boolean Values as Integers", + "blurb": "Use True and False as integer values to calculate the score of the dart throw.", + "authors": ["bethanyg"] + }, + { + "uuid": "0b2dbcd3-f0ac-45f7-af75-3451751fd21f", + "slug": "dict-and-dict-get", + "title": "Use a Dictionary with dict.get", + "blurb": "Loop over a dictionary and retrieve score via dct.get.", + "authors": ["bethanyg"] + } + ] +} \ No newline at end of file diff --git a/exercises/practice/darts/.approaches/dict-and-dict-get/content.md b/exercises/practice/darts/.approaches/dict-and-dict-get/content.md new file mode 100644 index 00000000000..a3c5bc2ac58 --- /dev/null +++ b/exercises/practice/darts/.approaches/dict-and-dict-get/content.md @@ -0,0 +1,72 @@ +# Using a Dictionary and `dict.get()` + + +```python +def score(x_coord, y_coord): + point = (x_coord**2 + y_coord**2) + scores = { + point <= 100: 1, + point <= 25: 5, + point <= 1: 10 + } + + return scores.get(True, 0) +``` + +At first glance, this approach looks similar to the [Booleans as Integers][approach-boolean-values-as-integers] approach, due to the Boolean evaluation used in the dictionary keys. +However, this approach is **not** interpreting Booleans as integers and is instead exploiting three key properties of [dictionaries][dicts]: + + +1. [Keys must be hashable][hashable-keys] — in other words, keys have to be _unique_. +2. Insertion order is preserved (_as of `Python 3.7`_), and evaluation/iteration happens in insertion order. +3. Duplicate keys _overwrite_ existing keys. + If the first key is `True` and the third key is `True`, the _value_ from the third key will overwrite the value from the first key. + +Finally, the `return` line uses [`dict.get()`][dict-get] to `return` a default value of 0 when a throw is outside the existing circle radii. +To see this in action, you can view this code on [Python Tutor][dict-get-python-tutor]. + + +Because of the listed dictionary qualities, **_order matters_**. +This approach depends on the outermost scoring circle containing all smaller circles and that +checks proceed from largest --> smallest circle. +Iterating in the opposite direction will not resolve to the correct score. +The following code variations do not pass the exercise tests: + + +```python + +def score(x_coord, y_coord): + point = (x_coord**2 + y_coord**2) + scores = { + point <= 1: 10, + point <= 25: 5, + point <= 100: 1, + } + + return scores.get(True, 0) + + #OR# + +def score(x_coord, y_coord): + point = (x_coord**2 + y_coord**2) + scores = { + point <= 25: 5, + point <= 1: 10, + point <= 100: 1, + } + + return scores.get(True, 0) + +``` + +While this approach is a _very clever_ use of dictionary properties, it is likely to be very hard to reason about for those who are not deeply knowledgeable. +Even those experienced in Python might take longer than usual to figure out what is happening in the code. +Extensibility could also be error-prone due to needing a strict order for the `dict` keys. + +This approach offers no space or speed advantages over using `if-statements` or other strategies, so is not recommended for use beyond a learning context. + +[approach-boolean-values-as-integers]: https://exercism.org/tracks/python/exercises/darts/approaches/boolean-values-as-integers +[dicts]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict +[dict-get]: https://docs.python.org/3/library/stdtypes.html#dict.get +[dict-get-python-tutor]: https://pythontutor.com/render.html#code=def%20score%28x_coord,%20y_coord%29%3A%0A%20%20%20%20point%20%3D%20%28x_coord**2%20%2B%20y_coord**2%29%0A%20%20%20%20scores%20%3D%20%7B%0A%20%20%20%20%20%20%20%20point%20%3C%3D%20100%3A%201,%0A%20%20%20%20%20%20%20%20point%20%3C%3D%2025%3A%205,%0A%20%20%20%20%20%20%20%20point%20%3C%3D%201%3A%2010%0A%20%20%20%20%7D%0A%20%20%20%20%0A%20%20%20%20return%20scores.get%28True,%200%29%0A%20%20%20%20%0Aprint%28score%281,3%29%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false +[hashable-keys]: https://www.pythonmorsels.com/what-are-hashable-objects/#dictionary-keys-must-be-hashable diff --git a/exercises/practice/darts/.approaches/dict-and-dict-get/snippet.txt b/exercises/practice/darts/.approaches/dict-and-dict-get/snippet.txt new file mode 100644 index 00000000000..6d496f54d33 --- /dev/null +++ b/exercises/practice/darts/.approaches/dict-and-dict-get/snippet.txt @@ -0,0 +1,5 @@ +def score(x_coord, y_coord): + point = (x_coord**2 + y_coord**2) + scores = {point <= 100: 1, point <= 25: 5, point <= 1: 10} + + return scores.get(True, 0) \ No newline at end of file diff --git a/exercises/practice/darts/.approaches/dict-and-generator/content.md b/exercises/practice/darts/.approaches/dict-and-generator/content.md new file mode 100644 index 00000000000..30ffeac1eb0 --- /dev/null +++ b/exercises/practice/darts/.approaches/dict-and-generator/content.md @@ -0,0 +1,69 @@ +# Use a Dictionary and a Generator Expression + +```python +def score(x_coord, y_coord): + throw = x_coord**2 + y_coord**2 + rules = {1: 10, 25: 5, 100: 1, 200: 0} + + return max(point for distance, point in + rules.items() if throw <= distance) +``` + + +This approach is very similar to the [tuple and loop][approach-tuple-and-loop] approach, but iterates over [`dict.items()`][dict-items] and writes the `loop` as a [`generator-expression`][generator-expression] inside `max()`. +In cases where the scoring circles overlap, `max()` will return the maximum score available for the throw. +The generator expression inside `max()` is the equivalent of using a `for-loop` and a variable to determine the max score: + + +```python +def score(x_coord, y_coord): + throw = x_coord**2 + y_coord**2 + rules = {1: 10, 25: 5, 100: 1} + max_score = 0 + + for distance, point in rules.items(): + if throw <= distance and point > max_score: + max_score = point + return max_score +``` + + +A `list` or `tuple` can also be used in place of `max()`, but then requires an index to return the max score: + +```python +def score(x_coord, y_coord): + throw = x_coord**2 + y_coord**2 + rules = {1: 10, 25: 5, 100: 1, 200: 0} + + return [point for distance, point in + rules.items() if throw <= distance][0] #<-- have to specify index 0. + +#OR# + +def score(x_coord, y_coord): + throw = x_coord**2 + y_coord**2 + rules = {1: 10, 25: 5, 100: 1, 200: 0} + + return tuple(point for distance, point in + rules.items() if throw <= distance)[0] +``` + + +This solution can even be reduced to a "one-liner". +However, this is not performant, and is difficult to read: + +```python +def score(x_coord, y_coord): + return max(point for distance, point in + {1: 10, 25: 5, 100: 1, 200: 0}.items() if + (x_coord**2 + y_coord**2) <= distance) +``` + +While all of these variations do pass the tests, they suffer from even more over-engineering/performance caution than the earlier tuple and loop approach (_although for the data in this problem, the performance hit is slight_). +Additionally, the dictionary will take much more space in memory than using a `tuple` of tuples to hold scoring values. +In some circumstances, these variations might also be harder to reason about for those not familiar with `generator-expressions` or `list comprehensions`. + + +[approach-tuple-and-loop]: https://exercism.org/tracks/python/exercises/darts/approaches/tuple-and-loop +[dict-items]: https://docs.python.org/3/library/stdtypes.html#dict.items +[generator-expression]: https://dbader.org/blog/python-generator-expressions diff --git a/exercises/practice/darts/.approaches/dict-and-generator/snippet.txt b/exercises/practice/darts/.approaches/dict-and-generator/snippet.txt new file mode 100644 index 00000000000..f6649cf3a92 --- /dev/null +++ b/exercises/practice/darts/.approaches/dict-and-generator/snippet.txt @@ -0,0 +1,8 @@ +def score(x_coord, y_coord): + length = x_coord**2 + y_coord**2 + rules = {1.0: 10, 25.0: 5, 100.0: 1, 200: 0} + score = max(point for + distance, point in + rules.items() if length <= distance) + + return score \ No newline at end of file diff --git a/exercises/practice/darts/.approaches/if-statements/content.md b/exercises/practice/darts/.approaches/if-statements/content.md new file mode 100644 index 00000000000..40e2886ddb5 --- /dev/null +++ b/exercises/practice/darts/.approaches/if-statements/content.md @@ -0,0 +1,73 @@ +# Use `if-statements` + + +```python +import math + +# Checks scores from the center --> edge. +def score(x_coord, y_coord): + distance = math.sqrt(x_coord**2 + y_coord**2) + + if distance <= 1: return 10 + if distance <= 5: return 5 + if distance <= 10: return 1 + + return 0 +``` + +This approach uses [concept:python/conditionals]() to check the boundaries for each scoring ring, returning the corresponding score. +Calculating the euclidian distance is assigned to the variable "distance" to avoid having to re-calculate it for every if check. +Because the `if-statements` are simple and readable, they're written on one line to shorten the function body. +Zero is returned if no other check is true. + + +To avoid importing the `math` module (_for a very very slight speedup_), (x**2 +y**2) can be calculated instead, and the scoring rings can be adjusted to 1, 25, and 100: + + +```python +# Checks scores from the center --> edge. +def score(x_coord, y_coord): + distance = x_coord**2 + y_coord**2 + + if distance <= 1: return 10 + if distance <= 25: return 5 + if distance <= 100: return 1 + + return 0 +``` + + +# Variation 1: Check from Edge to Center Using Upper and Lower Bounds + + +```python +import math + +# Checks scores from the edge --> center +def score(x_coord, y_coord): + distance = math.sqrt(x_coord**2 + y_coord**2) + + if distance > 10: return 0 + if 5 < distance <= 10: return 1 + if 1 < distance <= 5: return 5 + + return 10 +``` + +This variant checks from the edge moving inward, checking both a lower and upper bound due to the overlapping scoring circles in this direction. + +Scores for any of these solutions can also be assigned to a variable to avoid multiple `returns`, but this isn't really necessary: + +```python +# Checks scores from the edge --> center +def score(x_coord, y_coord): + distance = x_coord**2 + y_coord**2 + points = 10 + + if distance > 100: points = 0 + if 25 < distance <= 100: points = 1 + if 1 < distance <= 25: points = 5 + + return points +``` + diff --git a/exercises/practice/darts/.approaches/if-statements/snippet.txt b/exercises/practice/darts/.approaches/if-statements/snippet.txt new file mode 100644 index 00000000000..18537416e2f --- /dev/null +++ b/exercises/practice/darts/.approaches/if-statements/snippet.txt @@ -0,0 +1,8 @@ +import math + +def score(x_coord, y_coord): + distance = math.sqrt(x_coord**2 + y_coord**2) + if distance <= 1: return 10 + if distance <= 5: return 5 + if distance <= 10: return 1 + return 0 \ No newline at end of file diff --git a/exercises/practice/darts/.approaches/introduction.md b/exercises/practice/darts/.approaches/introduction.md new file mode 100644 index 00000000000..588c266184b --- /dev/null +++ b/exercises/practice/darts/.approaches/introduction.md @@ -0,0 +1,146 @@ +# Introduction + + +There are multiple Pythonic ways to solve the Darts exercise. +Among them are: + +- Using `if-statements` +- Using a `tuple` (or `list` or `dict`) and a `for-loop` +- Using a `dict` (or `tuple` or `list`) and a `generator-expression` +- Using `boolean` values as `ints` +- Using a `dict` and `dict.get()` +- Using `match/case` (_Python 3.10+ only_) + +
+ +## General guidance + +The goal of the Darts exercise is to score a single throw in a Darts game. +The scoring areas are _concentric circles_, so boundary values need to be checked in order to properly score a throw. +The key is to determine how far from the center the dart lands (_by calculating sqrt(x**2 + y**2), or a variation_) and then determine what scoring ring it falls into. + + +**_Order matters_** - each bigger target circle contains all the smaller circles, so the most straightforward solution is to check the smallest circle first. +Otherwise, you must box your scoring by checking both a _lower bound_ and an _upper bound_. + + +Darts that fall on a _boundary_ are scored based on the area below the line (_closer to center_), so checking `<=` or `>=` is advised. + + +## Approach: Using `if` statements + + +```python +import math + +# Checks scores from the center --> edge. +def score(x_coord, y_coord): + distance = math.sqrt(x_coord**2 + y_coord**2) + + if distance <= 1: return 10 + if distance <= 5: return 5 + if distance <= 10: return 1 + + return 0 +``` + + +This approach uses [concept:python/conditionals]() to check the boundaries for each scoring ring, returning the corresponding score. +For more details, see the [if statements][approach-if-statements] approach. + + +## Approach: Using a `tuple` and a `loop` + +```python +def score(x_coord, y_coord): + throw = x_coord**2 + y_coord**2 + rules = (1, 10), (25, 5), (100, 1), (200, 0) + + for distance, points in rules: + if throw <= distance: + return points +``` + + +This approach uses a loop to iterate through the _rules_ `tuple`, unpacking each `distance` and corresponding`score`. +For more details, see the [tuple and loop][approach-tuple-and-loop] approach. + + +## Approach: Using a `dict` with a `generator-expression` + +```python +def score(x_coord, y_coord): + throw = x_coord**2 + y_coord**2 + rules = {1: 10, 25: 5, 100: 1, 200: 0} + + return max(point for distance, point in + rules.items() if throw <= distance) +``` + +This approach is very similar to the [tuple and loop][approach-tuple-and-loop] approach, but iterates over [`dict.items()`][dict-items]. +For more information, see the [dict with generator-expression][approach-dict-with-generator-expression] approach. + + +## Approach: Using Boolean Values as Integers + +```python +def score(x_coord, y_coord): + radius = (x_coord**2 + y_coord**2) + return (radius<=1)*5 + (radius<=25)*4 +(radius<=100)*1 +``` + + +This approach exploits the fact that Boolean values are an integer subtype in Python. +For more information, see the [boolean values as integers][approach-boolean-values-as-integers] approach. + + +## Approach: Using a `Dictionary` and `dict.get()` + +```python +def score(x_coord, y_coord): + point = (x_coord**2 + y_coord**2) + scores = { + point <= 100: 1, + point <= 25: 5, + point <= 1: 10 + } + + return scores.get(True, 0) +``` + +This approach uses a dictionary to hold the distance --> scoring mappings and `dict.get()` to retrieve the correct points value. +For more details, read the [`Dictionary and dict.get()`][approach-dict-and-dict-get] approach. + + +## Approach: Using `match/case` (structural pattern matching) + +```python +from math import hypot, ceil + + +def score(x, y): + match ceil(hypot(x, y)): + case 0 | 1: return 10 + case 2 | 3 | 4 | 5: return 5 + case 6 | 7 | 8 | 9 | 10: return 1 + case _: return 0 +``` + + +This approach uses `Python 3.10`'s structural pattern matching with `return` values on the same line as `case`. +A fallthrough case (`_`) is used if the dart throw is outside the outer circle of the target (_greater than 10_). +For more details, see the [structural pattern matching][approach-struct-pattern-matching] approach. + + +## Which approach to use? + +Many of these approaches are a matter of personal preference - there are not significant memory or performance differences. +Although a strong argument could be made for simplicity and clarity — many listed solutions (_while interesting_) are harder to reason about or are over-engineered for the current scope of the exercise. + +[approach-boolean-values-as-integers]: https://exercism.org/tracks/python/exercises/darts/approaches/boolean-values-as-integers +[approach-dict-and-dict-get]: https://exercism.org/tracks/python/exercises/darts/approaches/dict-and-dict-get +[approach-dict-with-generator-expression]: https://exercism.org/tracks/python/exercises/darts/approaches/dict-with-gnerator-expresson +[approach-if-statements ]: https://exercism.org/tracks/python/exercises/darts/approaches/if-statements +[approach-struct-pattern-matching]: https://exercism.org/tracks/python/exercises/darts/approaches/struct-pattern-matching +[approach-tuple-and-loop]: https://exercism.org/tracks/python/exercises/darts/approaches/tuple-and-loop +[dict-items]: https://docs.python.org/3/library/stdtypes.html#dict.items diff --git a/exercises/practice/darts/.approaches/match-case/content.md b/exercises/practice/darts/.approaches/match-case/content.md new file mode 100644 index 00000000000..04430a5dc52 --- /dev/null +++ b/exercises/practice/darts/.approaches/match-case/content.md @@ -0,0 +1,85 @@ +# Use `match/case` (Structural Pattern Matching) + + +```python +from math import hypot, ceil + + +def score(x, y): + throw = ceil(hypot(x, y)) + + match throw: + case 0 | 1: return 10 + case 2 | 3 | 4 | 5: return 5 + case 6 | 7 | 8 | 9 | 10: return 1 + case _: return 0 + +#OR# + +def score(x, y): + match ceil(hypot(x, y)): + case 0 | 1: return 10 + case 2 | 3 | 4 | 5: return 5 + case 6 | 7 | 8 | 9 | 10: return 1 + case _: return 0 +``` + +This approach uses `Python 3.10`'s [`structural pattern matching`][structural-pattern-matching] with `return` values on the same line as `case`. +Because the match is numeric, each case explicitly lists allowed values using the `|` (OR) operator. +A fallthrough case (`_`) is used if the dart throw is greater than 10 (_the outer circle radius of the target_). +This is equivalent to using `if-statements` to check throw values although some might argue it is clearer to read. +An `if-statement` equivalent would be: + +```python +from math import hypot, ceil + + +def score(x, y): + throw = ceil(hypot(x, y)) + + if throw in (0, 1): return 10 + if throw in (2, 3, 4, 5): return 5 + if throw in (6, 7, 8, 9, 10): return 1 + + return 0 +``` + +One can also use `<`, `>`, or `<=` and `>=` in structural pattern matching, although the syntax becomes almost identical to using them with `if-statements`, but more verbose: + + +```python +from math import hypot, ceil + + +def score(x, y): + throw = ceil(hypot(x, y)) + + match throw: + case throw if throw <= 1: return 10 + case throw if throw <= 5: return 5 + case throw if throw <= 10: return 1 + case _: return 0 +``` + + +Finally, one can use an [assignment expression][assignment-expression] or [walrus operator][walrus] to calculate the throw value rather than calculating and assigning a variable on a separate line. +This isn't necessary (_the first variations shows this clearly_) and might be harder to reason about/understand for some programmers: + + +```python +from math import hypot, ceil + +def score(x, y): + match throw := ceil(hypot(x, y)): + case throw if throw <= 1: return 10 + case throw if throw <=5: return 5 + case throw if throw <=10: return 1 + case _: return 0 +``` + +Using structural pattern matching for this exercise doesn't offer any clear performance advantages over the `if-statement`, but might be "cleaner", more "organized looking", or easier for others to scan/read. + + +[assignment-expression]: https://docs.python.org/3/reference/expressions.html#grammar-token-python-grammar-assignment_expression +[structural-pattern-matching]: https://peps.python.org/pep-0636/ +[walrus]: https://peps.python.org/pep-0572/ diff --git a/exercises/practice/darts/.approaches/match-case/snippet.txt b/exercises/practice/darts/.approaches/match-case/snippet.txt new file mode 100644 index 00000000000..e66b5382b21 --- /dev/null +++ b/exercises/practice/darts/.approaches/match-case/snippet.txt @@ -0,0 +1,8 @@ +from math import hypot, ceil + +def score(x, y): + match ceil(hypot(x, y)): + case 0 | 1: return 10 + case 2 | 3 | 4 | 5: return 5 + case 6 | 7 | 8 | 9 | 10: return 1 + case _: return 0 \ No newline at end of file diff --git a/exercises/practice/darts/.approaches/tuple-and-loop/content.md b/exercises/practice/darts/.approaches/tuple-and-loop/content.md new file mode 100644 index 00000000000..042b9e88ae9 --- /dev/null +++ b/exercises/practice/darts/.approaches/tuple-and-loop/content.md @@ -0,0 +1,55 @@ +# Use a tuple with a loop + +```python +def score(x_coord, y_coord): + throw = x_coord**2 + y_coord**2 + rules = (1, 10), (25, 5), (100, 1), (200, 0) + + for distance, points in rules: + if throw <= distance: + return points +``` + +This approach uses a loop to iterate through the _rules_ `tuple`, unpacking each (`distance`, `points`) pair (_For a little more on unpacking, see [Tuple Unpacking Improves Python Code Readability][tuple-unpacking]_). +If the calculated distance of the throw is less than or equal to a given distance, the score for that region is returned. +A `list` of `lists`, a `list` of `tuples`, or a dictionary could be used here to the same effect: + +```python +def score(x_coord, y_coord): + throw = x_coord**2 + y_coord**2 + rules = [[1, 10], [25, 5], [100, 1]] + + for distance, points in rules: + if throw <= distance: + return points + + return 0 + +#OR# + +def score(x_coord, y_coord): + throw = x_coord**2 + y_coord**2 + rules = [(1, 10), (25, 5), (100, 1), (200, 0)] + + for distance, points in rules: + if throw <= distance: + return points + +#OR# + +def score(x_coord, y_coord): + throw = x_coord**2 + y_coord**2 + rules = {1: 10, 25: 5, 100: 1} + + for distance, points in rules.items(): + if throw <= distance: + return points + + return 0 +``` + +This approach would work nicely in a scenario where you expect to be adding more scoring "rings", since it is cleaner to edit the data structure than to add additional `if-statements` as you would have to in the [`if-statement` approach][approach-if-statements ]. +For the three rings as defined by the current exercise, it is a bit over-engineered to use a data structure + `loop`, and results in a slight (_**very** slight_) slowdown over using `if-statements`. + +[tuple-unpacking]: https://treyhunner.com/2018/03/tuple-unpacking-improves-python-code-readability/#Unpacking_in_a_for_loop +[approach-if-statements ]: https://exercism.org/tracks/python/exercises/darts/approaches/if-statements diff --git a/exercises/practice/darts/.approaches/tuple-and-loop/snippet.txt b/exercises/practice/darts/.approaches/tuple-and-loop/snippet.txt new file mode 100644 index 00000000000..ad505005263 --- /dev/null +++ b/exercises/practice/darts/.approaches/tuple-and-loop/snippet.txt @@ -0,0 +1,7 @@ +def score(x_coord, y_coord): + distance = x_coord**2 + y_coord**2 + rules = (1.0, 10), (25.0, 5), (100.0, 1), (200.0, 0) + + for distance, point in rules: + if length <= distance: + return point \ No newline at end of file From f214698e53d8d30f49eeb3c958781741ae7873ea Mon Sep 17 00:00:00 2001 From: Charles Ross Date: Sat, 30 Nov 2024 16:33:20 -0800 Subject: [PATCH 202/277] Remove extraneous periods (#3833) --- exercises/practice/simple-linked-list/.docs/hints.md | 2 +- .../practice/simple-linked-list/.docs/instructions.append.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/simple-linked-list/.docs/hints.md b/exercises/practice/simple-linked-list/.docs/hints.md index da373540ad0..c017108a610 100644 --- a/exercises/practice/simple-linked-list/.docs/hints.md +++ b/exercises/practice/simple-linked-list/.docs/hints.md @@ -13,7 +13,7 @@ In order for _custom objects_ to support `len()`, the special method [`__len__`][__len__] needs to be defined. - Iteration in Python is supported for most sequence, container, or collection type objects. In order for a _custom_ object to support looping or re-ordering, the special method `__iter__` needs to be defined. -[Implementing an iterator for a class.][implementing iterators] can help show you how. +[Implementing an iterator for a class][implementing iterators] can help show you how. [Baeldung: The Stack Data Structure]: https://www.baeldung.com/cs/stack-data-structure [Koder Dojo Coding an ADS Stack in Python]: https://www.koderdojo.com/blog/coding-a-stack-abstract-data-structure-using-linked-list-in-python diff --git a/exercises/practice/simple-linked-list/.docs/instructions.append.md b/exercises/practice/simple-linked-list/.docs/instructions.append.md index 888e675a841..7f848fbaab1 100644 --- a/exercises/practice/simple-linked-list/.docs/instructions.append.md +++ b/exercises/practice/simple-linked-list/.docs/instructions.append.md @@ -46,7 +46,7 @@ For details on implementing special or "dunder" methods in Python, see [Python D ## Building an Iterator To support looping through or reversing your `LinkedList`, you will need to implement the `__iter__` special method. -See [implementing an iterator for a class.][custom iterators] for implementation details. +See [implementing an iterator for a class][custom iterators] for implementation details.
@@ -56,7 +56,7 @@ Sometimes it is necessary to both [customize][customize errors] and [`raise`][ra When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. -Custom exceptions can be created through new exception classes (see [`classes`][classes tutorial] for more detail.) that are typically subclasses of [`Exception`][exception base class]. +Custom exceptions can be created through new exception classes (see [`classes`][classes tutorial] for more detail) that are typically subclasses of [`Exception`][exception base class]. For situations where you know the error source will be a derivative of a certain exception _type_, you can choose to inherit from one of the [`built in error types`][built-in errors] under the _Exception_ class. When raising the error, you should still include a meaningful message. From 26635914c1f754dbb38bbb89f511536bda7a2701 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sun, 1 Dec 2024 22:30:59 -0800 Subject: [PATCH 203/277] [Simple Linked List]: Updated Stub File (#3834) * Reworked stub to clean up mutable default argument and missing special method. * Added bethanyg as contributor. --- .../practice/simple-linked-list/.meta/config.json | 1 + .../simple-linked-list/simple_linked_list.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/exercises/practice/simple-linked-list/.meta/config.json b/exercises/practice/simple-linked-list/.meta/config.json index 2fc136a325f..2134b492371 100644 --- a/exercises/practice/simple-linked-list/.meta/config.json +++ b/exercises/practice/simple-linked-list/.meta/config.json @@ -3,6 +3,7 @@ "cmccandless" ], "contributors": [ + "Bethanyg", "Dog", "N-Parsons", "rootulp", diff --git a/exercises/practice/simple-linked-list/simple_linked_list.py b/exercises/practice/simple-linked-list/simple_linked_list.py index cbf120e2fcb..dfb9e6c9798 100644 --- a/exercises/practice/simple-linked-list/simple_linked_list.py +++ b/exercises/practice/simple-linked-list/simple_linked_list.py @@ -1,3 +1,7 @@ +class EmptyListException(Exception): + pass + + class Node: def __init__(self, value): pass @@ -10,7 +14,10 @@ def next(self): class LinkedList: - def __init__(self, values=[]): + def __init__(self, values=None): + pass + + def __iter__(self): pass def __len__(self): @@ -27,7 +34,3 @@ def pop(self): def reversed(self): pass - - -class EmptyListException(Exception): - pass From 7bec634f5c51cce82d233ad88f7ae81a3e98242a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Mon, 2 Dec 2024 12:45:49 -0800 Subject: [PATCH 204/277] Revert "Testing fix for no-important-files workflow. (#3824)" (#3835) This reverts commit 8dde6fd43646a1e071aca2d5a1a324b503dc47a6. --- .github/workflows/no-important-files-changed.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml index cfcd6db4d44..812e9129668 100644 --- a/.github/workflows/no-important-files-changed.yml +++ b/.github/workflows/no-important-files-changed.yml @@ -17,7 +17,7 @@ permissions: jobs: check: - uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@fix-no-important-files + uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main with: repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} ref: ${{ github.head_ref }} From 07bf542d0ff9aa4ae5954688679014e9ccb75310 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 9 Jan 2025 21:30:04 -0800 Subject: [PATCH 205/277] Updated test cases for pig-latin and regenerated test file. (#3839) --- exercises/practice/pig-latin/.meta/tests.toml | 60 +++++++++++-------- .../practice/pig-latin/pig_latin_test.py | 5 +- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/exercises/practice/pig-latin/.meta/tests.toml b/exercises/practice/pig-latin/.meta/tests.toml index 49ce6e110e8..d524305b45c 100644 --- a/exercises/practice/pig-latin/.meta/tests.toml +++ b/exercises/practice/pig-latin/.meta/tests.toml @@ -1,69 +1,79 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [11567f84-e8c6-4918-aedb-435f0b73db57] -description = "word beginning with a" +description = "ay is added to words that start with vowels -> word beginning with a" [f623f581-bc59-4f45-9032-90c3ca9d2d90] -description = "word beginning with e" +description = "ay is added to words that start with vowels -> word beginning with e" [7dcb08b3-23a6-4e8a-b9aa-d4e859450d58] -description = "word beginning with i" +description = "ay is added to words that start with vowels -> word beginning with i" [0e5c3bff-266d-41c8-909f-364e4d16e09c] -description = "word beginning with o" +description = "ay is added to words that start with vowels -> word beginning with o" [614ba363-ca3c-4e96-ab09-c7320799723c] -description = "word beginning with u" +description = "ay is added to words that start with vowels -> word beginning with u" [bf2538c6-69eb-4fa7-a494-5a3fec911326] -description = "word beginning with a vowel and followed by a qu" +description = "ay is added to words that start with vowels -> word beginning with a vowel and followed by a qu" [e5be8a01-2d8a-45eb-abb4-3fcc9582a303] -description = "word beginning with p" +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with p" [d36d1e13-a7ed-464d-a282-8820cb2261ce] -description = "word beginning with k" +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with k" [d838b56f-0a89-4c90-b326-f16ff4e1dddc] -description = "word beginning with x" +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with x" [bce94a7a-a94e-4e2b-80f4-b2bb02e40f71] -description = "word beginning with q without a following u" +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with q without a following u" + +[e59dbbe8-ccee-4619-a8e9-ce017489bfc0] +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with consonant and vowel containing qu" [c01e049a-e3e2-451c-bf8e-e2abb7e438b8] -description = "word beginning with ch" +description = "some letter clusters are treated like a single consonant -> word beginning with ch" [9ba1669e-c43f-4b93-837a-cfc731fd1425] -description = "word beginning with qu" +description = "some letter clusters are treated like a single consonant -> word beginning with qu" [92e82277-d5e4-43d7-8dd3-3a3b316c41f7] -description = "word beginning with qu and a preceding consonant" +description = "some letter clusters are treated like a single consonant -> word beginning with qu and a preceding consonant" [79ae4248-3499-4d5b-af46-5cb05fa073ac] -description = "word beginning with th" +description = "some letter clusters are treated like a single consonant -> word beginning with th" [e0b3ae65-f508-4de3-8999-19c2f8e243e1] -description = "word beginning with thr" +description = "some letter clusters are treated like a single consonant -> word beginning with thr" [20bc19f9-5a35-4341-9d69-1627d6ee6b43] -description = "word beginning with sch" +description = "some letter clusters are treated like a single consonant -> word beginning with sch" [54b796cb-613d-4509-8c82-8fbf8fc0af9e] -description = "word beginning with yt" +description = "some letter clusters are treated like a single vowel -> word beginning with yt" [8c37c5e1-872e-4630-ba6e-d20a959b67f6] -description = "word beginning with xr" +description = "some letter clusters are treated like a single vowel -> word beginning with xr" [a4a36d33-96f3-422c-a233-d4021460ff00] -description = "y is treated like a consonant at the beginning of a word" +description = "position of y in a word determines if it is a consonant or a vowel -> y is treated like a consonant at the beginning of a word" [adc90017-1a12-4100-b595-e346105042c7] -description = "y is treated like a vowel at the end of a consonant cluster" +description = "position of y in a word determines if it is a consonant or a vowel -> y is treated like a vowel at the end of a consonant cluster" [29b4ca3d-efe5-4a95-9a54-8467f2e5e59a] -description = "y as second letter in two letter word" +description = "position of y in a word determines if it is a consonant or a vowel -> y as second letter in two letter word" [44616581-5ce3-4a81-82d0-40c7ab13d2cf] -description = "a whole phrase" +description = "phrases are translated -> a whole phrase" diff --git a/exercises/practice/pig-latin/pig_latin_test.py b/exercises/practice/pig-latin/pig_latin_test.py index e5a441eb6b9..1217d6883f9 100644 --- a/exercises/practice/pig-latin/pig_latin_test.py +++ b/exercises/practice/pig-latin/pig_latin_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/pig-latin/canonical-data.json -# File last updated on 2023-07-19 +# File last updated on 2025-01-10 import unittest @@ -40,6 +40,9 @@ def test_word_beginning_with_x(self): def test_word_beginning_with_q_without_a_following_u(self): self.assertEqual(translate("qat"), "atqay") + def test_word_beginning_with_consonant_and_vowel_containing_qu(self): + self.assertEqual(translate("liquid"), "iquidlay") + def test_word_beginning_with_ch(self): self.assertEqual(translate("chair"), "airchay") From f94d0fd31e2c3379b6fef840d104ff9d31002a91 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 9 Jan 2025 21:40:56 -0800 Subject: [PATCH 206/277] Updated bob tests.toml and regenerated test file. (#3840) --- exercises/practice/bob/.meta/tests.toml | 18 +++++++++++++++--- exercises/practice/bob/bob_test.py | 13 ++++++------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/exercises/practice/bob/.meta/tests.toml b/exercises/practice/bob/.meta/tests.toml index 6304855792d..5299e2895fc 100644 --- a/exercises/practice/bob/.meta/tests.toml +++ b/exercises/practice/bob/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [e162fead-606f-437a-a166-d051915cea8e] description = "stating something" @@ -64,6 +71,7 @@ description = "alternate silence" [66953780-165b-4e7e-8ce3-4bcb80b6385a] description = "multiple line question" +include = false [5371ef75-d9ea-4103-bcfa-2da973ddec1b] description = "starting with whitespace" @@ -76,3 +84,7 @@ description = "other whitespace" [12983553-8601-46a8-92fa-fcaa3bc4a2a0] description = "non-question ending with whitespace" + +[2c7278ac-f955-4eb4-bf8f-e33eb4116a15] +description = "multiple line question" +reimplements = "66953780-165b-4e7e-8ce3-4bcb80b6385a" diff --git a/exercises/practice/bob/bob_test.py b/exercises/practice/bob/bob_test.py index faba5f9612e..755d5c935e4 100644 --- a/exercises/practice/bob/bob_test.py +++ b/exercises/practice/bob/bob_test.py @@ -1,6 +1,6 @@ # These tests are auto-generated with test data from: # https://github.com/exercism/problem-specifications/tree/main/exercises/bob/canonical-data.json -# File last updated on 2023-07-20 +# File last updated on 2025-01-10 import unittest @@ -79,12 +79,6 @@ def test_prolonged_silence(self): def test_alternate_silence(self): self.assertEqual(response("\t\t\t\t\t\t\t\t\t\t"), "Fine. Be that way!") - def test_multiple_line_question(self): - self.assertEqual( - response("\nDoes this cryogenic chamber make me look fat?\nNo."), - "Whatever.", - ) - def test_starting_with_whitespace(self): self.assertEqual(response(" hmmmmmmm..."), "Whatever.") @@ -100,3 +94,8 @@ def test_non_question_ending_with_whitespace(self): self.assertEqual( response("This is a statement ending with whitespace "), "Whatever." ) + + def test_multiple_line_question(self): + self.assertEqual( + response("\nDoes this cryogenic chamber make\n me look fat?"), "Sure." + ) From 8ddd19549b09c13ff21e0da01c5f4e6ede8985ce Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 9 Jan 2025 21:41:46 -0800 Subject: [PATCH 207/277] Synced metadata for practice exercises. (#3841) --- exercises/practice/atbash-cipher/.meta/config.json | 2 +- exercises/practice/collatz-conjecture/.meta/config.json | 4 ++-- exercises/practice/pythagorean-triplet/.meta/config.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exercises/practice/atbash-cipher/.meta/config.json b/exercises/practice/atbash-cipher/.meta/config.json index 9f678c6f203..5df506281a0 100644 --- a/exercises/practice/atbash-cipher/.meta/config.json +++ b/exercises/practice/atbash-cipher/.meta/config.json @@ -27,7 +27,7 @@ ".meta/example.py" ] }, - "blurb": "Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East.", + "blurb": "Create an implementation of the Atbash cipher, an ancient encryption system created in the Middle East.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Atbash" } diff --git a/exercises/practice/collatz-conjecture/.meta/config.json b/exercises/practice/collatz-conjecture/.meta/config.json index e5eda73e1df..cfed91f3bdf 100644 --- a/exercises/practice/collatz-conjecture/.meta/config.json +++ b/exercises/practice/collatz-conjecture/.meta/config.json @@ -26,6 +26,6 @@ ] }, "blurb": "Calculate the number of steps to reach 1 using the Collatz conjecture.", - "source": "An unsolved problem in mathematics named after mathematician Lothar Collatz", - "source_url": "https://en.wikipedia.org/wiki/3x_%2B_1_problem" + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Collatz_conjecture" } diff --git a/exercises/practice/pythagorean-triplet/.meta/config.json b/exercises/practice/pythagorean-triplet/.meta/config.json index a9bed96083f..040a45106fd 100644 --- a/exercises/practice/pythagorean-triplet/.meta/config.json +++ b/exercises/practice/pythagorean-triplet/.meta/config.json @@ -31,7 +31,7 @@ ".meta/example.py" ] }, - "blurb": "There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the triplet.", - "source": "Problem 9 at Project Euler", + "blurb": "Given an integer N, find all Pythagorean triplets for which a + b + c = N.", + "source": "A variation of Problem 9 from Project Euler", "source_url": "https://projecteuler.net/problem=9" } From c8b3a3958f12bda1158997763f9f63f6c8f81ed2 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 9 Jan 2025 21:42:10 -0800 Subject: [PATCH 208/277] Synced practice exercise intros and instructions to problem-specs. (#3842) --- .../affine-cipher/.docs/instructions.md | 2 +- .../atbash-cipher/.docs/instructions.md | 2 +- .../practice/change/.docs/instructions.md | 14 +-- .../practice/change/.docs/introduction.md | 26 +++++ .../collatz-conjecture/.docs/instructions.md | 28 +---- .../collatz-conjecture/.docs/introduction.md | 28 +++++ .../complex-numbers/.docs/instructions.md | 107 +++++++++++++++--- .../practice/dominoes/.docs/instructions.md | 4 +- .../practice/dominoes/.docs/introduction.md | 13 +++ .../eliuds-eggs/.docs/introduction.md | 48 +++++--- .../grade-school/.docs/instructions.md | 20 ++-- .../practice/hamming/.docs/instructions.md | 11 -- .../practice/hamming/.docs/introduction.md | 12 ++ .../practice/knapsack/.docs/instructions.md | 8 +- .../practice/knapsack/.docs/introduction.md | 12 +- exercises/practice/luhn/.docs/instructions.md | 8 +- exercises/practice/luhn/.docs/introduction.md | 11 ++ .../pascals-triangle/.docs/introduction.md | 2 +- .../phone-number/.docs/introduction.md | 12 ++ .../protein-translation/.docs/instructions.md | 8 +- .../pythagorean-triplet/.docs/instructions.md | 2 +- .../pythagorean-triplet/.docs/introduction.md | 19 ++++ .../square-root/.docs/instructions.md | 17 ++- .../square-root/.docs/introduction.md | 10 ++ 24 files changed, 304 insertions(+), 120 deletions(-) create mode 100644 exercises/practice/change/.docs/introduction.md create mode 100644 exercises/practice/collatz-conjecture/.docs/introduction.md create mode 100644 exercises/practice/dominoes/.docs/introduction.md create mode 100644 exercises/practice/hamming/.docs/introduction.md create mode 100644 exercises/practice/luhn/.docs/introduction.md create mode 100644 exercises/practice/phone-number/.docs/introduction.md create mode 100644 exercises/practice/pythagorean-triplet/.docs/introduction.md create mode 100644 exercises/practice/square-root/.docs/introduction.md diff --git a/exercises/practice/affine-cipher/.docs/instructions.md b/exercises/practice/affine-cipher/.docs/instructions.md index 4eff918de78..f6329db9369 100644 --- a/exercises/practice/affine-cipher/.docs/instructions.md +++ b/exercises/practice/affine-cipher/.docs/instructions.md @@ -4,7 +4,7 @@ Create an implementation of the affine cipher, an ancient encryption system crea The affine cipher is a type of monoalphabetic substitution cipher. Each character is mapped to its numeric equivalent, encrypted with a mathematical function and then converted to the letter relating to its new numeric value. -Although all monoalphabetic ciphers are weak, the affine cipher is much stronger than the atbash cipher, because it has many more keys. +Although all monoalphabetic ciphers are weak, the affine cipher is much stronger than the Atbash cipher, because it has many more keys. [//]: # " monoalphabetic as spelled by Merriam-Webster, compare to polyalphabetic " diff --git a/exercises/practice/atbash-cipher/.docs/instructions.md b/exercises/practice/atbash-cipher/.docs/instructions.md index 21ca2ce0aa8..1e7627b1e59 100644 --- a/exercises/practice/atbash-cipher/.docs/instructions.md +++ b/exercises/practice/atbash-cipher/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East. +Create an implementation of the Atbash cipher, an ancient encryption system created in the Middle East. The Atbash cipher is a simple substitution cipher that relies on transposing all the letters in the alphabet such that the resulting alphabet is backwards. The first letter is replaced with the last letter, the second with the second-last, and so on. diff --git a/exercises/practice/change/.docs/instructions.md b/exercises/practice/change/.docs/instructions.md index 30fa567750e..5887f4cb693 100644 --- a/exercises/practice/change/.docs/instructions.md +++ b/exercises/practice/change/.docs/instructions.md @@ -1,14 +1,8 @@ # Instructions -Correctly determine the fewest number of coins to be given to a customer such that the sum of the coins' value would equal the correct amount of change. +Determine the fewest number of coins to give a customer so that the sum of their values equals the correct amount of change. -## For example +## Examples -- An input of 15 with [1, 5, 10, 25, 100] should return one nickel (5) and one dime (10) or [5, 10] -- An input of 40 with [1, 5, 10, 25, 100] should return one nickel (5) and one dime (10) and one quarter (25) or [5, 10, 25] - -## Edge cases - -- Does your algorithm work for any given set of coins? -- Can you ask for negative change? -- Can you ask for a change value smaller than the smallest coin value? +- An amount of 15 with available coin values [1, 5, 10, 25, 100] should return one coin of value 5 and one coin of value 10, or [5, 10]. +- An amount of 40 with available coin values [1, 5, 10, 25, 100] should return one coin of value 5, one coin of value 10, and one coin of value 25, or [5, 10, 25]. diff --git a/exercises/practice/change/.docs/introduction.md b/exercises/practice/change/.docs/introduction.md new file mode 100644 index 00000000000..b4f8308a1b1 --- /dev/null +++ b/exercises/practice/change/.docs/introduction.md @@ -0,0 +1,26 @@ +# Introduction + +In the mystical village of Coinholt, you stand behind the counter of your bakery, arranging a fresh batch of pastries. +The door creaks open, and in walks Denara, a skilled merchant with a keen eye for quality goods. +After a quick meal, she slides a shimmering coin across the counter, representing a value of 100 units. + +You smile, taking the coin, and glance at the total cost of the meal: 88 units. +That means you need to return 12 units in change. + +Denara holds out her hand expectantly. +"Just give me the fewest coins," she says with a smile. +"My pouch is already full, and I don't want to risk losing them on the road." + +You know you have a few options. +"We have Lumis (worth 10 units), Viras (worth 5 units), and Zenth (worth 2 units) available for change." + +You quickly calculate the possibilities in your head: + +- one Lumis (1 × 10 units) + one Zenth (1 × 2 units) = 2 coins total +- two Viras (2 × 5 units) + one Zenth (1 × 2 units) = 3 coins total +- six Zenth (6 × 2 units) = 6 coins total + +"The best choice is two coins: one Lumis and one Zenth," you say, handing her the change. + +Denara smiles, clearly impressed. +"As always, you've got it right." diff --git a/exercises/practice/collatz-conjecture/.docs/instructions.md b/exercises/practice/collatz-conjecture/.docs/instructions.md index ba060483e4d..af332a810f0 100644 --- a/exercises/practice/collatz-conjecture/.docs/instructions.md +++ b/exercises/practice/collatz-conjecture/.docs/instructions.md @@ -1,29 +1,3 @@ # Instructions -The Collatz Conjecture or 3x+1 problem can be summarized as follows: - -Take any positive integer n. -If n is even, divide n by 2 to get n / 2. -If n is odd, multiply n by 3 and add 1 to get 3n + 1. -Repeat the process indefinitely. -The conjecture states that no matter which number you start with, you will always reach 1 eventually. - -Given a number n, return the number of steps required to reach 1. - -## Examples - -Starting with n = 12, the steps would be as follows: - -0. 12 -1. 6 -2. 3 -3. 10 -4. 5 -5. 16 -6. 8 -7. 4 -8. 2 -9. 1 - -Resulting in 9 steps. -So for input n = 12, the return value would be 9. +Given a positive integer, return the number of steps it takes to reach 1 according to the rules of the Collatz Conjecture. diff --git a/exercises/practice/collatz-conjecture/.docs/introduction.md b/exercises/practice/collatz-conjecture/.docs/introduction.md new file mode 100644 index 00000000000..c35bdeb67dc --- /dev/null +++ b/exercises/practice/collatz-conjecture/.docs/introduction.md @@ -0,0 +1,28 @@ +# Introduction + +One evening, you stumbled upon an old notebook filled with cryptic scribbles, as though someone had been obsessively chasing an idea. +On one page, a single question stood out: **Can every number find its way to 1?** +It was tied to something called the **Collatz Conjecture**, a puzzle that has baffled thinkers for decades. + +The rules were deceptively simple. +Pick any positive integer. + +- If it's even, divide it by 2. +- If it's odd, multiply it by 3 and add 1. + +Then, repeat these steps with the result, continuing indefinitely. + +Curious, you picked number 12 to test and began the journey: + +12 ➜ 6 ➜ 3 ➜ 10 ➜ 5 ➜ 16 ➜ 8 ➜ 4 ➜ 2 ➜ 1 + +Counting from the second number (6), it took 9 steps to reach 1, and each time the rules repeated, the number kept changing. +At first, the sequence seemed unpredictable — jumping up, down, and all over. +Yet, the conjecture claims that no matter the starting number, we'll always end at 1. + +It was fascinating, but also puzzling. +Why does this always seem to work? +Could there be a number where the process breaks down, looping forever or escaping into infinity? +The notebook suggested solving this could reveal something profound — and with it, fame, [fortune][collatz-prize], and a place in history awaits whoever could unlock its secrets. + +[collatz-prize]: https://mathprize.net/posts/collatz-conjecture/ diff --git a/exercises/practice/complex-numbers/.docs/instructions.md b/exercises/practice/complex-numbers/.docs/instructions.md index 50b19aedff6..2b8a7a49d82 100644 --- a/exercises/practice/complex-numbers/.docs/instructions.md +++ b/exercises/practice/complex-numbers/.docs/instructions.md @@ -1,29 +1,100 @@ # Instructions -A complex number is a number in the form `a + b * i` where `a` and `b` are real and `i` satisfies `i^2 = -1`. +A **complex number** is expressed in the form `z = a + b * i`, where: -`a` is called the real part and `b` is called the imaginary part of `z`. -The conjugate of the number `a + b * i` is the number `a - b * i`. -The absolute value of a complex number `z = a + b * i` is a real number `|z| = sqrt(a^2 + b^2)`. The square of the absolute value `|z|^2` is the result of multiplication of `z` by its complex conjugate. +- `a` is the **real part** (a real number), -The sum/difference of two complex numbers involves adding/subtracting their real and imaginary parts separately: -`(a + i * b) + (c + i * d) = (a + c) + (b + d) * i`, -`(a + i * b) - (c + i * d) = (a - c) + (b - d) * i`. +- `b` is the **imaginary part** (also a real number), and -Multiplication result is by definition -`(a + i * b) * (c + i * d) = (a * c - b * d) + (b * c + a * d) * i`. +- `i` is the **imaginary unit** satisfying `i^2 = -1`. -The reciprocal of a non-zero complex number is -`1 / (a + i * b) = a/(a^2 + b^2) - b/(a^2 + b^2) * i`. +## Operations on Complex Numbers -Dividing a complex number `a + i * b` by another `c + i * d` gives: -`(a + i * b) / (c + i * d) = (a * c + b * d)/(c^2 + d^2) + (b * c - a * d)/(c^2 + d^2) * i`. +### Conjugate -Raising e to a complex exponent can be expressed as `e^(a + i * b) = e^a * e^(i * b)`, the last term of which is given by Euler's formula `e^(i * b) = cos(b) + i * sin(b)`. +The conjugate of the complex number `z = a + b * i` is given by: -Implement the following operations: +```text +zc = a - b * i +``` -- addition, subtraction, multiplication and division of two complex numbers, -- conjugate, absolute value, exponent of a given complex number. +### Absolute Value -Assume the programming language you are using does not have an implementation of complex numbers. +The absolute value (or modulus) of `z` is defined as: + +```text +|z| = sqrt(a^2 + b^2) +``` + +The square of the absolute value is computed as the product of `z` and its conjugate `zc`: + +```text +|z|^2 = z * zc = a^2 + b^2 +``` + +### Addition + +The sum of two complex numbers `z1 = a + b * i` and `z2 = c + d * i` is computed by adding their real and imaginary parts separately: + +```text +z1 + z2 = (a + b * i) + (c + d * i) + = (a + c) + (b + d) * i +``` + +### Subtraction + +The difference of two complex numbers is obtained by subtracting their respective parts: + +```text +z1 - z2 = (a + b * i) - (c + d * i) + = (a - c) + (b - d) * i +``` + +### Multiplication + +The product of two complex numbers is defined as: + +```text +z1 * z2 = (a + b * i) * (c + d * i) + = (a * c - b * d) + (b * c + a * d) * i +``` + +### Reciprocal + +The reciprocal of a non-zero complex number is given by: + +```text +1 / z = 1 / (a + b * i) + = a / (a^2 + b^2) - b / (a^2 + b^2) * i +``` + +### Division + +The division of one complex number by another is given by: + +```text +z1 / z2 = z1 * (1 / z2) + = (a + b * i) / (c + d * i) + = (a * c + b * d) / (c^2 + d^2) + (b * c - a * d) / (c^2 + d^2) * i +``` + +### Exponentiation + +Raising _e_ (the base of the natural logarithm) to a complex exponent can be expressed using Euler's formula: + +```text +e^(a + b * i) = e^a * e^(b * i) + = e^a * (cos(b) + i * sin(b)) +``` + +## Implementation Requirements + +Given that you should not use built-in support for complex numbers, implement the following operations: + +- **addition** of two complex numbers +- **subtraction** of two complex numbers +- **multiplication** of two complex numbers +- **division** of two complex numbers +- **conjugate** of a complex number +- **absolute value** of a complex number +- **exponentiation** of _e_ (the base of the natural logarithm) to a complex number diff --git a/exercises/practice/dominoes/.docs/instructions.md b/exercises/practice/dominoes/.docs/instructions.md index 1ced9f6448f..75055b9e892 100644 --- a/exercises/practice/dominoes/.docs/instructions.md +++ b/exercises/practice/dominoes/.docs/instructions.md @@ -2,7 +2,9 @@ Make a chain of dominoes. -Compute a way to order a given set of dominoes in such a way that they form a correct domino chain (the dots on one half of a stone match the dots on the neighboring half of an adjacent stone) and that dots on the halves of the stones which don't have a neighbor (the first and last stone) match each other. +Compute a way to order a given set of domino stones so that they form a correct domino chain. +In the chain, the dots on one half of a stone must match the dots on the neighboring half of an adjacent stone. +Additionally, the dots on the halves of the stones without neighbors (the first and last stone) must match each other. For example given the stones `[2|1]`, `[2|3]` and `[1|3]` you should compute something like `[1|2] [2|3] [3|1]` or `[3|2] [2|1] [1|3]` or `[1|3] [3|2] [2|1]` etc, where the first and last numbers are the same. diff --git a/exercises/practice/dominoes/.docs/introduction.md b/exercises/practice/dominoes/.docs/introduction.md new file mode 100644 index 00000000000..df248c2116e --- /dev/null +++ b/exercises/practice/dominoes/.docs/introduction.md @@ -0,0 +1,13 @@ +# Introduction + +In Toyland, the trains are always busy delivering treasures across the city, from shiny marbles to rare building blocks. +The tracks they run on are made of colorful domino-shaped pieces, each marked with two numbers. +For the trains to move, the dominoes must form a perfect chain where the numbers match. + +Today, an urgent delivery of rare toys is on hold. +You've been handed a set of track pieces to inspect. +If they can form a continuous chain, the train will be on its way, bringing smiles across Toyland. +If not, the set will be discarded, and another will be tried. + +The toys are counting on you to solve this puzzle. +Will the dominoes connect the tracks and send the train rolling, or will the set be left behind? diff --git a/exercises/practice/eliuds-eggs/.docs/introduction.md b/exercises/practice/eliuds-eggs/.docs/introduction.md index 49eaffd8bc3..81989748099 100644 --- a/exercises/practice/eliuds-eggs/.docs/introduction.md +++ b/exercises/practice/eliuds-eggs/.docs/introduction.md @@ -12,36 +12,54 @@ The position information encoding is calculated as follows: 2. Convert the number from binary to decimal. 3. Show the result on the display. -Example 1: +## Example 1 + +![Seven individual nest boxes arranged in a row whose first, third, fourth and seventh nests each have a single egg.](https://assets.exercism.org/images/exercises/eliuds-eggs/example-1-coop.svg) ```text -Chicken Coop: _ _ _ _ _ _ _ |E| |E|E| | |E| +``` + +### Resulting Binary + +![1011001](https://assets.exercism.org/images/exercises/eliuds-eggs/example-1-binary.svg) + +```text + _ _ _ _ _ _ _ +|1|0|1|1|0|0|1| +``` -Resulting Binary: - 1 0 1 1 0 0 1 +### Decimal number on the display -Decimal number on the display: 89 -Actual eggs in the coop: +### Actual eggs in the coop + 4 + +## Example 2 + +![Seven individual nest boxes arranged in a row where only the fourth nest has an egg.](https://assets.exercism.org/images/exercises/eliuds-eggs/example-2-coop.svg) + +```text + _ _ _ _ _ _ _ +| | | |E| | | | ``` -Example 2: +### Resulting Binary + +![0001000](https://assets.exercism.org/images/exercises/eliuds-eggs/example-2-binary.svg) ```text -Chicken Coop: - _ _ _ _ _ _ _ _ -| | | |E| | | | | + _ _ _ _ _ _ _ +|0|0|0|1|0|0|0| +``` -Resulting Binary: - 0 0 0 1 0 0 0 0 +### Decimal number on the display -Decimal number on the display: 16 -Actual eggs in the coop: +### Actual eggs in the coop + 1 -``` diff --git a/exercises/practice/grade-school/.docs/instructions.md b/exercises/practice/grade-school/.docs/instructions.md index 9a63e398d85..3cb1b5d5f90 100644 --- a/exercises/practice/grade-school/.docs/instructions.md +++ b/exercises/practice/grade-school/.docs/instructions.md @@ -1,21 +1,21 @@ # Instructions -Given students' names along with the grade that they are in, create a roster for the school. +Given students' names along with the grade they are in, create a roster for the school. In the end, you should be able to: -- Add a student's name to the roster for a grade +- Add a student's name to the roster for a grade: - "Add Jim to grade 2." - "OK." -- Get a list of all students enrolled in a grade +- Get a list of all students enrolled in a grade: - "Which students are in grade 2?" - - "We've only got Jim just now." + - "We've only got Jim right now." - Get a sorted list of all students in all grades. - Grades should sort as 1, 2, 3, etc., and students within a grade should be sorted alphabetically by name. - - "Who all is enrolled in school right now?" + Grades should be sorted as 1, 2, 3, etc., and students within a grade should be sorted alphabetically by name. + - "Who is enrolled in school right now?" - "Let me think. - We have Anna, Barb, and Charlie in grade 1, Alex, Peter, and Zoe in grade 2 and Jim in grade 5. - So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe and Jim" + We have Anna, Barb, and Charlie in grade 1, Alex, Peter, and Zoe in grade 2, and Jim in grade 5. + So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe, and Jim." -Note that all our students only have one name (It's a small town, what do you want?) and each student cannot be added more than once to a grade or the roster. -In fact, when a test attempts to add the same student more than once, your implementation should indicate that this is incorrect. +Note that all our students only have one name (it's a small town, what do you want?), and each student cannot be added more than once to a grade or the roster. +If a test attempts to add the same student more than once, your implementation should indicate that this is incorrect. diff --git a/exercises/practice/hamming/.docs/instructions.md b/exercises/practice/hamming/.docs/instructions.md index b9ae6efc516..8f47a179e01 100644 --- a/exercises/practice/hamming/.docs/instructions.md +++ b/exercises/practice/hamming/.docs/instructions.md @@ -2,15 +2,6 @@ Calculate the Hamming distance between two DNA strands. -Your body is made up of cells that contain DNA. -Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. -In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime! - -When cells divide, their DNA replicates too. -Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. -If we compare two strands of DNA and count the differences between them we can see how many mistakes occurred. -This is known as the "Hamming distance". - We read DNA using the letters C, A, G and T. Two strands might look like this: @@ -20,8 +11,6 @@ Two strands might look like this: They have 7 differences, and therefore the Hamming distance is 7. -The Hamming distance is useful for lots of things in science, not just biology, so it's a nice phrase to be familiar with :) - ## Implementation notes The Hamming distance is only defined for sequences of equal length, so an attempt to calculate it between sequences of different lengths should not work. diff --git a/exercises/practice/hamming/.docs/introduction.md b/exercises/practice/hamming/.docs/introduction.md new file mode 100644 index 00000000000..8419bf479e5 --- /dev/null +++ b/exercises/practice/hamming/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +Your body is made up of cells that contain DNA. +Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. +In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime! + +When cells divide, their DNA replicates too. +Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. +If we compare two strands of DNA and count the differences between them, we can see how many mistakes occurred. +This is known as the "Hamming distance". + +The Hamming distance is useful in many areas of science, not just biology, so it's a nice phrase to be familiar with :) diff --git a/exercises/practice/knapsack/.docs/instructions.md b/exercises/practice/knapsack/.docs/instructions.md index 3411db9886a..0ebf7914c55 100644 --- a/exercises/practice/knapsack/.docs/instructions.md +++ b/exercises/practice/knapsack/.docs/instructions.md @@ -1,11 +1,11 @@ # Instructions -Your task is to determine which items to take so that the total value of his selection is maximized, taking into account the knapsack's carrying capacity. +Your task is to determine which items to take so that the total value of her selection is maximized, taking into account the knapsack's carrying capacity. Items will be represented as a list of items. Each item will have a weight and value. All values given will be strictly positive. -Bob can take only one of each item. +Lhakpa can take only one of each item. For example: @@ -21,5 +21,5 @@ Knapsack Maximum Weight: 10 ``` For the above, the first item has weight 5 and value 10, the second item has weight 4 and value 40, and so on. -In this example, Bob should take the second and fourth item to maximize his value, which, in this case, is 90. -He cannot get more than 90 as his knapsack has a weight limit of 10. +In this example, Lhakpa should take the second and fourth item to maximize her value, which, in this case, is 90. +She cannot get more than 90 as her knapsack has a weight limit of 10. diff --git a/exercises/practice/knapsack/.docs/introduction.md b/exercises/practice/knapsack/.docs/introduction.md index 9b2bed8b4e9..9ac9df596b6 100644 --- a/exercises/practice/knapsack/.docs/introduction.md +++ b/exercises/practice/knapsack/.docs/introduction.md @@ -1,8 +1,10 @@ # Introduction -Bob is a thief. -After months of careful planning, he finally manages to crack the security systems of a fancy store. +Lhakpa is a [Sherpa][sherpa] mountain guide and porter. +After months of careful planning, the expedition Lhakpa works for is about to leave. +She will be paid the value she carried to the base camp. -In front of him are many items, each with a value and weight. -Bob would gladly take all of the items, but his knapsack can only hold so much weight. -Bob has to carefully consider which items to take so that the total value of his selection is maximized. +In front of her are many items, each with a value and weight. +Lhakpa would gladly take all of the items, but her knapsack can only hold so much weight. + +[sherpa]: https://en.wikipedia.org/wiki/Sherpa_people#Mountaineering diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md index 49934c10646..5bbf007b075 100644 --- a/exercises/practice/luhn/.docs/instructions.md +++ b/exercises/practice/luhn/.docs/instructions.md @@ -1,12 +1,10 @@ # Instructions -Given a number determine whether or not it is valid per the Luhn formula. +Determine whether a credit card number is valid according to the [Luhn formula][luhn]. -The [Luhn algorithm][luhn] is a simple checksum formula used to validate a variety of identification numbers, such as credit card numbers and Canadian Social Insurance Numbers. +The number will be provided as a string. -The task is to check if a given string is valid. - -## Validating a Number +## Validating a number Strings of length 1 or less are not valid. Spaces are allowed in the input, but they should be stripped before checking. diff --git a/exercises/practice/luhn/.docs/introduction.md b/exercises/practice/luhn/.docs/introduction.md new file mode 100644 index 00000000000..ec2bd709d2b --- /dev/null +++ b/exercises/practice/luhn/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +At the Global Verification Authority, you've just been entrusted with a critical assignment. +Across the city, from online purchases to secure logins, countless operations rely on the accuracy of numerical identifiers like credit card numbers, bank account numbers, transaction codes, and tracking IDs. +The Luhn algorithm is a simple checksum formula used to ensure these numbers are valid and error-free. + +A batch of identifiers has just arrived on your desk. +All of them must pass the Luhn test to ensure they're legitimate. +If any fail, they'll be flagged as invalid, preventing errors or fraud, such as incorrect transactions or unauthorized access. + +Can you ensure this is done right? The integrity of many services depends on you. diff --git a/exercises/practice/pascals-triangle/.docs/introduction.md b/exercises/practice/pascals-triangle/.docs/introduction.md index 60b8ec30dc8..eab454e5a69 100644 --- a/exercises/practice/pascals-triangle/.docs/introduction.md +++ b/exercises/practice/pascals-triangle/.docs/introduction.md @@ -13,7 +13,7 @@ Over the next hour, your teacher reveals some amazing things hidden in this tria - It contains the Fibonacci sequence. - If you color odd and even numbers differently, you get a beautiful pattern called the [Sierpiński triangle][wikipedia-sierpinski-triangle]. -The teacher implores you and your classmates to lookup other uses, and assures you that there are lots more! +The teacher implores you and your classmates to look up other uses, and assures you that there are lots more! At that moment, the school bell rings. You realize that for the past hour, you were completely absorbed in learning about Pascal's triangle. You quickly grab your laptop from your bag and go outside, ready to enjoy both the sunshine _and_ the wonders of Pascal's triangle. diff --git a/exercises/practice/phone-number/.docs/introduction.md b/exercises/practice/phone-number/.docs/introduction.md new file mode 100644 index 00000000000..c4142c5af72 --- /dev/null +++ b/exercises/practice/phone-number/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +You've joined LinkLine, a leading communications company working to ensure reliable connections for everyone. +The team faces a big challenge: users submit phone numbers in all sorts of formats — dashes, spaces, dots, parentheses, and even prefixes. +Some numbers are valid, while others are impossible to use. + +Your mission is to turn this chaos into order. +You'll clean up valid numbers, formatting them appropriately for use in the system. +At the same time, you'll identify and filter out any invalid entries. + +The success of LinkLine's operations depends on your ability to separate the useful from the unusable. +Are you ready to take on the challenge and keep the connections running smoothly? diff --git a/exercises/practice/protein-translation/.docs/instructions.md b/exercises/practice/protein-translation/.docs/instructions.md index 7dc34d2edfd..44880802c57 100644 --- a/exercises/practice/protein-translation/.docs/instructions.md +++ b/exercises/practice/protein-translation/.docs/instructions.md @@ -2,12 +2,12 @@ Translate RNA sequences into proteins. -RNA can be broken into three nucleotide sequences called codons, and then translated to a polypeptide like so: +RNA can be broken into three-nucleotide sequences called codons, and then translated to a protein like so: RNA: `"AUGUUUUCU"` => translates to Codons: `"AUG", "UUU", "UCU"` -=> which become a polypeptide with the following sequence => +=> which become a protein with the following sequence => Protein: `"Methionine", "Phenylalanine", "Serine"` @@ -27,9 +27,9 @@ Protein: `"Methionine", "Phenylalanine", "Serine"` Note the stop codon `"UAA"` terminates the translation and the final methionine is not translated into the protein sequence. -Below are the codons and resulting Amino Acids needed for the exercise. +Below are the codons and resulting amino acids needed for the exercise. -| Codon | Protein | +| Codon | Amino Acid | | :----------------- | :------------ | | AUG | Methionine | | UUU, UUC | Phenylalanine | diff --git a/exercises/practice/pythagorean-triplet/.docs/instructions.md b/exercises/practice/pythagorean-triplet/.docs/instructions.md index 1c1a8aea61c..ced833d7a5b 100644 --- a/exercises/practice/pythagorean-triplet/.docs/instructions.md +++ b/exercises/practice/pythagorean-triplet/.docs/instructions.md @@ -1,4 +1,4 @@ -# Instructions +# Description A Pythagorean triplet is a set of three natural numbers, {a, b, c}, for which, diff --git a/exercises/practice/pythagorean-triplet/.docs/introduction.md b/exercises/practice/pythagorean-triplet/.docs/introduction.md new file mode 100644 index 00000000000..3453c6ed48f --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.docs/introduction.md @@ -0,0 +1,19 @@ +# Introduction + +You are an accomplished problem-solver, known for your ability to tackle the most challenging mathematical puzzles. +One evening, you receive an urgent letter from an inventor called the Triangle Tinkerer, who is working on a groundbreaking new project. +The letter reads: + +> Dear Mathematician, +> +> I need your help. +> I am designing a device that relies on the unique properties of Pythagorean triplets — sets of three integers that satisfy the equation a² + b² = c². +> This device will revolutionize navigation, but for it to work, I must program it with every possible triplet where the sum of a, b, and c equals a specific number, N. +> Calculating these triplets by hand would take me years, but I hear you are more than up to the task. +> +> Time is of the essence. +> The future of my invention — and perhaps even the future of mathematical innovation — rests on your ability to solve this problem. + +Motivated by the importance of the task, you set out to find all Pythagorean triplets that satisfy the condition. +Your work could have far-reaching implications, unlocking new possibilities in science and engineering. +Can you rise to the challenge and make history? diff --git a/exercises/practice/square-root/.docs/instructions.md b/exercises/practice/square-root/.docs/instructions.md index e9905e9d416..d258b86876e 100644 --- a/exercises/practice/square-root/.docs/instructions.md +++ b/exercises/practice/square-root/.docs/instructions.md @@ -1,13 +1,18 @@ # Instructions -Given a natural radicand, return its square root. +Your task is to calculate the square root of a given number. -Note that the term "radicand" refers to the number for which the root is to be determined. -That is, it is the number under the root symbol. +- Try to avoid using the pre-existing math libraries of your language. +- As input you'll be given a positive whole number, i.e. 1, 2, 3, 4… +- You are only required to handle cases where the result is a positive whole number. -Check out the Wikipedia pages on [square root][square-root] and [methods of computing square roots][computing-square-roots]. +Some potential approaches: -Recall also that natural numbers are positive real whole numbers (i.e. 1, 2, 3 and up). +- Linear or binary search for a number that gives the input number when squared. +- Successive approximation using Newton's or Heron's method. +- Calculating one digit at a time or one bit at a time. -[square-root]: https://en.wikipedia.org/wiki/Square_root +You can check out the Wikipedia pages on [integer square root][integer-square-root] and [methods of computing square roots][computing-square-roots] to help with choosing a method of calculation. + +[integer-square-root]: https://en.wikipedia.org/wiki/Integer_square_root [computing-square-roots]: https://en.wikipedia.org/wiki/Methods_of_computing_square_roots diff --git a/exercises/practice/square-root/.docs/introduction.md b/exercises/practice/square-root/.docs/introduction.md new file mode 100644 index 00000000000..1d692934f28 --- /dev/null +++ b/exercises/practice/square-root/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +We are launching a deep space exploration rocket and we need a way to make sure the navigation system stays on target. + +As the first step in our calculation, we take a target number and find its square root (that is, the number that when multiplied by itself equals the target number). + +The journey will be very long. +To make the batteries last as long as possible, we had to make our rocket's onboard computer very power efficient. +Unfortunately that means that we can't rely on fancy math libraries and functions, as they use more power. +Instead we want to implement our own square root calculation. From 8141d07da3c486471760c1c9fac8cfad6f178986 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 16 Jan 2025 16:02:39 -0800 Subject: [PATCH 209/277] Synced Anagram instructions to problem specifications. (#3849) --- exercises/practice/anagram/.docs/instructions.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/exercises/practice/anagram/.docs/instructions.md b/exercises/practice/anagram/.docs/instructions.md index a7298485b3f..dca24f52627 100644 --- a/exercises/practice/anagram/.docs/instructions.md +++ b/exercises/practice/anagram/.docs/instructions.md @@ -1,13 +1,12 @@ # Instructions -Your task is to, given a target word and a set of candidate words, to find the subset of the candidates that are anagrams of the target. +Given a target word and one or more candidate words, your task is to find the candidates that are anagrams of the target. An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`. A word is _not_ its own anagram: for example, `"stop"` is not an anagram of `"stop"`. -The target and candidates are words of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`). -Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `StoP` is not an anagram of `sTOp`. -The anagram set is the subset of the candidate set that are anagrams of the target (in any order). -Words in the anagram set should have the same letter case as in the candidate set. +The target word and candidate words are made up of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`). +Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `"StoP"` is not an anagram of `"sTOp"`. +The words you need to find should be taken from the candidate words, using the same letter case. -Given the target `"stone"` and candidates `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, `"Seton"`, the anagram set is `"tones"`, `"notes"`, `"Seton"`. +Given the target `"stone"` and the candidate words `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, and `"Seton"`, the anagram words you need to find are `"tones"`, `"notes"`, and `"Seton"`. From 82e77cedafaac4c2ba00b3a5463f44a43697f6c7 Mon Sep 17 00:00:00 2001 From: qadzek <84473512+qadzek@users.noreply.github.com> Date: Mon, 27 Jan 2025 01:33:35 +0100 Subject: [PATCH 210/277] Use correct double quotes (#3853) --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 5a38cf4c55d..8c01c524816 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -256,7 +256,7 @@ Commands may vary by Linux distro, and whether a `fish` or `zsh` shell is used. Replace `` with the output of `python3 -c "import os, sys; print(os.path.dirname(sys.executable))"` ```bash -export PATH=”$PATH:” +export PATH="$PATH:" ``` [Code Quality: Tools and Best Practices]: https://realpython.com/python-code-quality/ From 550d1aba093959a19c818343624664856985f0d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Feb 2025 20:19:26 -0800 Subject: [PATCH 211/277] Bump actions/setup-python from 5.3.0 to 5.4.0 (#3858) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.3.0 to 5.4.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/0b93645e9fea7318ecaed2b359559ac225c90a2b...42375524e23c412d93fb67b49958b491fce71c38) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 8c9d9f7fb60..75154ccd5ef 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - name: Set up Python - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 with: python-version: 3.11.2 @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b + - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 with: python-version: ${{ matrix.python-version }} From bd05215644a8bbba990fa430383cd7b779e46ef9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Feb 2025 20:19:55 -0800 Subject: [PATCH 212/277] Bump actions/stale from 9.0.0 to 9.1.0 (#3857) Bumps [actions/stale](https://github.com/actions/stale) from 9.0.0 to 9.1.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/28ca1036281a5e5922ead5184a1bbf96e5fc984e...5bef64f19d7facfb25b37b414482c7164d639639) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index f526390311f..03023bc033a 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -8,7 +8,7 @@ jobs: stale: runs-on: ubuntu-22.04 steps: - - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 21 From 445e49f3a623fca83762b5f026f08099f65d4682 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 11 Feb 2025 18:06:11 -0800 Subject: [PATCH 213/277] [Mecha Munch Management]: Reformatted Test File & Pulled Data out into Its Own File (#3860) [no important files changed] * Reformatted test file to test file plus test data file. * Reverted stub to empty. * Updated config in hopes the tests pass. --- .../mecha-munch-management/.meta/config.json | 3 + .../dict_methods_test.py | 166 +------- .../dict_methods_test_data.py | 393 ++++++++++++++++++ 3 files changed, 419 insertions(+), 143 deletions(-) create mode 100644 exercises/concept/mecha-munch-management/dict_methods_test_data.py diff --git a/exercises/concept/mecha-munch-management/.meta/config.json b/exercises/concept/mecha-munch-management/.meta/config.json index f09d0f29537..b75803ad5a8 100644 --- a/exercises/concept/mecha-munch-management/.meta/config.json +++ b/exercises/concept/mecha-munch-management/.meta/config.json @@ -14,6 +14,9 @@ ], "exemplar": [ ".meta/exemplar.py" + ], + "editor": [ + "dict_methods_test_data.py" ] }, "icon": "gross-store", diff --git a/exercises/concept/mecha-munch-management/dict_methods_test.py b/exercises/concept/mecha-munch-management/dict_methods_test.py index 63fc1874a05..4d8dab865a1 100644 --- a/exercises/concept/mecha-munch-management/dict_methods_test.py +++ b/exercises/concept/mecha-munch-management/dict_methods_test.py @@ -1,29 +1,29 @@ import unittest import pytest from collections import OrderedDict -from dict_methods import (add_item, - read_notes, - update_recipes, - sort_entries, - send_to_store, - update_store_inventory) - +from dict_methods import ( + add_item, + read_notes, + update_recipes, + sort_entries, + send_to_store, + update_store_inventory, +) + +from dict_methods_test_data import ( + add_item_data, + read_notes_data, + update_recipes_data, + sort_entries_data, + send_to_store_data, + update_store_inventory_data, +) class MechaMunchManagementTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_add_item(self): - input_data = [ - ({'Apple': 1, 'Banana': 4 }, ('Apple', 'Banana', 'Orange')), - ({'Orange': 1, 'Raspberry': 1, 'Blueberries': 10}, ['Raspberry', 'Blueberries', 'Raspberry']), - ({'Broccoli': 1, 'Banana': 1}, ('Broccoli', 'Kiwi', 'Kiwi', 'Kiwi', 'Melon', 'Apple', 'Banana', 'Banana')) - ] - - output_data = [{'Apple': 2, 'Banana': 5, 'Orange': 1}, - {'Orange': 1, 'Raspberry': 3, 'Blueberries': 11}, - {'Broccoli': 2, 'Banana': 3, 'Kiwi': 3, 'Melon': 1, 'Apple': 1}] - - for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + for variant, (input_data, expected) in enumerate(add_item_data, start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): actual_result = add_item(input_data[0], input_data[1]) error_msg= (f'Called add_item({input_data[0]}, {input_data[1]}). ' @@ -34,13 +34,7 @@ def test_add_item(self): @pytest.mark.task(taskno=2) def test_read_notes(self): - input_data = [('Apple', "Banana"), ('Orange', 'Raspberry', 'Blueberries'), - ['Broccoli', 'Kiwi', 'Melon', 'Apple', 'Banana']] - - output_data = [{'Apple': 1, 'Banana': 1}, {'Orange': 1, 'Raspberry': 1, 'Blueberries': 1}, - {'Broccoli': 1, 'Kiwi': 1, 'Melon': 1, 'Apple': 1, 'Banana': 1}] - - for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + for variant, (input_data, expected) in enumerate(read_notes_data, start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): actual_result = read_notes(input_data) error_msg = (f'Called read_notes({input_data}). ' @@ -51,36 +45,7 @@ def test_read_notes(self): @pytest.mark.task(taskno=3) def test_update_recipes(self): - input_data = [ - ({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, - 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, - (('Banana Bread', {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)), - - ({'Apple Pie': {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1}, - 'Blueberry Pie': {'Blueberries': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, - (('Blueberry Pie', {'Blueberries': 2, 'Pie Crust': 1, 'Cream Custard': 1}), - ('Apple Pie', {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1}))), - - ({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, - 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}, - 'Pasta Primavera': {'Eggs': 1, 'Carrots': 1, 'Spinach': 2, 'Tomatoes': 3, 'Parmesan': 2, 'Milk': 1, 'Onion': 1}}, - (('Raspberry Pie', {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2}), - ('Pasta Primavera', {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1}), - ('Blueberry Crumble', {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3}))) - ] - - output_data = [ - {'Banana Bread': {'Banana': 4, 'Walnuts': 2, 'Flour': 1, 'Butter': 1, 'Milk': 2, 'Eggs': 3}, - 'Raspberry Pie': {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}, - {'Apple Pie': {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1}, - 'Blueberry Pie': {'Blueberries': 2, 'Pie Crust': 1, 'Cream Custard': 1}}, - {'Banana Bread': {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1}, - 'Raspberry Pie': {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2}, - 'Pasta Primavera': {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1}, - 'Blueberry Crumble': {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3}} - ] - - for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + for variant, (input_data, expected) in enumerate(update_recipes_data, start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): actual_result = update_recipes(input_data[0], input_data[1]) error_msg = (f'Called update_recipes({input_data[0]}, {input_data[1]}). ' @@ -91,21 +56,7 @@ def test_update_recipes(self): @pytest.mark.task(taskno=4) def test_sort_entries(self): - input_data = [ - {'Banana': 4, 'Apple': 2, 'Orange': 1, 'Pear': 12}, - {'Apple': 3, 'Orange': 5, 'Banana': 1, 'Avocado': 2}, - {'Orange': 3, 'Banana': 2, 'Apple': 1}, - {'Apple': 2, 'Raspberry': 2, 'Blueberries': 5, 'Broccoli' : 2, 'Kiwi': 1, 'Melon': 4} - ] - - output_data = [ - {'Apple': 2, 'Banana': 4, 'Orange': 1, 'Pear': 12}, - {'Apple': 3, 'Avocado': 2, 'Banana': 1, 'Orange': 5}, - {'Apple': 1, 'Banana': 2, 'Orange': 3}, - {'Apple' : 2, 'Blueberries': 5, 'Broccoli': 2, 'Kiwi': 1, 'Melon': 4, 'Raspberry': 2} - ] - - for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + for variant, (input_data, expected) in enumerate(sort_entries_data, start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expecred=expected): actual_result = sort_entries(input_data) error_msg = (f'Called sort_entries({input_data}). ' @@ -119,49 +70,7 @@ def test_sort_entries(self): @pytest.mark.task(taskno=5) def test_send_to_store(self): - input_data = [ - ({'Banana': 3, 'Apple': 2, 'Orange': 1, 'Milk': 2}, - {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], - 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}), - - ({'Kiwi': 3, 'Juice': 5, 'Yoghurt': 2, 'Milk': 5}, - {'Kiwi': ['Aisle 6', False], 'Juice': ['Aisle 5', False], - 'Yoghurt': ['Aisle 2', True], 'Milk': ['Aisle 2', True]}), - - ({'Apple': 2, 'Raspberry': 2, 'Blueberries': 5, - 'Broccoli': 2, 'Kiwi': 1, 'Melon': 4}, - - {'Apple': ['Aisle 1', False], 'Raspberry': ['Aisle 6', False], - 'Blueberries': ['Aisle 6', False], 'Broccoli': ['Aisle 3', False], - 'Kiwi': ['Aisle 6', False], 'Melon': ['Aisle 6', False]}), - - ({'Orange': 1}, - {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], - 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}), - - ({'Banana': 3, 'Apple': 2, 'Orange': 1}, - {'Banana': ['Aisle 5', False], 'Apple': ['Aisle 4', False], - 'Orange': ['Aisle 4', False], 'Milk': ['Aisle 2', True]}), - ] - - output_data = [ - {'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], - 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]}, - - {'Yoghurt': [2, 'Aisle 2', True], 'Milk': [5, 'Aisle 2', True], - 'Kiwi': [3, 'Aisle 6', False], 'Juice': [5, 'Aisle 5', False]}, - - {'Raspberry': [2, 'Aisle 6', False], 'Melon': [4, 'Aisle 6', False], - 'Kiwi': [1, 'Aisle 6', False], 'Broccoli': [2, 'Aisle 3', False], - 'Blueberries': [5, 'Aisle 6', False], 'Apple': [2, 'Aisle 1', False]}, - - {'Orange': [1, 'Aisle 4', False]}, - - {'Orange': [1, 'Aisle 4', False], 'Banana': [3, 'Aisle 5', False], - 'Apple': [2, 'Aisle 4', False]}, - ] - - for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + for variant, (input_data, expected) in enumerate(send_to_store_data, start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): actual_result = send_to_store(input_data[0], input_data[1]) error_msg = (f'Called send_to_store({input_data[0]}, {input_data[1]}). ' @@ -175,36 +84,7 @@ def test_send_to_store(self): @pytest.mark.task(taskno=6) def test_update_store_inventory(self): - input_data = [ - ({'Orange': [1, 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True], - 'Banana': [3, 'Aisle 5', False], 'Apple': [2, 'Aisle 4', False]}, - {'Banana': [15, 'Aisle 5', False], 'Apple': [12, 'Aisle 4', False], - 'Orange': [1, 'Aisle 4', False], 'Milk': [4, 'Aisle 2', True]}), - - ({'Kiwi': [3, 'Aisle 6', False]},{'Kiwi': [3, 'Aisle 6', False], 'Juice': [5, 'Aisle 5', False], - 'Yoghurt': [2, 'Aisle 2', True], 'Milk': [5, 'Aisle 2', True]}), - - ({'Kiwi': [1, 'Aisle 6', False], 'Melon': [4, 'Aisle 6', False], 'Apple': [2, 'Aisle 1', False], - 'Raspberry': [2, 'Aisle 6', False], 'Blueberries': [5, 'Aisle 6', False], - 'Broccoli': [1, 'Aisle 3', False]}, - {'Apple': [2, 'Aisle 1', False], 'Raspberry': [5, 'Aisle 6', False], - 'Blueberries': [10, 'Aisle 6', False], 'Broccoli': [4, 'Aisle 3', False], - 'Kiwi': [1, 'Aisle 6', False], 'Melon': [8, 'Aisle 6', False]}) - ] - - output_data = [ - {'Banana': [12, 'Aisle 5', False], 'Apple': [10, 'Aisle 4', False], - 'Orange': ['Out of Stock', 'Aisle 4', False], 'Milk': [2, 'Aisle 2', True]}, - - {'Juice': [5, 'Aisle 5', False], 'Yoghurt': [2, 'Aisle 2', True], - 'Milk': [5, 'Aisle 2', True], 'Kiwi': ["Out of Stock", 'Aisle 6', False]}, - - {'Kiwi': ['Out of Stock', 'Aisle 6', False], 'Melon': [4, 'Aisle 6', False], - 'Apple': ['Out of Stock', 'Aisle 1', False], 'Raspberry': [3, 'Aisle 6', False], - 'Blueberries': [5, 'Aisle 6', False], 'Broccoli': [3, 'Aisle 3', False]} - ] - - for variant, (input_data, expected) in enumerate(zip(input_data, output_data), start=1): + for variant, (input_data, expected) in enumerate(update_store_inventory_data, start=1): with self.subTest(f'variation #{variant}', input_data=input_data, expected=expected): actual_result = update_store_inventory(input_data[0], input_data[1]) error_msg = (f'Called update_store_inventory({input_data[0]}, {input_data[1]}). ' diff --git a/exercises/concept/mecha-munch-management/dict_methods_test_data.py b/exercises/concept/mecha-munch-management/dict_methods_test_data.py new file mode 100644 index 00000000000..eea18cf541a --- /dev/null +++ b/exercises/concept/mecha-munch-management/dict_methods_test_data.py @@ -0,0 +1,393 @@ +##add_item test cases## +add_item_inputs = [ + ({"Apple": 1, "Banana": 4}, ("Apple", "Banana", "Orange")), + ( + {"Orange": 1, "Raspberry": 1, "Blueberries": 10}, + ["Raspberry", "Blueberries", "Raspberry"], + ), + ( + {"Broccoli": 1, "Banana": 1}, + ("Broccoli", "Kiwi", "Kiwi", "Kiwi", "Melon", "Apple", "Banana", "Banana"), + ), +] + +add_item_outputs = [ + {"Apple": 2, "Banana": 5, "Orange": 1}, + {"Orange": 1, "Raspberry": 3, "Blueberries": 11}, + {"Broccoli": 2, "Banana": 3, "Kiwi": 3, "Melon": 1, "Apple": 1}, +] + +add_item_data = zip(add_item_inputs, add_item_outputs) + + +##read_notes test cases## +read_notes_inputs = [ + ("Apple", "Banana"), + ("Orange", "Raspberry", "Blueberries"), + ["Broccoli", "Kiwi", "Melon", "Apple", "Banana"], +] + +read_notes_outputs = [ + {"Apple": 1, "Banana": 1}, + {"Orange": 1, "Raspberry": 1, "Blueberries": 1}, + {"Broccoli": 1, "Kiwi": 1, "Melon": 1, "Apple": 1, "Banana": 1}, +] + +read_notes_data = zip(read_notes_inputs, read_notes_outputs) + + +##update_recipes test cases## +update_recipes_inputs = [ + ( + { + "Banana Bread": { + "Banana": 1, + "Apple": 1, + "Walnuts": 1, + "Flour": 1, + "Eggs": 2, + "Butter": 1, + }, + "Raspberry Pie": { + "Raspberry": 1, + "Orange": 1, + "Pie Crust": 1, + "Cream Custard": 1, + }, + }, + ( + ( + "Banana Bread", + { + "Banana": 4, + "Walnuts": 2, + "Flour": 1, + "Butter": 1, + "Milk": 2, + "Eggs": 3, + }, + ), + ), + ), + ( + { + "Apple Pie": {"Apple": 1, "Pie Crust": 1, "Cream Custard": 1}, + "Blueberry Pie": {"Blueberries": 1, "Pie Crust": 1, "Cream Custard": 1}, + }, + ( + ("Blueberry Pie", {"Blueberries": 2, "Pie Crust": 1, "Cream Custard": 1}), + ("Apple Pie", {"Apple": 1, "Pie Crust": 1, "Cream Custard": 1}), + ), + ), + ( + { + "Banana Bread": { + "Banana": 1, + "Apple": 1, + "Walnuts": 1, + "Flour": 1, + "Eggs": 2, + "Butter": 1, + }, + "Raspberry Pie": { + "Raspberry": 1, + "Orange": 1, + "Pie Crust": 1, + "Cream Custard": 1, + }, + "Pasta Primavera": { + "Eggs": 1, + "Carrots": 1, + "Spinach": 2, + "Tomatoes": 3, + "Parmesan": 2, + "Milk": 1, + "Onion": 1, + }, + }, + ( + ( + "Raspberry Pie", + { + "Raspberry": 3, + "Orange": 1, + "Pie Crust": 1, + "Cream Custard": 1, + "Whipped Cream": 2, + }, + ), + ( + "Pasta Primavera", + { + "Eggs": 1, + "Mixed Veggies": 2, + "Parmesan": 2, + "Milk": 1, + "Spinach": 1, + "Bread Crumbs": 1, + }, + ), + ( + "Blueberry Crumble", + { + "Blueberries": 2, + "Whipped Creme": 2, + "Granola Topping": 2, + "Yogurt": 3, + }, + ), + ), + ), +] + +update_recipes_outputs = [ + { + "Banana Bread": { + "Banana": 4, + "Walnuts": 2, + "Flour": 1, + "Butter": 1, + "Milk": 2, + "Eggs": 3, + }, + "Raspberry Pie": { + "Raspberry": 1, + "Orange": 1, + "Pie Crust": 1, + "Cream Custard": 1, + }, + }, + { + "Apple Pie": {"Apple": 1, "Pie Crust": 1, "Cream Custard": 1}, + "Blueberry Pie": {"Blueberries": 2, "Pie Crust": 1, "Cream Custard": 1}, + }, + { + "Banana Bread": { + "Banana": 1, + "Apple": 1, + "Walnuts": 1, + "Flour": 1, + "Eggs": 2, + "Butter": 1, + }, + "Raspberry Pie": { + "Raspberry": 3, + "Orange": 1, + "Pie Crust": 1, + "Cream Custard": 1, + "Whipped Cream": 2, + }, + "Pasta Primavera": { + "Eggs": 1, + "Mixed Veggies": 2, + "Parmesan": 2, + "Milk": 1, + "Spinach": 1, + "Bread Crumbs": 1, + }, + "Blueberry Crumble": { + "Blueberries": 2, + "Whipped Creme": 2, + "Granola Topping": 2, + "Yogurt": 3, + }, + }, +] + +update_recipes_data = zip(update_recipes_inputs, update_recipes_outputs) + + +##sort_entries test cases## +sort_entries_inputs = [ + {"Banana": 4, "Apple": 2, "Orange": 1, "Pear": 12}, + {"Apple": 3, "Orange": 5, "Banana": 1, "Avocado": 2}, + {"Orange": 3, "Banana": 2, "Apple": 1}, + { + "Apple": 2, + "Raspberry": 2, + "Blueberries": 5, + "Broccoli": 2, + "Kiwi": 1, + "Melon": 4, + }, +] + +sort_entries_outputs = [ + {"Apple": 2, "Banana": 4, "Orange": 1, "Pear": 12}, + {"Apple": 3, "Avocado": 2, "Banana": 1, "Orange": 5}, + {"Apple": 1, "Banana": 2, "Orange": 3}, + { + "Apple": 2, + "Blueberries": 5, + "Broccoli": 2, + "Kiwi": 1, + "Melon": 4, + "Raspberry": 2, + }, +] + + +sort_entries_data = zip(sort_entries_inputs, sort_entries_outputs) + + +##send_to_store test cases## +send_to_store_inputs = [ + ( + {"Banana": 3, "Apple": 2, "Orange": 1, "Milk": 2}, + { + "Banana": ["Aisle 5", False], + "Apple": ["Aisle 4", False], + "Orange": ["Aisle 4", False], + "Milk": ["Aisle 2", True], + }, + ), + ( + {"Kiwi": 3, "Juice": 5, "Yoghurt": 2, "Milk": 5}, + { + "Kiwi": ["Aisle 6", False], + "Juice": ["Aisle 5", False], + "Yoghurt": ["Aisle 2", True], + "Milk": ["Aisle 2", True], + }, + ), + ( + { + "Apple": 2, + "Raspberry": 2, + "Blueberries": 5, + "Broccoli": 2, + "Kiwi": 1, + "Melon": 4, + }, + { + "Apple": ["Aisle 1", False], + "Raspberry": ["Aisle 6", False], + "Blueberries": ["Aisle 6", False], + "Broccoli": ["Aisle 3", False], + "Kiwi": ["Aisle 6", False], + "Melon": ["Aisle 6", False], + }, + ), + ( + {"Orange": 1}, + { + "Banana": ["Aisle 5", False], + "Apple": ["Aisle 4", False], + "Orange": ["Aisle 4", False], + "Milk": ["Aisle 2", True], + }, + ), + ( + {"Banana": 3, "Apple": 2, "Orange": 1}, + { + "Banana": ["Aisle 5", False], + "Apple": ["Aisle 4", False], + "Orange": ["Aisle 4", False], + "Milk": ["Aisle 2", True], + }, + ), +] + +send_to_store_outputs = [ + { + "Orange": [1, "Aisle 4", False], + "Milk": [2, "Aisle 2", True], + "Banana": [3, "Aisle 5", False], + "Apple": [2, "Aisle 4", False], + }, + { + "Yoghurt": [2, "Aisle 2", True], + "Milk": [5, "Aisle 2", True], + "Kiwi": [3, "Aisle 6", False], + "Juice": [5, "Aisle 5", False], + }, + { + "Raspberry": [2, "Aisle 6", False], + "Melon": [4, "Aisle 6", False], + "Kiwi": [1, "Aisle 6", False], + "Broccoli": [2, "Aisle 3", False], + "Blueberries": [5, "Aisle 6", False], + "Apple": [2, "Aisle 1", False], + }, + {"Orange": [1, "Aisle 4", False]}, + { + "Orange": [1, "Aisle 4", False], + "Banana": [3, "Aisle 5", False], + "Apple": [2, "Aisle 4", False], + }, +] + +send_to_store_data = zip(send_to_store_inputs, send_to_store_outputs) + + +##update_store_inventory test cases## +update_store_inventory_inputs = [ + ( + { + "Orange": [1, "Aisle 4", False], + "Milk": [2, "Aisle 2", True], + "Banana": [3, "Aisle 5", False], + "Apple": [2, "Aisle 4", False], + }, + { + "Banana": [15, "Aisle 5", False], + "Apple": [12, "Aisle 4", False], + "Orange": [1, "Aisle 4", False], + "Milk": [4, "Aisle 2", True], + }, + ), + ( + {"Kiwi": [3, "Aisle 6", False]}, + { + "Kiwi": [3, "Aisle 6", False], + "Juice": [5, "Aisle 5", False], + "Yoghurt": [2, "Aisle 2", True], + "Milk": [5, "Aisle 2", True], + }, + ), + ( + { + "Kiwi": [1, "Aisle 6", False], + "Melon": [4, "Aisle 6", False], + "Apple": [2, "Aisle 1", False], + "Raspberry": [2, "Aisle 6", False], + "Blueberries": [5, "Aisle 6", False], + "Broccoli": [1, "Aisle 3", False], + }, + { + "Apple": [2, "Aisle 1", False], + "Raspberry": [5, "Aisle 6", False], + "Blueberries": [10, "Aisle 6", False], + "Broccoli": [4, "Aisle 3", False], + "Kiwi": [1, "Aisle 6", False], + "Melon": [8, "Aisle 6", False], + }, + ), +] + +update_store_inventory_outputs = [ + { + "Banana": [12, "Aisle 5", False], + "Apple": [10, "Aisle 4", False], + "Orange": ["Out of Stock", "Aisle 4", False], + "Milk": [2, "Aisle 2", True], + }, + { + "Juice": [5, "Aisle 5", False], + "Yoghurt": [2, "Aisle 2", True], + "Milk": [5, "Aisle 2", True], + "Kiwi": ["Out of Stock", "Aisle 6", False], + }, + { + "Kiwi": ["Out of Stock", "Aisle 6", False], + "Melon": [4, "Aisle 6", False], + "Apple": ["Out of Stock", "Aisle 1", False], + "Raspberry": [3, "Aisle 6", False], + "Blueberries": [5, "Aisle 6", False], + "Broccoli": [3, "Aisle 3", False], + }, +] + +update_store_inventory_data = zip( + update_store_inventory_inputs, update_store_inventory_outputs +) From 1b095278cde317fe0c77c61e9d0761733d921214 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 12 Feb 2025 12:03:12 -0800 Subject: [PATCH 214/277] Added link to dict.keys for Mecha Munch Management hints file. (#3861) --- exercises/concept/mecha-munch-management/.docs/hints.md | 1 + 1 file changed, 1 insertion(+) diff --git a/exercises/concept/mecha-munch-management/.docs/hints.md b/exercises/concept/mecha-munch-management/.docs/hints.md index 3287768ff5f..2d2f49e2cc8 100644 --- a/exercises/concept/mecha-munch-management/.docs/hints.md +++ b/exercises/concept/mecha-munch-management/.docs/hints.md @@ -50,6 +50,7 @@ The dictionary section of the [official tutorial][dicts-docs] and the mapping ty [dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries [fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys [items]: https://docs.python.org/3/library/stdtypes.html#dict.items +[keys]: https://docs.python.org/3/library/stdtypes.html#dict.keys [mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict [mvp]: https://en.wikipedia.org/wiki/Minimum_viable_product [set-default]: https://docs.python.org/3/library/stdtypes.html#dict.setdefault From 6352edb4530657217532adfacaf89fef3e4a61e1 Mon Sep 17 00:00:00 2001 From: Jeremy Walker Date: Thu, 13 Feb 2025 23:34:50 +0000 Subject: [PATCH 215/277] Update CONTRIBUTING.md (#3863) --- CONTRIBUTING.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bc278b027e5..3c9c33c4689 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,23 +8,30 @@   [![Exercism_III](https://img.shields.io/badge/PAUSED-C73D4E?labelColor=3D454D&label=Contributions)](https://exercism.org/blog/freeing-our-maintainers)   [![Build Status](https://github.com/exercism/python/workflows/Exercises%20check/badge.svg)](https://github.com/exercism/python/actions?query=workflow%3A%22Exercises+check%22) -
+--- +--- -Hi.  👋🏽  👋  **We are happy you are here.**  🎉 🌟 +### We are not accepting community contributions at this time. -
-We 💛 💙   our community. -**`But our maintainers are not accepting community contributions at this time.`** -Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for details. +We're grateful for your interested in helping improve the Python track. but our maintainers are **not accepting community contributions at this time.** +If you would like to discuss possible future changes, please open a [thread on the forum](https://forum.exercism.org/). + +This [community blog post](https://exercism.org/blog/freeing-our-maintainers) contains more details.
+ +--- +--- +
+Hi.  👋🏽  👋  **We are happy you are here.**  🎉 🌟 + **`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website]. This repo holds all the instructions, tests, code, & support files for Python _exercises_ currently under development or implemented & available for students. From c99a16e8378f984b9c0242c105a2c0fdd987504f Mon Sep 17 00:00:00 2001 From: BethanyG Date: Thu, 13 Feb 2025 20:45:19 -0800 Subject: [PATCH 216/277] [Bank Account]: Added Explicit Instruction Append for not Doing Concurrency Tests. (#3864) * Added explicit instruction append for not doing concurrency tests. * Apply suggestions from code review Co-authored-by: Isaac Good --------- Co-authored-by: Isaac Good --- .../bank-account/.docs/instructions.append.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/exercises/practice/bank-account/.docs/instructions.append.md b/exercises/practice/bank-account/.docs/instructions.append.md index 6204bee7ddc..f49101c319f 100644 --- a/exercises/practice/bank-account/.docs/instructions.append.md +++ b/exercises/practice/bank-account/.docs/instructions.append.md @@ -1,5 +1,22 @@ # Instructions append +````exercim/note +Python doesn't support "true" concurrency due to the [Global Interpreter Lock][GIL]. +While work is ongoing to create support for [free-threading in Python][free-threading], it is still experimental. +Current standard library solutions such as [multiprocessing][multiprocessing-module] and [threading][threading-module] are difficult to implement with the current track tooling. + + +As a result, the concurrency requirement has been set aside for this exercise. +Account operations are sequential on a single thread, and no concurrency or "race condition" tests are run. + +[GIL]: https://realpython.com/python-gil/ +[free-threading]: https://docs.python.org/3/howto/free-threading-python.html +[threading-module]: https://docs.python.org/3/library/threading.html#module-threading +[multiprocessing-module]: https://docs.python.org/3/library/multiprocessing.html#sharing-state-between-processes +```` + +
+ ## Exception messages Sometimes it is necessary to [raise an exception](https://docs.python.org/3/tutorial/errors.html#raising-exceptions). When you do this, you should always include a **meaningful error message** to indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. For situations where you know that the error source will be a certain type, you can choose to raise one of the [built in error types](https://docs.python.org/3/library/exceptions.html#base-classes), but should still include a meaningful message. From 664f1dbd91fd7e6c4d5e0c6811f32c221ac01e0a Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 14 Feb 2025 11:56:08 -0800 Subject: [PATCH 217/277] [Sieve]: Update content.md for Comprehension Approach (#3865) Added language about the disallowed `%` operator to the Comprehensions approach. --- exercises/practice/sieve/.approaches/comprehensions/content.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/practice/sieve/.approaches/comprehensions/content.md b/exercises/practice/sieve/.approaches/comprehensions/content.md index 664ea32c11a..2f0d778bd39 100644 --- a/exercises/practice/sieve/.approaches/comprehensions/content.md +++ b/exercises/practice/sieve/.approaches/comprehensions/content.md @@ -27,7 +27,8 @@ def primes(limit): if all(number % divisor != 0 for divisor in range(2, number))] ``` -This second example using a `list-comprehension` with `all()` is certainly concise and _relatively_ readable, but the performance is again quite poor. +This second example using a `list-comprehension` with `all()` is certainly concise and _relatively_ readable, but it uses **`%`** (_which the instructions ask you not to use_) and the performance is again quite poor. + This is not quite a fully nested loop (_there is a short-circuit when `all()` evaluates to `False`_), but it is by no means "performant". In this case, scaling with input size is intermediate between linear and quadratic, so not quite as bad as the first example. From 889cf840544cb77c350161cb82ce1fce5416109e Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 14 Feb 2025 16:24:01 -0800 Subject: [PATCH 218/277] Added admonition for not accepting community contributions and updated Python versions. (#3866) --- CONTRIBUTING.md | 70 ++++++++++++++++++++----------------------------- README.md | 28 +++++++++++++++----- 2 files changed, 51 insertions(+), 47 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3c9c33c4689..d9c30d85e0a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,29 +4,26 @@

Contributing

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

We are not accepting community contributions at this time.

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

In General

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

We are not accepting community contributions at this time.

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

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

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






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





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