Skip to content

Commit

Permalink
[Wordy]: Moar Typos & Missing Links. (#3789)
Browse files Browse the repository at this point in the history
* Moar typos and missing links.  Hopefully, this is the last round.

* Added in missing hints file.

* Further touchup on intro md.
  • Loading branch information
BethanyG authored Oct 15, 2024
1 parent 63cc10b commit c592280
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 19 deletions.
54 changes: 35 additions & 19 deletions exercises/practice/wordy/.approaches/introduction.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,45 @@
# Introduction

The objective of the Wordy exercise is to parse and evaluate small/simple mathematical word problems, returning the result as an integer.
These problems do not require complex or [PEMDAS][PEMDAS]-based evaluation and are instead processed from left-to-right _in sequence_.
These questions do not require complex or [PEMDAS][PEMDAS]-based evaluation and are instead processed from left-to-right _in sequence_.
This means that for some of the test cases, the solution will not be the same as if the word problem was evaluated like a traditional math problem.

<br>


## General Guidance

The key to a Wordy solution is to remove the "question" portion of the sentence (_"What is", "?"_) and process the remaining words between numbers as [operators][mathematical operators].


If a single number remains after removing the "question" pieces, it should be converted to an [`int`][int] and returned as the answer.


Any words or word-number combinations that do not fall into the simple mathematical evaluation pattern (_number-operator-number_) should [`raise`][raise-statement] a [`ValueError`][value-error] with a message.
Any words or word-number combinations that do not fall into the simple mathematical evaluation pattern (_number-operator-number_) should [`raise`][raise-statement] a ["ValueError('syntax error")`][value-error] with a message.
This includes any "extra" spaces between numbers.


One way to reduce the number of `raise` statements/ `ValueError`s needed in the code is to determine if a problem is a "valid" question _before_ proceeding to parsing and calculation.
As shown in various approaches, there are multiple strategies for validating questions, with no one "canonical" solution.


One very effective validation approach is to check if a question starts with "What is", ends with "?", and does not include the word "cubed".
A whole class of error can be eliminated up front by checking if a question starts with "What is", ends with "?", and does not include the word "cubed".
Any other question formulation becomes a `ValueError("unknown operation")`.
This very restrictive approach could lead to future maintenance issues if the definition of a question ever changes or operations are added, but for the purposes of passing the current Wordy tests, it works well.
This could lead to future maintenance issues if the definition of a question ever changes or operations are added, but for the purposes of passing the current Wordy tests, it works well.


Proceeding from validation, there are many Pythonic ways to go about the cleaning, parsing, and calculation steps of Wordy.
However, they all follow these general steps:
~~~~exercism/note
There are many Pythonic ways to go about the cleaning, parsing, and calculation steps of Wordy.
However, the solutions all follow these general steps:
1. Remove the parts of the question string that do not apply to calculating the answer.
2. Iterate over the question, determining which words are numbers, and which are meant to be mathematical operations.
- _Converting the question string into a `list` of words is hugely helpful here, but not absolutely necessary._
3. **_Starting from the left_**, take the first three elements and convert number strings to `int` and operations words to +, -, *, /.
-- _Converting the question string into a `list` of words is hugely helpful here._
3. **_Starting from the left_**, take the first three elements and convert number strings to `int` and operations words to the mathematical operations +, -, *, and /.
4. Apply the operation to the numbers, which should result in a single number.
- _Employing a `try-except` block around the conversion and operator application steps can trap any errors thrown and make the code both "safer" and less complex._
-- _Employing a `try-except` block can trap any errors thrown and make the code both "safer" and less complex._
5. Use the calculated number from step 4 as the start for the next "trio" (_number, operation, number_) in the question. The calculated number + the remainder of the question becomes the question being worked on in the next iteration.
- _Using a `while-loop` with a test on the length of the question to do calculation is a very common strategy._
-- _Using a `while-loop` with a test on the length of the question to do calculation is a very common strategy._
6. Once the question is calculated down to a single number, that is the answer. Anything else that happens in the loop/iteration or within the accumulated result is a `ValueError("syntax error")`.
~~~~


For cleaning the question, [`str.removeprefix`][removeprefix] and
For question cleaning, [`str.removeprefix`][removeprefix] and
[`str.removesuffix`][removesuffix] introduced in `Python 3.9` can be very useful:


Expand Down Expand Up @@ -79,20 +78,37 @@ Different combinations of [`str.find`][find], [`str.rfind`][rfind], or [`str.ind
A [regex][regex] could be used to process the question as well, but might be considered overkill given the fixed nature of the prefix/suffix and operations.
Finally, [`str.strip`][strip] and its variants are very useful for cleaning up any leftover leading or trailing whitespace.

Many solutions then use [`str.split`][split] to process the remaining "cleaned" question into a `list` for convenient looping/iteration, although other strategies can also be used.
Many solutions then use [`str.split`][split] to process the remaining "cleaned" question into a `list` for convenient looping/iteration, although other strategies can also be used:


For math operations, many solutions involve importing and using methods from the [operator][operator] module in combination with different looping, parsing, and substitution strategies.
```python
>>> sentence = "The quick brown fox jumped over the lazy dog 10 times"
>>> sentence.split()
['The', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dog', '10', 'times']
```


For math operations, many solutions involve importing and using methods from the [operator][operator] module.
Some solutions use either [lambda][lambdas] expressions, [dunder/"special" methods][dunder-methods], or even `eval()` to replace words with arithmetic operations.
However, the exercise can be solved **without** using `operator`, `lambdas`, `dunder-methods` or `eval`.


However, the exercise can be solved without using `operator`, `lambdas`, `dunder-methods` or `eval`.
It is recommended that you first start by solving it _without_ "advanced" strategies, and then refine your solution into something more compact or complex as you learn and practice.


~~~~exercism/caution
Using [`eval`][eval] for the operations might seem convenient, but it is a [dangerous][eval-danger] and possibly [destructive][eval-destructive] approach.
It is also entirely unnecessary, as the other methods described here are safer and equally performant.
[eval-danger]: https://softwareengineering.stackexchange.com/questions/311507/why-are-eval-like-features-considered-evil-in-contrast-to-other-possibly-harmfu
[eval-destructive]: https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
[eval]: https://docs.python.org/3/library/functions.html?#eval
~~~~

<br>

_____________


## Approach: String, List, and Dictionary Methods

Expand Down
40 changes: 40 additions & 0 deletions exercises/practice/wordy/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Hints

## General

- This challenge is all about validating, "cleaning up", and processing the given question in the **_correct order_**.
- If you read the directions and tests carefully, you will find there are three conditions that need to be met for a question to be "valid".
If a question is not valid, you will need to [raise ][raise-statement] a [ValueError][value-error] with a message (_`ValueError("unknown operation")`_).
It is best if you get this particular check out of the way before doing anything else.
- Processing a question before calculating the answer is all about utilizing [string methods][str-methods].
A few popular ones to investigate include `str.removeprefix`, `str.removesuffix`, `str.replace`, and `str.strip`.
Others you might want to check out are `str.startswith`, `str.endswith`, and `in` (_which can apply to more than just strings_).

- It is possible to iterate over a string. However, it is **much** easier to iterate over a list of _words_ if the string you are processing is a sentence or fragment with spaces. [`str.split`][split] can break apart a string and return a list of "words".
- A [`while-loop`][while-loop] is very useful for iterating over the question to process items.
- For fewer error checks and cleaner error-handling, a [`try-except`][handling-exceptions] is recommended as you process the question.
- **Remember**: the question is processed **_left-to-right_**. That means "1 plus 12 minus 3 multiplied by 4" gets processed by:
- Calculating "1 plus 12" (13),
- Calculating "13 minus 3" (10),
- Calculating "10 multiplied by 4" (40).
- The result of the first calculation is _concatenated with the remainder of the question to form a new question for the next step_.
- This technique is sometimes called [the accumulator pattern][accumulator-pattern], or [fold][fold] / [foldl][foldl] in functional programming languages like [Haskell][haskell-folds] or Lisp.
- Python includes two methods that are purpose-built to apply the `accumulator-pattern` to iterable data structures like `lists`, `tuples`, and `strings`.
[`functools.reduce`][reduce] and [`itertools.accumulate`][accumulate] could be interesting to investigate here.

- This exercise has many potential solutions and many paths you can take along the way.
No path is manifestly "better" than another, although a particular path may be more interesting or better suited to what you want to learn or explore right now.


[accumulate]: https://docs.python.org/3/library/itertools.html#itertools.accumulate
[accumulator-pattern]: https://muzny.github.io/csci1200-notes/08/2/accumulator.html
[fold]: https://en.wikipedia.org/wiki/Fold_(higher-order_function)
[foldl]: https://slim.computer/eecs-111-ta-guide/material/higher-order/Fold.html
[handling-exceptions]: https://docs.python.org/3.11/tutorial/errors.html#handling-exceptions
[haskell-folds]: https://www.ashwinnarayan.com/post/a-study-on-haskell-folds/
[raise-statement]: https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement
[reduce]: https://docs.python.org/3/library/functools.html#functools.reduce
[split]: https://docs.python.org/3.9/library/stdtypes.html#str.split
[str-methods]: https://docs.python.org/3/library/stdtypes.html#string-methods
[value-error]: https://docs.python.org/3.11/library/exceptions.html#ValueError
[while-loop]: https://python-practice.com/learn/loops/while_loop/

0 comments on commit c592280

Please sign in to comment.