Разбор вложенных круглых скобок в python, захват содержимого по уровню
По-видимому, эта проблема возникает довольно часто после прочтения
Регулярное выражение для обнаружения циклов С++ и для циклов while и while с запятой и т.д.
и некоторое время думая о проблеме, я написал функцию для возврата содержимого, содержащегося внутри произвольного числа вложенных()
Функция может быть легко распространена на любой объект регулярного выражения, размещая здесь свои мысли и соображения.
любые рекомендации по рефакторингу будут оценены
(заметьте, я еще новичок в python, и мне не хотелось выяснять, как поднимать исключения или что-то еще, поэтому я просто получил функцию return 'fail', если он не мог понять, что происходит )
Отредактированная функция для учета комментариев:
def ParseNestedParen(string, level):
"""
Return string contained in nested (), indexing i = level
"""
CountLeft = len(re.findall("\(", string))
CountRight = len(re.findall("\)", string))
if CountLeft == CountRight:
LeftRightIndex = [x for x in zip(
[Left.start()+1 for Left in re.finditer('\(', string)],
reversed([Right.start() for Right in re.finditer('\)', string)]))]
elif CountLeft > CountRight:
return ParseNestedParen(string + ')', level)
elif CountLeft < CountRight:
return ParseNestedParen('(' + string, level)
return string[LeftRightIndex[level][0]:LeftRightIndex[level][1]]
Ответы
Ответ 1
Вы не уточняете, что такое спецификация вашей функции, но это поведение кажется мне неправильным:
>>> ParseNestedParen('(a)(b)(c)', 0)
['a)(b)(c']
>>> nested_paren.ParseNestedParen('(a)(b)(c)', 1)
['b']
>>> nested_paren.ParseNestedParen('(a)(b)(c)', 2)
['']
Другие комментарии к вашему коду:
- Docstring говорит "generate", но функция возвращает список, а не генератор.
- Поскольку возвращается только одна строка, зачем возвращать ее в список?
- При каких обстоятельствах функция возвращает строку
fail
?
- Повторяя вызов
re.findall
, а затем отбрасывая результат, расточительно.
- Вы пытаетесь перебалансировать круглые скобки в строке, но вы делаете это только по одной скобке за раз:
>>> ParseNestedParen(')' * 1000, 1)
RuntimeError: maximum recursion depth exceeded while calling a Python object
Как сказал Томи в вопросе который вы связали с, "регулярные выражения действительно являются неправильным инструментом для работы!"
Обычный способ разбора вложенных выражений состоит в использовании стека в следующих строках:
def parenthetic_contents(string):
"""Generate parenthesized contents in string as pairs (level, contents)."""
stack = []
for i, c in enumerate(string):
if c == '(':
stack.append(i)
elif c == ')' and stack:
start = stack.pop()
yield (len(stack), string[start + 1: i])
>>> list(parenthetic_contents('(a(b(c)(d)e)(f)g)'))
[(2, 'c'), (2, 'd'), (1, 'b(c)(d)e'), (1, 'f'), (0, 'a(b(c)(d)e)(f)g')]
Ответ 2
Для совпадения в скобках требуется синтаксический анализатор с автоматом. Некоторые библиотеки существуют, но правила достаточно просты, чтобы мы могли написать их с нуля:
def push(obj, l, depth):
while depth:
l = l[-1]
depth -= 1
l.append(obj)
def parse_parentheses(s):
groups = []
depth = 0
try:
for char in s:
if char == '(':
push([], groups, depth)
depth += 1
elif char == ')':
depth -= 1
else:
push(char, groups, depth)
except IndexError:
raise ValueError('Parentheses mismatch')
if depth > 0:
raise ValueError('Parentheses mismatch')
else:
return groups
print(parse_parentheses('a(b(cd)f)')) # ['a', ['b', ['c', 'd'], 'f']]
Ответ 3
#!/usr/bin/env python
import re
def ParseNestedParen(string, level):
"""
Generate strings contained in nested (), indexing i = level
"""
if len(re.findall("\(", string)) == len(re.findall("\)", string)):
LeftRightIndex = [x for x in zip(
[Left.start()+1 for Left in re.finditer('\(', string)],
reversed([Right.start() for Right in re.finditer('\)', string)]))]
elif len(re.findall("\(", string)) > len(re.findall("\)", string)):
return ParseNestedParen(string + ')', level)
elif len(re.findall("\(", string)) < len(re.findall("\)", string)):
return ParseNestedParen('(' + string, level)
else:
return 'fail'
return [string[LeftRightIndex[level][0]:LeftRightIndex[level][1]]]
Тесты:
if __name__ == '__main__':
teststring = "outer(first(second(third)second)first)outer"
print(ParseNestedParen(teststring, 0))
print(ParseNestedParen(teststring, 1))
print(ParseNestedParen(teststring, 2))
teststring_2 = "outer(first(second(third)second)"
print(ParseNestedParen(teststring_2, 0))
print(ParseNestedParen(teststring_2, 1))
print(ParseNestedParen(teststring_2, 2))
teststring_3 = "second(third)second)first)outer"
print(ParseNestedParen(teststring_3, 0))
print(ParseNestedParen(teststring_3, 1))
print(ParseNestedParen(teststring_3, 2))
выход:
Running tool: python3.1
['first(second(third)second)first']
['second(third)second']
['third']
['first(second(third)second)']
['second(third)second']
['third']
['(second(third)second)first']
['second(third)second']
['third']
>>>