Skip to main content
deleted 82 characters in body
Source Link
Reinderien
  • 71.1k
  • 5
  • 76
  • 256

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)

fnumber and snumber (short for "first" and "second") are non-obvious, and should just use 1 and 2 instead of letters.

import operator
from typing import Sequence, NamedTuple, Literal

ops = {"+": operator.add, "-": operator.sub}


class Problem(NamedTuple):
    number_1: int
    number_2: int
    op: Literal['+', '-']

    @classmethod
    def parse(cls, s: str) -> 'Problem':
        operand_1, op, operand_2 = s.split()
 
        try:
            number_1 = int(operand_1)
            number_2 = int(operand_2)
        except ValueError as e:
            raise ValueError('Error: Numbers must only contain digits.') from e

        return cls(number_1=number_1, number_2=number_2, op=op)

    def validate(self) -> None:
        for n in (self.number_1, self.number_2):
            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_1, self.number_2)
        width = len(str(longest))
        lines = (
            f'{self.number_1:>{width+2}}',
            f'{self.op} {self.number_2:>{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_1, self.number_2)


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

    groups = zip(*(p.format_lines(solve) for p in problems))
    print(
        '\n'.join(
            '    '.join(group) for group in groups
        )
    )


if __name__ == "__main__":
    arithmetic_arranger((
        "32 + 698",
        "3801 - 2",
        "45 + 43",
        "123 + 49",
    ), solve=False)

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.

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)
Source Link
Reinderien
  • 71.1k
  • 5
  • 76
  • 256

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 just use 1 and 2 instead of letters.

One potential refactor could look like:

  • Create a simple class representing a problem
  • On the class, have a parse that loads a class instance from a string
  • If parse fails with a ValueError, the string isn't parseable: bail.
  • If parse succeeds but validate() 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, zip the lines of all of the problems together, join the lines with \n and 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):
    number_1: int
    number_2: int
    op: Literal['+', '-']

    @classmethod
    def parse(cls, s: str) -> 'Problem':
        operand_1, op, operand_2 = s.split()

        try:
            number_1 = int(operand_1)
            number_2 = int(operand_2)
        except ValueError as e:
            raise ValueError('Error: Numbers must only contain digits.') from e

        return cls(number_1=number_1, number_2=number_2, op=op)

    def validate(self) -> None:
        for n in (self.number_1, self.number_2):
            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_1, self.number_2)
        width = len(str(longest))
        lines = (
            f'{self.number_1:>{width+2}}',
            f'{self.op} {self.number_2:>{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_1, self.number_2)


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

    groups = zip(*(p.format_lines(solve) for p in problems))
    print(
        '\n'.join(
            '    '.join(group) for group in groups
        )
    )


if __name__ == "__main__":
    arithmetic_arranger((
        "32 + 698",
        "3801 - 2",
        "45 + 43",
        "123 + 49",
    ), solve=False)