【问题标题】:Regular expression: who's greedier?正则表达式:谁更贪婪?
【发布时间】:2011-02-03 16:30:20
【问题描述】:

我主要关心的是 Java 风格,但我也希望了解其他方面的信息。

假设您有这样的子模式:

(.*)(.*)

目前不是很有用,但假设这两个捕获组(例如,\1\2)是与这些组的反向引用等匹配的更大模式的一部分。

所以两者都是贪婪的,因为他们试图尽可能多地捕获,只在必要时减少。

我的问题是:谁更贪婪? \1 是否获得第一优先权,仅在必要时给予 \2 其份额?

怎么样:

(.*)(.*)(.*)

假设\1 确实获得了第一优先级。假设它太贪心了,然后吐出一个字符。谁先得到它?总是\2 还是\3

假设\2\1 拒绝。如果这仍然不起作用,那么现在谁吐出来了? \2 是先吐给\3,还是\1 先吐出另一个给\2


奖金问题

如果你这样写会发生什么:

(.*)(.*?)(.*)

现在\2 很不情愿。这是否意味着\1 吐出给\3,而\2 只能勉强接受\3 的拒绝?


示例

也许我没有给出具体的例子来展示我如何使用这些模式是一个错误,但这里有一些:

System.out.println(
    "OhMyGod=MyMyMyOhGodOhGodOhGod"
    .replaceAll("^(.*)(.*)(.*)=(\\1|\\2|\\3)+$", "<$1><$2><$3>")
); // prints "<Oh><My><God>"

// same pattern, different input string
System.out.println(
    "OhMyGod=OhMyGodOhOhOh"
    .replaceAll("^(.*)(.*)(.*)=(\\1|\\2|\\3)+$", "<$1><$2><$3>")
); // prints "<Oh><MyGod><>"

// now \2 is reluctant
System.out.println(
    "OhMyGod=OhMyGodOhOhOh"
    .replaceAll("^(.*)(.*?)(.*)=(\\1|\\2|\\3)+$", "<$1><$2><$3>")
); // prints "<Oh><><MyGod>"

【问题讨论】:

    标签: java regex


    【解决方案1】:

    添加您的具体示例极大地改变了问题的性质。它仍然像我在第一个答案中描述的那样开始,第一个 (.*) 吞噬了所有字符,第二组和第三组让它拥有它们,但是它必须匹配一个等号。

    很明显,字符串的末尾没有字符,因此组#1 一个一个地返回字符,直到正则表达式中的= 可以匹配目标中的=。然后正则表达式引擎开始尝试匹配(\1|\2|\3)+$,真正的乐趣开始了。

    第 1 组放弃了 d,第 2 组(仍然为空)接受了它,但正则表达式的其余部分仍然无法匹配。第 1 组放弃 o,第 2 组匹配 od,但正则表达式的其余部分仍然无法匹配。就这样,第三组参与进来,他们三个人以各种可能的方式分割输入,直到实现整体匹配。 RegexBuddy 报告说需要 13,426 步才能到达那里。

    在第一个例子中,贪婪(或缺乏贪婪)并不是一个真正的因素;实现匹配的唯一方法是,如果 OhMyGod 被捕获在不同的组中,那么最终会发生这种情况。哪个组捕获哪个单词甚至都无关紧要 - 正如我之前所说,这只是先到先得。

    在第二个和第三个示例中,只需将前缀分成两个块:OhMyGod。第 2 组在第二个示例中捕获 MyGod,因为它排在下一个并且是贪婪的,就像在第一个示例中一样。在第三个示例中,每次第 1 组丢掉一个角色时,第 2 组(不情愿)让第 3 组取而代之,因此最终拥有MyGod

    当然,它比这更复杂(且乏味),但我希望这能回答您的问题。我不得不说,这是你选择的一个有趣的目标字符串;如果正则表达式引擎有可能达到性高潮,我认为这些正则表达式会带来高潮。 :D

    【讨论】:

    • “哪个组捕获哪个单词甚至都没有关系”——这实际上是我问题的核心。当有多个解决方案时,实际选择哪个?是否有一个正则表达式规范说明确切的行为应该是什么,事情的优先级如何明确等?另外,您说 RegexBuddy 和 n-steps 等,这听起来几乎像一个步进调试器,这将是非常棒的。会调查。谢谢。
    • 哦,是的,我基本上认为这些群体是 3 只小鸡,它们竞相进食、狂饮和清除等,直到希望它们达到和谐幸福的高潮状态。它让使用正则表达式变得更加有趣。
    • 正则表达式没有标准或规范——至少对于 NFAregex-directed 引擎(如 Java、Perl、Python、 .NET 等——但如果它们中的任何一个在相同的输入下产生不同的结果,我会感到惊讶。
    【解决方案2】:

    作为一个简单的一般规则:最左边的量词获胜。因此,只要以下量词识别纯可选子模式(不管它们是否不贪心),第一个就通吃。

    【讨论】:

    • “第一通吃”——我只是添加了一个示例来说明情况并非总是如此。
    • 仅当您向模式添加反向引用时,因为所有规则都位于使模式实际匹配的更高需求的前面。在您的原始消息中没有反向引用,只有量词。
    • 您可以查看修订历史记录并注意我一直说这些捕获组是稍后与反向引用一起使用的子模式,因为否则它不是很有用等等 - 但你是的,我的错误是第一次没有包含示例以使其更加明确。
    【解决方案3】:

    正则表达式按顺序工作,这意味着正则表达式评估者只会在他无法找到该组的解决方案时离开该组,并最终进行一些回溯以使字符串适合下一组。如果您执行此正则表达式,您将在第一组中评估所有字符,而在下一组中没有(问号也无关紧要)。

    【讨论】:

    • 回复:“您将在第一组中评估所有字符,在下一组中没有”和“(问号也无关紧要)。” -- 我只是添加了一些例子来表明这两种说法都不正确。
    • 您没有与组正常工作,我测试了以下正则表达式: String s = "Oh(MyGod"; System.out.println( s.replaceAll("^(\\w+) (.*)(.*)$", "") ); 这返回了我的预期:“”。你不必明确分配组,或者至少你做错了。
    【解决方案4】:

    默认情况下,量词并不是真正的贪婪,它们只是仓促。在您的示例中,第一个 (.*) 将首先吞噬所有可能的东西,而不考虑整个正则表达式的需求。只有这样,它才会将控制权交给下一部分,如果有必要,它会返还部分或全部刚刚使用的内容(即回溯),以便正则表达式的其余部分可以完成其工作。

    在这种情况下没有必要这样做,因为其他所有内容都可以合法地匹配零个字符。如果量词真的很贪心,这三个小组会讨价还价,直到他们尽可能平均地分配输入;相反,第二组和第三组让第一组保留它所需要的东西。如果它放在他们面前,他们会接受,但他们不会为它而战。 (即使他们有所有格量词也是如此,即(.*)(.*+)(.*+)。)

    让第二个点星不情愿不会改变任何事情,但切换第一个会改变。一个不情愿的量词从只匹配它必须匹配的部分开始,然后交给下一部分。所以(.*?)(.*)(.*) 中的第一组一开始什么都不匹配,然后第二组狼吞虎咽,第三组一路喊着“weee weee weee”回家。

    这是的一个额外问题:如果你让所有三个量词都不情愿会发生什么? (提示:在 Java 中,这既是一个 API 问题,也是一个正则表达式问题。)

    【讨论】:

    • Re: Q 对我来说:在我看来,只要正则表达式不匹配,\3 就必须开始吃东西,如果吃饱了,那么\3 会吐出来, \2 咬了一口,\3 又开始吃东西,等等。
    • 如果你使用matches(),那是真的,因为正则表达式隐式地锚定在两端。 find() 方法不是这种情况,因此它不匹配任何内容。
    【解决方案5】:

    \1 将具有优先权,\2\3 将始终不匹配。然后\2 将优先于\3

    按照一般规则这样想,回溯只会满足匹配,不会满足贪婪,所以最好是左:)

    解释回溯和贪婪对我来说很重要,我建议friedl's Mastering Regular Expressions

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-27
      • 2010-10-20
      • 2020-06-08
      相关资源
      最近更新 更多