Fixed links and other lingering typos. (#3790)

This commit is contained in:
BethanyG
2024-10-15 15:18:06 -07:00
committed by GitHub
parent c5922804a8
commit 7875b77415
4 changed files with 40 additions and 13 deletions

View File

@@ -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.
<br>
~~~~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` [`<object>.__dict__`](https://docs.python.org/3/reference/datamodel.html#object.__dict__) is a mapping of an objects writable attributes.
~~~~
<br>
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.

View File

@@ -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.
<br>
## 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

View File

@@ -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.
<br>
~~~~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")`.
~~~~
<br>
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.
<br>
~~~~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.
<br>
## 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.
<br>
## 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.
<br>
## 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.
<br>
## 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.
<br>
## 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.
<br>
## 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

View File

@@ -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.
<br>
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.
<br>
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.
<br>
Other algorithms such as [breadth-first search][bfs], [Dijkstra's algorithm][dijkstra], and the [Bellman-Ford Algorithm][bellman-ford] lend themselves better to loops.
<br>
```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.
<br>
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 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 without creating conflict or confusion.
`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.
<br>
`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.
```
<br>
## 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].
<br>
## 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].