Вы можете легко использовать operator
и dict
для хранения операций вместо длинного списка if-else
Это решение также может вычислять более сложные выражения с помощью рекурсии.
Определить операции и их порядок
from operator import add, sub, mul, floordiv, truediv
from functools import reduce
OPERATIONS = {
'+': add,
'-': sub,
'*': mul,
'/': floordiv, # or '/': truediv,
'//': floordiv,
}
OPERATION_ORDER = (('+', '-'), ('//', '/', '*'))
Простой случай одного числа
def calculate(expression):
# expression = expression.strip()
try:
return int(expression)
except ValueError:
pass
Расчет
for operations in OPERATION_ORDER:
for operation in operations:
if operation not in expression:
continue
parts = expression.split(operation)
parts = map(calculate, parts) # recursion
value = reduce(OPERATIONS[operation], parts)
# print(expression, value)
return value
Отрицательное первое число
до вычисления:
negative = False
if expression[0] == '-':
negative = True
expression = expression[1:]
в расчете, после разбиения строки:
if negative:
parts[0] = '-' + parts[0]
В итоге получается:
def calculate(expression):
try:
return int(expression)
except ValueError:
pass
negative = False
if expression[0] == '-':
negative = True
expression = expression[1:]
for operations in OPERATION_ORDER:
for operation in operations:
if operation not in expression:
continue
parts = expression.split(operation)
if negative:
parts[0] = '-' + parts[0]
parts = map(calculate, parts) # recursion
return reduce(OPERATIONS[operation], parts)
Круглые скобки
Проверка наличия круглых скобок может быть легко выполнена с помощью re
.Сначала нам нужно убедиться, что он не распознает «простые» промежуточные результаты в скобках (например, (-1)
)
PATTERN_PAREN_SIMPLE= re.compile('\((-?\d+)\)')
PAREN_OPEN = '|'
PAREN_CLOSE = '#'
def _encode(expression):
return PATTERN_PAREN_SIMPLE.sub(rf'{PAREN_OPEN}\1{PAREN_CLOSE}', expression)
def _decode(expression):
return expression.replace(PAREN_OPEN, '(').replace(PAREN_CLOSE, ')')
def contains_parens(expression):
return '(' in _encode(expression)
Затем, чтобы вычислить крайние левые внешние скобки, вы можете использовать эту функцию
def _extract_parens(expression, func=calculate):
# print('paren: ', expression)
expression = _encode(expression)
begin, expression = expression.split('(', 1)
characters = iter(expression)
middle = _search_closing_paren(characters)
middle = _decode(''.join(middle))
middle = func(middle)
end = ''.join(characters)
result = f'{begin}({middle}){end}' if( begin or end) else str(middle)
return _decode(result)
def _search_closing_paren(characters, close_char=')', open_char='('):
count = 1
for char in characters:
if char == open_char:
count += 1
if char == close_char:
count -= 1
if not count:
return
else:
yield char
Причина ()
вокруг calculate(middle)
заключается в том, что промежуточный результат может быть отрицательным, и это может создать проблемы позже, если оставить здесь круглые скобки.
НачалоЗатем алгоритм изменяется на:
def calculate(expression):
expression = expression.replace(' ', '')
while contains_parens(expression):
expression = _extract_parens(expression)
if PATTERN_PAREN_SIMPLE.fullmatch(expression):
expression = expression[1:-1]
try:
return int(expression)
except ValueError:
pass
Поскольку промежуточные результаты могут быть отрицательными, нам необходимо разделить регулярные выражения на -
, чтобы предотвратить разбиение 5 * (-1)
на -
Поэтому я переупорядочил возможные операции следующим образом:
OPERATIONS = (
(re.compile('\+'), add),
(re.compile('(?<=[\d\)])-'), sub), # not match the - in `(-1)`
(re.compile('\*'), mul),
(re.compile('//'), floordiv),
(re.compile('/'), floordiv), # or '/': truediv,
)
Шаблон для -
соответствует, только если ему предшествует )
или цифра.Таким образом, мы можем сбросить флаг negative
и обработать
Остальные алгоритмы затем изменятся на:
operation, parts = split_expression(expression)
parts = map(calculate, parts) # recursion
return reduce(operation, parts)
def split_expression(expression):
for pattern, operation in OPERATIONS:
parts = pattern.split(expression)
if len(parts) > 1:
return operation, parts
полный алгоритм
Полный код можно найти здесь
тестирование:
def test_expression(expression):
return calculate(expression) == eval(expression.replace('/','//')) # the replace to get floor division
def test_calculate():
assert test_expression('1')
assert test_expression(' 1 ')
assert test_expression('(1)')
assert test_expression('(-1)')
assert test_expression('(-1) - (-1)')
assert test_expression('((-1) - (-1))')
assert test_expression('4 * 3 - 4 * 4')
assert test_expression('4 * 3 - 4 / 4')
assert test_expression('((87/(64*(98-94)))+((3-(97-27))-(89/69)))')
test_calculate()
мощность:
Добавление мощности становится таким же простым, как добавление
(re.compile('\*\*'), pow),
(re.compile('\^'), pow),
к OPERATIONS
calculate('2 + 4 * 10^5')
400002