Editar:falsetru's nested parser, que he modificado ligeramente para que los patrones de expresiones regulares arbitrarias para especificar delimitadores y separador de elementos, es más rápido y más simple que mi solución original re.Scanner
:
import re
def parse_nested(text, left=r'[(]', right=r'[)]', sep=r','):
""" https://stackoverflow.com/a/17141899/190597 (falsetru) """
pat = r'({}|{}|{})'.format(left, right, sep)
tokens = re.split(pat, text)
stack = [[]]
for x in tokens:
if not x or re.match(sep, x):
continue
if re.match(left, x):
# Nest a new list inside the current list
current = []
stack[-1].append(current)
stack.append(current)
elif re.match(right, x):
stack.pop()
if not stack:
raise ValueError('error: opening bracket is missing')
else:
stack[-1].append(x)
if len(stack) > 1:
print(stack)
raise ValueError('error: closing bracket is missing')
return stack.pop()
text = "a {{c1::group {{c2::containing::HINT}} a few}} {{c3::words}} or three"
print(parse_nested(text, r'\s*{{', r'}}\s*'))
produce
['a', ['c1::group', ['c2::containing::HINT'], 'a few'], ['c3::words'], 'or three']
Las estructuras anidadas no se pueden combinar con Python regex solo, pero es notablemente fácil t o construir un analizador de base (que puede manejar estructuras anidadas) usando re.Scanner:
import re
class Node(list):
def __init__(self, parent=None):
self.parent = parent
class NestedParser(object):
def __init__(self, left='\(', right='\)'):
self.scanner = re.Scanner([
(left, self.left),
(right, self.right),
(r"\s+", None),
(".+?(?=(%s|%s|$))" % (right, left), self.other),
])
self.result = Node()
self.current = self.result
def parse(self, content):
self.scanner.scan(content)
return self.result
def left(self, scanner, token):
new = Node(self.current)
self.current.append(new)
self.current = new
def right(self, scanner, token):
self.current = self.current.parent
def other(self, scanner, token):
self.current.append(token.strip())
Se puede utilizar la siguiente manera:
p = NestedParser()
print(p.parse("((a+b)*(c-d))"))
# [[['a+b'], '*', ['c-d']]]
p = NestedParser()
print(p.parse("((a ((c) b)) (d) e)"))
# [[['a', [['c'], 'b']], ['d'], 'e']]
Por defecto NestedParser
partidos paréntesis anidados. Puede pasar otras expresiones regulares para que coincidan con otros patrones anidados, como los corchetes, []
. For example,
p = NestedParser('\[', '\]')
result = (p.parse("Lorem ipsum dolor sit amet [@a xxx yyy [@b xxx yyy [@c xxx yyy]]] lorem ipsum sit amet"))
# ['Lorem ipsum dolor sit amet', ['@a xxx yyy', ['@b xxx yyy', ['@c xxx yyy']]],
# 'lorem ipsum sit amet']
p = NestedParser('<foo>', '</foo>')
print(p.parse("<foo>BAR<foo>BAZ</foo></foo>"))
# [['BAR', ['BAZ']]]
Por supuesto, pyparsing
puede hacer mucho más que el código anterior lata. Sin embargo, para este único propósito, lo anterior NestedParser
es de aproximadamente 5 veces más rápido para las pequeñas cadenas:
In [27]: import pyparsing as pp
In [28]: data = "((a ((c) b)) (d) e)"
In [32]: %timeit pp.nestedExpr().parseString(data).asList()
1000 loops, best of 3: 1.09 ms per loop
In [33]: %timeit NestedParser().parse(data)
1000 loops, best of 3: 234 us per loop
y alrededor de 28x más rápido para las cadenas más grandes:
In [44]: %timeit pp.nestedExpr().parseString('({})'.format(data*10000)).asList()
1 loops, best of 3: 8.27 s per loop
In [45]: %timeit NestedParser().parse('({})'.format(data*10000))
1 loops, best of 3: 297 ms per loop
PCRE compatible con los patrones recursivos usando la directiva entre (R?) otras cosas. Python podría haber admitido una PCRE anterior, pero no las versiones más recientes. http://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions –