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

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

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

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

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

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

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

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

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

8 Solutions collect form web for “Как я могу разбить строку математических выражений в python?”

Дерево с 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 чтобы проверить, соответствуют ли круглые скобки, или если каждый элемент является известным.

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

  • (
  • )
  • +
  • *
  • /
  • \ 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 , но тогда вы будете пропускать не совпадения, которые являются синтаксическими ошибками в этом случае.

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

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

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

 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 ) ) 

Если вы не хотите использовать модуль 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', ')', ')'] 

Это не дает вполне необходимого результата, но может представлять интерес для других, которые рассматривают этот вопрос. Он использует библиотеку 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]]] 

Использование 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']]] 

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

Быстрый регулярный re.findall(r"\d+|[()+\-*\/]", str_in) : 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', ')', ')'] 

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

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

 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) с 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, ')', ')', ')'] 
  • Tokenizer с Pyigs в Python
  • Токизация арабских слов с использованием NLTK
  • NLTK regexp tokenizer не играет хорошо с десятичной точкой в ​​регулярном выражении
  • RegEx Tokenizer для разделения текста на слова, цифры и знаки препинания
  • Как использовать токенизатор по умолчанию для NLTK, чтобы получить пробелы вместо строк?
  • Невозможно правильно пометить предложение хинди
  • Заполнение нескольких символов пробелом - python
  • Python - лучший язык программирования в мире.