关于问号的正则表达式中的“懒惰”模式

5

我理解这里的问号标记表示“懒惰匹配”。

我的问题本质上是 [0-9]{2}?[0-9]{2} 的区别。

它们相同吗?
如果是,我们为什么要写前者的表达式?懒惰模式性能上更昂贵不是吗?
如果不同,你能告诉我区别吗?


\d{1}\d 是相同的。 - anubhava
2
{1}是一个无意义的量词。与\d相同,提供问号\d?将匹配“零到一次”之间。 - hwnd
谢谢。我应该删除\d并重新表述为:[0-9]{2}? vs [0-9]{2}。你是说这两个是相同的吗? - KTrover
量词 {1} 的唯一用途是使其具有所有权:{1}+(匹配一次,无回溯)。{1}? 没有意义。 - Unihedron
我更新了我的问题。谢谢大家的回答。这个论坛太棒了。 - KTrover
2个回答

12

什么是“懒惰”(勉强)匹配?

在使用正则表达式进行匹配时,默认情况下指针是贪婪的:

Left | Right
\d+    12345
^      ^
\d+    12345
  ^    ^^^^^ Matched!

懒惰是贪婪的反面,即“懒”与“贪”的区别在于:
Left | Right
\d+?   12345
^      ^
\d+?   12345
  ^^    ^
       12345
         ^
       12345
          ^
       12345
           ^ Matched!

为什么这很重要?

在匹配中,量词符号* + ?默认是贪婪的。这可能会导致意外的行为,特别是当我们只想在必要时匹配某些字符以完成匹配,并在其他情况下省略

一个典型的例子是当我们想要匹配单个XML标记时:我们将无法使用<.*>来完成匹配。

Left | Right
<.*>   <p>hi</p><br /><p>bye</p>
^      ^
<.*>   <p>hi</p><br /><p>bye</p>
 ^^     ^^^^^^^^^^^^^^^^^^^^^^^^
<.*>   <p>hi</p><br /><p>bye</p>
   ^                           < [backtrack!]
<.*>   <p>hi</p><br /><p>bye</p>
   ^                           ^ Matched "<p>hi</p><br /><p>bye</p>"!

Left* | Right
<.*?>   <p>hi</p><br /><p>bye</p>
^       ^
<.*?>   <p>hi</p><br /><p>bye</p>
 ^^^     ^ [can we stop? we're lazy [yes]]
<.*?>   <p>hi</p><br /><p>bye</p>
    ^     ^ Matched "<p>"!

什么可以被归为懒惰?

你可以在量词和范围后面添加 ? 来构建:

+ (一个或多个), * (零个或多个), ? (可选);
{n,m} (n 到 m 之间,其中 n < m), {n,} (n 或更多), {n} (恰好 n 次).
(例子中的 n 和 m 是实数并满足 n,m ϵ N)

  1.     Reluctant quantifiers are unwilling to push on.
    The match is allowed to match as much as possible or as little as possible, under the consideration that the engine only attempts to match when absolutely necessary for the rest of the rest to succeed. See the following cases:

    Left | Right
    abc*   abccccd
    ^      ^
    abc*   abccccd
     ^      ^
    abc*   abccccd
      ^      ^
    abc*   abccccd
      ^^     ^^^^ Matched "abcccc"!
    
    Left* | Right
    abc*?   abccccd
    ^       ^
    abc*?   abccccd
     ^       ^
    abc*?   abccccd
      ^^^     ^ [must we do this? we're lazy [no]]
               Matched "ab"!
    

    As demonstrated, they match as few as possible.

  2.     Reluctant quantifiers give up to entertain other quantifiers.
    (Demonstration purposes; If anyone asked, I did not tell you it is OK to use RegExp like this.)

    Left | Right
    c+c+   abccccd
    ^        ^
    c+c+   abccccd
    ^^       ^^^^
    c+c+   abccccd
      ^         < [backtrack]
    c+c+   abccccd
      ^^        ^ Matched "cccc"!
                  (c+ -> @ccc; c+ -> @c)
    
    Left* | Right
    c+?c+   abccccd
    ^         ^
    c+?c+   abccccd
    ^^^       ^ [pass]
    c+?c+   abccccd
       ^^      ^^^ Matched "cccc"!
                   (c+? -> @c; c+ -> @c)
    
  3.     Exact range quantifiers are not affected.
    Between X{n} and X{n}?, there are virtually no differences; And most engines internally optimizes away the reluctant flag. This is because the lazy construct only applies when the match is dynamic, of which the engine can behave one way or the other for the quantifier (need or greed), but is not applicable for this case.

请查看regex101,这是一个做得很好的正则表达式引擎,它提供了解释和调试日志,以显示指针步骤。 还要阅读Stack Overflow正则表达式参考


2
做得好,实际上写出了内部指针的位置。我认为我们解释了类似的观点,但我跳过了扩展示例。 - Sam
我可能没有理解你想表达的意思,但是你不是指针对主题12345,模式\d+?的第一个匹配项是整个字符串吧?在我看来,这似乎是你的意思 - 而实际上第一个匹配项是1,第二个匹配项是2,以此类推。 - zx81

5

“[0-9]{2}”和“[0-9]{2}? ”之间没有区别。

贪婪匹配和惰性匹配(添加“?”)之间的区别与回溯有关。正则表达式引擎是构建用于匹配文本(从左到右)。因此,当您要求表达式匹配一定范围的字符时,它将尽可能地匹配。


假设我们有字符串“acac123”。

如果我们使用贪婪匹配“[a-z]+c ”(“+”表示1个或多个重复,或者“{1,}”):

  • “[a-z]+”会匹配“acac”,但在“1”处失败
  • 然后我们尝试匹配“c”,但在“1”处失败
  • 现在我们开始回溯,并成功匹配“aca”和“c”

如果我们将其变成惰性匹配(“[a-z]+? c”),我们将得到不同的响应(在这种情况下)并且效率更高:

  • “[a-z]+?”会匹配“a”,但停止,因为它看到下一个字符匹配了剩余的表达式“c”
  • 然后“c”将匹配,成功匹配“a”和“c”(没有回溯)

现在您可以看到,“X {#}”和“X {#}?”之间将没有任何区别,因为“{#}”不是一个范围,即使贪婪匹配也不会经历任何回溯。惰性匹配通常与“*”(0个或多个重复或“{0,}”)或“+”一起使用,但也可以与范围“{m,n}”一起使用(其中“n”是可选的)。

当你想匹配尽可能少的字符时,这是必不可少的。当你想填充一些空格时,你经常会在表达式中看到“.*?”(例如,在字符串“foo bar filler text bar”上使用“foo .*?bar”)。然而,很多时候,惰性匹配是一个糟糕/低效的正则表达式示例。许多人会像“foo:“(.*?) “”这样做,以匹配双引号中的所有内容,当您可以通过像“foo:“([^“] +)”这样编写您的表达式来避免惰性匹配,并匹配除了“ ”之外的任何内容。“


最后注意,?通常意味着“可选”,或者匹配{0,1}次。只有在范围表达式({m,n}, *, +, 或另一个?)中使用?才会使匹配变成惰性匹配。这意味着X?不会使X惰性匹配(因为我们已经说过{#}?没有意义),而是表示X是可选的。不过,你可以进行惰性“可选”匹配:[0-9]??将惰性匹配0-1次。


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