如何在python中拆分一串数学表达式?

Fer*_*aku 14 python string split tokenize python-3.x

我做了一个程序,在python中将中缀转换为postfix.问题是当我介绍这些论点时.如果我介绍这样的东西:(这将是一个字符串)

( ( 73 + ( ( 34 - 72 ) / ( 33 - 3 ) ) ) + ( 56 + ( 95 - 28 ) ) )
Run Code Online (Sandbox Code Playgroud)

它将使用.split()拆分它,程序将正常工作.但我希望用户能够介绍这样的东西:

((73 + ( (34- 72 ) / ( 33 -3) )) + (56 +(95 - 28) ) )
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,我希望空格可以是微不足道的,但是程序继续用括号,整数(不是数字)和操作数来分割字符串.

我尝试解决它for但我不知道如何捕获整数(73,34,72)而不是一位数(7,3,3,4,7,2)

总而言之,我想要的是将一个字符串((81 * 6) /42+ (3-1)) 分成:

[(, (, 81, *, 6, ), /, 42, +, (, 3, -, 1, ), )]
Run Code Online (Sandbox Code Playgroud)

Eri*_*nil 21

树与 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)
Run Code Online (Sandbox Code Playgroud)

它输出:

<_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
Run Code Online (Sandbox Code Playgroud)

正如@ 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', ')', ')']
Run Code Online (Sandbox Code Playgroud)

它寻找:

  • 多位数
  • 或任何不是数字或空格的字符

获得元素列表后,您可以检查语法是否正确,例如使用a stack检查括号是否匹配,或者每个元素是否为已知元素.

  • 注意 - "ast.parse"解析*Python*语法,而不是数学表达式.例如,`(1 + 2)(3 + 4)`被解析为函数调用,`3 ^ 4`被视为异或,并且`[x**2 for x in range(5)]`被处理作为一个完全普通的输入,而不是应该被拒绝的废话. (4认同)

Hor*_*man 12

您需要为输入实现一个非常简单的标记生成器.您有以下类型的令牌:

  • (
  • )
  • +
  • -
  • *
  • /
  • \ 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)
Run Code Online (Sandbox Code Playgroud)

这将打印 ['(', '3', '+', '(', '2', '*', '5', ')', ')']

你也可以使用re.findallre.finditer,但是你会跳过不匹配,在这种情况下是语法错误.


McG*_*ady 5

如果你不想使用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:])
Run Code Online (Sandbox Code Playgroud)

输出:

['(', '(', '81', '*', '6', ')', '/', '42', '+', '(', '3', '-', '1', ')', ')']
Run Code Online (Sandbox Code Playgroud)


Chr*_*ean 5

手动滚动一个简单的表达式标记化器实际上是非常简单的.而且我认为你也会以这种方式学到更多东西.

因此,为了教育和学习,这是一个可以扩展的简单表达式标记器实现.它基于"最大 - 多"规则工作.这意味着它行为"贪婪",试图消耗尽可能多的字符来构造每个令牌.

不用多说,这里是tokenizer:

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
Run Code Online (Sandbox Code Playgroud)

这是一个演示用法:

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

产生输出:

(
(
81
*
6
)
/
42
+
(
3
-
1
)
)
Run Code Online (Sandbox Code Playgroud)