lookaround 续
测试字符串的同一部分是否超过要求
在上一节中详细介绍的Lookaround是一个非常强大的概念。不幸的是,对于正则表达式不熟悉的人不会使用它,因为lookaround有点令人困惑。令人困惑的是,lookaround是零长度。因此,如我们有一个正则表达式,在该正则表达式中,前瞻断言后面是另一条正则表达式,或者是后瞻断言前面是另一条正则表达式,则该正则表达式遍历字符串的同一部分两次。
一个更实际的例子可以清楚地说明这一点。假设我们要查找一个长度为六个字母并包含三个连续字母cat的单词。实际上,我们无需lookaround即可匹配它。我们只需指定所有选项,然后使用选择项将它们放在一起:cat\w{3}|\wcat\w{2}|\w{2}cat\w|\w{3}cat
。很简单。但是,如果要查找6到12个字母之间的任何单词(包含“cat”,“dog”或“mouse”),则此方法将变得笨拙。
lookaround来救场
在此示例中,成功匹配基本上有两个要求。首先,我们想要一个长度为6个字母的单词。其次,我们发现单词必须包含“cat”一词。
使用\b\w{6}\b
可以轻松匹配6个字母的单词。匹配包含“ cat”的单词同样容易:\b\w*cat\w*\b
。
结合两者,我们得到:(?=\b\w{6}\b)\b\w*cat\w*\b
。简单!是这样的。在尝试正则表达式的字符串中的每个字符位置处,引擎首先在肯定前瞻内尝试正则表达式。仅当字符串中的当前字符位置位于字符串中6个字母的单词的开头时,此子正则表达式(因此才是前瞻)才匹配。如果不是,则前瞻失败,引擎将继续从头开始在字符串中的下一个字符位置尝试正则表达式。
前瞻断言长度为零。因此,当前瞻内部的正则表达式找到6个字母的单词时,字符串中的当前位置仍位于6个字母的单词的开头。正则表达式引擎在此位置尝试其余的正则表达式。因为我们已经知道可以在当前位置匹配6个字母的单词,所以我们知道\b匹配并且第一个\w*
匹配6次。然后,引擎回溯,减少\w*
匹配的字符数,直到可以匹配cat为止。如果无法匹配cat ,则引擎别无选择,只能在正则表达式的开头(字符串中的下一个字符位置)重新启动。这是我们刚刚发现的6个字母的单词中的第二个字母,前瞻搜索将失败,从而导致引擎逐个字符前进直到下一个6个字母的单词。
如果cat可以成功匹配,则第二个\w*
将使用6个字母的单词中剩余的字母(如果有)。之后,保证正则表达式中的最后一个\b
与前瞻中的第二个\b匹配。我们的double-required-regex已成功匹配。
优化我们的解决方案
尽管上述正则表达式可以正常工作,但它不是最佳解决方案。如果我们只是在文本编辑器中进行搜索,那么这不是问题。但是,如果要重复使用此正则表达式在我们正在开发的应用程序中的大量数据上使用,则对正则进行优化是个很好的主意。
如果我们仔细地检查了正则表达式并遵循正则表达式引擎的应用方式(如上文所述),则可以自己发现这些优化。保证第三个和最后一个\b匹配。由于单词边界为零长度,因此不会更改正则表达式引擎返回的结果,因此我们可以删除它们,而剩下:(?=\b\w{6}\b)\w*cat\w*
。尽管也保证最后一个\w*
匹配,但是我们不能删除它,因为它会在正则表达式匹配项中添加字符。请记住,前瞻断言会丢弃其匹配项,因此它不会对正则表达式引擎返回的匹配项有所影响。如果我们省略\w* ,则结果匹配将是包含6个字母的单词的开头,该单词包含“cat”,而不是整个单词。
但是我们可以优化第一个\w*
。就目前而言,它将匹配6个字母,然后回溯。但是我们知道,一个成功的匹配中,“cat”前的字母不能超过3个。因此,我们可以将其优化为\w{0,3}
。请注意,使*
变得懒惰不会充分优化此效果。懒惰的星号*
会更快地找到成功的匹配项,但是,如果一个6个字母的单词不包含“ cat”,它仍会导致正则表达式引擎尝试在最后两个字母匹配,甚至即使是6个字母以内的一个字符也是如此。
所以我们有(?=\b\w{6}\b)\w{0,3}cat\w*
。最后一个不是很重要的优化涉及第一个\b
。由于它本身为零长度,因此无需将其放入前瞻断言中。因此,最终的正则表达式为:\b(?=\w{6}\b)\w{0,3}cat\w*
。
我们也可以用\w{0,3}
替换最终的\w*
。但这不会有任何区别。前瞻断言已经检查出我们使用的是6个字母的单词,\w{0,3}cat
已经匹配了该单词的3至6个字母。无论我们用\w*
还是\w{0,3}
结束正则表达式都没有关系,因为无论哪种方式,我们都将匹配所有剩余的单词字符。因为找到它的速度是相同的,所以我们可以使用更容易键入的版本。
一个更复杂的问题
那么,我们将使用什么查找介于6到12个字母之间,包含“cat”,“dog”或“mouse”的单词呢? 同样,我们有两个要求,我们可以使用前瞻断言轻松地将其组合为:
\b(?=\w{6,12}\b)\w{0,9}(cat|dog|mouse)\w*
一经掌握,就非常容易。 此正则表达式还将“cat”,“dog”或“mouse”放入第一个反向引用中。