您可以轻松地使用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
完整算法
完整代码可见here
测试:
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