Решить основные математические операции в Python 3 - PullRequest
0 голосов
/ 25 апреля 2018

Я пытаюсь разработать простой метод 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: неверный литерал для int () с основанием 10: ''

Базовый список операций определен здесь:

basicoperationslist = re.findall('[-]*\d+[+/*-]+\d+', step2processedoperation)

Как видите, я использую регулярное выражение для извлечения основных операций из более крупной операции. Операция step2processedoperation - это строка, которую сервер отправляет на мой компьютер. Но в качестве примера он может содержать:

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

Содержит полные и сбалансированные математические операции.

Может быть, кто-то может помочь мне решить эту проблему, или, возможно, мне следует полностью изменить этот метод.

Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 25 апреля 2018

Вы можете легко использовать 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
0 голосов
/ 25 апреля 2018

Я бы отбросил весь подход к расщеплению, так как он слишком сложен и в некоторых случаях может дать сбой, как вы заметили.

Вместо этого я бы использовал регулярное выражение и модуль 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

Этот подход легче адаптировать к новым случаям (например, к новым операторам)

...