2017-04-13 143 views
14

我做了一个程序,将中缀转换为python中的后缀。问题是当我介绍参数时。 如果我介绍是这样的:(这将是一个字符串)如何在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,),)] 
+9

你在做什么的词是“标记” - 我用[tag:tokenize]代替了你的[tag:list]标签。 – Eric

+1

正则表达式不适合嵌套括号。 grako是好的,但如果这是你需要做的,可能是重量级的。我喜欢grako,因为你最终得到了可读的代码。 –

+0

感谢这个问题,它帮助我达到了银色的Python徽章! –

回答

20

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来检查括号是否匹配,或者每个元素是否是已知的。

+4

小心 - 'ast.parse'解析* Python *语法,而不是数学表达式。例如,'(1 + 2)(3 + 4)'被解析为函数调用,'3^4'被视为异或,[[x ** 2用于范围(5)中的x]]被处理作为完美的普通输入,而不是废话,应该被拒绝。 – user2357112

+0

我见过很多挑剔的计算器,但是我不记得任何具体的例子。 AFAIK,'^'也不会被每个计算器使用。不过,你提出了一个很好的观点。我试图将它整合到答案中。谢谢 –

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) 

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

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

+0

@EricDuminil oops。当更新current_pos时,我应该使用=而不是+ =,因为match.end()是相对于整个字符串而不是current_pos。我还添加了一个例子。 –

+0

现在工作正常。不过,'input'不应该被用作变量名称。 –

1

快速的正则表达式的答案: 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', 
')', ')'] 

对于嵌套的括号部分,你可以使用堆栈跟踪水平。

2

如果你不想使用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', ')', ')'] 
+1

@EricDuminil感谢您的提醒,我通过添加一个空字符串来修正索引错误。 – McGrady

4

这实际是很琐碎手工卷的简单表达标记生成器。而且我认为你也会更多地学习。

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

事不宜迟,这里是标记者:

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

这不提供完全是你想要的结果,但可能会对查看此问题的其他人感兴趣。它使用pyaprsing库。

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

,这将给你的结果:

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

使用grako:

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

grako transpiles to python。

在这个例子中,返回值是这样的:

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

通常你会使用生成的语义类作为进一步处理的模板。

相关问题