From 8cb4533dff88a86b3c16ead09a481ce5994399d5 Mon Sep 17 00:00:00 2001
From: Yrahcaz7 <74512479+Yrahcaz7@users.noreply.github.com>
Date: Wed, 20 May 2026 23:02:42 -0400
Subject: [PATCH 1/9] `string-list-and-dict-methods` cleanup
also some formatting fixes
---
.../practice/wordy/.approaches/config.json | 37 ++++-----
.../.approaches/functools-reduce/content.md | 24 +++---
.../import-callables-from-operator/content.md | 2 +-
.../wordy/.approaches/introduction.md | 55 +++++++------
.../lambdas-in-a-dictionary/content.md | 16 ++--
.../wordy/.approaches/recursion/content.md | 42 +++++-----
.../regex-with-operator-module/content.md | 10 +--
.../string-list-and-dict-methods/content.md | 81 ++++++++++---------
.../string-list-and-dict-methods/snippet.txt | 8 +-
.../wordy/.docs/instructions.append.md | 6 +-
10 files changed, 143 insertions(+), 138 deletions(-)
diff --git a/exercises/practice/wordy/.approaches/config.json b/exercises/practice/wordy/.approaches/config.json
index 670284d4715..417458430a4 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,44 +9,45 @@
"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",
+ },
+ {
+ "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",
+ },
+ {
+ "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",
+ },
+ {
+ "uuid": "2e643b88-9b76-45a1-98f4-b211919af061",
"slug": "recursion",
"title": "Recursion for Iteration.",
"blurb": "Use recursion with other strategies to solve word problems.",
"authors": ["BethanyG"]
- },
- {
- "uuid": "1e136304-959c-4ad1-bc4a-450d13e5f668",
+ },
+ {
+ "uuid": "1e136304-959c-4ad1-bc4a-450d13e5f668",
"slug": "functools-reduce",
"title": "Functools.reduce for Calculation",
"blurb": "Use functools.reduce with other strategies to calculate solutions.",
"authors": ["BethanyG"]
- },
- {
+ },
+ {
"uuid": "d643e2b4-daee-422d-b8d3-2cad2f439db5",
"slug": "dunder-getattribute",
"title": "dunder with __getattribute__",
diff --git a/exercises/practice/wordy/.approaches/functools-reduce/content.md b/exercises/practice/wordy/.approaches/functools-reduce/content.md
index 8bc42449fa0..b8f51710ffd 100644
--- a/exercises/practice/wordy/.approaches/functools-reduce/content.md
+++ b/exercises/practice/wordy/.approaches/functools-reduce/content.md
@@ -16,8 +16,8 @@ def answer(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"),
+ question = list(filter(lambda x:
+ x not in ("What", "is", "by"),
question.strip("?").split()))
# Separate candidate operators and numbers into two lists.
@@ -25,8 +25,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.
@@ -51,13 +51,13 @@ However, this could easily be accomplished by either using [chained][method-chai
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.
# 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.
+ return [item for item in
+ question.strip("?").split()
+ if item not in ("What", "is", "by")] # <-- The [] of the comprehension invokes implicit concatenation.
```
@@ -98,8 +98,8 @@ def answer(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 +107,8 @@ def answer(question):
# Convert candidate elements to int(), checking for "-".
# All other values are replaced with None.
- digits = [int(element) if
- (element.isdigit() or element[1:].isdigit())
+ digits = [int(element) if
+ (element.isdigit() or element[1:].isdigit())
else None for element in question[::2]]
# If there is a mis-match between operators and numbers, toss error.
diff --git a/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md b/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md
index 9fdf3e20e09..7622dcfee8f 100644
--- a/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md
+++ b/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md
@@ -66,7 +66,7 @@ Using a `list-comprehension` to filter out "by" can be replaced with the [`str.r
question = (question.removeprefix("What is")
.removesuffix("?")
.replace("by", "")
- .strip()) #<-- Enclosing () means these lines are automatically joined by the interpreter.
+ .strip()) # <-- Enclosing () means these lines are automatically joined by the interpreter.
```
diff --git a/exercises/practice/wordy/.approaches/introduction.md b/exercises/practice/wordy/.approaches/introduction.md
index 821b1228425..5234061bf7d 100644
--- a/exercises/practice/wordy/.approaches/introduction.md
+++ b/exercises/practice/wordy/.approaches/introduction.md
@@ -13,7 +13,7 @@ The key to a Wordy solution is to remove the "question" portion of the sentence
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.
@@ -26,18 +26,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")`.
~~~~
@@ -99,7 +99,7 @@ Some solutions use either [lambda][lambdas] expressions, [dunder/"special" metho
However, the exercise can be solved without using `operator`, `lambdas`, `dunder-methods` or `eval`.
- It is recommended that you first start by solving it _without_ "advanced" strategies, and then refine your solution into something more compact or complex as you learn and practice.
+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.
@@ -157,11 +157,14 @@ 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], loops, etc._) from core Python, and does not import any extra modules.
It may have more lines of code than average, but it is clear to follow and fairly straightforward to reason about.
-It does use a [try-except][handling-exceptions] block for handling unknown operators.
-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] statment 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.
@@ -185,7 +188,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 = [word for word in question.split() if word != 'by']
@@ -203,7 +206,7 @@ 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).
+It also uses a [list-comprehension][list-comprehension] to create the parsed "formula" and employs [concept: unpacking and multiple assignment](/tracks/python/concepts/unpacking-and-multiple-assignment).
For more details and options, take a look at the [Import Callables from the Operator Module][approach-import-callables-from-operator] approach.
@@ -228,7 +231,7 @@ def get_number(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):
@@ -289,7 +292,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 = [word for word in question.split() if word != 'by']
@@ -345,7 +348,7 @@ def calculate(equation):
else:
try:
x_value, operation, y_value, *rest = equation
- equation = [OPERATIONS[operation](int(x_value),
+ equation = [OPERATIONS[operation](int(x_value),
int(y_value)), *rest]
except:
raise ValueError("syntax error")
@@ -379,13 +382,13 @@ def answer(question):
if not question.startswith( "What is") or "cubed" in question:
raise ValueError("unknown operation")
- question = list(filter(lambda x:
- x not in ("What", "is", "by"),
- question.strip("?").split()))
+ 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
+ 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:
@@ -456,8 +459,7 @@ This is why the `operator` module exists - as a vehicle for providing callable m
For more detail on this solution, take a look at the [dunder method with `__getattribute__` approach][approach-dunder-getattribute].
-
-[PEMDAS]: https://www.mathnasium.com/math-centers/eagan/news/what-pemdas-e
+[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
@@ -470,6 +472,7 @@ For more detail on this solution, take a look at the [dunder method with `__geta
[dict]: https://docs.python.org/3/library/stdtypes.html#dict
[dunder-methods]: https://www.pythonmorsels.com/what-are-dunder-methods/?watch
[endswith]: https://docs.python.org/3.9/library/stdtypes.html#str.endswith
+[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..985cec8a83b 100644
--- a/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md
+++ b/exercises/practice/wordy/.approaches/lambdas-in-a-dictionary/content.md
@@ -3,11 +3,11 @@
```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 +19,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()
@@ -40,7 +40,7 @@ The major difference here is the use of [`lambda expressions`][lambdas] in place
`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`:
@@ -70,7 +70,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()
diff --git a/exercises/practice/wordy/.approaches/recursion/content.md b/exercises/practice/wordy/.approaches/recursion/content.md
index 794f1b41c19..fe8be593d57 100644
--- a/exercises/practice/wordy/.approaches/recursion/content.md
+++ b/exercises/practice/wordy/.approaches/recursion/content.md
@@ -13,9 +13,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.
@@ -44,9 +44,9 @@ def clean(question):
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
+# Recursively calculate the first piece of the equation, calling
# calculate() on the product + the remainder.
# Return the solution when len(equation) is one.
def calculate(equation):
@@ -60,7 +60,7 @@ def calculate(equation):
# Redefine the equation list as the product of the first three
# variables concatenated with the unpacked remainder.
- equation = [OPERATIONS[operation](int(x_value),
+ equation = [OPERATIONS[operation](int(x_value),
int(y_value)), *rest]
except:
raise ValueError("syntax error")
@@ -90,15 +90,15 @@ The difference being that the `while-loop` test for `len()` 1 now occurs as an `
```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.
+ 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.
```
@@ -115,13 +115,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).
@@ -145,7 +145,7 @@ def answer(question):
# Call the recursive calculate() function.
return calculate(question)
-# Recursively calculate the first piece of the equation, calling
+# Recursively calculate the first piece of the equation, calling
# calculate() on the product + the remainder.
# Return the solution when len(equation) is one.
def calculate(question):
@@ -211,7 +211,7 @@ OPERATORS = (
(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:
@@ -230,13 +230,13 @@ def calculate(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.
+ return operation(calculate(match['x']), calculate(match['y'])) # <-- The loop is paused here to make the two recursive calls.
raise ValueError("syntax error")
```
This solution uses a `tuple` of nested `tuples` containing the operators from `operator` and regex in place of the dictionaries that have been used in the previous approaches.
This saves some space, but requires that the nested `tuples` be unpacked as the main `tuple` is iterated over (_note the `for operation, pattern in OPERATORS:` in the `for-loop`_ ) so that operations can be matched to strings in the question.
- The regex is also more generic than the example above (_anything before and after the operation words is allowed_).
+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.
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..bc9f24b9b0e 100644
--- a/exercises/practice/wordy/.approaches/regex-with-operator-module/content.md
+++ b/exercises/practice/wordy/.approaches/regex-with-operator-module/content.md
@@ -22,9 +22,9 @@ def get_number(question):
if not pattern:
raise ValueError("syntax error")
- # Remove the matched pattern from the question, and convert
+ # 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(),
+ return [question.removeprefix(pattern.group(0)).lstrip(),
int(pattern.group(0))]
# Helper function to extract an operation from the question.
@@ -36,7 +36,7 @@ def get_operation(question):
if not pattern:
raise ValueError("unknown operation")
- # Remove the matched pattern from the question, and look up
+ # 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)]]
@@ -64,11 +64,11 @@ def answer(question):
# 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
+ # Perform the calculation, using result and num as
# arguments to operation.
result = operation(result, num)
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..151ab41b2b9 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,24 +42,25 @@ 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.
@@ -69,7 +69,7 @@ Once `len(formula) == 1`, the first element (`formula[0]`) is converted to an `i
```python
-OPERATIONS = {"plus": '+', "minus": '-', "multiplied": '*', "divided": '/'}
+OPERATIONS = {"plus": "+", "minus": "-", "multiplied": "*", "divided": "/"}
def answer(question):
@@ -110,21 +110,21 @@ def answer(question):
````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][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
````
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.
@@ -134,17 +134,16 @@ 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":
```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
@@ -153,27 +152,27 @@ There are a couple of common alternatives to the `loop-append` used here:
[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: 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`:
- ```python
+```python
x_value, operation, y_value, *remainder = formula # <-- Unpacking won't allow conversion to int() here.
...
- if symbol == "+":
- formula = [int(x_value) + int(y_value)] + remainder # <-- Instead, conversion to int() must happen here.
+ 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`
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
@@ -189,29 +188,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
...
...
From d764d802617492762f6429b266536ab50bc95e14 Mon Sep 17 00:00:00 2001
From: Yrahcaz7 <74512479+Yrahcaz7@users.noreply.github.com>
Date: Thu, 21 May 2026 11:39:40 -0400
Subject: [PATCH 2/9] `import-callables-from-operator` cleanup
also more formatting fixes
---
.../practice/wordy/.approaches/config.json | 20 +++--
.../dunder-getattribute/content.md | 2 -
.../.approaches/functools-reduce/content.md | 32 ++++---
.../import-callables-from-operator/content.md | 32 ++++---
.../snippet.txt | 2 +-
.../wordy/.approaches/introduction.md | 83 +++++++------------
.../lambdas-in-a-dictionary/content.md | 12 ++-
.../lambdas-in-a-dictionary/snippet.txt | 10 +--
.../wordy/.approaches/recursion/content.md | 18 ++--
.../regex-with-operator-module/content.md | 13 ++-
.../string-list-and-dict-methods/content.md | 27 +++---
11 files changed, 108 insertions(+), 143 deletions(-)
diff --git a/exercises/practice/wordy/.approaches/config.json b/exercises/practice/wordy/.approaches/config.json
index 417458430a4..43164f352c8 100644
--- a/exercises/practice/wordy/.approaches/config.json
+++ b/exercises/practice/wordy/.approaches/config.json
@@ -17,42 +17,48 @@
"slug": "import-callables-from-operator",
"title": "Import Callables from the Operator Module",
"blurb": "Use Operator Module Methods to Solve Word Problems.",
- "authors": ["BethanyG"]
+ "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"]
+ "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"]
+ "authors": ["BethanyG"],
+ "contributors": ["yrahcaz7"]
},
{
"uuid": "2e643b88-9b76-45a1-98f4-b211919af061",
"slug": "recursion",
"title": "Recursion for Iteration.",
"blurb": "Use recursion with other strategies to solve word problems.",
- "authors": ["BethanyG"]
+ "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__",
"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..4e0c81aeeb7 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__",
@@ -35,7 +34,6 @@ def answer(question):
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.
diff --git a/exercises/practice/wordy/.approaches/functools-reduce/content.md b/exercises/practice/wordy/.approaches/functools-reduce/content.md
index b8f51710ffd..892752763ea 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
@@ -39,28 +38,26 @@ 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
+ # The wrapping () invoke implicit concatenation for the chained functions.
return (question.removeprefix("What is")
.removesuffix("?")
.replace("by", "")
.strip()).split() # <-- This split() turns the string into a list.
-
-
- # Alternative 2 to the nested calls to filter and split is to use a list-comprehension:
+
+
+ # Alternative 2 to the nested calls to filter and split is to use a list comprehension:
return [item for item in
question.strip("?").split()
if item not in ("What", "is", "by")] # <-- The [] of the comprehension invokes implicit concatenation.
```
-
Since "valid" questions are all in the form of `digit-operator-digit` (_and so on_), it is safe to assume that every other element beginning at index 0 is a "number", and every other element beginning at index 1 is an operator.
By that definition, the operators `list` is 1 shorter in `len()` than the digits list.
Anything else (_or having None/an unknown operation in the operations list_) is a `ValueError("syntax error")`.
@@ -75,24 +72,23 @@ It could be argued that writing the code as a `while-loop` or recursive function
-## 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 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:
raise ValueError("unknown operation")
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 7622dcfee8f..63c8f2563c4 100644
--- a/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md
+++ b/exercises/practice/wordy/.approaches/import-callables-from-operator/content.md
@@ -1,6 +1,5 @@
# Import Callables from the Operator Module
-
```python
from operator import add, mul, sub
from operator import floordiv as div
@@ -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][callables] 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()
@@ -79,13 +75,13 @@ equation = question.replace("by", "").split()
[aliasing]: https://mimo.org/glossary/python
[approach-string-list-and-dict-methods]: https://exercism.org/tracks/python/exercises/wordy/approaches/string-list-and-dict-methods
-[callable]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/
+[callables]: https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/
[chaining-method-calls]: https://nikhilakki.in/understanding-method-chaining-in-python
[handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions
[implicit-concatenation]: https://docs.python.org/3/reference/lexical_analysis.html#implicit-line-joining
[list-comprehension]: https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions
[operator]: https://docs.python.org/3/library/operator.html#module-operator
-[pythontutor]: https://pythontutor.com/render.html#code=from%20operator%20import%20add,%20mul,%20sub%0Afrom%20operator%20import%20floordiv%20as%20div%0A%0AOPERATIONS%20%3D%20%7B%22plus%22%3A%20add,%20%22minus%22%3A%20sub,%20%22multiplied%22%3A%20mul,%20%22divided%22%3A%20div%7D%0A%0Adef%20answer%28question%29%3A%0A%20%20%20%20if%20not%20question.startswith%28%22What%20is%22%29%20or%20%22cubed%22%20in%20question%3A%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22unknown%20operation%22%29%0A%20%20%20%20%0A%20%20%20%20question%20%3D%20question.removeprefix%28%22What%20is%22%29.removesuffix%28%22%3F%22%29.strip%28%29%0A%0A%20%20%20%20if%20question.isdigit%28%29%3A%20%0A%20%20%20%20%20%20%20%20return%20int%28question%29%0A%20%20%20%20%0A%20%20%20%20if%20not%20question%3A%20%0A%20%20%20%20%20%20%20%20raise%20ValueError%28%22syntax%20error%22%29%0A%20%20%20%20%0A%20%20%20%20equation%20%3D%20%5Bword%20for%20word%20in%20question.split%28%29%20if%20word%20!%3D%20'by'%5D%0A%20%20%20%20%0A%20%20%20%20while%20len%28equation%29%20%3E%201%3A%0A%20%20%20%20%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20x_value,%20operation,%20y_value,%20*rest%20%3D%20equation%0A%20%20%20%20%20%20%20%20%20%20%20%20equation%20%3D%20%5BOPERATIONS%5Boperation%5D%28int%28x_value%29,%20int%28y_value%29%29,%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20*rest%5D%0A%20%20%20%20%20%20%20%20except%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%28%22syntax%20error%22%29%0A%20%20%20%20%0A%20%20%20%20return%20equation%5B0%5D%0A%20%20%20%20%0Aprint%28answer%28%22What%20is%202%20plus%202%20plus%203%3F%22%29%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
+[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 5234061bf7d..b23518eff86 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`][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.
@@ -45,59 +42,52 @@ 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.
@@ -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:
@@ -160,9 +148,10 @@ def answer(question):
This approach uses only data structures and methods (_[`str` methods][str-methods], [`list()`][list], loops, etc._) from core Python, and does not import any extra modules.
It may have more lines of code than average, but it is clear to follow and fairly straightforward to reason about.
-This approach uses a [`try-except`][handling-exceptions] statment for handling unknown operators.
+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.)
+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.
@@ -172,7 +161,6 @@ For more details and variations, read the [String, List and Dictionary Methods][
## Approach: Import Callables from the Operator Module
-
```python
from operator import add, mul, sub
from operator import floordiv as div
@@ -191,7 +179,7 @@ def answer(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:
@@ -205,8 +193,8 @@ 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.
@@ -214,28 +202,28 @@ For more details and options, take a look at the [Import Callables from the Oper
## Approach: Regex and the Operator Module
-
```python
import re
from operator import add, mul, sub
from operator import floordiv as div
OPERATIONS = {"plus": add, "minus": sub, "multiplied by": mul, "divided by": div}
+
REGEX = {
- 'number': re.compile(r'-?\d+'),
- 'operator': re.compile(f'(?:{"|".join(OPERATIONS)})\\b')
+ "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(),
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(),
@@ -250,7 +238,7 @@ def answer(question):
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)
@@ -261,7 +249,6 @@ 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.
@@ -273,14 +260,13 @@ For more details, take a look at the [regex-with-operator-module][approach-regex
## 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):
@@ -295,7 +281,7 @@ def answer(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:
@@ -308,7 +294,6 @@ 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.
These `lambdas` then return a function that takes two numbers as arguments, returning the result.
@@ -323,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
@@ -356,8 +340,7 @@ def calculate(equation):
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 `loops` that append to a `list` -- `recursion` can be substituted for the `while-loop` that many solutions use to process a parsed word problem.
Depending on who is reading the code, `recursion` may or may not be easier to reason about.
It may also be more (_or less!_) performant than using a `while-loop` or `functools.reduce` (_see below_), depending on how the various cleaning and error-checking actions are performed.
@@ -367,8 +350,7 @@ For more details, take a look at the [recursion][approach-recursion] approach.
-## Approach: functools.reduce()
-
+## Approach: `functools.reduce()`
```python
from operator import add, mul, sub
@@ -399,9 +381,8 @@ 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`.
@@ -412,7 +393,6 @@ For more details and variations, take a look at the [functools.reduce for Calcul
## Approach: Dunder methods with `__getattribute__`
-
```python
OPS = {
"plus": "__add__",
@@ -447,7 +427,6 @@ def answer(question):
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 [`