Fixed links and other lingering typos. (#3790)
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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].
|
||||
|
||||
|
||||
Reference in New Issue
Block a user