这个递归正则表达式究竟是如何工作的?

时间:2017-05-10 10:19:28

标签: regex recursion pcre regex-group regex-recursion

这是this question的后续内容。

看看这种模式:

(o(?1)?o)

它匹配o的任何序列,长度为2 n ,n≥1。 It works, see regex101.com(为了更好的示范而添加了单词边界) 问题是:为什么?

在下文中,字符串的描述(匹配与否)将只是粗体数字或描述长度的粗体术语,如 2 n

细分(添加空格):

( o (?1)? o )
(           ) # Capture group 1
  o       o   # Matches an o each at the start and the end of the group
              # -> the pattern matches from the outside to the inside.
    (?1)?     # Again the regex of group 1, or nothing.
              # -> Again one 'o' at the start and one at the end. Or nothing.

我不明白为什么这不匹配 2n ,而 2 n ,因为我会将模式描述为* an未定义的o o数量,相互堆叠。

可视化:

没有递归, 2 是匹配:

oo

一次递归, 4 是匹配:

o  o
 oo

到目前为止,这很容易。

两次递归。显然是错误的,因为模式与 6

不匹配
o    o
 o  o
  oo

但为什么呢?它似乎符合这种模式。

我的结论是,重复的不仅仅是普通模式,因为否则 6 必须匹配。

但根据regular-expressions.info

  

(?P<name>[abc])(?1)(?P>name)匹配(?P<name>[abc])[abc][abc]之类的三个字母。

  

[abc])(?1){3} [...]相当于([abc])[abc]{3}

所以它似乎只是重新匹配正则表达式代码而没有关于捕获组的上一个匹配的信息。

有人可以解释并可能想象出这种模式与 2 n 相匹配的原因吗?

修改

评论中提到了这一点:

  

我怀疑引用自身内部的捕获组实际上是一个支持的案例。

regular-expressions.info does mention the technique:

  

如果您在其呼叫的组内发出呼叫,您将拥有一个递归捕获组。

2 个答案:

答案 0 :(得分:4)

您正确了解递归。词边界让你感到困惑。模式周围的\b要求正则表达式引擎仅匹配字符串(如果字符串之前没有字符串并且后跟字符串。)

看看递归如何:

( o      (?1)?         o )  => oo
然后将

(?1)替换为(o(?1)?o)

( o   (?>o(?1)?o)?     o )  => oo or oooo

然后再说:

(o (?>o(?>o(?1)?o)?o)?  o) => oo, oooo, oooooo

请参阅regex demo without word boundaries

为什么在上面的例子中添加(?>...) Each recursion level in PHP recursive regexes is atomic与Perl 不同,一旦前一级别失败,引擎就不会去回到下面的那个。

当您添加字边界时,匹配的第一个o和最后o在之前/之后不能包含任何其他字词。那么,ooo won't match那么。

请参阅rexegg.com上逐步解释的Recursive Regular ExpressionsWord Boundary: \b

  

为什么oooooo不能作为整体匹配,而是oooooo

同样,每个递归级别都是原子级的。 oooooo匹配如下:

  • (o(?1)?o)与第一个o
  • 相匹配
  • (?1)?展开,模式现在为(o(?>o(?1)?o)?o),与输入中的第二个o相匹配
  • 直到(o(?>o(?>o(?>o(?>o(?>o(?>o(?1)?o)?o)?o)?o)?o)?o)?o)再与输入不匹配,回溯发生,我们进入第6级,
  • 整个第6个递归级别也失败,因为它无法匹配必要的o s
  • 这一直持续到可以匹配必要数量o s。
  • 的级别

请参阅regex debugger

enter image description here

答案 1 :(得分:2)

这或多或少都是Wikors回答的后续行动 - 即使在删除单词边界后,我也很难弄清楚为什么oooooo(6)匹配为oooo和{{1 } {} oo(7)匹配为ooooooo

以下是它的详细工作原理:

扩展递归模式时,内部递归是原子的。使用我们的模式,我们可以将其展开到

oooooo

(在实际模式中,这个获得再次展开,但这不会改变解释)

以下是字符串的匹配方式 - 首先(?>o(?>o(?>o(?>o(?>oo)?o)?o)?o)?o) (6)

oooooo

现在(?>o(?>o(?>o(?>o(?>oo)?o)?o)?o)?o) o |ooooo <- first o gets matched by first atomic group o o |oooo <- second o accordingly o o o |ooo <- third o accordingly o o o o |oo <- fourth o accordingly o o o o oo| <- fifth/sixth o by the innermost atomic group ^ <- there is no more o to match, so backtracking starts - innermost ag is not matched, cursor positioned after 4th character o o o o xx o |o <- fifth o matches, fourth ag is successfully matched (thus no backtracking into it) o o o o xx o o| <- sixth o matches, third ag is successfully matched (thus no backtracking into it) ^ <- no more o, backtracking again - third ag can't be backtracked in, so backtracking into second ag (with matching 3rd 0 times) o o |oo<oo <- third and fourth o close second and first atomic group -> match returned (4 os) (7)

ooooooo