fnumber and snumber (short for "first" and "second") are non-obvious, and should just use something else: 1number_1, or more easily just x and 2y instead of letterssince it's pretty obvious what's going on.
import operator
from typing import Sequence, NamedTuple, Literal
ops = {"+": operator.add, "-": operator.sub}
class Problem(NamedTuple):
number_1x: int
number_2y: int
op: Literal['+', '-']
@classmethod
def parse(cls, s: str) -> 'Problem':
operand_1x, op, operand_2y = s.split()
try:
for number_1n =in int(operand_1x, y):
number_2if =not intn.isdigit(operand_2):
except ValueError as e:
raise ValueError('Error: Numbers must only contain digits.') from e
return cls(number_1=number_1x=int(x), number_2=number_2y=int(y), op=op)
def validate(self) -> None:
for n in (self.number_1x, self.number_2y):
if abs(n) >= 1e4:
raise ValueError('Error: Number cannot be more than four digits.')
if self.op not in ops:
raise ValueError(
'Error: Operator must be '
+ ' or '.join(f"'{o}'" for o in ops.keys())
)
def format_lines(self, solve: bool = False) -> tuple[str, ...]:
longest = max(self.number_1x, self.number_2y)
width = len(str(longest))
lines = (
f'{self.number_1x:>{width+2width + 2}}',
f'{self.op} {self.number_2y:>{width}}',
f'{"":->{width+2}}',
)
if solve:
lines += (
f'{self.answer:>{width+2}}',
)
return lines
@property
def answer(self) -> int:
return ops[self.op](self.number_1x, self.number_2y)
def arithmetic_arranger(problem_strings: Sequence[str], solve: bool = False) -> None:
if len(problem_strings) > 5:
print('Error: Too many problems.')
return
try:
problems = [Problem.parse(s) for s in problem_strings]
for problem in problems:
problem.validate()
except ValueError as e:
print(e)
return
groupslines = zip(*(p.format_lines(solve) for p in problems))
print(
'\n'.join(
' '.join(groupgroups) for groupgroups in groupslines
)
)
if __name__ == "__main__":
arithmetic_arranger((
"32 + 698",
"3801 - 2",
"45"4 + 43"4553",
"123 + 49",
"1234 - 9876"
), solve=Falsesolve=True)