“贪婪”和“勉强”正则表达式量词有什么区别?

24
从Java的正则表达式库中Pattern javadocs中可以看到以下内容:
贪婪量词: X? X,出现一次或零次 X* X,出现零次或多次 X+ X,出现一次或多次 X{n} X,恰好出现n次 X{n,} X,至少出现n次 X{n,m} X,出现n到m次
懒惰量词: X?? X,出现一次或零次 X*? X,出现零次或多次 X+? X,出现一次或多次 X{n}? X,恰好出现n次 X{n,}? X,至少出现n次 X{n,m}? X,出现n到m次
它们的作用描述是相同的...那么,它们有什么不同之处呢?
我真的很希望能够提供一些示例。
(我在使用Java编码,但我听说这个概念对于大多数现代正则表达式实现都是相同的。)

1
此外,这个问题中的“在Java中”的部分有点不相关。贪婪量词与勉强量词在几乎任何正则表达式实现中都是一回事。大多数现代实现中甚至语法都非常相似:Java模式实际上是基于Perl正则表达式的,你会在Python、Ruby和甚至通过PCRE在C/C++中找到同样的东西。 - Laurence Gonsalves
5个回答

40

贪婪操作符总是试图尽可能“抓住”输入的内容,而不愿意的量词则会匹配尽可能少的输入内容并仍然创建匹配。

示例:

"The red fox jumped over the red fence"
/(.*)red/ => \1 = "The red fox jumped over the "
/(.*?)red/ => \1 = "The "

"aaa"
/a?a*/ => \1 = "a", \2 = "aa"
/a??a*/ => \1 = "", \2 = "aaa"

"Mr. Doe, John"
/^(?:Mrs?.)?.*\b(.*)$/ => \1 = "John"
/^(?:Mrs?.)?.*?\b(.*)$/ => \1 = "Doe, John"

11
此链接上,教程作者承认了你的问题所体现的精神:

乍一看,X?、X?? 和 X?+这些量词似乎完全相同,因为它们都承诺匹配“X,一次或者不匹配”。在本节的最后将解释其中微妙的实现差异。

他们继续给出示例并提供解释:

贪婪量词被认为是“贪婪”的,因为它们迫使匹配器在尝试第一个匹配之前读取或消耗整个输入字符串。如果第一次匹配尝试(整个输入字符串)失败,则匹配器回退输入字符串一个字符并重试,重复此过程,直到找到匹配项或没有更多可回退的字符。根据表达式中使用的量词,它最后尝试匹配的是1个或0个字符。

然而,勉强量词采取相反的方法:它们从输入字符串的开头开始,然后一次只吃掉一个字符,并且谨慎地寻找匹配项。它们尝试匹配的最后一件事是整个输入字符串。

对于额外的学分,这里有个所有权解释:

最后,占有量词始终吃掉整个输入字符串,仅尝试一次进行匹配。与贪婪量词不同,即使这样做可以使整体匹配成功,占有量词也永远不会回退。


1
谢谢您提供有关所有格的额外信息。 - jjnguy

3

贪婪量词会尽可能匹配并仍然得到匹配结果。

懒惰量词会匹配可能的最小数量。

例如,给定字符串

abcdef

贪婪量词

ab[a-z]*[a-z] 会匹配 abcdef

懒惰量词

ab[a-z]*?[a-z] 会匹配 abc


实际上,贪婪字符类将匹配“cde”,因为a将匹配a,b将匹配b,最后的[a-z]将匹配f。不过,勉强的字符组将完全匹配相同的内容。 - PatrikAkerstrand
1
@Machine:你错了,[a-z]?[a-z] 总是会匹配到第一个 [a-z] 字符!1. [a-z]? 首先跳到下一个规则:[a-z],如果不匹配,则 [a-z]*? 也不会匹配,这就是故事的结局。但是2. 如果 [a-z] 匹配成功,那么大家都很开心... - Vili

3

假设你有一个正则表达式"a\w*b",并将其用于"abab"。贪婪匹配会匹配"abab"(它查找a,尽可能多的\w出现,然后是一个b),而懒惰匹配只会匹配"ab"(尽可能少的\w)。


2
这里有关于Perl如何处理这些量词的文档perldoc perlre
默认情况下,量化子模式是“贪婪”的,也就是说,在允许整个模式匹配的前提下,它将尽可能多地匹配(给定特定的起始位置)。如果你想让它尽可能少地匹配,可以在量词后面加上“?”。请注意,意义不会改变,只是“贪婪性”改变了:
    *?     匹配 0 次或多次,非贪婪地
    +?     匹配 1 次或多次,非贪婪地
    ??     匹配 0 次或 1 次,非贪婪地
    {n}?   匹配恰好 n 次,非贪婪地
    {n,}?  匹配至少 n 次,非贪婪地
    {n,m}? 匹配至少 n 次但不超过 m 次,非贪婪地
默认情况下,当量化子模式不允许整个模式匹配时,Perl 将回溯。然而,有时候这种行为是不可取的。因此,Perl 还提供了“占有”量化器形式。
    *+     匹配 0 次或多次,并且不回溯
    ++     匹配 1 次或多次,并且不回溯
    ?+     匹配 0 次或 1 次,并且不回溯
    {n}+   匹配恰好 n 次,并且不回溯(多余的)
    {n,}+  匹配至少 n 次,并且不回溯
    {n,m}+ 匹配至少 n 次但不超过 m 次,并且不回溯
例如,
  'aaaa' =~ /a++a/
永远不会匹配,因为 a++ 将吞掉字符串中所有的 a,不会留下任何内容供模式的其余部分使用。这个特性在给 Perl 提供关于不应该回溯的位置的提示时非常有用。例如,“匹配双引号字符串”的典型问题可以通过以下方式最有效地执行:
   /"(?:[^"\\]++|\\.)*+"/
因为我们知道,如果最后一个引号没有匹配成功,回溯是无济于事的。请参见独立子表达式 (?>...) 以获取更多详细信息;占有量化器只是那个结构的语法糖。例如,上面的示例也可以写成以下形式:
   /"(?>(?:(?>[^"\\]+)|\\.)*)"/

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接