2012-08-28 47 views
7

我有一个关于字符串内数学表达式求值的问题。 例如我的字符串如下:Python - 评估字符串内的数学表达式

my_str='I have 6 * (2 + 3) apples' 

我想知道如何评价这个字符串,并得到以下结果:

'I have 30 apples' 

是在没有办法做到这一点?

在此先感谢。

P.S. python的eval函数在这种情况下不起作用。当尝试使用eval函数进行评估时,它提出了一个错误。

+3

这是功课? –

+3

http://stackoverflow.com/questions/2371436/evaluating-a-mathematical-expression-in-a-string –

+1

@jeffery_the_wind不是真正的重点,因为(不像那样)需要丢弃字符串的非数学部分。 –

回答

0

这是一个非常棘手的问题,可能几乎不可能解决一般情况。但是,下面是一个简单的方法来解决与示例输入一起使用的问题。

*第1步 - 消毒输入。一般来说,这是最难的部分。基本上,您需要一种方法将单个数学表达式从字符串中提取出来,而不会对其进行修改。这里一个简单的正则表达式将工作:

sanitized = re.sub(r'[a-zA-Z]','',my_str).strip() 

*第2步 - 评估使用eval

value = eval(sanitized, {'__builtins__':None}) 

*第3步 - 回替补

new_string = my_str.replace(sanitized, str(value)) 
+0

好吧,这并不是说它不能一般解决。这是一个不明确的问题(什么构成要评估的表达式,什么不是?),但是一旦我们指出了这个问题,如果你真的很麻烦解析一些东西而不是滥用'eval',就很容易解决。 – delnan

+1

@delnan - 如果这是'eval'滥用,那么什么是'eval'滥用(在哪一点上,不应该完全从语言中删除?)。我认为这是一个使用'eval'的完全不错的地方,因为这个问题已经足够用来解析要从输入字符串中计算出来的表达式。 – mgilson

+0

我认为'eval'不应该放在显着位置(即在全局名称空间中)。我知道用于'compile'和'exec'的用例(它们与此有很大不同,因为它们控制着输入字符串100%,并知道它会做什么)。我还没有遇到'eval'的好用例 - 当你想评估一个数学表达式时,写一个调车场评估器或其他东西。如果你想运行Python代码,使用'exec',因为它的限制较少。我并不是为了正确而避免使用'eval',而是不要将代码与数据混淆。 (拧说说代码是数据的lisp家伙。) – delnan

2

这里是我的尝试:

>>> import string 
>>> s = 'I have 6 * (2+3) apples' 
>>> symbols = '^*()/+-' 
>>> formula = [(x,s.index(x)) for x in s if x in string.digits+symbols] 
>>> result = eval(''.join(x[0] for x in formula), {'__builtins__':None}) 
>>> s = s[:formula[0][1]] + str(result) + s[formula[-1][1]+1:] 
>>> s 
'I have 30 apples' 

备注:

这很简单,它不会处理复杂的方程 - 像平方根,pi等那些方程,但我相信它符合问题之后的精神。对于确实健壮的答案见question posted by jeffery_the_wind;但我认为对于这个过于简单的案例来说可能是过分的。

0

对于没有使用eval的解决方案,这是我会做的。通过查找所有的字符串,我会定义为包含空格,括号,数字和操作的字符串中的数学表达式的开始,那么剔除这都是空白的比赛:

>>> import re 
>>> my_str = 'I have 6 * (2 + 3) apples' 
>>> exprs = list(re.finditer(r"[\d\.\s\*\+\-\/\(\)]+", my_str)) 
>>> exprs = [e for e in exprs if len(my_str[e.start():e.end()].strip()) > 0] 

接下来,评估使用NumericStringParser类的表达从this question,它使用pyparsing

>>> nsp = NumericStringParser() 
>>> results = [nsp.eval(my_str[e.start():e.end()]) for e in exprs] 
>>> results 
[30.0] 

然后,将结果代回的表达,可以通过起始索引反转排序的表达,并把它们放回原始字符串:

>>> new_str = my_str 
>>> for expr, res in sorted(zip(exprs, results), key=lambda t: t[0].start(), reverse=True): 
...  new_str = new_str[:expr.start()] + (" %d " % res) + new_str[expr.end():] 
... 
>>> new_str 
'I have 30 apples' 
2

有时最好简化问题而不是提出复杂的解决方案。您可能希望通过为您的代码,以简化提供这样

my_str='I have {6 * (2 + 3)} apples' 

这样你可以使用一个简单的regex和eval里面有什么分析它的问题。否则,你会遇到很多复杂问题。

0

我的选项:

>>> import re 
>>> def calc(s): 
...  val = s.group() 
...  if not val.strip(): return val 
...  return " %s " % eval(val.strip(), {'__builtins__': None}) 
>>> re.sub(r"([0-9\ \.\+\*\-\/(\)]+)", calc, "I have 6 * (2 + 3) apples") 
'I have 30 apples' 
1

感谢所有您的帮助。实际上,我提供的例子非常简单,它比较了我在实际任务中的情况。我从文件中读取这些字符串,有时是可以有鉴于这样的:

my_str='ENC M6_finger_VNCAPa (AA SYZE BY (0.14*2)) < (0.12 + 0.07) OPPOSITE REGION' 

数学公式很简单,但可以发生在一个字符串很多时间,应分别进行评估。

所以我写了一个示例代码,这是能够处理这种情况: 也许不是这样好,但解决问题:

def eval_math_expressions(filelist): 
     for line in filelist: 
       if re.match('.*[\-|\+|\*|\/].*',line): 
         lindex=int(filelist.index(line)) 
         line_list=line.split() 
         exp=[] 
         for word in line_list: 
           if re.match('^\(+\d+',word) or re.match('^[\)+|\d+|\-|\+|\*|\/]',word): 
             exp.append(word) 
           else: 
             ready=' '.join(exp) 
             if ready: 
               eval_ready=str(eval(ready)) 
               line_new=line.replace(ready,eval_ready) 
               line=line_new 
               filelist[lindex]=line 
             exp=[] 
     return filelist 
0

[我知道这是一个老问题,但因为他们弹出]

由于python3.6,现在这个能力内置到语言,创造“F-字符串”它值得指出的是新的有用的解决方案。

参见:PEP 498 -- Literal String Interpolation

例如(注意f前缀):

f'I have {6 * (2 + 3)} apples' 
=> 'I have 30 apples' 
color = 'green' 
f'I have {6 * (2 + 3)} {color} apples' 
=> 'I have 30 green apples'