diff --git a/exercises/practice/wordy/.approaches/config.json b/exercises/practice/wordy/.approaches/config.json
index 670284d4715..957fc960bfd 100644
--- a/exercises/practice/wordy/.approaches/config.json
+++ b/exercises/practice/wordy/.approaches/config.json
@@ -1,7 +1,7 @@
{
"introduction": {
"authors": ["BethanyG"],
- "contributors": ["bobahop"]
+ "contributors": ["bobahop", "yrahcaz7"]
},
"approaches": [
{
@@ -9,49 +9,56 @@
"slug": "string-list-and-dict-methods",
"title": "String, List, and Dictionary Methods",
"blurb": "Use Core Python Features to Solve Word Problems.",
- "authors": ["BethanyG"]
+ "authors": ["BethanyG"],
+ "contributors": ["yrahcaz7"]
},
- {
- "uuid": "d3ff485a-defe-42d9-b9c6-c38019221ffa",
+ {
+ "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",
+ "authors": ["BethanyG"],
+ "contributors": ["yrahcaz7"]
+ },
+ {
+ "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",
+ "authors": ["BethanyG"],
+ "contributors": ["yrahcaz7"]
+ },
+ {
+ "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",
+ "authors": ["BethanyG"],
+ "contributors": ["yrahcaz7"]
+ },
+ {
+ "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"]
- },
- {
- "uuid": "1e136304-959c-4ad1-bc4a-450d13e5f668",
+ "authors": ["BethanyG"],
+ "contributors": ["yrahcaz7"]
+ },
+ {
+ "uuid": "1e136304-959c-4ad1-bc4a-450d13e5f668",
"slug": "functools-reduce",
- "title": "Functools.reduce for Calculation",
+ "title": "functools.reduce for Calculation",
"blurb": "Use functools.reduce with other strategies to calculate solutions.",
- "authors": ["BethanyG"]
- },
- {
+ "authors": ["BethanyG"],
+ "contributors": ["yrahcaz7"]
+ },
+ {
"uuid": "d643e2b4-daee-422d-b8d3-2cad2f439db5",
"slug": "dunder-getattribute",
- "title": "dunder with __getattribute__",
+ "title": "Dunder with __getattribute__",
"blurb": "Use dunder methods with __getattribute__.",
- "authors": ["bobahop"]
+ "authors": ["bobahop"],
+ "contributors": ["yrahcaz7"]
}
]
}
diff --git a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md
index 167460f2d3c..26652cc693f 100644
--- a/exercises/practice/wordy/.approaches/dunder-getattribute/content.md
+++ b/exercises/practice/wordy/.approaches/dunder-getattribute/content.md
@@ -1,6 +1,5 @@
# Dunder methods with `__getattribute__`
-
```python
OPS = {
"plus": "__add__",
@@ -12,11 +11,12 @@ OPS = {
def answer(question):
question = question.removeprefix("What is").removesuffix("?").strip()
- if not question: raise ValueError("syntax error")
+ if not question:
+ raise ValueError("syntax error")
if question.startswith("-") and question[1:].isdigit():
return -int(question[1:])
- elif question.isdigit():
+ if question.isdigit():
return int(question)
found_op = False
@@ -24,30 +24,31 @@ def answer(question):
if name in question:
question = question.replace(name, op)
found_op = True
- if not found_op: raise ValueError("unknown operation")
+ if not found_op:
+ raise ValueError("unknown operation")
ret = question.split()
while len(ret) > 1:
try:
x, op, y, *tail = ret
- if op not in OPS.values(): raise ValueError("syntax error")
+ if op not in OPS.values():
+ raise ValueError("syntax error")
ret = [int(x).__getattribute__(op)(int(y)), *tail]
except:
raise ValueError("syntax error")
return ret[0]
-
```
-This approach begins by defining a [dictionary][dictionaries] of the word keys with their related [`dunder-methods`][dunder] methods.
-Since only whole numbers are involved, the available `dunder-methods` are those for the [`int`][int] class/namespace.
+This approach begins by defining a [dictionary][dictionaries] of the word keys with their related [dunder method][dunder] values.
+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 more details.
+See [this StackOverflow post][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 dunder method [`.__dict__`](https://docs.python.org/3/reference/datamodel.html#object.__dict__) is a mapping of an object's writable attributes.
~~~~
@@ -56,35 +57,37 @@ The `OPS` dictionary is defined with all uppercase letters, which is the naming
It indicates that the value should not be changed.
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 a `ValueError("syntax error")`.
+The method calls are [chained][method-chaining], so 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 a `ValueError("syntax error")`.
-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 [`str.startswith()`][startswith] and [`str.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 used for the remaining string.
+If the `str.isdigit()` checks pass, the [`int()` constructor][int-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.
-If none of the key names are found in the input, a `ValueError("unknown operation")` is returned.
+If the key name is in the input, 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 raised.
+
+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.
-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.
+Within a [`try-except`][exception-handling] block, the list is [unpacked][unpacking] (_see also [concept:python/unpacking-and-multiple-assignment]()_) into the variables `x`, `op`, `y`, and `*tail`.
+If `op` is not in the `OPS` dictionary, a `ValueError("syntax error")` is raised.
-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")`.
+The `except` block will catch this error (or any other error raised inside the `try` block), and `raise` a `ValueError("syntax error")` instead.
+(You can look at [exception chaining in the Python docs][exception-chaining] for further detail on this subject.)
-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`.
+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 argument to `op`.
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.
+When `ret` reaches `len() == 1` and the loop ends, the first element of `ret` is returned as the answer.
[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-chaining]: https://docs.python.org/3/tutorial/errors.html#exception-chaining
[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__
@@ -101,4 +104,3 @@ When the loop exhausts, the first element of the list is selected as the functio
[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/dunder-getattribute/snippet.txt b/exercises/practice/wordy/.approaches/dunder-getattribute/snippet.txt
index d3cc3d16701..7e648873b33 100644
--- a/exercises/practice/wordy/.approaches/dunder-getattribute/snippet.txt
+++ b/exercises/practice/wordy/.approaches/dunder-getattribute/snippet.txt
@@ -5,4 +5,4 @@ while len(ret) > 1:
ret = [int(x).__getattribute__(op)(int(y)), *tail]
except:
raise ValueError("syntax error")
-return ret[0]
+return ret[0]
\ No newline at end of file
diff --git a/exercises/practice/wordy/.approaches/functools-reduce/content.md b/exercises/practice/wordy/.approaches/functools-reduce/content.md
index 8bc42449fa0..01c03a54f7c 100644
--- a/exercises/practice/wordy/.approaches/functools-reduce/content.md
+++ b/exercises/practice/wordy/.approaches/functools-reduce/content.md
@@ -1,5 +1,4 @@
-# Functools.reduce for Calculation
-
+# `functools.reduce()` for Calculation
```python
from operator import add, mul, sub
@@ -12,25 +11,25 @@ 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:
+ if not question.startswith("What is") or "cubed" in question:
raise ValueError("unknown operation")
-
- # Using the built-in filter() to clean & split the question..
- question = list(filter(lambda x:
- x not in ("What", "is", "by"),
+
+ # Use the built-in filter() to clean and split the question.
+ 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:
+ 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, throw an 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().
@@ -39,67 +38,64 @@ def answer(question):
```
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)_).
+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:python/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`:
-
+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")
+ # The wrapping () invoke implicit concatenation for the chained functions.
+ question = (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.
-```
+ .strip()).split() # <-- This split() turns the string into a list.
+ # Alternative 2 to the nested calls is to use a list comprehension:
+ question = [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")`.
+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.
+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` (_used as a [callable][callable] with `()`_), 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.
+`functools.reduce` is certainly convenient, and it makes the solution much shorter.
+However, 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.
+The imports from the `operator` module 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}
+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:
+ 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
+ question = [item for item in
+ question.strip("?").split() if
item not in ("What", "is", "by")]
# Separate candidate operators and numbers into two lists.
@@ -107,8 +103,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.
@@ -116,7 +112,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.
result = reduce(lambda x, y: OPERATORS[operations.pop(0)](x, y), digits)
return result
diff --git a/exercises/practice/wordy/.approaches/functools-reduce/snippet.txt b/exercises/practice/wordy/.approaches/functools-reduce/snippet.txt
index f8d5a294195..2c1cb3afbd6 100644
--- a/exercises/practice/wordy/.approaches/functools-reduce/snippet.txt
+++ b/exercises/practice/wordy/.approaches/functools-reduce/snippet.txt
@@ -1,7 +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]]
+ 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
index 9fdf3e20e09..618b7daf198 100644
--- a/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md
+++ b/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md
@@ -1,5 +1,4 @@
-# Import Callables from the Operator Module
-
+# Import Callables from the `operator` Module
```python
from operator import add, mul, sub
@@ -21,7 +20,7 @@ def answer(question):
if (question.startswith("-") and question[1:].isdigit()) or question.isdigit():
return int(question)
- equation = [word for word in question.split() if word != 'by']
+ equation = [word for word in question.split() if word != "by"]
while len(equation) > 1:
try:
@@ -34,44 +33,41 @@ def answer(question):
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.
+`add`, `mul` and `sub` keep their original names, while `floordiv` is [aliased][aliasing] to `div`.
+These functions are then stored in a dictionary that serves as a lookup table when the problems are processed.
+These operations are later used as [callables][callable] by putting `()` after the name, and supplying arguments between the parentheses.
-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].
+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], [`str.strip()`][strip], and [`str.split()`][split].
+Next, checks for digits and an empty string are done, and the word "by" is filtered from the equation `list` by 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 equation `list` is then processed in a `while-loop` within a [`try-except`][handling-exceptions] block.
+The `list` is [unpacked][unpacking] (_see also [concept:python/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.
+The processing of the equation `list` continues until its `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.
+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.
+ .strip()) # <-- Enclosing parentheses 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`:
-
+The call to `str.replace` could instead be chained with the call to `str.split` when creating the equation `list`:
```python
equation = question.replace("by", "").split()
@@ -85,7 +81,7 @@ equation = question.replace("by", "").split()
[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
+[pythontutor]: https://pythontutor.com/visualize.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%22by%22%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&curInstr=0&mode=display&origin=opt-frontend.js&py=311
[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
diff --git a/exercises/practice/wordy/.approaches/import-callables-from-operator/snippet.txt b/exercises/practice/wordy/.approaches/import-callables-from-operator/snippet.txt
index d5cb5a13547..de87954eaef 100644
--- a/exercises/practice/wordy/.approaches/import-callables-from-operator/snippet.txt
+++ b/exercises/practice/wordy/.approaches/import-callables-from-operator/snippet.txt
@@ -2,7 +2,7 @@ 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]
+ 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 821b1228425..13834278857 100644
--- a/exercises/practice/wordy/.approaches/introduction.md
+++ b/exercises/practice/wordy/.approaches/introduction.md
@@ -6,18 +6,15 @@ 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" 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('syntax error")`][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`][value-error] with a message.
This includes any "extra" spaces between numbers.
As shown in various approaches, there are multiple strategies for validating questions, with no one "canonical" solution.
-
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 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.
@@ -26,18 +23,18 @@ This could lead to future maintenance issues if the definition of a question eve
~~~~exercism/note
There are many Pythonic ways to go about the cleaning, parsing, and calculation steps of Wordy.
-However, solutions all follow the same 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.
- — _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 /.
+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._
+3. **_Starting from the left_**, take the first three elements and convert number strings to `int`s and operation 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._
-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")`.
+ - _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 plus 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")`.
~~~~
@@ -45,61 +42,54 @@ However, solutions all follow the same general steps:
For question cleaning, [`str.removeprefix`][removeprefix] and
[`str.removesuffix`][removesuffix] introduced in `Python 3.9` can be very useful:
-
```python
->>> 'Supercalifragilisticexpialidocious'.removeprefix('Super')
+>>> "Supercalifragilisticexpialidocious".removeprefix("Super")
'califragilisticexpialidocious'
->>> 'Supercalifragilisticexpialidocious'.removesuffix('expialidocious')
+>>> "Supercalifragilisticexpialidocious".removesuffix("expialidocious")
'Supercalifragilistic'
-#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'))
+# 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'
```
-
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:]
+>>> if "Supercalifragilisticexpialidocious".startswith("Super"):
+ new_string = "Supercalifragilisticexpialidocious"[5:]
>>> new_string
'califragilisticexpialidocious'
->>> if new_string.endswith('expialidocious'):
+>>> 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 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 looping/iteration, although other strategies can also be used:
-
```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`.
- 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.
+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.
@@ -116,10 +106,8 @@ It is also entirely unnecessary, as the other methods described here are safer a
_____________
-
## Approach: String, List, and Dictionary Methods
-
```python
def answer(question):
if not question.startswith("What is") or "cubed" in question:
@@ -157,18 +145,21 @@ def answer(question):
return int(formula[0])
```
-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.
+This approach uses only data structures and methods (_[`str` methods][str-methods], [`list()`][list], loOPERATORS, 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.
-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.
+This approach uses a [`try-except`][handling-exceptions] statement for handling unknown operators.
+It does this by raising an error inside the `try` block when `symbol` does not match any operator word.
+The `except` block will catch this error (or any other error raised inside the `try` block), and `raise` a `ValueError("syntax error")` instead.
+(You can look at [exception chaining in the Python docs][exception-chaining] for further detail on this subject.)
+
+Alternatives could use a [dictionary][dict] to store word to 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.
-## Approach: Import Callables from the Operator Module
-
+## Approach: Import Callables from the `operator` Module
```python
from operator import add, mul, sub
@@ -185,10 +176,10 @@ def answer(question):
if (question.startswith("-") and question[1:].isdigit()) or question.isdigit():
return int(question)
- if not question:
+ if not question:
raise ValueError("syntax error")
- equation = [word for word in question.split() if word != 'by']
+ equation = [word for word in question.split() if word != "by"]
while len(equation) > 1:
try:
@@ -202,15 +193,14 @@ def answer(question):
```
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).
+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:python/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.
+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
-
+## Approach: Regex with the `operator` Module
```python
import re
@@ -218,21 +208,22 @@ 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')
+ "number": re.compile(r"-?\d+"),
+ "operator": re.compile(f"(?:{'|'.join(OPERATIONS)})\\b")
}
def get_number(question):
- pattern = REGEX['number'].match(question)
+ pattern = REGEX["number"].match(question)
if not pattern:
raise ValueError("syntax error")
- return [question.removeprefix(pattern.group(0)).lstrip(),
+ return [question.removeprefix(pattern.group(0)).lstrip(),
int(pattern.group(0))]
def get_operation(question):
- pattern = REGEX['operator'].match(question)
+ pattern = REGEX["operator"].match(question)
if not pattern:
raise ValueError("unknown operation")
return [question.removeprefix(pattern.group(0)).lstrip(),
@@ -242,12 +233,12 @@ 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):
+ if REGEX["number"].match(question):
raise ValueError("syntax error")
question, operation = get_operation(question)
@@ -258,26 +249,24 @@ def answer(question):
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.
+For more details, take a look at the [Regex with the `operator` Module][approach-regex-with-operator-module] approach.
-## Approach: Lambdas in a Dictionary to return Functions
-
+## 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
- }
+ "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):
@@ -289,10 +278,10 @@ def answer(question):
if (question.startswith("-") and question[1:].isdigit()) or question.isdigit():
return int(question)
- if not question:
+ if not question:
raise ValueError("syntax error")
- equation = [word for word in question.split() if word != 'by']
+ equation = [word for word in question.split() if word != "by"]
while len(equation) > 1:
try:
@@ -305,8 +294,7 @@ def answer(question):
return equation[0]
```
-
-Rather than import methods from the `operator` module, this approach defines a series of [`lambda expressions`][lambdas] in the OPERATIONS dictionary.
+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.
@@ -320,7 +308,6 @@ For more details, take a look at the [Lambdas in a Dictionary][approach-lambdas-
## Approach: Recursion
-
```python
from operator import add, mul, sub
from operator import floordiv as div
@@ -338,34 +325,32 @@ def clean(question):
.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)
-```
+ 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.
+Like previous approaches that substitute methods from `operator` for `lambdas` or list comprehensions for `loOPERATORS` 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.
+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.
+For more details, take a look at the [Recursion for Iteration][approach-recursion] approach.
-## Approach: functools.reduce()
-
+## Approach: `functools.reduce()`
```python
from operator import add, mul, sub
@@ -376,17 +361,17 @@ 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:
+ 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()))
+ 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]]
+ 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")
@@ -396,20 +381,18 @@ def answer(question):
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].
+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.
+For more details and variations, take a look at the [`functools.reduce` for Calculation][approach-functools-reduce] approach.
## Approach: Dunder methods with `__getattribute__`
-
```python
OPS = {
"plus": "__add__",
@@ -421,11 +404,12 @@ OPS = {
def answer(question):
question = question.removeprefix("What is").removesuffix("?").strip()
- if not question: raise ValueError("syntax error")
-
+ if not question:
+ raise ValueError("syntax error")
+
if question.startswith("-") and question[1:].isdigit():
return -int(question[1:])
- elif question.isdigit():
+ if question.isdigit():
return int(question)
found_op = False
@@ -433,31 +417,31 @@ def answer(question):
if name in question:
question = question.replace(name, op)
found_op = True
- if not found_op: raise ValueError("unknown operation")
+ if not found_op:
+ raise ValueError("unknown operation")
ret = question.split()
while len(ret) > 1:
try:
x, op, y, *tail = ret
- if op not in OPS.values(): raise ValueError("syntax error")
+ if op not in OPS.values():
+ raise ValueError("syntax error")
ret = [int(x).__getattribute__(op)(int(y)), *tail]
except:
raise ValueError("syntax error")
return ret[0]
-
```
-This approach 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 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 numeric types_) and are automatically loaded when the Python interpreter is loaded.
-This is why the `operator` module exists - as a vehicle for providing callable methods for basic math when **not** overloading or customizing class functionality.
+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_).
-For more detail on this solution, take a look at the [dunder method with `__getattribute__` approach][approach-dunder-getattribute].
+This is why the `operator` module exists — It is 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 methods with `__getattribute__` approach][approach-dunder-getattribute].
-[PEMDAS]: https://www.mathnasium.com/math-centers/eagan/news/what-pemdas-e
+[PEMDAS]: https://www.mathnasium.com/blog/what-is-pemdas
[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
@@ -468,8 +452,9 @@ For more detail on this solution, take a look at the [dunder method with `__geta
[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
+[dunder-methods]: https://www.pythonmorsels.com/what-are-dunder-methods/
[endswith]: https://docs.python.org/3.9/library/stdtypes.html#str.endswith
+[exception-chaining]: https://docs.python.org/3/tutorial/errors.html#exception-chaining
[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
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 d78f3e7db83..75f5da8a260 100644
--- a/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md
+++ b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md
@@ -1,13 +1,12 @@
# 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
- }
+ "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):
@@ -19,7 +18,7 @@ def answer(question):
if (question.startswith("-") and question[1:].isdigit()) or question.isdigit():
return int(question)
- if not question:
+ if not question:
raise ValueError("syntax error")
equation = question.replace("by", "").split()
@@ -35,32 +34,31 @@ def answer(question):
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.
+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` module][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.
+`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):
+def _add(x, y):
return x + y
-def mul_(x, y):
+def _mul(x, y):
return x * y
-def div_(x, y):
- return x//y
+def _div(x, y):
+ return x // y
-def sub_(x, y):
+def _sub(x, y):
return x - y
def answer(question):
- operations = {'minus': sub_,'plus': add_,'multiplied': mul_,'divided': div_}
+ operations = {"minus": _sub, "plus": _add, "multiplied": _mul, "divided": _div}
if not question.startswith("What is") or "cubed" in question:
raise ValueError("unknown operation")
@@ -70,11 +68,11 @@ def answer(question):
if (question.startswith("-") and question[1:].isdigit()) or question.isdigit():
return int(question)
- if not question:
+ if not question:
raise ValueError("syntax error")
- equation = question.replace("by", "").split()
-
+ equation = question.replace("by", "").split()
+
while len(equation) > 1:
try:
x_value, operation, y_value, *rest = equation
@@ -87,8 +85,9 @@ def answer(question):
```
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.
+In addition, the functions need to have a leading underscore to indicate that they are internal functions for the module, which helps avoid potential shadowing or name conflict (see [PEP 8][pep-8-naming-styles] for more detail).
+
+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://exercism.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods
@@ -98,3 +97,4 @@ It is better and cleaner in this circumstance to use `lambda expressions` for th
[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
+[pep-8-naming-styles]: https://peps.python.org/pep-0008/#descriptive-naming-styles
diff --git a/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/snippet.txt b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/snippet.txt
index 3769bef8c5c..1364338e161 100644
--- a/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/snippet.txt
+++ b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/snippet.txt
@@ -1,6 +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
+ "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
index 794f1b41c19..4cad29778d4 100644
--- a/exercises/practice/wordy/.approaches/recursion/content.md
+++ b/exercises/practice/wordy/.approaches/recursion/content.md
@@ -1,6 +1,5 @@
# 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.
@@ -13,9 +12,9 @@ That being said, Python famously does not perform [tail-call optimization][tail-
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-first 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 loops.
@@ -25,63 +24,63 @@ Other algorithms such as [breadth-first search][bfs], [Dijkstra's algorithm][dij
from operator import add, mul, sub
from operator import floordiv as div
-# Define a lookup table for mathematical operations
+# 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()
+ # 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'.
+ # 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
+ # 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.
+ .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.
+# Recursively calculate the first piece of the equation,
+# calling calculate() on the product plus 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)
+
+ 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.
+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][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 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()` performs the same steps as the `while-loop` from the [Import Callables from the `operator` Module][approach-import-callables-from-operator] approach 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.
@@ -89,25 +88,23 @@ The difference being that the `while-loop` test for `len()` 1 now occurs as an `
`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 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.
+ 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
-
+## Variation 1: Use regex for matching, cleaning, and calculating
```python
-
import re
from operator import add, mul, sub
from operator import floordiv as div
@@ -115,13 +112,13 @@ 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.
+# 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+)"),
- }
+ 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).
@@ -129,76 +126,83 @@ VALIDATE = re.compile(r"(?P-?\d+) (multiplied by|divided by|plus|minus) (?P
-## Variation 2: Use Regex, Recurse within the For-loop
+## Variation 2: Use regex, recurse within the for-loop
+
+~~~~exercism/caution
+As of the time of writing, this variation of the approach passes all of the tests that are currently in the Exercism test suite.
+**However**, it gives an incorrect answer to any sufficiently long problem, such as the following: "What is 1 plus -10 multiplied by 3 minus 4?"
+
+Parsing this example from left to right, we get `((1 + -10) * 3) - 4 == -31`.
+However, this variation returns `9` in this case, [as seen here][recursion-in-loop-pythontutor].
+[recursion-in-loop-pythontutor]: https://pythontutor.com/visualize.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%0A%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%29%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%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%20not%20question%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22syntax%20error%22%29%0A%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%0A%20%20%20%20for%20operation,%20pattern%20in%20OPERATORS%3A%0A%20%20%20%20%20%20%20%20if%20matches%20%3A%3D%20pattern.match%28question%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20operation%28calculate%28matches%5B%22x%22%5D%29,%20calculate%28matches%5B%22y%22%5D%29%29%20%23%20%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%203%20minus%204%3F%22%29%29%0A%0Aprint%28%22Answer%20should%20be%3A%22,%20%28%281%20%2B%20-10%29%20*%203%29%20-%204%29%0A%0Aprint%28%22Result%20from%20wrong%20order%3A%22,%20%281%20%2B%20-10%29%20*%20%283%20-%204%29%29&curInstr=0&mode=display&origin=opt-frontend.js&py=311
+~~~~
```python
import re
@@ -206,55 +210,57 @@ 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:
+ 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("?").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.
+ if matches := pattern.match(question):
+ return operation(calculate(matches["x"]), calculate(matches["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_).
+This solution uses a `tuple` of nested `tuple`s containing the operators and the regex in place of the dictionaries that have been used in the previous approaches.
+This saves some space, but requires that the nested `tuple`s 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.
+Because the regex are more generic, they will match a `digit-operation-digit` trio in a longer question, so the line `return operation(calculate(matches["x"]), calculate(matches["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 is re-arranged to `mul(calculate("1 plus -10"), calculate("13 divided by 2"))`
-3. At this point, the loop pauses as the two recursive calls to `calculate()` spawn
+2. This is re-arranged to `mul(calculate("1 plus -10"), calculate("13 divided by 2"))`.
+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].
+For a more visual picture of the process (including how it fails on long inputs), 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
+[base-case]: https://en.wikipedia.org/wiki/Recursion_(computer_science)#Base_case
[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
@@ -272,7 +278,7 @@ For a more visual picture, you can step through the code on [pythontutor.com][re
[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-in-loop-pythontutor]: https://pythontutor.com/visualize.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%0A%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%29%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%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%20not%20question%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22syntax%20error%22%29%0A%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%0A%20%20%20%20for%20operation,%20pattern%20in%20OPERATORS%3A%0A%20%20%20%20%20%20%20%20if%20matches%20%3A%3D%20pattern.match%28question%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20operation%28calculate%28matches%5B%22x%22%5D%29,%20calculate%28matches%5B%22y%22%5D%29%29%20%23%20%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%203%20minus%204%3F%22%29%29%0A%0Aprint%28%22Answer%20should%20be%3A%22,%20%28%281%20%2B%20-10%29%20*%203%29%20-%204%29%0A%0Aprint%28%22Result%20from%20wrong%20order%3A%22,%20%281%20%2B%20-10%29%20*%20%283%20-%204%29%29&curInstr=0&mode=display&origin=opt-frontend.js&py=311
[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
diff --git a/exercises/practice/wordy/.approaches/recursion/snippet.txt b/exercises/practice/wordy/.approaches/recursion/snippet.txt
index 373481f8f4b..0a6a15c20d3 100644
--- a/exercises/practice/wordy/.approaches/recursion/snippet.txt
+++ b/exercises/practice/wordy/.approaches/recursion/snippet.txt
@@ -1,8 +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
+ 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
index d3d5c21430d..0bf7362cf17 100644
--- a/exercises/practice/wordy/.approaches/regex-with-operator-module/content.md
+++ b/exercises/practice/wordy/.approaches/regex-with-operator-module/content.md
@@ -1,5 +1,4 @@
-# Regex and the Operator Module
-
+# Regex with the `operator` Module
```python
import re
@@ -9,85 +8,85 @@ 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')
+ "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)
-
+ 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(),
+
+ # 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)
-
+ # 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.
+
+ # 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
+ # 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):
+ # 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
+
+ # 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.
+ # 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.
+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".
+It defines two "helper" functions (`get_number()` and `get_operation()`), that both 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].
+In the `answer()` function, the question is checked for validity (_if it starts 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] and [`str.removesuffix`][removesuffix], removing "What is" and "?".
+Left-trailing whitespace is stripped with the help of [`str.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.
+The returned operation is then used as a [callable][callable] by putting `()` after the name, with `result` and the "new" number (_returned from `get_number()`_) passed as arguments.
+The loop then proceeds with the processing of the "new question", until the `len()` is 0.
Once there is no more question to process, `result` is returned as the answer.
diff --git a/exercises/practice/wordy/.approaches/regex-with-operator-module/snippet.txt b/exercises/practice/wordy/.approaches/regex-with-operator-module/snippet.txt
index 4d89edb5377..e9f549370a8 100644
--- a/exercises/practice/wordy/.approaches/regex-with-operator-module/snippet.txt
+++ b/exercises/practice/wordy/.approaches/regex-with-operator-module/snippet.txt
@@ -1,5 +1,5 @@
while len(question) > 0:
- if REGEX['number'].match(question):
+ if REGEX["number"].match(question):
raise ValueError("syntax error")
question, operation = get_operation(question)
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 cce88a4bb06..eaa5b334048 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,5 @@
# String, List, and Dictionary Methods
-
```python
def answer(question):
if not question.startswith("What is") or "cubed" in question:
@@ -43,33 +42,34 @@ This eliminates all the [current cases][unknown-operation-tests] where a [`Value
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.
+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 whitespace.
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.
+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.
+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.
+`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.
+(Note that this is still inside the `try-except` block, so the [exception is chained][exception-chaining].)
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
-
+## Variation 1: Use a Dictionary for Lookup/Replace
```python
-OPERATIONS = {"plus": '+', "minus": '-', "multiplied": '*', "divided": '/'}
+OPERATIONS = {"plus": "+", "minus": "-", "multiplied": "*", "divided": "/"}
def answer(question):
@@ -108,73 +108,67 @@ def answer(question):
return int(formula[0])
```
+~~~~exercism/note
+[Method 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.
-````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
-````
-
+[method-chaining]: https://www.tutorialspoint.com/Explain-Python-class-method-chaining
+~~~~
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].
+Here the default for `dict.get()` is set to the element being iterated over, which is effectively _"if not found, skip it"_.
+This means that 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].
-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_).
+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][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
+~~~~exercism/note
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. This one also includes filtering out "by":
+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
- operation in question.split() if operation != 'by']
- ```
+ 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:
+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())))
+ formula = list(map(lambda op: OPERATIONS.get(op, op),
+ filter(lambda op: op != "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
-````
-
- 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`:
+~~~~
+Rather than indexing and slicing, [concept:python/unpacking-and-multiple-assignment]() can be used to assign the variables.
+However, 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.
+```python
+ x_value, symbol, 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.
+ if symbol == "+":
+ formula = [int(x_value) + int(y_value)] + remainder # <-- Instead, conversion to int() must happen here.
...
- return int(formula[0])
- ```
-
+ return int(formula[0])
+```
-## Variation 2: Structural Pattern Matching to Replace `if-elif-else`
+
+## Variation 2: Structural Pattern Matching to Replace `if-elif-else`
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:
-
+In some circumstances, this could be easier to read and/or reason about:
```python
def answer(question):
@@ -189,29 +183,31 @@ def answer(question):
formula = question.split()
while len(formula) > 1:
try:
- x_value, symbol, y_value, *remainder = formula #<-- unpacking and multiple assignment.
+ x_value, symbol, y_value, *remainder = formula # <-- Unpacking and multiple assignment.
match symbol:
- case "plus":
+ case "plus":
formula = [int(x_value) + int(y_value)] + remainder
- case "minus":
+ case "minus":
formula = [int(x_value) - int(y_value)] + remainder
- case "multiplied":
+ case "multiplied":
formula = [int(x_value) * int(y_value)] + remainder
- case "divided":
+ case "divided":
formula = [int(x_value) / int(y_value)] + remainder
- case _:
- raise ValueError("syntax error") #<-- "fall through case for no match."
- except: raise ValueError("syntax error") # <-- error handling for anything else that goes wrong.
+ 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])
-```
+```
[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
+[exception-chaining]: https://docs.python.org/3/tutorial/errors.html#exception-chaining
[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
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
index 700804b6d18..ccf9cc3052d 100644
--- a/exercises/practice/wordy/.approaches/string-list-and-dict-methods/snippet.txt
+++ b/exercises/practice/wordy/.approaches/string-list-and-dict-methods/snippet.txt
@@ -1,8 +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
+ 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 d26afab5fff..c5972700aad 100644
--- a/exercises/practice/wordy/.docs/instructions.append.md
+++ b/exercises/practice/wordy/.docs/instructions.append.md
@@ -20,14 +20,14 @@ raise ValueError("syntax error")
```
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:
+`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.
+ # It can contain statements, loops, if-else blocks, or other executable code.
x_value, operation, y_value, *rest = equation
...
...