【问题标题】:Can regular expressions be used to match nested patterns? [duplicate]可以使用正则表达式匹配嵌套模式吗? [复制]
【发布时间】:2010-09-13 02:42:53
【问题描述】:

是否可以编写一个匹配出现次数未知的嵌套模式的正则表达式?例如,当外部大括号中嵌套了未知数量的打开/关闭大括号时,正则表达式是否可以匹配左大括号和右大括号?

例如:

public MyMethod()
{
  if (test)
  {
    // More { }
  }

  // More { }
} // End

应该匹配:

{
  if (test)
  {
    // More { }
  }

  // More { }
}

【问题讨论】:

  • 要明确回答这个问题,首先需要定义术语:“正则表达式”。
  • 从书中看,正则表达式不能做到这一点,但上下文无关表达式可以。从这些工具中,现代表达式解析器将调用 regular expression 使用外部堆栈的东西,这意味着能够回溯,意味着能够递归:实际上是 context-free expressions,因此您可以使用simili-PCRE2 (PHP, Java, .NET, Perl, ...) 或 ICU 兼容 (Obj-C/Swift) 工具,通常使用 (?>...) 语法,或 (?R) 等替代方法或(?0) 语法。

标签: regex nested finite-automata


【解决方案1】:

是的

...假设有一些您愿意停下来的最大嵌套数。

让我解释一下。


@torsten-marek 是正确的,正则表达式无法检查这样的嵌套模式,但是可以定义一个嵌套的正则表达式模式,它可以让您捕获像这样的嵌套结构达到某个最大深度。我创建了一个来捕获EBNF-style cmets (try it out here),比如:

(* This is a comment (* this is nested inside (* another level! *) hey *) yo *)

正则表达式(用于单深度 cmets)如下:

m{1} = \(+\*+(?:[^*(]|(?:\*+[^)*])|(?:\(+[^*(]))*\*+\)+

这可以通过将\(+\*+\*+\)+ 替换为{} 并将其间的所有内容替换为简单的[^{}] 来轻松适应您的目的:

p{1} = \{(?:[^{}])*\}

Here's the link 尝试一下。)

要嵌套,只需在块本身内允许此模式:

p{2} = \{(?:(?:p{1})|(?:[^{}]))*\}
  ...or...
p{2} = \{(?:(?:\{(?:[^{}])*\})|(?:[^{}]))*\}

要查找三重嵌套块,请使用:

p{3} = \{(?:(?:p{2})|(?:[^{}]))*\}
  ...or...
p{3} = \{(?:(?:\{(?:(?:\{(?:[^{}])*\})|(?:[^{}]))*\})|(?:[^{}]))*\}

一个清晰的模式已经出现。要查找嵌套到 N 深度的 cmets,只需使用正则表达式:

p{N} = \{(?:(?:p{N-1})|(?:[^{}]))*\}

  where N > 1 and
  p{1} = \{(?:[^{}])*\}

可以编写一个脚本来递归地生成这些正则表达式,但这超出了我需要的范围。 (这留给读者作为练习。?)

【讨论】:

  • Java 版练习:String getNestedBraceRegex(int n) {if(n == 1) return "\\{(?:[^{}])*\\}"; else return "\\{(?:(?:" + getNestedBraceRegex(n-1) + ")|(?:[^{}]))*\\}";} 完美,谢谢。 PCRE/2 更优雅:(\{((?>[^{}]+|(?1))*)\}) 但这不起作用,例如在 Java 中。
【解决方案2】:

不,那时你正在进入Context Free Grammars 的领域。

【讨论】:

    【解决方案3】:

    是的,如果它是 .NET RegEx 引擎。 .Net 引擎支持由外部堆栈提供的有限状态机。见details

    【讨论】:

    • 正如其他人所提到的,.NET不是唯一能够做到这一点的正则表达式引擎。
    【解决方案4】:

    如果字符串在一行上,可能是有效的 Perl 解决方案:

    my $NesteD ;
    $NesteD = qr/ \{( [^{}] | (??{ $NesteD }) )* \} /x ;
    
    if ( $Stringy =~ m/\b( \w+$NesteD )/x ) {
        print "Found: $1\n" ;
      }
    

    HTH

    编辑:检查:

    Torsten Marek 的另一件事(他正确指出,它不再是正则表达式):

    【讨论】:

    • 是的。 Perl 的“正则表达式”不是(并且已经很久没有出现了)。应该注意的是,递归正则表达式是 Perl 5.10 中的一项新功能,即使您可以这样做,但在大多数常见的情况下(例如解析 HTML)可能不应该这样做。
    【解决方案5】:

    在 PHP 正则表达式引擎中使用递归匹配比括号的过程匹配要快得多。尤其是较长的字符串。

    http://php.net/manual/en/regexp.reference.recursive.php

    例如

    $patt = '!\( (?: (?: (?>[^()]+) | (?R) )* ) \)!x';
    
    preg_match_all( $patt, $str, $m );
    

    对比

    matchBrackets( $str );
    
    function matchBrackets ( $str, $offset = 0 ) {
    
        $matches = array();
    
        list( $opener, $closer ) = array( '(', ')' );
    
        // Return early if there's no match
        if ( false === ( $first_offset = strpos( $str, $opener, $offset ) ) ) {
            return $matches;
        }
    
        // Step through the string one character at a time storing offsets
        $paren_score = -1;
        $inside_paren = false;
        $match_start = 0;
        $offsets = array();
    
        for ( $index = $first_offset; $index < strlen( $str ); $index++ ) {
            $char = $str[ $index ];
    
            if ( $opener === $char ) {
                if ( ! $inside_paren ) {
                    $paren_score = 1;
                    $match_start = $index;
                }
                else {
                    $paren_score++;
                }
                $inside_paren = true;
            }
            elseif ( $closer === $char ) {
                $paren_score--;
            }
    
            if ( 0 === $paren_score ) {
                $inside_paren = false;
                $paren_score = -1;
                $offsets[] = array( $match_start, $index + 1 );
            }
        }
    
        while ( $offset = array_shift( $offsets ) ) {
    
            list( $start, $finish ) = $offset;
    
            $match = substr( $str, $start, $finish - $start );
            $matches[] = $match;
        }
    
        return $matches;
    }
    

    【讨论】:

      【解决方案6】:

      使用正则表达式检查嵌套模式非常容易。

      '/(\((?>[^()]+|(?1))*\))/'
      

      【讨论】:

      • 我同意。然而,(?&gt;...) 原子组语法(在 PHP 5.2 下)的一个问题是 ?&gt; 部分被解释为:“脚本结束”!我会这样写:/\((?:[^()]++|(?R))*+\)/。这对于匹配和非匹配都更有效。以最小的形式,/\(([^()]|(?R))*\)/,它确实是一件美丽的事情!
      • 双+?我使用 (?1) 允许 cmets 位于其他文本中(我将其撕掉并从我的电子邮件地址正则表达式中简化了它)。并且使用了(?&gt;,因为我相信它会使它更快地失败(如果需要)。这不正确吗?
      • 能否为正则表达式的每个部分添加解释?
      • 对于字符串'(a (b c)) (d e)',使用简单的表达式'/\([^()]*\)/' 给我同样的结果。长表达有什么好处吗?
      • 尝试使用/^(\((?&gt;[^()]+|(?1))*\))+$//^\([^()]*\)+$/ 来匹配(a (b c))(d e)。前者匹配,但后者不匹配。
      【解决方案7】:

      这似乎有效:/(\{(?:\{.*\}|[^\{])*\})/m

      【讨论】:

      • 它似乎也与不应该匹配的 {{} 匹配
      【解决方案8】:

      正如 zsolt 提到的,一些正则表达式引擎支持递归——当然,这些引擎通常使用回溯算法,因此它不会特别有效。例如:/(?&gt;[^{}]*){(?&gt;[^{}]*)(?R)*(?&gt;[^{}]*)}/sm

      【讨论】:

        【解决方案9】:

        正确的正则表达式将无法做到这一点,因为您将离开正则语言领域进入上下文无关语言领域。

        尽管如此,许多语言提供的“正则表达式”包更强大。

        例如,Lua 正则表达式具有匹配平衡括号的“%b()”识别器。在您的情况下,您将使用“%b{}

        另一个类似于 sed 的复杂工具是 gema,您可以在其中非常轻松地将平衡的花括号与 {#} 匹配。

        因此,根据您可以使用的工具,您的“正则表达式”(在更广泛的意义上)可能能够匹配嵌套括号。

        【讨论】:

          【解决方案10】:

          Pumping lemma for regular languages 是你不能这样做的原因。

          生成的自动机将具有有限数量的状态,例如 k,因此一串 k+1 个左大括号必然会在某处重复一个状态(当自动机处理字符时)。同一状态之间的字符串部分可以无限重复,自动机不知道区别。

          特别是,如果它接受 k+1 个左大括号,后跟 k+1 个右大括号(它应该接受),它还将接受泵送的左大括号数,然后是不变的 k+1 个右大括号(它不应该)。

          【讨论】:

            【解决方案11】:

            没有。就这么容易。有限自动机(它是正则表达式基础的数据结构)除了它所处的状态之外没有内存,如果你有任意深度的嵌套,你需要一个任意大的自动机,这与 的概念相冲突有限的自动机。

            您可以将嵌套/配对元素匹配到固定深度,其中深度仅受您的记忆限制,因为自动机变得非常大。然而,在实践中,您应该使用下推自动机,即用于上下文无关文法的解析器,例如 LL(自上而下)或 LR(自下而上)。您必须考虑更糟糕的运行时行为:O(n^3) vs. O(n),n = length(input)。

            有许多可用的解析器生成器,例如用于 Java 的 ANTLR。为 Java(或 C)找到现有的语法也不难。
            更多背景信息:Automata Theory 维基百科

            【讨论】:

            • 托斯滕就理论而言是正确的。在实践中,许多实现都有一些技巧,以允许您执行递归“正则表达式”。例如。请参阅php.net/manual/en/regexp.reference.php中的“递归模式”一章
            • 我被自然语言处理领域的成长和其中包含的自动机理论宠坏了。
            • 一个令人耳目一新的清晰答案。我见过的最好的“为什么不”。
            • 语言理论中的正则表达式和实践中的正则表达式是不同的野兽......因为 regular 表达式不能有诸如反向引用、前向引用等细节。跨度>
            • @TorstenMarek - 你能确认这仍然是真的吗?其他来源指出,如果正则表达式引擎支持诸如反向引用之类的功能,它将成为 2 类语法(无上下文)而不是 3 类(常规语法)。因此,例如 PCRE - 能够处理嵌套结构。混淆来自现实世界中的“正则表达式”在技术意义上不再是规则的事实。如果这是正确的,那么更新这个答案会很棒。
            猜你喜欢
            • 2018-07-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-02-17
            • 1970-01-01
            相关资源
            最近更新 更多