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

Vic*_*Yan 17 python regex recursive-regex

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

什么是python相当于这个功能?这可以使用正则表达式实现一些解决方法吗?(虽然这似乎是当前正则表达式的实现不是为此而设计的那种问题)

Sva*_*nte 21

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

今天的"正则表达式"引擎有时支持一些有限的"嵌套"结构,但从技术角度来看,它们不应再被称为"常规".

  • 这条重要信息的+1.应该注意的是,添加嵌套支持并非无害.关于真正的正则表达式引擎的一个很酷的事情是在处理时不需要额外的内存,除了存储状态机的常量和记住当前状态的变量.另一个是运行速度,我认为它与输入的长度成线性关系.添加嵌套支持会增加这两个好处. (6认同)

ars*_*ars 19

通常使用Python正则表达式不能这样做.(.NET正则表达式已经使用"平衡组"进行了扩展,这是允许嵌套匹配的.)

但是,PyParsing对于这类事物来说是一个非常好的包:

from pyparsing import nestedExpr

data = "( (a ( ( c ) b ) ) ( d ) e )"
print nestedExpr().parseString(data).asList()
Run Code Online (Sandbox Code Playgroud)

输出是:

[[['a', [['c'], 'b']], ['d'], 'e']]
Run Code Online (Sandbox Code Playgroud)

有关PyParsing的更多信息:


unu*_*tbu 14

编辑: falsetru的嵌套解析器,我稍微修改为接受任意正则表达式模式来指定分隔符和项目分隔符,比我原来的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*'))
Run Code Online (Sandbox Code Playgroud)

产量

['a', ['c1::group', ['c2::containing::HINT'], 'a few'], ['c3::words'], 'or three']
Run Code Online (Sandbox Code Playgroud)

嵌套结构不能与Python正则表达式匹配的单独的,但它是非常容易建立一个基本解析器使用(其可以处理嵌套结构)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())
Run Code Online (Sandbox Code Playgroud)

它可以像这样使用:

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']]
Run Code Online (Sandbox Code Playgroud)

默认情况下,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']]]
Run Code Online (Sandbox Code Playgroud)

当然,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
Run Code Online (Sandbox Code Playgroud)

大字符串的速度提高约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
Run Code Online (Sandbox Code Playgroud)