Python - оценка математического выражения в строке
У меня есть вопрос, касающийся оценки математического выражения в строке. Например, моя строка выглядит следующим образом:
my_str='I have 6 * (2 + 3) apples'
Мне интересно, как оценить эту строку и получить следующий результат:
'I have 30 apples'
Есть ли способ сделать это?
Заранее спасибо.
PS питона eval
функция не помогает в этом случае. Возникла ошибка при попытке оценить с помощью eval
функция.
7 ответов
Вот моя попытка:
>>> 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'
Заметки:
Это очень просто, оно не будет иметь дело со сложными уравнениями - например, с квадратным корнем, пи и т. Д., Но я считаю, что это в духе того, что за вопросом. Для действительно надежного ответа см. Вопрос, отправленный jeffery_the_wind; но я считаю, что это может быть излишним для этого упрощенного случая.
Иногда лучше упростить вопрос, чем придумывать сложные решения. Вы можете упростить проблему, предоставив свой код следующим образом
my_str='I have {6 * (2 + 3)} apples'
Таким образом, вы можете разобрать его с помощью простого регулярного выражения и оценить, что внутри. В противном случае вас ждет много сложностей.
Это очень сложная проблема, которую, вероятно, почти невозможно решить вообще. Тем не менее, вот простой способ решения проблемы, который работает с примером ввода.
* шаг 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))
Используйте f-струны или нарезку.
F-струна: f'I have {str(6*(2+3))} apples'
Спасибо всем за вашу помощь. На самом деле мой приведенный пример очень прост по сравнению с тем, что я имею в реальной задаче. Я читаю эти строки из файла, и иногда это может выглядеть так:
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
Мой вариант:
>>> 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'
Для решения без использования 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
Класс из этого вопроса, который использует 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'
[Я знаю, что это старый вопрос, но стоит указать новые полезные решения, когда они появятся]
Начиная с python3.6, эта возможность теперь встроена в язык, придуманный "f-strings".
См.: PEP 498 - Интерполяция буквенных строк
Например (обратите внимание на 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'