Python - Evaluate math expression within string [duplicate]

馋奶兔 提交于 2019-12-05 03:57:59
Burhan Khalid

Here's my attempt:

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

Notes:

This is very simple, it won't deal with complex equations - like those with square root, pi, etc. but I believe its in the spirit of what the question is after. For a really robust answer see the question posted by jeffery_the_wind; but I believe it may be overkill for this simplistic case.

Sometimes it's better to simplify the question rather than coming up with complicated solutions. You may want to simplify the problem by having your code be provided like this

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

This way you can parse it using a simple regex and eval what's inside. Otherwise you're in for a lot of complexity.

This is a very tricky problem which is probably nearly impossible to solve in general. However, here's a simple way to attack the problem which works with the example input.

*step 1 -- sanitize the input. This is the hardest part to do in general. Basically, you need a way to pull a single math expression out of the string without mangling it. Here a simple regex will work:

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

*step 2 -- evaluate using eval:

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

*step 3 -- back substitute

new_string = my_str.replace(sanitized, str(value))
Arsen Manukyan

Thanks to all for your help. Actually my provided example is very simple compared what I have in real task. I read these string from file and sometimes is can have view like this:

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

Math equation are simple but can occurs many time in one string, and should be evaluated separately.

So I write a sample code, which is able to handle this cases: Maybe it is not such good, but solve the problem:

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
jterrace

For a solution without using eval, here's what I would do. Start by finding all of the mathematical expressions in the string, which I will define as a string that contains spaces, parenthesis, numbers, and operations, then strip out the matches that are all whitespace:

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

Next, evaluate the expressions using the NumericStringParser class from this question, which uses pyparsing:

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

Then, to substitute the results back into the expression, reverse sort the expressions by their starting index and place them back into the original string:

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

My option:

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

Use f-strings or slicing.

F-string: f'I have {str(6*(2+3))} apples'

[I know this is an old question, but it is worth pointing out new useful solutions as they pop up]

Since python3.6, this capability is now built into the language, coined "f-strings".

See: PEP 498 -- Literal String Interpolation

For example (note the f prefix):

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'
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!