描述和发明一种新的正则表达式量词

2020-09-21 13:50:16

量词用于量化正则表达式的一部分应该重复多少次。“每次要在正则表达式(单个字符、字符类或子表达式)中重复某些内容时,可以在它后面写一个量词,以指定应该重复多少次。

本文将只在类似Perl的正则表达式(不是POSIX正则表达式)的上下文中讨论这些量词。这一点值得一提的是,因为较旧的POSIX样式正则表达式限定符缺乏某些功能,并且在某些情况下具有显著不同的行为。

大多数典型的正则表达式指南可能会从立即深入每个符号的功能的详细描述开始。他们可能会从解释符号的含义开始,这些符号分别是。但是,这并不是您对正则表达式的典型指南。

这种非典型方法的原因很简单:一旦你理解了这个量词的作用,你就已经学完了所有关于量词的知识。让我们举几个例子。

假设您需要编写一个正则表达式,该表达式可以匹配单词enroll的两种拼写。“enroll”这个词有时可以用一个或两个拼写,这取决于作者选择使用英式还是美式拼写约定。*使用此正则表达式:

在这种情况下,量词是';{1,2}';部分。在英语中,量词可以解释为表示紧接在这个量词之前的任何东西,重复1到2次。*如果要将正则表达式更改为也匹配具有3个的enroll&39;的任何拼写错误,您只需将范围的上限从2更改为3:

现在,使用上面的正则表达式,您可以匹配单词';enroll';的所有三个拼写:

您还可以更进一步,通过降低范围0:的下限来更改正则表达式,使其包含根本不含任何字母的拼写错误。

这个量词使用的模式现在应该更明显了:*每当我们想要指定前一个字符可以重复N次下限到上限M次时,我们可以在字符后面写这个量词:

当然,这里的字母';N&39;和';M&39;是用于文档目的的占位符。但在现实中,您永远不会在正则表达式中将字面上的{N,M}';写为量词。您可能会写出类似于';{2,8}';、';{0,20}';或任何其他正整数对的内容。

为了让事情一清二楚,有几种情况下,范围的端点没有显式地写出来,而是隐含地写出来了。以下是一个示例:

这个量词的解释是将这个字符重复5次(它与时间无关)。另一种描述同一事物的方法是说";重复字符';a';在任何地方重复0次到5次";,这可以用以下等效的正则表达式来实现:

在这些情况下,假设范围的上端是无穷大,但是没有等价的方法来显式地写出这一点。“你根本不能写这样的东西:

因为大多数正则表达式引擎默认情况下会将范围量词解释为文字文本,如果它看不到预期的格式。因此,当您的范围上限是无穷大时,您必须简单地省略第二个数字,例如:{3,}';。

最后一个需要考虑的重要情况是,当使用单个数字来描述前一个字符应该精确匹配指定的次数时。*例如,此正则表达式将完全匹配任何4';a';个字符的序列:

到目前为止,我们已经检查过的量词中,很明显它们都可以用以下标准化形式重写:

我们还没有讨论量词、量词和量词的含义,所以让我们现在就来讨论一下:

这是一个交互式可视化示例,它显示了使字符成为可选的量词(';a';a#39;a';)。*正如您所看到的,控制流图分为两条可能的路径:一条跳过字符';a';,另一条需要字符';a';。因此,如果一条路径失败,正则表达式引擎只会回溯到尝试另一条路径。

以下是交互式可视化示例,显示与字符匹配一次或多次的量词。*如您所见,控制流图首先需要一个字符。*如果没有字符,它会立即失败。如果至少有一条路径,控制权将分成两条可能的路径:一条路径返回尝试获取另一个字符,另一条路径继续处理正则表达式的其余部分。*默认情况下,正则表达式引擎将首先尝试使用另一个字符的路径,直到搜索文本中没有其他字符。

以下是一个交互式可视化示例,显示了与字符零次或多次匹配的限定符(';*';Quantifier of the character';a';a';a#39;a';)。*如您所见,控制流图从拆分成两条可能的路径开始:一条路径立即与正则表达式的其余部分一起继续,另一条路径尝试使用一个字符。*默认情况下,此限定符将首先尝试使用字符的路径。一旦它找不到另一个角色,它就会放弃,继续其他的正则表达式。

如前所述,可以使用限定符指定正则表达式中的一个字符可以重复的次数:

例如,您可以使用此正则表达式来匹配书籍中的章节标题,而不管章节编号中有多少位:

最重要的是,您还可以对子表达式应用量词来重复字符序列。*例如,此正则表达式:

正如早先承诺的那样,你已经学完了所有关于量词的知识。“到目前为止,我们看到的量词比我们看到的要多得多,但幸运的是,每个正则表达式量词只有4个基本方面,在本文结束时您将全部了解它们。”以下是这4个方面:

3)它是更愿意匹配尽可能多的次数,还是尽可能少的匹配次数。

如您所见,我们已经讨论了前两个问题,因此2/4=50%完成。

现在,让';涵盖上面列表中的第#3项:它是希望尽可能多地匹配,还是希望尽可能少地匹配。我们考虑一种我们想要匹配此正则表达式的情况:

您可以在这里通过该正则表达式的匹配过程的交互式可视化来跟踪匹配过程。

由于量词';{2,4}&39;可以在任意位置匹配2到4次,并且我们重新搜索的文本以4个连续的';a';s开始,因此可以让正则表达式引擎继续搜索并匹配所有4个。*在匹配4';a&39;s之后,量词已经满意,因此我们继续尝试匹配正则表达式的';(aabbcc|bb)';部分。“我们已经用完了字符串的一部分,所以只剩下';bbcc&39;。因此,交替只能使用满足整体正则表达式的';bb';,并且完成的匹配是';aaaabb';:

但是,如果我们不那么贪婪,决定只接受开头的两个字符,而不是所有的4个字符,结果会怎样?毕竟,这是量词允许的可接受范围。事实证明,如果我们只使用2';a&39;个字符,那么替换将能够匹配';aabbcc&39;,而不只是';bb&39;,并且结果匹配将是更长的两个字符:

这里的总体结论是,无论何时使用量词,都存在一个隐含的问题,即您希望量词尽可能多地匹配还是尽可能少地匹配。但事实证明,到目前为止,我们讨论过的每一个量词都会努力成为贪婪的人,并在有机会的情况下尽可能多地匹配。

这里再一次列出了我们到目前为止所见过的每一个量词。*默认情况下,这些限定符都是贪婪的,这意味着它们将尝试尽可能多地匹配:

那么,如何让他们尽可能少地匹配呢?如果这很简单,那么只需在任何限定符后面加上一个字符就可以了:

正如简介中提到的,较旧的POSIX样式正则表达式表现出许多不同之处,这里(以及本文的其余部分)就是这样一种情况。POSIX样式的正则表达式不包括对贪婪或懒惰概念的支持。

这就是关于懒惰量词的所有要说的。以下是上一节动画的更新版本,显示了';a{2,4}?(aabbcc|bb)';与字符串';aaaabbcc';的匹配过程。

当谈到理解量词时,还有一个剩余的概念需要考虑:回溯。*当您使用像';{2,5}';这样的量词时,它会尝试匹配多达5次,但您最终可能只会重复2、3或4次(取决于您重新搜索的文本)。

为了理解为什么你会有很多重复的结果,这些重复在你的范围中间(比如刚才提到的例子中的3个或4个),你需要了解匹配过程中所做的决定,这就是为什么你可能最终会有很多重复的结果(就像刚才提到的例子中的3或4个),你需要了解在匹配过程中做出的决定。在像a{2,4}(aabbcc|bb)';这样的正则表达式中,限定符';{2,4}';指定前2&39;a';个字符是必需的。如果要搜索的文本没有至少两个连续的(由量词范围指定的最小值),正则表达式将无法完全匹配,也就没有什么可考虑的了。如果它确实至少有两个字符,则在接受这两个字符后,正则表达式引擎必须立即开始做出选择:我是应该留在这里继续尝试匹配字符,还是应该取走我已有的字符并继续尝试匹配这两个字符?#39;(aabbcc|bb)';部件?#34;是应该留在这里并继续尝试匹配';a";部件吗?##34;我是应该留在这里并继续尝试匹配';a";部件吗?#39;我应该使用我已有的内容并继续尝试匹配这两个部件。当量词满足其范围所要求的最小重复次数时,每次量词接受另一个可选字符时,都会考虑这种潜在的选择。对于贪婪的量词来说,默认的第一选择总是试图得到更多。对于懒惰的量词来说,默认的第一选择总是拿着你已经得到的东西,然后试着继续。

对于贪婪的量词,每当正则表达式引擎做出这些选择时,它都会保存当时正在做的事情,这样它就可以回溯到相同的位置,并可能尝试其他选项。“如果它最终发现服用太多会导致整个比赛失败,就需要发生这种情况。”然后,它将在比赛剩下的时间里尝试,在那个位置少拿一个。

对于懒惰的情况,情况正好相反:如果吃得太少会导致整个比赛失败,它会记住回溯,并再重复一次。

偶尔会出现这样的情况:您希望量词尽可能多次尝试贪婪地匹配,但也永远不要放弃它已经匹配的任何字符,而不是试图回溯,而是整体匹配失败。*例如,考虑尝试匹配此正则表达式的过程:

您可以在此处使用此动画完成匹配算法。“正如你从动画中看到的,正则表达式从来不与搜索字符串匹配,但它会花费大量的时间回溯!

在这种情况下,{1,10}&39;量词是贪婪的,因此在进入下一个模式之前,它将继续尝试尽可能多地使用它允许的';a&39;字符。但碰巧的是,这个量词后面的模式也由一长串的';a';s组成,并且模式的两个部分之间没有足够的';a';s可供共享!事实上,正则表达式引擎会首先通过选择10;a';s来尝试整个搜索,只会在';Z#39;字符处意识到它犯了一个错误。然后,它再次尝试9,然后再尝试8,依此类推,直到它尝试使用1,然后才意识到整个模式不会匹配并失败。

在这种情况下,可以使用所有格量词来加速失败的过程。它通过禁用回溯并在少重复一次的情况下重新尝试剩下的比赛的能力来做到这一点。对于所有格量词的这个用例,我们只关心加速失败的匹配,而不是匹配不同的东西。以下是几个例子,展示了这种情况下常规贪婪量词和占有量词的对比:

正如我们在上面看到的,如果在使用贪婪的量词时也失败了,那么使用所有格量词仍然会导致匹配失败,但是现在,如果贪婪的量词在第一次尝试时会消耗太多的量,那么它也会失败。

任何量词都可以通过简单地在其末尾粘贴一个所有格字符来构成所有格:

我会相当固执己见地建议,所有格量词不是特别有用。你可能想用所有格量词做的很多事情很可能都可以通过另一种方法来完成。同样,许多现代正则表达式引擎使用的优化和算法技巧经常(但不总是)使这样的手动优化变得不必要。尽管如此,如果不讨论所有格量词,本指南将是不完整的。

从我们列出的4个基本方面到量词,第3项是关于选择尽可能少的内容,而不是尽可能多的内容。第四项是关于是否禁用回溯的选项。*就讨论剩下的这两个方面而言,到目前为止,我们已经考虑了这三个可能的组合:

但我们遗漏了一个组合:“如果不允许回溯,那么尽可能少的组合呢?”“没有,至少还没有人发明它,这正是我现在要做的,就在这篇文章里。”“不,没有!至少还没有人发明它,这正是我现在要做的,就在这篇文章的这篇文章里,我要做的就是这篇文章里的这篇文章。”“没有”,“至少还没有人发明这个词,而这正是我现在要做的,就在这篇文章里。”在介绍一堆便便量词时:

与“懒惰”和“所有格”量词的用法类似,我们用我们熟悉的量词之一,然后在后面加上一堆便便(💩:U+1F4A9)来描述“便便”这一堆量词的用法。(unicode:u+1F4A9)(unicode:u+1F4A9);(unicode:u+1F4A9)。(unicode:u+1F4A9)。

现在,让我们花一点时间来考虑这个量词的属性。但它是一个懒惰的量词,总是尽可能少地使用字符,而且也不允许回溯。因此,像{3,5}💩这样的量词将首先匹配字母的3个重复,然后选择尝试并继续匹配正则表达式的其余部分,而不尝试匹配更多重复。因为这个限定符禁用回溯,所以如果它发现需要接受更多的';a';个字符来匹配整个正则表达式,它永远不能尝试回溯。相反,它只接受3个a&a;s,然后继续执行regex的其余部分。如果正则表达式的其余部分都失败了,就这样了!没有匹配项!

因此,量词a{3,5}💩实际上只是要做与{3}💩相同的事情,所以真的没有理由发明这个量词(这可能就是为什么没有人这样做的原因),因为它不会做任何新的事情。这个量词的贪婪版本(所有格量词)确实有意义,因为匹配的可选重复次数每次都可能不同。

上一节中暗示但没有充分讨论的事实是,{N}和{N,N}是特例。正如我们之前所讨论的,任何量词都可以与最小和最大可接受重复次数相关联。量词{N}可以改写为{N,N},当我们考虑贪婪、懒惰和走回头路时,它是一个有趣的特例。大多数正则表达式引擎支持使用量词';{N}?';、';{N,N}?';和';{N,N}+';但实际上,当范围的上限和下限相等时,贪婪、懒惰或占有型量词没有区别!。对于这个范围,前N个重复始终是必选的,没有可选的重复。在这种情况下,正则表达式引擎将永远不会做出决定或回溯,所以这些额外的匹配首选项并不重要。

下面的汇总表概述了贪婪或懒惰以及启用或禁用回溯的4种可能组合: