Use PEP484 type hints.
The too-many-problems and too-many-digits constraints are not good ones, but whatever: we'll keep them because the problem asks so.
You call split() three times when you should only call it once and tuple-unpack to three substrings.
You should not make literal comparisons of the operator to potential strings, nor should you include + and - verbatim in the error message. Instead, derive these from your ops dictionary (you adding it was a good idea!)
Where possible, pass validation problems from subroutines to your main routine using exceptions.
Your len(fnumber) is technically not correct if they expand the problem to allow negative numbers. The minus sign constitutes another character. They want you to limit digit count, not character count. I think a safer check would be comparing to abs() >= 1e4.
fnumber and snumber (short for "first" and "second") are non-obvious, and should use something else: number_1, or more easily just x and y since it's pretty obvious what's going on.
One potential refactor could look like:
- Create a simple class representing a problem
- On the class, have a
parsethat loads a class instance from a string - If
parsefails with aValueError, the string isn't parseable: bail. - If
parsesucceeds butvalidate()fails, the string is parseable but invalid: bail. - Write a formatting method that returns a tuple of lines for one problem.
- In your upper method,
zipthe lines of all of the problems together, join the lines with\nand the groups among each line with however many spaces you want (looks like 4).
Suggested
import operator
from typing import Sequence, NamedTuple, Literal
ops = {"+": operator.add, "-": operator.sub}
class Problem(NamedTuple):
x: int
y: int
op: Literal['+', '-']
@classmethod
def parse(cls, s: str) -> 'Problem':
x, op, y = s.split()
for n in (x, y):
if not n.isdigit():
raise ValueError('Error: Numbers must only contain digits.')
return cls(x=int(x), y=int(y), op=op)
def validate(self) -> None:
for n in (self.x, self.y):
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.x, self.y)
width = len(str(longest))
lines = (
f'{self.x:>{width + 2}}',
f'{self.op} {self.y:>{width}}',
f'{"":->{width+2}}',
)
if solve:
lines += (
f'{self.answer:>{width+2}}',
)
return lines
@property
def answer(self) -> int:
return ops[self.op](self.x, self.y)
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
lines = zip(*(p.format_lines(solve) for p in problems))
print(
'\n'.join(
' '.join(groups) for groups in lines
)
)
if __name__ == "__main__":
arithmetic_arranger((
"32 + 698",
"3801 - 2",
"4 + 4553",
"123 + 49",
"1234 - 9876"
), solve=True)