解决 python 3 中的基本数学运算
Solve basic mathematical operations in python 3
我正在尝试开发一种简单的 python 方法来计算基本的数学运算。这里的重点是我不能使用 eval()、exec() 或任何其他评估 python 状态的函数,所以我必须手动执行。到目前为止,我遇到过这段代码:
solutionlist = list(basicoperationslist)
for i in range(0, len(solutionlist)):
if '+' in solutionlist[i]:
y = solutionlist[i].split('+')
solutionlist[i] = str(int(y[0]) + int(y[1]))
elif '*' in solutionlist[i]:
y = solutionlist[i].split('*')
solutionlist[i] = str(int(y[0]) * int(y[1]))
elif '/' in solutionlist[i]:
y = solutionlist[i].split('/')
solutionlist[i] = str(int(y[0]) // int(y[1]))
elif '-' in solutionlist[i]:
y = solutionlist[i].split('-')
solutionlist[i] = str(int(y[0]) - int(y[1]))
print("The solutions are: " + ', '.join(solutionlist))
所以我们有两个字符串列表,基本操作列表具有以下格式的操作:2940-81、101-16、46/3、10*9、145/24、-34-40。
他们总是有两个数字,中间有一个操作数。我的解决方案的问题是,当我进行与上一个类似的操作时,.split() 方法将我的列表拆分为一个空列表和一个包含完整操作的列表。总之,当我们将负数与减法运算混合使用时,此解决方案效果不佳。我不知道它是否在任何其他情况下都失败了,因为我只设法注意到我之前描述的错误。
这个想法是,在方法的最后,我将解决方案列表作为字符串列表,这些字符串将成为基本数学运算的有序答案。
这是每当我的代码遇到像上一个这样的操作时提示的错误:ValueError: invalid literal for int() with base 10: ''
这里定义了基本操作列表:
basicoperationslist = re.findall('[-]*\d+[+/*-]+\d+', step2processedoperation)
如您所见,我使用正则表达式从较大的操作中提取基本操作。 step2processedoperation 是服务器发送到我的机器的字符串。但例如它可能包含:
((87/(64*(98-94)))+((3-(97-27))-(89/69)))
它包含完整和平衡的数学运算。
也许有人可以帮我解决这个问题,或者我应该彻底改变这个方法。
提前致谢。
我会放弃整个拆分方法,因为它太复杂了,并且在某些情况下可能会像您注意到的那样失败。
相反,我会使用正则表达式和 operator
模块来简化事情。
import re
import operator
operators = {'+': operator.add,
'-': operator.sub,
'*': operator.mul,
'/': operator.truediv}
regex = re.compile(r'(-?\d+)' # 1 or more digits with an optional leading '-'
r'(\+|-|\*|/)' # one of +, - , *, /
r'(\d+)', # 1 or more digits
re.VERBOSE)
exprssions = ['2940-81', '101-16', '46/3', '10*9', '145/24', '-34-40']
for expr in exprssions:
a, op, b = regex.search(expr).groups()
print(operators[op](int(a), int(b)))
# 2859
# 85
# 15.333333333333334
# 90
# 6.041666666666667
# -74
这种方法更容易适应新情况(例如新运营商)
您可以轻松地使用 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
可以很容易地检查是否有括号。首先,我们需要确保它不识别 'simple' 带括号的中间结果(如 (-1)
)
PATTERN_PAREN_SIMPLE= re.compile('\((-?\d+)\)')
PAREN_OPEN = '|'
PAREN_CLOSE = '#'
def _encode(expression):
return PATTERN_PAREN_SIMPLE.sub(rf'{PAREN_OPEN}{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
我正在尝试开发一种简单的 python 方法来计算基本的数学运算。这里的重点是我不能使用 eval()、exec() 或任何其他评估 python 状态的函数,所以我必须手动执行。到目前为止,我遇到过这段代码:
solutionlist = list(basicoperationslist)
for i in range(0, len(solutionlist)):
if '+' in solutionlist[i]:
y = solutionlist[i].split('+')
solutionlist[i] = str(int(y[0]) + int(y[1]))
elif '*' in solutionlist[i]:
y = solutionlist[i].split('*')
solutionlist[i] = str(int(y[0]) * int(y[1]))
elif '/' in solutionlist[i]:
y = solutionlist[i].split('/')
solutionlist[i] = str(int(y[0]) // int(y[1]))
elif '-' in solutionlist[i]:
y = solutionlist[i].split('-')
solutionlist[i] = str(int(y[0]) - int(y[1]))
print("The solutions are: " + ', '.join(solutionlist))
所以我们有两个字符串列表,基本操作列表具有以下格式的操作:2940-81、101-16、46/3、10*9、145/24、-34-40。 他们总是有两个数字,中间有一个操作数。我的解决方案的问题是,当我进行与上一个类似的操作时,.split() 方法将我的列表拆分为一个空列表和一个包含完整操作的列表。总之,当我们将负数与减法运算混合使用时,此解决方案效果不佳。我不知道它是否在任何其他情况下都失败了,因为我只设法注意到我之前描述的错误。 这个想法是,在方法的最后,我将解决方案列表作为字符串列表,这些字符串将成为基本数学运算的有序答案。 这是每当我的代码遇到像上一个这样的操作时提示的错误:ValueError: invalid literal for int() with base 10: ''
这里定义了基本操作列表:
basicoperationslist = re.findall('[-]*\d+[+/*-]+\d+', step2processedoperation)
如您所见,我使用正则表达式从较大的操作中提取基本操作。 step2processedoperation 是服务器发送到我的机器的字符串。但例如它可能包含:
((87/(64*(98-94)))+((3-(97-27))-(89/69)))
它包含完整和平衡的数学运算。
也许有人可以帮我解决这个问题,或者我应该彻底改变这个方法。
提前致谢。
我会放弃整个拆分方法,因为它太复杂了,并且在某些情况下可能会像您注意到的那样失败。
相反,我会使用正则表达式和 operator
模块来简化事情。
import re
import operator
operators = {'+': operator.add,
'-': operator.sub,
'*': operator.mul,
'/': operator.truediv}
regex = re.compile(r'(-?\d+)' # 1 or more digits with an optional leading '-'
r'(\+|-|\*|/)' # one of +, - , *, /
r'(\d+)', # 1 or more digits
re.VERBOSE)
exprssions = ['2940-81', '101-16', '46/3', '10*9', '145/24', '-34-40']
for expr in exprssions:
a, op, b = regex.search(expr).groups()
print(operators[op](int(a), int(b)))
# 2859
# 85
# 15.333333333333334
# 90
# 6.041666666666667
# -74
这种方法更容易适应新情况(例如新运营商)
您可以轻松地使用 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
可以很容易地检查是否有括号。首先,我们需要确保它不识别 'simple' 带括号的中间结果(如 (-1)
)
PATTERN_PAREN_SIMPLE= re.compile('\((-?\d+)\)')
PAREN_OPEN = '|'
PAREN_CLOSE = '#'
def _encode(expression):
return PATTERN_PAREN_SIMPLE.sub(rf'{PAREN_OPEN}{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