重要提示:本文介绍了PHP中的递归正则表达式(使用PCRE库)。在Perl本身中,递归正则表达式的工作方式有所不同。
注意:这是按照可以概念化的顺序解释的。正则表达式引擎实际上是反向执行的;它会向下深入到基本情况,然后反向执行回来。
由于外层的a
已经明确定义,因此它将匹配两个a
之间的a
,或前一个递归匹配的整个模式在两个a
之间的内容。因此,它只能匹配奇数个a
(中间一个加上两个的倍数)。
当长度为三时,aaa
是当前递归的匹配模式,因此在第四个递归中,它正在寻找两个a
之间的a
(即aaa
)或前一个递归匹配的模式在两个a
之间的内容(即a
+aaa
+a
)。显然,当字符串长度不够时,它无法匹配五个a
,因此它所能找到的最长匹配是三个。
长度为六的情况类似,因为它只能匹配“默认”的aaa
,或前一个递归的匹配在两个a
之间的内容(即a
+aaaaa
+a
)。
然而,并不是所有奇数长度都能匹配。
由于您正在进行递归匹配,因此只能匹配字面上的aaa
或a
+(上一次递归匹配)+ a
。因此,每次连续的匹配都将比上次的匹配多两个a
,否则将返回到aaa
。
在长度为七(匹配aaaaaaa
)时,上一个递归的匹配是回退aaa
。因此,即使有七个a
,它也只会匹配三个(aaa
)或五个(a
+aaa
+a
)。
当循环到更长的长度时(例如80),请查看模式(仅显示匹配项,不显示输入内容):
no match
aa
aaa
aaa
aaaaa
aaa
aaaaa
aaaaaaa
aaaaaaaaa
aaa
aaaaa
aaaaaaa
aaaaaaaaa
aaaaaaaaaaa
aaaaaaaaaaaaa
aaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaa
aaa
aaaaa
aaaaaaa
aaaaaaaaa
aaaaaaaaaaa
aaaaaaaaaaaaa
aaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaa
aaaaa
aaaaaaa
aaaaaaaaa
aaaaaaaaaaa
aaaaaaaaaaaaa
aaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaa
aaaaa
aaaaaaa
aaaaaaaaa
aaaaaaaaaaa
aaaaaaaaaaaaa
aaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaa
这里发生了什么?好的,我告诉你!:-)
当递归匹配长度比输入字符串多一个字符时,它会返回到aaa
,我们已经看到过了。在此之后的每次迭代中,模式重新开始匹配比上一次匹配多两个字符。每次迭代中,输入的长度增加1,但匹配的长度增加2。当匹配长度最终追上并超过输入字符串的长度时,它会返回到aaa
。以此类推。
或者换个角度看,我们可以看到在每次迭代中,输入长度与匹配长度相比多出了多少个字符:
(input len.) - (match len.) = (difference)
1 - 0 = 1
2 - 2 = 0
3 - 3 = 0
4 - 3 = 1
5 - 5 = 0
6 - 3 = 3
7 - 5 = 2
8 - 7 = 1
9 - 9 = 0
10 - 3 = 7
11 - 5 = 6
12 - 7 = 5
13 - 9 = 4
14 - 11 = 3
15 - 13 = 2
16 - 15 = 1
17 - 17 = 0
18 - 3 = 15
19 - 5 = 14
20 - 7 = 13
21 - 9 = 12
22 - 11 = 11
23 - 13 = 10
24 - 15 = 9
25 - 17 = 8
26 - 19 = 7
27 - 21 = 6
28 - 23 = 5
29 - 25 = 4
30 - 27 = 3
31 - 29 = 2
32 - 31 = 1
33 - 33 = 0
34 - 3 = 31
35 - 5 = 30
36 - 7 = 29
37 - 9 = 28
38 - 11 = 27
39 - 13 = 26
40 - 15 = 25
41 - 17 = 24
42 - 19 = 23
43 - 21 = 22
44 - 23 = 21
45 - 25 = 20
46 - 27 = 19
47 - 29 = 18
48 - 31 = 17
49 - 33 = 16
50 - 35 = 15
51 - 37 = 14
52 - 39 = 13
53 - 41 = 12
54 - 43 = 11
55 - 45 = 10
56 - 47 = 9
57 - 49 = 8
58 - 51 = 7
59 - 53 = 6
60 - 55 = 5
61 - 57 = 4
62 - 59 = 3
63 - 61 = 2
64 - 63 = 1
65 - 65 = 0
66 - 3 = 63
67 - 5 = 62
68 - 7 = 61
69 - 9 = 60
70 - 11 = 59
71 - 13 = 58
72 - 15 = 57
73 - 17 = 56
74 - 19 = 55
75 - 21 = 54
76 - 23 = 53
77 - 25 = 52
78 - 27 = 51
79 - 29 = 50
80 - 31 = 49
出于现在应该清楚的原因,这会以2的倍数发生。
手动逐步执行
我为此示例略微简化了原始模式。记住这个。我们将回到它。
a((?R)|a)a
作者Jeffrey Friedl所说的“
the (?R) construct makes a recursive reference to the entire regular expression”的意思是,正则表达式引擎会尽可能多地替换整个模式以代替
(?R)
。
a((?R)|a)a # this
a((a((?R)|a)a)|a)a # becomes this
a((a((a((?R)|a)a)|a)a)|a)a # becomes this
# and so on...
手动追踪时,您可以从内部开始。在 (?R)|a
中,a
是您的基本情况,所以我们将从那里开始。
a(a)a
如果匹配输入字符串,则将该匹配 (aaa
) 带回到原始表达式中,并将其放在 (?R)
的位置。
a(aaa|a)a
如果输入字符串与我们的递归值匹配,则将该匹配项 (aaaaa
) 替换回原始表达式以再次递归。
a(aaaaa|a)a
重复执行直到你无法使用前一次递归的结果匹配输入内容。
示例
输入: aaaaaa
正则表达式: a((?R)|a)a
从基本情况aaa
开始。
输入与该值匹配吗?是:aaa
通过将aaa
放入原始表达式中进行递归:
a(aaa|a)a
输入是否与我们的递归值匹配?是:aaaaa
通过将aaaaa
放入原始表达式中进行递归:
a(aaaaa|a)a
输入是否与我们的递归值匹配?不匹配:aaaaaaa
那么我们就在这里停止。上述表达式可以简化为:
aaaaaaa|aaa
因为它不匹配 aaaaaaa
,所以它必须匹配 aaa
。我们完成了,aaa
是最终结果。
^
,$
或\b
),它应该可以做到你想要的。我猜 PCRE 在没有锚定时会进行一些影响结果的优化。在 Perl 中,此模式始终匹配所有长度超过 1 的完整长度。 - Qtax$regex='#^(a(?:(?1)|a?)a)#';
在这个表达式中,(?1)是一个递归语句,它访问第一个括号中的表达式(或匹配,如果Wiseguy是正确的),因此排除了插入符号锚点。 - zx81