/(abc\g<1>?xyz)+/ =~ "abcxyz" #=> 0 /(abc\g<1>?xyz)+/ =~ "abcxyzabcxyz" #=> 0 /(abc\g<1>?xyz)+/ =~ "abcxyzabcxyzabcxyz" #=> 0 /(abc\g<1>?xyz)+/ =~ "abcxyz" * 10 #=> 0
如果量词?选择为1次,那么进行一轮递归后,匹配的字符串模式为:"abcabc_?xyzxyz" * N。再次进行?量词的次数选择,假如选0次,那么匹配的字符串是"abcabcxyzxyz" * N:
/(abc\g<1>?xyz)+/ =~ "abcabcxyzxyz" #=> 0 /(abc\g<1>?xyz)+/ =~ "abcabcxyzxyzabcabcxyzxyz" #=> 0 /(abc\g<1>?xyz)+/ =~ "abcabcxyzxyz" * 3 #=> 0
再继续分析一轮递归。假设这是?量词选择1次,那么进行第二轮的递归,匹配的字符串模式为:"abcabcabc_?xyzxyzxyz" * N。
至此,应该不难推测出递归正则表达式/(abc\g<1>?xyz)+/匹配的字符串的模式:
"abcxyz" * N "abcabcxyzxyz" * N "abcabcabcxyzxyzxyz" * N # 归纳后,即匹配如下通用模式:n和N均大于等于1 ("abc" * n + "xyz" * n) * N
将目光集中于刚才的递归正则表达式/(abc\g<1>?xyz)+/,如果能通过这个正则表达式直接推测匹配何种类型字符串呢?
量词+或其它可能的量词先不看,先将焦点放在分组捕获。这个分组捕获匹配的是abc_?xyz,如果要进行递归N轮,那么每一轮都是abc_?xyz这种模式,直接将其替换到该正则中去观察:abc(abc_?xyz)*xyz,其中(abc_?xyz)*表示这部分重复0或N次。当然替换后的这部分不是标准的正则,只是为了有助于理解才将不同地方的概念混在一起,我想并不会对你的理解造成歧义。
这样理解起来就不难了。当然这个递归正则比较简单,如果把上面的\g<1>?换成\g<1>*,看上去又会更复杂一点。那么它匹配什么样的字符串呢?
同样的分析方式,将/(abc\g<1>*xyz)+/看作是"abc_*xyz" * N的结构,然后对*取值,假设取值3次,所以递归后的结果看上去类似于:
"abc(abc_*xyz)(abc_*xyz)(abc_*xyz)xyz" * N
上面的每个括号里都可以对量词*做选择,但要到达递归的终点,最后(可能是递归了好多轮后)每一个递归里的*都必须取值0次才能终结这个递归。
所以,假如现在这3个括号里的每个*都选择0次,那么匹配的字符串模式类似于:
"abc(abcxyz)(abcxyz)(abcxyz)xyz" * N # 即等价于:n和N均大于等于1 ( "abc" + "abcxyz" * n + "xyz" ) * N
例如:
/(abc\g<1>*xyz)+/ =~ ( "abc" + "abcxyz" * 1 + "xyz" ) * 1 #=> 0 /(abc\g<1>*xyz)+/ =~ ( "abc" + "abcxyz" * 1 + "xyz" ) * 2 #=> 0 /(abc\g<1>*xyz)+/ =~ ( "abc" + "abcxyz" * 4 + "xyz" ) * 2 #=> 0
假如上面三个括号里第一个括号里的*取值1次,后面两个括号里的*取值0次,那么再次递归后,匹配的字符串模式类似于:
"abc(abc(abc_*xyz)xyz)(abcxyz)(abcxyz)xyz" * N
没错,又要做量词的次数选择。假如这次*取0次,那么将终结本次递归匹配,它匹配的字符串模式为:
"abc(abc(abcxyz)xyz)(abcxyz)(abcxyz)xyz" * N
那么如果*不是按照上面的次数进行选择的,那么匹配的字符串模式是怎样的?
没有答案,唯一准确的答案就是回归这个正则表达式的含义:它匹配的字符串模式为(abc\g<1>*xyz)+。
深入递归(2):写递归正则(入门)
前面一直都是根据给定的递归正则表达式去分析能匹配什么样的字符串,这对于理解递归正则有所帮助。但是我们更想要掌握的是如何根据字符串写出递归的正则表达式。
一般来说,要使用递归正则去匹配,往往是要匹配嵌套的一些东西,如果不是匹配嵌套内容,很可能不会想到要去用递归正则。这里,假设也要去匹配嵌套的东西。
先从简单的嵌套开始。比如,如何匹配无限嵌套的空括号()、(())、((())),即"(" * n + ")" * n?
分析一下。如果不递归的话,那就是匹配一对小括号(),所以这两小括号字符必须要在分组内,即(\(\))。(如果使用\g<0>来递归的话,则可以不用在分组内,不过这里先不考虑这种情况。)
按照前文多次对递归正则表达式匹配何种字符串的分析,用占位符替代要递归的话,要匹配的嵌套括号的字符串模式大概是这样的:(_)。所以递归表达式\g<1>要在\(和\)的中间,即(\(\g<1>\))。
这里还少了个量词来保证递归的终点。那么使用什么样的量词呢?
使用\g<1>*肯定没问题,只要*号每次递归都只选择量词1次,并且最后一轮递归选择0次终结递归即可,那么匹配的模式是((_*))、(((_*)))等等,这正好符合嵌套匹配。
/(\(\g<1>*\))/ =~ "(" * 1 + ")" * 1 #=> 0 /(\(\g<1>*\))/ =~ "(" * 3 + ")" * 3 #=> 0 /(\(\g<1>*\))/ =~ "(" * 10 + ")" * 10 #=> 0