我刚刚浏览了Atomic Grouping和rubyinfo的文档,我想到了一些简单的问题如下:
我尝试了下面的代码来理解,但是对输出有疑问,以及它们在同一个字符串上的工作方式有多么不同?
irb(main):001:0> /a(?>bc|b)c/ =~ "abbcdabcc"
=> 5
irb(main):004:0> $~
=> #<MatchData "abcc">
irb(main):005:0> /a(bc|b)c/ =~ "abcdabcc"
=> 0
irb(main):006:0> $~
=> #<MatchData "abc" 1:"b">
答案 0 :(得分:39)
()
有一些属性(包括(?!pattern)
,(?=pattern)
等属性和普通(pattern)
),但所有属性之间的共同属性是分组,它使任意模式成为一个单元(单位是我自己的术语),这在重复中很有用。
正常捕获(pattern)
具有捕获和组的属性。捕获意味着文本与内部模式匹配将被捕获,以便您可以在匹配或替换时使用反向引用。非捕获组(?:pattern)
没有捕获属性,因此与(pattern)
相比,它将节省一些空间并加快一点,因为它不存储开始和结束索引匹配内部模式的字符串。
原子分组(?>pattern)
也具有非捕获属性,因此不会捕获内部匹配的文本的位置。
与捕获或非捕获组相比,原子分组添加了 atomic 的属性。 Atomic在这里意味着:在当前位置,找到第一个序列(首先由引擎如何根据给定的模式匹配)与原子分组内的模式匹配并保持原样(因此回溯)是不被允许的。)
没有原子性的组将允许回溯 - 它仍然会找到第一个序列,然后如果匹配提前失败,它将回溯并找到下一个序列,直到找到整个正则表达式的匹配或所有可能性都用完为止
示例强>
输入字符串:bbabbbabbbbc
模式:/(?>.*)c/
由于贪婪量词.*
,bbabbbabbbbc
的第一场比赛为*
。它会保留此匹配,禁止c
进行匹配。匹配器将在下一个位置重试到字符串的末尾,同样的事情发生。所以没有什么能与正则表达式相提并论。
输入字符串:bbabbbabbbbc
模式:/((?>.*)|b*)[ac]/
,用于测试/(((?>.*))|(b*))[ac]/
此正则表达式有3个匹配项,分别为bba
,bbba
,bbbbc
。如果您使用第二个正则表达式,它是相同的但添加了捕获组以进行调试,您可以看到所有匹配都是匹配b*
内部的结果。
您可以在此处查看回溯行为。
如果没有原子分组/(.*|b*)[ac]/
,字符串将只有一个匹配,即整个字符串,因为在末尾回溯以匹配[ac]
。请注意,引擎将返回.*
以回溯1个字符,因为它仍有其他可能性。
Pattern: /(.*|b*)[ac]/
bbabbbabbbbc
^ -- Start matching. Look at first item in alternation: .*
bbabbbabbbbc
^ -- First match of .*, due to greedy quantifier
bbabbbabbbbc
X -- [ac] cannot match
-- Backtrack to ()
bbabbbabbbbc
^ -- Continue explore other possibility with .*
-- Step back 1 character
bbabbbabbbbc
^ -- [ac] matches, end of regex, a match is found
通过原子分组,.*
的所有可能性都被切断并限制在第一场比赛中。因此,在贪吃整个字符串并且无法匹配之后,引擎必须转到b*
模式,在那里它成功找到与正则表达式的匹配。
Pattern: /((?>.*)|b*)[ac]/
bbabbbabbbbc
^ -- Start matching. Look at first item in alternation: (?>.*)
bbabbbabbbbc
^ -- First match of .*, due to greedy quantifier
-- The atomic grouping will disallow .* to be backtracked and rematched
bbabbbabbbbc
X -- [ac] cannot match
-- Backtrack to ()
-- (?>.*) is atomic, check the next possibility by alternation: b*
bbabbbabbbbc
^ -- Starting to rematch with b*
bbabbbabbbbc
^ -- First match with b*, due to greedy quantifier
bbabbbabbbbc
^ -- [ac] matches, end of regex, a match is found
随后的比赛将从此处继续。
答案 1 :(得分:4)
“原子组”是正则表达式永远不会回溯的过程。因此,在您的第一个示例/a(?>bc|b)c/
中,如果组中的bc
替换匹配,那么它将永远不会回溯并尝试b
替换。如果您略微更改第一个示例以匹配"abcdabcc"
,那么您会看到它仍然匹配字符串末尾的"abcc"
而不是开头的"abc"
。如果您不使用原子组,那么它可以回溯到bc
并尝试b
更改,并在开始时最终匹配"abc"
。
关于问题二,它是如何不同的,这只是对你的第一个问题的改写。
最后,原子组不是“被称为”非捕获组。这不是他们的替代名称。非捕获组是不捕获其内容的组。通常,当您将正则表达式与字符串匹配时,您可以检索所有匹配的组,如果使用替换,则可以在替换中使用反向引用,如\1
,以在那里插入捕获的组。但是非捕获组不提供此功能。经典的非捕获组是(?:pattern)
。原子组碰巧也具有非捕获属性,因此它被称为非捕获组。
答案 2 :(得分:3)
我最近不得不向其他人解释Atomic Groups,我想我会在这里调整并分享这个例子。
考虑the (big|small|biggest) (cat|dog|bird)
。
以粗体显示
对于第一行,正则表达式引擎会找到the
。
然后它会继续我们的形容词(big
,small
,biggest
),它会找到big
。
匹配&#34;大&#34;,它继续并找到空间。
然后它会查看我们的宠物(cat
,dog
,bird
),找到cat
,跳过它,找到dog
。
对于第二行,我们的正则表达式会找到the
。
它将继续并查看big
,跳过它,查看并找到small
。
然后找到&#34; &#34 ;.
它看着&#34; cat&#34 ;,跳过它,看着&#34; dog&#34;,跳过它,然后找到&#34; bird&#34;。
对于第三行,我们的正则表达式会找到the
,
它继续并找到与{em>即时要求匹配的big
,然后继续。
它无法找到空间,因此它回溯(将位置倒回到它所做的最后一个选择)。
它会跳过big
,查看small
并跳过它。它找到最大的也匹配立即要求。
然后找到&#34; &#34 ;.
它会查看cat
并跳过它,并匹配dog
。
对于第四行,我们的正则表达式会找到the
。
它将继续查看big
,跳过它,查看并找到small
。
然后找到&#34; &#34 ;.
它查看并匹配cat
。
现在考虑the (?>big|small|biggest) (cat|dog|bird)
注意形容词上的?>
原子组。
以粗体显示
对于第一行,第二行和第四行,我们的引擎以相同的方式运行。
对于第三行,我们的正则表达式会找到the
,
它继续发现&#34; big&#34;它匹配立即要求,然后继续。
它无法找到空间,但作为引擎制作的最后选择的原子组不会允许重新检查 选项(禁止回溯)。
由于它无法做出新的选择,因此匹配必须失败,因为我们的简单表达式没有其他选择。
这只是一个基本的总结。引擎不需要查看整个cat
就知道它与dog
不匹配,只需查看c就足够了。尝试匹配鸟时,c
中的cat
和狗中的d
足以告诉引擎检查其他选项。
但是如果你有...... ((cat|snake)|dog|bird)
,那么引擎当然也需要在蛇掉到前一组并检查狗和鸟之前检查它。
还有很多选择,引擎无法通过可能看起来不匹配的东西来决定。如果您有((red)?cat|dog|bird)
,则引擎会查看&#34; r&#34;,退出,注意?
量词,忽略子组(red)
,然后查找匹配项。