|
| 1 | +""" |
| 2 | +Parse and evaluate simple math word problems |
| 3 | +returning the answer as an integer. |
| 4 | +
|
| 5 | +Handle a set of operations, in sequence. |
| 6 | +
|
| 7 | +Since these are verbal word problems, evaluate |
| 8 | +the expression from left-to-right, ignoring the |
| 9 | +typical order of operations. |
| 10 | +""" |
| 11 | + |
| 12 | +OPERATORS: tuple = ("plus", "minus", "multiplied", "divided") |
| 13 | +REPLACEABLE: tuple = ("What", "is", "by") |
| 14 | + |
| 15 | + |
| 16 | +def answer(question: str) -> int: |
| 17 | + """ |
| 18 | + Evaluate a simple word-based arithmetic question from left to right. |
| 19 | +
|
| 20 | + :param question: The input question, e.g., "What is 5 plus 13?" |
| 21 | + :type question: str |
| 22 | + :returns: The evaluated integer result. |
| 23 | + :rtype: int |
| 24 | + :raises ValueError: If the operation is unknown or the syntax is invalid. |
| 25 | + """ |
| 26 | + new_question: list[str] = [ |
| 27 | + word |
| 28 | + for word in question.replace("?", "").split() |
| 29 | + if word not in REPLACEABLE |
| 30 | + ] |
| 31 | + # Reduce iteratively: |
| 32 | + # evaluate the first two-token slice and fold the result left-to-right. |
| 33 | + try: |
| 34 | + result, new_question = int(new_question[0]), new_question[1:] |
| 35 | + while new_question: |
| 36 | + _err_check(new_question) |
| 37 | + result, new_question = ( |
| 38 | + _math_operation(result, new_question), |
| 39 | + new_question[2:], |
| 40 | + ) |
| 41 | + except ValueError as exc: |
| 42 | + if exc.args[0] == "unknown operation": |
| 43 | + raise exc |
| 44 | + raise ValueError("syntax error") from exc |
| 45 | + except IndexError as exc: |
| 46 | + raise ValueError("syntax error") from exc |
| 47 | + |
| 48 | + return result |
| 49 | + |
| 50 | + |
| 51 | +def _math_operation(result: int, question: list[str]) -> int: |
| 52 | + """ |
| 53 | + Compute a single binary arithmetic step for the current operator. |
| 54 | +
|
| 55 | + Expects the next tokens of the question to begin with the operator word |
| 56 | + (e.g. ``plus``, ``minus``, ``multiplied``, ``divided``) followed by the |
| 57 | + right-hand side operand. Division uses floor division (``//``) to comply |
| 58 | + with the exercise rules. |
| 59 | +
|
| 60 | + :param result: Accumulated left-hand value computed so far. |
| 61 | + :type result: int |
| 62 | + :param question: Remaining tokens starting with the operator then rhs |
| 63 | + integer, e.g. ``['plus', '4', ...]``. |
| 64 | + :type question: list[str] |
| 65 | + :returns: Accumulated result after consuming the operator and rhs. |
| 66 | + :rtype: int |
| 67 | + """ |
| 68 | + math_operator: str = question[0] |
| 69 | + operand: int = int(question[1]) |
| 70 | + |
| 71 | + match math_operator: |
| 72 | + case "plus": |
| 73 | + result += operand |
| 74 | + case "minus": |
| 75 | + result -= operand |
| 76 | + case "divided": |
| 77 | + result //= operand |
| 78 | + case "multiplied": |
| 79 | + result *= operand |
| 80 | + |
| 81 | + return result |
| 82 | + |
| 83 | + |
| 84 | +def _err_check(question: list[str]) -> None: |
| 85 | + """ |
| 86 | + Validate the upcoming operator/operand tokens and raise appropriate errors. |
| 87 | +
|
| 88 | + :param question: Remaining tokens starting at the operator; used to detect |
| 89 | + malformed sequences (e.g., missing rhs integer). |
| 90 | + :type question: list[str] |
| 91 | + :raises ValueError: If an unknown operation is encountered or the token |
| 92 | + sequence is syntactically invalid. |
| 93 | + """ |
| 94 | + math_operator: str = question[0] |
| 95 | + # if the question contains an unknown operation. |
| 96 | + if math_operator not in OPERATORS and not math_operator.isdigit(): |
| 97 | + raise ValueError("unknown operation") |
| 98 | + # if the question is malformed or invalid. |
| 99 | + if math_operator.isdigit(): |
| 100 | + raise ValueError("syntax error") |
| 101 | + # if the question is malformed or invalid. |
| 102 | + if math_operator in OPERATORS and len(question) == 1: |
| 103 | + raise ValueError("syntax error") |
0 commit comments