【问题标题】:How does regular expression engine parse regex with recursive subpatterns?正则表达式引擎如何解析带有递归子模式的正则表达式?
【发布时间】:2012-07-25 02:06:14
【问题描述】:

这个正则表达式匹配回文: ^((.)(?1)\2|.?)$

无法理解它是如何工作的。 递归何时结束,何时正则表达式从递归子模式中中断并转到"|.?" 部分?

谢谢。

编辑:抱歉我没有解释\2(?1)

(?1) - 指代第一个子模式(对其自身)

\2 - 反向引用第二个子模式的匹配,即(.)

上面的例子是用 PHP 编写的。匹配“abba”(没有中间回文字符)和“abcba” - 有一个中间的非反射字符

【问题讨论】:

  • 这在程序员中可能会更好,因为这真的不是一个“实用”的问题。
  • .? 可能用于奇数长度的字符串。
  • 抱歉,请问(?1)\2 是什么?
  • Perl 的调试工具比 PCRE 好,试试:echo 123454321 | perl -Mre=debug -ne '/^((.)(?1)\2|.?)$/x'
  • @AlvinWong: (?1)=recurse into group 1, \2=backreference to match from group 2

标签: php regex recursion pcre palindrome


【解决方案1】:

^((.)(?1)\2|.?)$

^$ 分别断言字符串的开头和结尾。让我们看看中间的内容,比较有趣:

((.)(?1)\2|.?)
1------------1 // Capturing group 1
 2-2           // Capturing group 2

查看第一部分(.)(?1)\2,我们可以看到它会尝试匹配任何字符,并且在末尾匹配相同的字符(反向引用\2,它指的是(.)匹配的字符)。在中间,它将递归匹配整个捕获组 1。请注意,有一个隐式断言(由 (.) 匹配开头的一个字符和 \2 匹配末尾的相同字符引起)需要字符串至少为 2 个字符。第一部分的目的是递归地切断字符串的相同末端。

查看第二部分.?,我们可以看到它将匹配一个或0个字符。仅当字符串最初的长度为 0 或 1,或者在递归匹配的剩余部分为 0 或 1 个字符之后,才会匹配。第二部分的目的是匹配空字符串或字符串从两端截断后的单个孤独字符。

递归匹配有效:

  • 整个字符串必须是回文才能通过,由^$ 断言。我们无法从任何随机位置开始匹配。
  • 如果字符串
  • 如果字符串> 2个字符,是否接受仅由第一部分决定。如果匹配,它将被切断 2 端。
  • 如果匹配的剩余,只能被 2 端切掉,或者如果长度

【讨论】:

  • 感谢您的回复。我已经用更多解释编辑了原始帖子。 \2 不是长度。
  • \2 不是长度,但它确实强制该表达式至少有两个字符长,因为单个字符不能同时匹配 (.) 和反向引用 \2。跨度>
  • 每次由于第一部分而递归,它正在处理删除两个结束字符的字符串。最终它缩小到 0 或 1 个字符,然后第二部分匹配并且递归停止。
  • 我承认这有点令人困惑。我已经编辑了我的帖子,但我不确定它是否更清晰。
  • @alexy2k:我不知道正则表达式引擎是如何工作的。我想它会首先愚蠢地进入递归匹配(?1),然后它会匹配\2(因为在一般的正则表达式中,你不会知道前面是否有更多的递归组)。这更多的是猜测,所以不要太认真地对待这条评论。
【解决方案2】:

正则表达式本质上等同于以下伪代码:

palin(str) {
    if (length(str) >= 2) {
      first = str[0];
      last = str[length(str)-1];
      return first == last && palin(substr(str, 1, length(str)-2));
    } else
      // empty and single-char trivially palindromes
      return true;
}

【讨论】:

    【解决方案3】:

    我还没有为 PCRE 正则表达式找到任何好的调试工具。我能找到的更多是如何转储字节码:

    $ pcretest -b
    PCRE version 7.6 2008-01-28
    
      re> /^((.)(?1)\2|.?)$/x
    ------------------------------------------------------------------
      0  39 Bra
      3     ^
      4  26 CBra 1
      9   6 CBra 2
     14     Any
     15   6 Ket
     18   6 Once
     21   4 Recurse
     24   6 Ket
     27     \2
     30   5 Alt
     33     Any?
     35  31 Ket
     38     $
     39  39 Ket
     42     End
    ------------------------------------------------------------------
    

    Perl 比 PCRE 有更好的调试工具,试试echo 123454321 | perl -Mre=debug -ne '/^((.)(?1)\2|.?)$/x'。这不仅给出了一些类似于 PCRE 的字节码,而且还显示了每一步,以及每一步输入的消耗和剩余部分:

    Compiling REx "^((.)(?1)\2|.?)$"
    Final program:
       1: BOL (2)
       2: OPEN1 (4)
       4:   BRANCH (15)
       5:     OPEN2 (7)
       7:       REG_ANY (8)
       8:     CLOSE2 (10)
      10:     GOSUB1[-8] (13)
      13:     REF2 (19)
      15:   BRANCH (FAIL)
      16:     CURLY {0,1} (19)
      18:       REG_ANY (0)
      19: CLOSE1 (21)
      21: EOL (22)
      22: END (0)
    floating ""$ at 0..2147483647 (checking floating) anchored(BOL) minlen 0 
    Guessing start of match in sv for REx "^((.)(?1)\2|.?)$" against "12321"
    Found floating substr ""$ at offset 5...
    Guessed: match at offset 0
    Matching REx "^((.)(?1)\2|.?)$" against "12321"
       0 <> <12321>              |  1:BOL(2)
       0 <> <12321>              |  2:OPEN1(4)
       0 <> <12321>              |  4:BRANCH(15)
       0 <> <12321>              |  5:  OPEN2(7)
       0 <> <12321>              |  7:  REG_ANY(8)
       1 <1> <2321>              |  8:  CLOSE2(10)
       1 <1> <2321>              | 10:  GOSUB1[-8](13)
       1 <1> <2321>              |  2:    OPEN1(4)
       1 <1> <2321>              |  4:    BRANCH(15)
       1 <1> <2321>              |  5:      OPEN2(7)
       1 <1> <2321>              |  7:      REG_ANY(8)
       2 <12> <321>              |  8:      CLOSE2(10)
       2 <12> <321>              | 10:      GOSUB1[-8](13)
       2 <12> <321>              |  2:        OPEN1(4)
       2 <12> <321>              |  4:        BRANCH(15)
       2 <12> <321>              |  5:          OPEN2(7)
       2 <12> <321>              |  7:          REG_ANY(8)
       3 <123> <21>              |  8:          CLOSE2(10)
       3 <123> <21>              | 10:          GOSUB1[-8](13)
       3 <123> <21>              |  2:            OPEN1(4)
       3 <123> <21>              |  4:            BRANCH(15)
       3 <123> <21>              |  5:              OPEN2(7)
       3 <123> <21>              |  7:              REG_ANY(8)
       4 <1232> <1>              |  8:              CLOSE2(10)
       4 <1232> <1>              | 10:              GOSUB1[-8](13)
       4 <1232> <1>              |  2:                OPEN1(4)
       4 <1232> <1>              |  4:                BRANCH(15)
       4 <1232> <1>              |  5:                  OPEN2(7)
       4 <1232> <1>              |  7:                  REG_ANY(8)
       5 <12321> <>              |  8:                  CLOSE2(10)
       5 <12321> <>              | 10:                  GOSUB1[-8](13)
       5 <12321> <>              |  2:                    OPEN1(4)
       5 <12321> <>              |  4:                    BRANCH(15)
       5 <12321> <>              |  5:                      OPEN2(7)
       5 <12321> <>              |  7:                      REG_ANY(8)
                                                            failed...
       5 <12321> <>              | 15:                    BRANCH(19)
       5 <12321> <>              | 16:                      CURLY {0,1}(19)
                                                            REG_ANY can match 0 times out of 1...
       5 <12321> <>              | 19:                        CLOSE1(21)
                                                              EVAL trying tail ... 9d86dd8
       5 <12321> <>              | 13:                          REF2(19)
                                                                failed...
                                                            failed...
                                                          BRANCH failed...
       4 <1232> <1>              | 15:                BRANCH(19)
       4 <1232> <1>              | 16:                  CURLY {0,1}(19)
                                                        REG_ANY can match 1 times out of 1...
       5 <12321> <>              | 19:                    CLOSE1(21)
                                                          EVAL trying tail ... 9d86d70
       5 <12321> <>              | 13:                      REF2(19)
                                                            failed...
       4 <1232> <1>              | 19:                    CLOSE1(21)
                                                          EVAL trying tail ... 9d86d70
       4 <1232> <1>              | 13:                      REF2(19)
                                                            failed...
                                                        failed...
                                                      BRANCH failed...
       3 <123> <21>              | 15:            BRANCH(19)
       3 <123> <21>              | 16:              CURLY {0,1}(19)
                                                    REG_ANY can match 1 times out of 1...
       4 <1232> <1>              | 19:                CLOSE1(21)
                                                      EVAL trying tail ... 9d86d08
       4 <1232> <1>              | 13:                  REF2(19)
                                                        failed...
       3 <123> <21>              | 19:                CLOSE1(21)
                                                      EVAL trying tail ... 9d86d08
       3 <123> <21>              | 13:                  REF2(19)
                                                        failed...
                                                    failed...
                                                  BRANCH failed...
       2 <12> <321>              | 15:        BRANCH(19)
       2 <12> <321>              | 16:          CURLY {0,1}(19)
                                                REG_ANY can match 1 times out of 1...
       3 <123> <21>              | 19:            CLOSE1(21)
                                                  EVAL trying tail ... 9d86ca0
       3 <123> <21>              | 13:              REF2(19)
       4 <1232> <1>              | 19:              CLOSE1(21)
                                                    EVAL trying tail ... 0
       4 <1232> <1>              | 13:                REF2(19)
       5 <12321> <>              | 19:                CLOSE1(21)
       5 <12321> <>              | 21:                EOL(22)
       5 <12321> <>              | 22:                END(0)
    Match successful!
    Freeing REx: "^((.)(?1)\2|.?)$"
    

    如您所见,Perl 首先消耗所有输入递归直到(.) 失败,然后开始回溯并尝试从交替.? 和第一部分的其余部分\2 的第二个分支,当失败时它回溯,直到最终成功。

    【讨论】:

    • 为什么在这个调试中,有六个OPEN1,而对应的是八个CLOSE1?不应该是六个CLOSE1吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-17
    • 1970-01-01
    • 2014-12-10
    • 1970-01-01
    • 2020-03-24
    • 1970-01-01
    相关资源
    最近更新 更多