Skip to content

Commit ccd9136

Browse files
authored
Merge pull request #177 from ikostan/exercism-sync/560543585f11467b
[Sync Iteration] python/wordy/3
2 parents 79f15a4 + 7daf3c7 commit ccd9136

File tree

1 file changed

+153
-0
lines changed

1 file changed

+153
-0
lines changed

solutions/python/wordy/3/wordy.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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+
STR_TO_OPERATOR: dict = {
13+
"plus": "+",
14+
"minus": "-",
15+
"multiplied": "*",
16+
"divided": "/",
17+
}
18+
19+
WRONG_OPERATORS: list[str] = [
20+
"plus?",
21+
"minus?",
22+
"multiplied?",
23+
"divided?",
24+
"plus plus",
25+
"plus multiplied",
26+
"minus multiplied",
27+
"minus minus",
28+
"multiplied multiplied",
29+
"divided divided",
30+
"What is?",
31+
]
32+
33+
34+
def answer(question: str) -> int:
35+
"""
36+
Evaluate a simple word-based arithmetic question from left to right.
37+
38+
:param question: The input question, e.g., "What is 5 plus 13?"
39+
:type question: str
40+
:returns: The evaluated integer result.
41+
:rtype: int
42+
:raises ValueError: If the operation is unknown or the syntax is invalid.
43+
"""
44+
_validate_errors(question)
45+
result: int = 0
46+
new_question: list[str] = _reformat(question)
47+
48+
if len(new_question) <= 3:
49+
try:
50+
_validate_evaluation_pattern(new_question)
51+
eval_str: str = "".join(new_question)
52+
result = eval(eval_str)
53+
return result
54+
except Exception as exc:
55+
raise ValueError("syntax error") from exc
56+
57+
# Reduce iteratively: evaluate the first three-token slice
58+
# and fold the result left-to-right.
59+
while len(new_question) >= 3:
60+
try:
61+
_validate_evaluation_pattern(new_question[:3])
62+
eval_str: str = "".join(new_question[:3])
63+
val: int = eval(eval_str)
64+
result = val
65+
new_question = [str(result)] + new_question[3:]
66+
if len(new_question) < 3:
67+
_validate_evaluation_pattern(new_question)
68+
return eval("".join(new_question))
69+
except Exception as exc:
70+
raise ValueError("syntax error") from exc
71+
return result
72+
73+
74+
def _validate_evaluation_pattern(val: list) -> None:
75+
"""
76+
Ensure a token slice matches expected evaluation patterns.
77+
78+
:param val: Token slice to validate, e.g.,
79+
['3', '+', '4'] or ['+', '4'] during reduction.
80+
:type val: list
81+
:raises ValueError: If the pattern is invalid (syntax error).
82+
"""
83+
if len(val) == 3 and val[1] not in STR_TO_OPERATOR.values():
84+
raise ValueError("syntax error")
85+
86+
if len(val) == 2 and val[0] not in STR_TO_OPERATOR.values():
87+
raise ValueError("syntax error")
88+
89+
90+
def _reformat(question: str) -> list:
91+
"""
92+
Tokenize a natural-language math question into numbers
93+
and operator symbols.
94+
95+
:param question: Raw question string.
96+
:type question: str
97+
:returns: Token list with numbers and operator symbols.
98+
:rtype: list[str]
99+
"""
100+
# 1: Remove '?' mark
101+
question = question.replace("?", "")
102+
# 2: Convert all operators writen in word into proper math sign
103+
question_list: list[str] = question.split()
104+
formated_question_list: list[str] = []
105+
for item in question_list:
106+
if not (
107+
item.isdigit()
108+
or item in STR_TO_OPERATOR
109+
or item in STR_TO_OPERATOR.values()
110+
or any(val in item for val in STR_TO_OPERATOR.values())
111+
):
112+
continue
113+
114+
if item in STR_TO_OPERATOR:
115+
formated_question_list.append(STR_TO_OPERATOR[item])
116+
elif item.isdigit():
117+
formated_question_list.append(item)
118+
elif item in STR_TO_OPERATOR.values():
119+
formated_question_list.append(item)
120+
elif any(val in item for val in STR_TO_OPERATOR.values()):
121+
formated_question_list.append(item)
122+
123+
return formated_question_list
124+
125+
126+
def _validate_errors(question: str) -> None:
127+
"""
128+
Pre-validate unsupported or malformed questions.
129+
130+
:param question: Raw question string.
131+
:type question: str
132+
:raises ValueError: Unknown operation or malformed
133+
phrasing (syntax error).
134+
"""
135+
if "cubed" in question:
136+
raise ValueError("unknown operation")
137+
138+
for item in WRONG_OPERATORS:
139+
if item in question:
140+
raise ValueError("syntax error")
141+
142+
digits: list[bool] = []
143+
for char in question:
144+
if char.isdigit():
145+
digits.append(True)
146+
147+
operators: list[bool] = []
148+
for key, val in STR_TO_OPERATOR.items():
149+
if key in question or val in question:
150+
operators.append(True)
151+
152+
if not any(digits + operators):
153+
raise ValueError("unknown operation")

0 commit comments

Comments
 (0)