【问题标题】:Solve basic mathematical operations in python 3解决python 3中的基本数学运算
【发布时间】:2018-04-25 22:27:17
【问题描述】:

我正在尝试开发一种简单的 python 方法,它可以让我计算基本的数学运算。这里的重点是我不能使用 eval()、exec() 或任何其他评估 python statemets 的函数,所以我必须手动完成。到目前为止,我遇到了这段代码:

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)

如您所见,我使用正则表达式从较大的操作中提取基本操作。 step2processed 操作是服务器发送到我的机器的字符串。但作为示例,它可能包含:

((87/(64*(98-94)))+((3-(97-27))-(89/69)))

它包含完整且平衡的数学运算。

也许有人可以帮我解决这个问题,或者我应该完全改变这个方法。

提前谢谢你。

【问题讨论】:

  • 欢迎来到 StackOverflow!您的代码 sn-p 不完整,因为您没有定义 basicoperationslist。请显示一个完整的、独立的 sn-p 以显示您的错误以及该 sn-p 的错误回溯。请阅读并关注How to create a Minimal, Complete, and Verifiable example
  • 您的解决方案非常简单,如果您确定您将有 2 个操作数,那么在拆分后检查列表中是否有 3 个值。如果有三个,那么其中一个是否定的。
  • 还有你如何处理-34-(-10)
  • 你提到的那个错误也说明它发生在哪一行。
  • 看看:docs.python.org/3/library/stdtypes.html#str.split,你可以指定你想添加的分割数

标签: python solver mathematical-expressions


【解决方案1】:

我会放弃整个拆分方法,因为它太复杂了,并且在某些情况下可能会失败,正如您所注意到的。

我会使用正则表达式和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

这种方式更容易适应新情况(比如新运营商)

【讨论】:

  • 您的解决方案非常有趣。我没有设法想出这种方法。这里的问题是我需要进行 int 操作。而且除法不是'/',是'//'python运算符(整除)
  • @enon97 我不确定你的意思。如果你想使用“整数除法”,你可以使用operator.floordiv,而不是我在operators字典中使用的operator.truediv
  • 是的,你是对的,我刚刚搜索了它:operator.floordiv(a, b)¶ operator.__floordiv__(a, b) Return a // b.
  • 顺便说一句,如果我们有例如 34--10 这是否有效?
  • @enon97 不是这样,但可以通过对正则表达式进行非常简单的更改来修复它(第二个 (\d+) 应该更改为 (-?\d+))。
【解决方案2】:

您可以轻松地使用operatordict 来存储操作,而不是一长串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

【讨论】:

  • 非常感谢您的想法。但我认为这对于我的需求来说太复杂了。如果我想递归地解决一个操作,它会是这样的: ((87/(64*(98-94)))+((3-(97-27))-(89/69)))
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-10
  • 2012-07-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多