据我了解,匹配失败时,正则表达式会回溯到上一个最接近的选择,选择下一个替代方案并继续匹配。如果没有匹配的可能性,正则表达式会避免回溯吗?
这是一个场景,我无法向自己解释这个regex101 调试器输出。
正则表达式:^(?<Major>0|[1-9][0-9]*)\.(?<Minor>0|[1-9][0-9]*)\.(?<Patch>0|[1-9][0-9]*)$
输入字符串:1.222222222222.33333333333.0
为什么上面的正则表达式在最后会回溯,当没有机会$
匹配避免的 时[0-9]
? 补丁组用尽后,它会避免回溯([1-9][0-9]*)
Minor 和 Major 的部分吗? 如果是这样,如何以及为什么?
如果有人有更好的例子来展示正则表达式如何决定回溯与否,我们将不胜感激。
编辑1:为什么Regex假设可以通过回溯[0-9] *找到$?
这是否取决于正则表达式引擎的类型?如果是,那么最佳做法是什么,以避免这种回溯。
理论上,比赛是这样进行的:
^
在位置 0 处匹配 0 个字符。0
在位置 0 处不匹配。⇒ 回溯[1-9]
匹配位置 0 处的 1 个字符。[0-9]*
在位置 1 处匹配 0 个字符。\.
匹配位置 1 处的 1 个字符。0
在位置 2 处不匹配。⇒ 回溯[1-9]
匹配位置 2 处的 1 个字符。[0-9]*
匹配位置 3 处的 11 个字符。\.
匹配位置 14 处的 1 个字符。0
在位置 15 处不匹配。⇒ 回溯[1-9]
匹配位置 15 处的 1 个字符。[0-9]*
匹配位置 16 处的 10 个字符。$
在第 26 位不匹配。⇒ 回溯[0-9]*
匹配位置 16 处的 9 个字符。$
在位置 25 处不匹配。⇒ 回溯[0-9]*
匹配位置 16 处的 1 个字符。$
在位置 17 处不匹配。⇒ 回溯[0-9]*
匹配位置 16 处的 0 个字符。$
在位置 16 处不匹配。⇒ 回溯[0-9]*
匹配位置 3 处的 10 个字符。\.
在位置 13 处不匹配。⇒ 回溯[0-9]*
匹配位置 3 处的 9 个字符。\.
在位置 12 处不匹配。⇒ 回溯[0-9]*
匹配位置 3 处的 1 个字符。\.
在位置 4 处不匹配。⇒ 回溯[0-9]*
在位置 3 处匹配 0 个字符。\.
在位置 3 处不匹配。⇒ 回溯^
在位置 1 处不匹配。⇒ 回溯^
在位置 2 处不匹配。⇒ 回溯^
在位置 27 处不匹配。⇒ 回溯^
在第 28 位不匹配。⇒ 回溯在实践中,只要结果相同,可以进行优化。
例如,优化器可以分析模式并发现字符串必须包含两个才能匹配,并通过计算字符串中
.
的数量来启动匹配过程。.
.
Perl 的引擎并没有走那么远——执行这种分析的成本可能会超过收益——但它在开始匹配之前确实会寻找。还请注意,与上述未优化的方法相比,它导致的回溯更少。
(请注意,您在 regex101 上使用的是 PCRE2 而不是 Perl。)
这完全取决于引擎、琴弦和图案。
这个问题毫无意义。你实际上想知道的是:
仍然是一个无效的问题。匹配失败不会回溯
[0-9]*
。它会回溯(?<Patch>A|B)
。如果它刚好[0-9]*
在之前$
,那么 Perl 的引擎实际上会足够聪明,跳过回溯。是的。
制定模式,使其仅在失败时回溯(除了确定正确的替代方案外),并防止回溯。在 Perl 中,可以使用“所有格量词”(
+
)或“独立子表达式”((?>PATTERN)
或(*atomic:PATTERN)
)来防止回溯。有时需要回溯,但是大多数回溯可以通过这种方式消除。
这只是一个常识性的答案,但据我所知,正则表达式匹配有许多不同的实现和算法。
在这种情况下,在我看来,当算法第一次无法匹配 $ 时,它就开始回溯,因为先前的匹配组之一可能已经匹配并因此消耗了一个换行符(因为正则表达式匹配默认是贪婪的,即每个表达式都会尝试吃掉尽可能多的字符),并且如果该换行符用于匹配 $,那么它之前的字符就可以匹配表达式的其余部分。
我们人类看到字符串中没有可匹配的换行符,并且正则表达式的任何部分都无法匹配换行符,但显然,算法没有注意到这一点。
考虑修改你的正则表达式,以便最后一个匹配的组允许换行:
如果将其传递到新的正则表达式中:
它会首先尝试将所有 3 放入最后一组。然后它会在最后的 .0 上失败,因为它与 $ 不匹配,因此它可以回溯并将 $ 与 3 之间的换行符匹配,这样第一行就会匹配整个正则表达式。大功告成!
您可以在此处查看其实际效果: https: //regex101.com/r/snqyOe/1
编辑2(回应另一条评论)
请注意,新的正则表达式不能替代 OP 的原始正则表达式,它只是一个示例,用于说明正则表达式算法回溯何时有意义。
编辑(回应评论):
这个特定的算法会回溯,因为该正则表达式实现的作者决定这样做,可能是为了出于教育目的而使其工作原理变得明显。
Brian Kernighan 提到了一种正则表达式匹配算法,该算法永不回溯(不是逐个字符地回溯),因为它会记住遇到的所有子正则表达式的所有可能匹配(参见正则表达式匹配器)。
另一种方法是通过识别最容易匹配的子正则表达式来匹配正则表达式,寻找该匹配项,然后从那里向左和向右工作以查看它是否真正匹配。