我想知道为什么以下正则表达式适用于某些字符串而不适用于其他一些字符串:
/^([0-3]+)(?!4|.*5)[0-9]+$/
1151 -> 这不匹配
1141 -> 这确实匹配,但为什么呢?因为我可以将 .* 视为空并且正则表达式变为/^([0-3]+)(?!4|5)[0-9]+$/
我认为我误解了前瞻的工作方式......
我想知道为什么以下正则表达式适用于某些字符串而不适用于其他一些字符串:
/^([0-3]+)(?!4|.*5)[0-9]+$/
1151 -> 这不匹配
1141 -> 这确实匹配,但为什么呢?因为我可以将 .* 视为空并且正则表达式变为/^([0-3]+)(?!4|5)[0-9]+$/
我认为我误解了前瞻的工作方式......
让我们一步一步地看看正则表达式如何解析你的字符串。
^([0-3]+)(?!4|.*5)[0-9]+$
首先,一些澄清。(?!4|.*5)
是一个否定的前瞻,检查是否4
或.*5
跟随最后一个消费的字符。如果是这样,则当前匹配失败并后退。也可以这样写,就(?!(4|.*5))
好像您希望它更清楚地了解它究竟是如何|
影响它的。
让我们从看看1141
首先,[0-3]+
消耗尽可能多的字符,因此它将消耗最多并包括11
in 1141
。剩下的是41
. 正则表达式现在检查是否4
在当前字符之后,并且由于?!
是负前瞻,如果找到匹配将失败。由于4
follow 11
,匹配失败,正则表达式倒退并再次尝试。
现在,它不再匹配两个1
s,而是尝试一个 match 和 matches 1
,141
剩下的。?!4
检查以确保4
是下一个字符,你知道什么,它不存在。由于不匹配,正则表达式会留下负前瞻,并继续处理正则表达式的其余部分。141
由 final 匹配[0-9]+
,因此匹配整个1141
字符串。请记住,环顾四周不消耗字符。
现在让我们看看1151
和上次一样,11
被消耗掉了,我们51
剩下了。现在我们看一下负前瞻,并评估其余的字符串。显然,4
is no 在这个字符串中,所以我们可以忽略它,所以让我们看一下.*5
.
因此,前瞻.*5
尝试匹配51
。如果它最终匹配,就像之前匹配将失败并且正则表达式将后退。现在,如果您完全知道任何正则表达式,很明显将匹配since.*5
的开头可以评估为空。51
.*
所以我们退后一步,现在我们已经匹配了一个1
而不是两个,我们再次处于负前瞻状态。
我们目前已经消费了1
,仍然有待151
匹配,并且是(?!4|.*5)
正则表达式的一部分。在这里,4
显然我们的字符串中不存在,所以它不会匹配,所以让我们.*5
再看一遍。
.*5
将匹配一部分151
since.*
将消耗第一个1
,并且5
将通过匹配结束5
。如果您知道正则表达式,这也应该很明显。
所以我们再次在否定前瞻中进行了匹配,这很糟糕......所以我们再次退后。我们没有更多的整数可以尝试与 [0-3] 匹配,并且由于您无法将 0 个整数与 a 匹配+
,因此整个字符串无法匹配正则表达式。
1141
匹配,因为正则表达式引擎可以从匹配 回溯11
到[0-3]+
仅匹配第一个1
,剩下的数字由 匹配[0-9]+
。
由于第一个字符之后的下一个字符1
is1
和 not 4
,因此仅查看下一个字符的负前瞻不会阻止匹配。
1151
不匹配,因为添加的负前瞻阻止.*
了它。
在前瞻之前添加.*
put5
现在意味着'如果下一个字符是 ' 则不匹配4
,或者在任意数量的任何字符之后下一个字符是5
' (忽略换行符)。
因此,即使引擎回溯以[0-3]+
仅匹配 中的第1
一个,字符串1151
中仍然存在前导,因此会阻止匹配。5
请记住,look-aheads 和look-behinds 是零宽度。
那是什么正则表达式的味道?
/^([0-3]+) (?!4|.*5) [0-9]+$/
老实说,我看到它匹配 1141 而不是 1151 的唯一方法是,如果正则表达式的突出显示部分将被评估为NOT 4 or .* followed by 5
. 如果是这种情况,那么正则表达式引擎将找不到匹配项,1141
因为它会匹配 4 但会错过 5 以完成内部匹配。
但是,通常交替将被理解为4
或.*5
- 这仍然不等于 4 或 5,因为.*
当引擎想要进行匹配时,表达式可以证明非常强大。
你在用什么测试表达式?
如果你想让它匹配 4 或 5 最好的选择是
/^[0-3]+[45][0-9]+$/
但是如果没有更好地解释它应该做什么,就很难提出更多的建议......