Как я могу разбить строку математических выражений в python?

Я создал программу, которая конвертирует infix в postfix в python. Проблема в том, когда я ввожу аргументы. Если я введу что-то вроде этого: (это будет строка)

( ( 73 + ( ( 34 - 72 ) / ( 33 - 3 ) ) ) + ( 56 + ( 95 - 28 ) ) )

он разделит его на .split(), и программа будет работать правильно. Но я хочу, чтобы пользователь мог ввести что-то вроде этого:

((73 + ( (34- 72 ) / ( 33 -3) )) + (56 +(95 - 28) ) )

Как вы можете видеть, я хочу, чтобы пустые пространства могли быть тривиальными, но программа продолжает разделять строку скобками, целыми числами (не цифрами) и операндами.

Я пытаюсь решить его с помощью for, но я не знаю, как поймать все число (73, 34, 72) вместо одной цифры по цифре (7, 3, 3, 4, 7, 2)

Подводя итог, я хочу разбить строку типа ((81 * 6) /42+ (3-1)) на:

[(, (, 81, *, 6, ), /, 42, +, (, 3, -, 1, ), )]

Ответы

Ответ 1

Дерево с ast

Вы можете использовать ast, чтобы получить дерево выражения:

import ast

source = '((81 * 6) /42+ (3-1))'
node = ast.parse(source) 

def show_children(node, level=0):
    if isinstance(node, ast.Num):
        print(' ' * level + str(node.n))
    else:
        print(' ' * level + str(node))
    for child in ast.iter_child_nodes(node):
        show_children(child, level+1)

show_children(node)

Он выводит:

<_ast.Module object at 0x7f56abbc5490>
 <_ast.Expr object at 0x7f56abbc5350>
  <_ast.BinOp object at 0x7f56abbc5450>
   <_ast.BinOp object at 0x7f56abbc5390>
    <_ast.BinOp object at 0x7f56abb57cd0>
     81
     <_ast.Mult object at 0x7f56abbd0dd0>
     6
    <_ast.Div object at 0x7f56abbd0e50>
    42
   <_ast.Add object at 0x7f56abbd0cd0>
   <_ast.BinOp object at 0x7f56abb57dd0>
    3
    <_ast.Sub object at 0x7f56abbd0d50>
    1

Как @user2357112 написал в комментариях: ast.parse интерпретирует синтаксис Python, а не математические выражения. (1+2)(3+4) будет анализироваться как вызов функции, и понимание списков будет принято, даже если они, вероятно, не должны считаться допустимым математическим выражением.

Список с регулярным выражением

Если вам нужна плоская структура, может работать регулярное выражение:

import re

number_or_symbol = re.compile('(\d+|[^ 0-9])')
print(re.findall(number_or_symbol, source))
# ['(', '(', '81', '*', '6', ')', '/', '42', '+', '(', '3', '-', '1', ')', ')']

Он также ищет:

  • несколько цифр
  • или любой символ, который не является цифрой или пробелом.

Как только у вас есть список элементов, вы можете проверить правильность синтаксиса, например, с помощью stack, чтобы проверить, скобки совпадают или каждый элемент известен.

Ответ 2

Вам нужно реализовать очень простой токенизатор для ввода. У вас есть следующие типы токенов:

  • (
  • )
  • +
  • -
  • *
  • /
  • \D +

Вы можете найти их в своей строке ввода, разделенной всеми видами пробелов.

Итак, первый шаг - обработать строку от начала до конца и извлечь эти токены, а затем выполнить разбор на токенах, а не на самой строке.

Отличный способ сделать это - использовать следующее регулярное выражение: '\s*([()+*/-]|\d+)'. Затем вы можете:

import re

the_input='(3+(2*5))'
tokens = []
tokenizer = re.compile(r'\s*([()+*/-]|\d+)')
current_pos = 0
while current_pos < len(the_input):
  match = tokenizer.match(the_input, current_pos)
  if match is None:
     raise Error('Syntax error')
  tokens.append(match.group(1))
  current_pos = match.end()
print(tokens)

Откроется ['(', '3', '+', '(', '2', '*', '5', ')', ')']

Вы также можете использовать re.findall или re.finditer, но тогда вы будете пропускать несоответствия, которые в этом случае являются синтаксическими ошибками.

Ответ 3

Фактически было бы довольно тривиально ручным прокрутить простой токенизатор выражения. И я думаю, вы тоже узнаете об этом.

Итак, ради образования и обучения, вот тривиальная реализация токенизатора выражений, которая может быть расширена. Он работает на основе правила "максимальное количество" . Это означает, что он действует "жадным", пытаясь потреблять столько символов, сколько может, чтобы создать каждый токен.

Без дальнейших церемоний, вот токенизатор:

class ExpressionTokenizer:
    def __init__(self, expression, operators):
        self.buffer = expression
        self.pos = 0
        self.operators = operators

    def _next_token(self):
        atom = self._get_atom()

        while atom and atom.isspace():
            self._skip_whitespace()
            atom = self._get_atom()

        if atom is None:
            return None
        elif atom.isdigit():
            return self._tokenize_number()
        elif atom in self.operators:
            return self._tokenize_operator()
        else:
            raise SyntaxError()

    def _skip_whitespace(self):
        while self._get_atom():
            if self._get_atom().isspace():
                self.pos += 1
            else:
                break

    def _tokenize_number(self):
        endpos = self.pos + 1
        while self._get_atom(endpos) and self._get_atom(endpos).isdigit():
            endpos += 1
        number = self.buffer[self.pos:endpos]
        self.pos = endpos
        return number

    def _tokenize_operator(self):
        operator = self.buffer[self.pos]
        self.pos += 1
        return operator

    def _get_atom(self, pos=None):
        pos = pos or self.pos
        try:
            return self.buffer[pos]
        except IndexError:
            return None

    def tokenize(self):
        while True:
            token = self._next_token()
            if token is None:
                break
            else:
                yield token

Вот демонстрация использования:

tokenizer = ExpressionTokenizer('((81 * 6) /42+ (3-1))', {'+', '-', '*', '/', '(', ')'})
for token in tokenizer.tokenize():
    print(token)

Что производит вывод:

(
(
81
*
6
)
/
42
+
(
3
-
1
)
)

Ответ 4

Если вы не хотите использовать модуль re, вы можете попробовать следующее:

s="((81 * 6) /42+ (3-1))"

r=[""]

for i in s.replace(" ",""):
    if i.isdigit() and r[-1].isdigit():
        r[-1]=r[-1]+i
    else:
        r.append(i)
print(r[1:])

Вывод:

['(', '(', '81', '*', '6', ')', '/', '42', '+', '(', '3', '-', '1', ')', ')']

Ответ 5

Это не дает желаемого результата, но может быть интересным для других, которые рассматривают этот вопрос. Он использует библиотеку pyparsing.

# Stolen from http://pyparsing.wikispaces.com/file/view/simpleArith.py/30268305/simpleArith.py
# Copyright 2006, by Paul McGuire
# ... and slightly altered

from pyparsing import *

integer = Word(nums).setParseAction(lambda t:int(t[0]))
variable = Word(alphas,exact=1)
operand = integer | variable

expop = Literal('^')
signop = oneOf('+ -')
multop = oneOf('* /')
plusop = oneOf('+ -')
factop = Literal('!')

expr = operatorPrecedence( operand,
    [("!", 1, opAssoc.LEFT),
     ("^", 2, opAssoc.RIGHT),
     (signop, 1, opAssoc.RIGHT),
     (multop, 2, opAssoc.LEFT),
     (plusop, 2, opAssoc.LEFT),]
    )

print (expr.parseString('((81 * 6) /42+ (3-1))'))

Вывод:

[[[[81, '*', 6], '/', 42], '+', [3, '-', 1]]]

Ответ 6

Использование grako:

start = expr $;
expr = calc | value;
calc = value operator value;
value = integer | "(" @:expr ")" ;
operator = "+" | "-" | "*" | "/";
integer = /\d+/;

grako переводит на python.

В этом примере возвращаемое значение выглядит следующим образом:

['73', '+', ['34', '-', '72', '/', ['33', '-', '3']], '+', ['56', '+', ['95', '-', '28']]]

Обычно вы должны использовать сгенерированный семантический класс в качестве шаблона для дальнейшей обработки.

Ответ 7

Быстрый регулярный вызов: re.findall(r"\d+|[()+\-*\/]", str_in)

Демонстрация:

>>> import re
>>> str_in = "((81 * 6) /42+ (3-1))"
>>> re.findall(r"\d+|[()+\-*\/]", str_in)
['(', '(', '81', '*', '6', ')', '/', '42', '+', '(', '3', '-', '1', 
')', ')']

Для части вложенных круглых скобок вы можете использовать стек для отслеживания уровня.

Ответ 8

Чтобы обеспечить более подробный подход регулярного выражения, который вы можете легко расширить:

import re

solution = []
pattern = re.compile('([\d\.]+)')

s = '((73 + ( (34- 72 ) / ( 33 -3) )) + (56 +(95 - 28) ) )'

for token in re.split(pattern, s):
    token = token.strip()
    if re.match(pattern, token):
        solution.append(float(token))
        continue
    for character in re.sub(' ', '', token):
        solution.append(character)

Что даст вам результат:

 solution = ['(', '(', 73, '+', '(', '(', 34, '-', 72, ')', '/', '(', 33, '-', 3, ')', ')', ')', '+', '(', 56, '+', '(', 95, '-', 28, ')', ')', ')']