用Python中的正则表达式匹配嵌套结构

用Python中的正则表达式匹配嵌套结构,python,regex,recursive-regex,Python,Regex,Recursive Regex,我似乎记得DotNet中的正则表达式有一种特殊的机制,允许正确匹配嵌套结构,比如“((a((c)b))(d)e)”中的分组 这个特性的python等价物是什么?这可以通过使用带有一些变通方法的正则表达式来实现吗?(尽管这似乎是当前正则表达式实现所没有考虑的那种问题)我建议从正则表达式本身移除嵌套,循环遍历结果并对其执行正则表达式。Python不支持正则表达式中的递归。因此,在Python中不可能立即实现与.NET的平衡组或Perl中的PCRE正则表达式等价的功能 就像你自己说的:这不是一个你真的

我似乎记得DotNet中的正则表达式有一种特殊的机制,允许正确匹配嵌套结构,比如“
((a((c)b))(d)e)
”中的分组


这个特性的python等价物是什么?这可以通过使用带有一些变通方法的正则表达式来实现吗?(尽管这似乎是当前正则表达式实现所没有考虑的那种问题)

我建议从正则表达式本身移除嵌套,循环遍历结果并对其执行正则表达式。

Python不支持正则表达式中的递归。因此,在Python中不可能立即实现与.NET的平衡组或Perl中的PCRE正则表达式等价的功能


就像你自己说的:这不是一个你真的应该用一个正则表达式来解决的问题。

你是说递归吗?你的问题不清楚。例如:

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')
ActivePython 2.6.1.1(ActiveState软件公司)基于
Python 2.6.1(r261:675152008年12月5日13:58:38)[MSC v.1500 32位(英特尔)]上
win32
有关详细信息,请键入“帮助”、“版权”、“信用证”或“许可证”。
>>>进口稀土
>>>p=重新编译(r“(\w+((\d+[.;]))(\s+\d)”)
>>>m=p.match(“Fred99.\t9”)
>>>m
>>>m.团体()
('Fred99.\t9','Fred99','9','9','t')
这显示嵌套组的匹配。组的编号取决于它们的左括号在模式中出现的顺序。

正则表达式无法解析嵌套结构。根据定义,嵌套结构不是规则的。它们不能由正则语法构造,也不能由有限状态自动机解析(正则表达式可以看作是FSA的简写符号)


今天的“regex”引擎有时支持一些有限的“嵌套”结构,但从技术角度来看,它们不应该再被称为“正则”了。

通常使用Python正则表达式无法做到这一点。(.NET正则表达式已通过“平衡组”进行扩展,这是允许嵌套匹配的原因。)

但是,PyParsing是一个非常好的包,适用于这种类型的东西:

from pyparsing import nestedExpr

data = "( (a ( ( c ) b ) ) ( d ) e )"
print nestedExpr().parseString(data).asList()
输出为:

[[['a', [['c'], 'b']], ['d'], 'e']]
有关PyParsing的更多信息:

编辑:,我稍微修改了它,以接受任意正则表达式模式来指定分隔符和项目分隔符,它比我原来的
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*'))
屈服

['a', ['c1::group', ['c2::containing::HINT'], 'a few'], ['c3::words'], 'or three']

嵌套结构不能单独与Python正则表达式匹配,但是使用以下方法构建基本解析器(可以处理嵌套结构)非常容易:

它可以这样使用:

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
匹配嵌套括号。您可以传递其他正则表达式以匹配其他嵌套模式,例如括号、
[]

对于较大的字符串,速度提高了约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

PCRE支持使用(?R)指令等递归模式。Python可能支持较旧的PCRE,但不支持较新的版本+1了解这一重要信息。应该注意的是,添加嵌套支持并不是无害的。真正的正则表达式引擎最酷的一点是,在处理过程中不需要额外的内存,只需要一个常量来存储状态机和一个变量来记住当前状态。另一个是运行速度,我认为它与输入的长度成线性关系。添加嵌套支持会破坏这两个好处。@危害:Python正则表达式已经是非规则的(它们支持反向引用),并且可以演示指数行为
R=R'[^()]*'\n表示范围内的uu(表达式计数)():R=f'(?:{R}\({R}\)+'\n re.fullmatch(R,expr)
。这里是
O(n**2)
算法:
是λn:不是re.fullmatch(R'1?;(11+)\1+,'1'*n)
。虽然添加递归支持会使问题比正则表达式更大,但它们现在的问题更严重“我们在这里都是同意的成年人”)。这是一个很好的提示!我甚至没有听说过re.Scanner。这将完全回答我昨天的问题。如果它不关闭,我会选择这个答案…再次感谢您让我知道!我正在与原始海报一样的问题进行斗争,但您的解决方案有一个方面使我无法使用它:它似乎删除了结果末尾的所有空白字符。如何才能保留列出的任何字符串末尾的空格?@DocBuckets:您能给出一个输入和所需输出的示例吗?要保留的是字符串末尾的空白字符,还是到处都是空白字符?以及la后面的空白字符st closing parethesis?@unutbu我正在解析
string=“a{{c1::group{{c2::containing::HINT}}几个}{c3::words}}}或三个”
,基于上面嵌套的解析器代码和作为分隔符的
{code>,
}
。代码不保留“group”和“{c2:”之间的空间:“例如,@docbucket:我已经编辑了我的答案,以显示另一种方法,该方法(我认为)可以根据需要解析
字符串
。Pyparsing不再托管在wikispaces.com上。转到”
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']]]
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
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