Если у меня есть эта строка:
2 + 24 * 48/32
Каков наиболее эффективный подход для создания этого списка:
['2', '+', '24', '*', '48', '/', '32']
Если у меня есть эта строка:
2 + 24 * 48/32
Каков наиболее эффективный подход для создания этого списка:
['2', '+', '24', '*', '48', '/', '32']
Так получилось, что токены, которые вы хотите разделить, уже являются токенами Python, поэтому вы можете использовать встроенный модуль tokenize
. Это почти однострочный:
from cStringIO import StringIO
from tokenize import generate_tokens
STRING = 1
list(token[STRING] for token
in generate_tokens(StringIO('2+24*48/32').readline)
if token[STRING])
['2', '+', '24', '*', '48', '/', '32']
Вы можете использовать split
из модуля re
.
re.split(pattern, string, maxsplit = 0, flags = 0)
Разделить строку на наличие шаблонов. Если скобки будут скопированы используются в шаблоне, тогда текст всех групп в шаблоне также возвращается как часть результирующего списка.
Пример кода:
import re
data = re.split(r'(\D)', '2+24*48/32')
\ D
Если флаг UNICODE не указан, \D соответствует любой нецифровой персонаж; это эквивалентно множеству [^ 0-9].
Это выглядит как проблема синтаксического анализа, и поэтому я вынужден представить решение, основанное на методах разбора.
Хотя может показаться, что вы хотите "разбить" эту строку, я думаю, что вы действительно хотите сделать это "tokenize". Токсификация или lexxing - это этап компиляции перед разбором. Я исправил свой оригинальный пример в редакции, чтобы реализовать правильный рекурсивный достойный парсер. Это самый простой способ реализовать парсер вручную.
import re
patterns = [
('number', re.compile('\d+')),
('*', re.compile(r'\*')),
('/', re.compile(r'\/')),
('+', re.compile(r'\+')),
('-', re.compile(r'\-')),
]
whitespace = re.compile('\W+')
def tokenize(string):
while string:
# strip off whitespace
m = whitespace.match(string)
if m:
string = string[m.end():]
for tokentype, pattern in patterns:
m = pattern.match(string)
if m:
yield tokentype, m.group(0)
string = string[m.end():]
def parseNumber(tokens):
tokentype, literal = tokens.pop(0)
assert tokentype == 'number'
return int(literal)
def parseMultiplication(tokens):
product = parseNumber(tokens)
while tokens and tokens[0][0] in ('*', '/'):
tokentype, literal = tokens.pop(0)
if tokentype == '*':
product *= parseNumber(tokens)
elif tokentype == '/':
product /= parseNumber(tokens)
else:
raise ValueError("Parse Error, unexpected %s %s" % (tokentype, literal))
return product
def parseAddition(tokens):
total = parseMultiplication(tokens)
while tokens and tokens[0][0] in ('+', '-'):
tokentype, literal = tokens.pop(0)
if tokentype == '+':
total += parseMultiplication(tokens)
elif tokentype == '-':
total -= parseMultiplication(tokens)
else:
raise ValueError("Parse Error, unexpected %s %s" % (tokentype, literal))
return total
def parse(tokens):
tokenlist = list(tokens)
returnvalue = parseAddition(tokenlist)
if tokenlist:
print 'Unconsumed data', tokenlist
return returnvalue
def main():
string = '2+24*48/32'
for tokentype, literal in tokenize(string):
print tokentype, literal
print parse(tokenize(string))
if __name__ == '__main__':
main()
Реализация обработки скобок оставлена в качестве упражнения для читателя. Этот пример будет корректно выполнять умножение перед добавлением.
>>> import re
>>> re.findall(r'\d+|\D+', '2+24*48/32=10')
['2', '+', '24', '*', '48', '/', '32', '=', '10']
Соответствует последовательным цифрам или последовательным нецифровым номерам.
Каждое совпадение возвращается как новый элемент в списке.
В зависимости от использования вам может потребоваться изменить регулярное выражение. Например, если вам нужно сопоставить числа с десятичной точкой.
>>> re.findall(r'[0-9\.]+|[^0-9\.]+', '2+24*48/32=10.1')
['2', '+', '24', '*', '48', '/', '32', '=', '10.1']
Это проблема синтаксического анализа, поэтому ни одно из регулярных выражений, а не split(), является "хорошим" решением. Вместо этого используйте генератор синтаксического анализатора.
Я бы внимательно посмотрел на pyparsing. Там также были некоторые достойные статьи о пирафинге в Python Magazine.
Регулярные выражения:
>>> import re
>>> splitter = re.compile(r'([+*/])')
>>> splitter.split("2+24*48/32")
Вы можете расширить регулярное выражение, чтобы включить любые другие символы, которые вы хотите разделить.
s = "2 + 24 * 48/32"
p = re.compile(r '(\ W +)')
p.split(ы)
Другим решением для этого было бы вообще не писать такой калькулятор. Написание парсера RPN намного проще и не имеет какой-либо двусмысленности, присущей написанию математики с нотной инфиксной записью.
import operator, math
calc_operands = {
'+': (2, operator.add),
'-': (2, operator.sub),
'*': (2, operator.mul),
'/': (2, operator.truediv),
'//': (2, operator.div),
'%': (2, operator.mod),
'^': (2, operator.pow),
'**': (2, math.pow),
'abs': (1, operator.abs),
'ceil': (1, math.ceil),
'floor': (1, math.floor),
'round': (2, round),
'trunc': (1, int),
'log': (2, math.log),
'ln': (1, math.log),
'pi': (0, lambda: math.pi),
'e': (0, lambda: math.e),
}
def calculate(inp):
stack = []
for tok in inp.split():
if tok in self.calc_operands:
n_pops, func = self.calc_operands[tok]
args = [stack.pop() for x in xrange(n_pops)]
args.reverse()
stack.append(func(*args))
elif '.' in tok:
stack.append(float(tok))
else:
stack.append(int(tok))
if not stack:
raise ValueError('no items on the stack.')
return stack.pop()
if stack:
raise ValueError('%d item(s) left on the stack.' % len(stack))
calculate('24 38 * 32 / 2 +')
>>> import re
>>> my_string = "2+24*48/32"
>>> my_list = re.findall(r"-?\d+|\S", my_string)
>>> print my_list
['2', '+', '24', '*', '48', '/', '32']
Это сделает трюк. Раньше я сталкивался с подобными проблемами.
Я уверен, что Тим имел в виду
splitter = re.compile(r'([\D])').
Если вы скопируете то, что у него есть, вы получите digits
не operators
.
Почему бы просто не использовать SymPy? Он должен делать то, что вы пытаетесь достичь.
Это точно не отвечает на вопрос, но я считаю, что он решает, чего вы пытаетесь достичь. Я бы добавил его в качестве комментария, но у меня пока нет разрешения.
Я лично воспользовался бы функциями математики Python непосредственно с помощью exec:
выражение = "2 + 24 * 48/32"
exec "result =" + выражение
распечатать результат
38