Согласование вложенных структур с регулярными выражениями в Python
Кажется, я помню, что регулярные выражения в DotNet имеют специальный механизм, который позволяет правильно сопоставлять вложенные структуры, такие как группировка в "( (a ( ( c ) b ) ) ( d ) e )
".
Что такое эквивалент python этой функции? Может ли это быть достигнуто с использованием регулярных выражений с некоторым обходным путем? (Хотя, похоже, это проблема, которая не предназначена для текущих реализаций регулярного выражения)
Ответы
Ответ 1
Вы не можете делать это, как правило, используя регулярные выражения Python. (Регулярные выражения .NET расширены с помощью "балансировочных групп", что позволяет вложенные совпадения.)
Однако PyParsing - очень приятный пакет для этого типа вещей:
from pyparsing import nestedExpr
data = "( (a ( ( c ) b ) ) ( d ) e )"
print nestedExpr().parseString(data).asList()
Вывод:
[[['a', [['c'], 'b']], ['d'], 'e']]
Подробнее о PyParsing:
Ответ 2
Регулярные выражения не могут анализировать вложенные структуры. Вложенные структуры по определению не являются регулярными. Они не могут быть построены с помощью регулярной грамматики, и они не могут быть проанализированы автоматом конечного состояния (регулярное выражение можно рассматривать как сокращенное обозначение для FSA).
Сегодня механизмы "регулярного выражения" иногда поддерживают некоторые ограниченные конструкции "вложенности", но с технической точки зрения их больше не следует называть "регулярными".
Ответ 3
Изменить: falsetru вложенный парсер, который я слегка изменил, чтобы принимать произвольные шаблоны регулярных выражений, чтобы указать разделители и разделители элементов, быстрее и проще, чем исходное решение re.Scanner
:
import re
def parse_nested(text, left=r'[(]', right=r'[)]', sep=r','):
""" /questions/297735/how-to-parse-a-string-and-return-a-nested-array/1462976#1462976 (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*'))
дает
['a', ['c1::group', ['c2::containing::HINT'], 'a few'], ['c3::words'], 'or three']
Вложенные структуры не могут быть сопоставлены только с регулярным выражением Python, но очень просто создать базовый синтаксический анализатор (который может обрабатывать вложенные структуры) с помощью re. сканер:
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())
Его можно использовать следующим образом:
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']]
По умолчанию NestedParser
соответствует вложенным круглым скобкам. Вы можете передать другое регулярное выражение для соответствия другим вложенным шаблонам, таким как скобки, []
. Например,,
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']]]
Конечно, pyparsing
может сделать намного больше, чем может быть приведенный выше код. Но для этой единственной цели выше NestedParser
примерно на 5 раз быстрее для небольших строк:
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
и около 28 раз быстрее для больших строк:
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
Ответ 4
Python не поддерживает рекурсию в регулярных выражениях. Таким образом, эквиваленты для групп балансировки .NET или регулярного выражения PCRE в Perl не сразу возможны в Python.
Как вы сами сказали: это НЕ проблема, которую вы действительно должны решать с помощью одного регулярного выражения.
Ответ 5
Я бы рекомендовал удалить вложенность из самого регулярного выражения, прокручивая результаты и выполняя регулярные выражения.
Ответ 6
Вы говорите о рекурсии? Это не ясно из вашего вопроса. Пример:
ActivePython 2.6.1.1 (ActiveState Software Inc.) based on
Python 2.6.1 (r261:67515, Dec 5 2008, 13:58:38) [MSC v.1500 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import re
>>> p = re.compile(r"((\w+((\d+)[.;]))(\s+)\d)")
>>> m = p.match("Fred99. \t9")
>>> m
<_sre.SRE_Match object at 0x00454F80>
>>> m.groups()
('Fred99. \t9', 'Fred99.', '9.', '9', ' \t')
Это показывает соответствие вложенных групп. Нумерация групп зависит от порядка, в котором их открывающая скобка встречается в шаблоне.