【问题标题】:Why is the minimal (non-greedy) match affected by the end of string character '$'?为什么最小(非贪婪)匹配受字符串字符“$”结尾的影响?
【发布时间】:2011-05-03 23:44:12
【问题描述】:

编辑:删除原始示例,因为它引发了辅助答案。还修正了标题。

问题是为什么正则表达式中的“$”会影响表达式的贪心:

这是一个更简单的例子:

>>> import re
>>> str = "baaaaaaaa"
>>> m = re.search(r"a+$", str)
>>> m.group()
'aaaaaaaa'
>>> m = re.search(r"a+?$", str)
>>> m.group()
'aaaaaaaa'

“?”似乎什么都不做。请注意,当“$”被删除时,“?”受到尊重:

>>> m = re.search(r"a+?", str)
>>> m.group()
'a'

编辑: 换句话说,"a+?$" 匹配所有的 a 而不仅仅是最后一个,这不是我所期望的。这是正则表达式“+?”的描述来自python docs: “添加'?'在限定符使其以非贪婪或最小方式执行匹配之后;将匹配尽可能少的字符。"

在这个例子中似乎不是这样:字符串“a”匹配正则表达式“a+?$”,那么为什么字符串“baaaaaaa”上的相同正则表达式匹配不只是一个a (最右边的)?

【问题讨论】:

  • 您介意澄清一下您的问题吗?我无法准确理解您想要什么。 “第一场比赛”是什么意思?你在说.+吗?
  • 用另一个库(在路径的上下文中)可能有更好的方法来做到这一点,但这基本上是一个关于正则表达式的问题。
  • 我所说的第一个匹配是第一个search(),我会编辑。
  • @krumpelstiltskin 好的,那么。我在下面的回答中说了这一点,但这是因为您将所有内容都放在了括号中,所以所有内容都放在了组中。括号外没有任何内容可以匹配字符串的其余部分。
  • @arussell84 我添加了第二个示例,使问题更加清晰。您在下面的回答没有解决问题。

标签: python regex non-greedy


【解决方案1】:

匹配由"left-most, then longest"“排序”;然而,“最长”是在允许非贪婪之前使用的术语,而是表示“每个原子的首选重复次数”之类的东西。位于最左边比重复次数更重要。因此,"a+?$" 将不会匹配 "baaaaa" 中的最后一个 A,因为在第一个 A 处的匹配在字符串中较早开始。

(在 cmets 中的 OP 澄清后,答案发生了变化。请参阅历史记录以获取以前的文本。)

【讨论】:

  • @krumpelstiltskin:这是一个复制粘贴错误。我最初使用匹配而不是搜索,重新阅读了您的问题,然后在'c'中添加并忘记替换其中的两个。
  • 我认为这不能解释为什么 search() 不尊重“?”在我上面的例子中。
  • @krumpelstiltskin:确实如此,但也许上面的新段落会更好地解释它。
  • @Fred Nurk:但为什么它与我示例中的最后一个“a”不匹配?你的例子都不符合这种可能性。
  • @krumpelstiltskin:啊,这一切都清楚了。它只是不那样工作。最左边的匹配被认为比“更长”(在这种情况下意味着更短,因为你从贪婪切换到非贪婪)匹配更重要。
【解决方案2】:

非贪婪修饰符只影响匹配停止的位置,从不影响它开始的位置。如果您想尽可能晚地开始比赛,则必须将.+? 添加到模式的开头。

没有$,您的模式允许不那么贪婪并更快停止,因为它不必匹配到字符串的末尾。

编辑:

更多细节......在这种情况下:

re.search(r"a+?$", "baaaaaaaa")

正则表达式引擎将忽略直到第一个“a”之前的所有内容,因为这就是re.search 的工作方式。它将匹配第一个a,并“想要”返回一个匹配项,除非它与模式不匹配,因为它必须达到$ 的匹配项。所以它只是一次又一次地吃a的一个并检查$。如果它是贪婪的,它不会在每个a 之后检查$,而只会在它无法匹配更多a 之后。

但在这种情况下:

re.search(r"a+?", "baaaaaaaa")

正则表达式引擎会在吃完第一场比赛后检查它是否有一个完整的比赛(因为它是非贪婪的)并且成功因为在这种情况下没有$

【讨论】:

  • 这是我见过的最好的答案,因为它是对正在发生的事情的描述。但它并没有解释为什么会发生这种情况。
【解决方案3】:

正则表达式中$ 的存在不会影响表达式的贪心度。它只是增加了另一个必须满足的条件才能使整体匹配成功。

a+a+? 都需要消耗他们找到的第一个 a。如果a 后面跟着更多的aa+ 也会继续消耗它们,而a+? 只满足于一个。如果正则表达式还有更多内容,a+ 将愿意满足于更少的a,而a+? 会消耗更多,如果这是实现匹配所需要的。

使用a+$a+?$,您添加了另一个条件:匹配至少一个a后跟字符串的结尾。 a+ 最初仍会消耗所有 a,然后将其移交给锚点 ($)。第一次尝试就成功了,所以a+ 不需要归还它的任何a

另一方面,a+? 在移交给$ 之前仅消耗了a。那失败了,所以控制权返回给a+?,它消耗另一个a并再次放弃。就这样,直到a+? 消耗最后一个a 并且$ 最终成功。所以是的,a+?$ 确实匹配了与a+$ 相同数量的a,但它不情愿地这样做,而不是贪婪地这样做。

至于其他地方提到的最左最长规则,它从未适用于 Python 等 Perl 派生的正则表达式风格。即使没有不情愿的量词,由于ordered alternation,它们总是可以返回一个小于最大值的匹配。我认为 Jan 的想法是正确的:Perl 派生(或正则表达式导向)风味应该称为 eager,而不是贪婪。

我相信最左边最长的规则仅适用于 POSIX NFA 正则表达式,它在底层使用 NFA 引擎,但需要返回与 DFA(文本导向)正则表达式相同的结果。

【讨论】:

  • 就是这样!最左边最长的规则就是原因。这个答案虽然冗长。你有没有机会缩短它只是说它匹配最左边的最长匹配子字符串。是否有指向某些 python 文档的链接?
  • 我发现类似的线程在谈论这种情况,但答案没有指向相应文档的链接:here
  • 仅此而已:像 Python 的 这样的正则表达式导向引擎不能支持最长的匹配。它采用它找到的第一个匹配项,即使从同一起点可以获得更长的匹配项。这些都不是 Python 独有的。 PHP、Java、.NET:所有流行的 Perl 派生风格都以相同的方式工作。您是否按照我的答案中的第二个链接进行操作? Jan 在这里很好地解释了这些问题:regular-expressions.info/engine.html
【解决方案4】:

回答原问题:

为什么第一个 search() 跨越 多个“/”而不是取 最短匹配?

非贪婪子模式将采用最短匹配与整个模式一致。在您的示例中,最后一个子模式是$,因此前面的子模式需要延伸到字符串的末尾。

修改后问题的答案:

非贪婪子模式将采用最短匹配与整个模式一致

另一种看待它的方式:非贪婪子模式最初会匹配最短的可能匹配项。但是,如果这导致整个模式失败,它将用一个额外的字符重试。这个过程一直持续到子模式失败(导致整个模式失败)或整个模式匹配。

【讨论】:

  • 这个答案已经过时了,因为我简化了这个例子。
  • @krumpelstiltskin:唯一过时的部分是您原始问题的引用。剩下的基本上对这两个问题都给出了正确的答案——不过我会更新它。
  • @Johh Machin:但整个模式在字符串“baaaaaaaa”的最后一个字符“a”上确实成功了“a+?$”。
  • @krumpelstiltskin:没错。 “这个过程一直持续到(假或整个模式匹配)”。我不明白你的“但是”。
  • 您说最初将执行最短的匹配,“但是,如果这导致整个模式失败,它将使用额外的字符重试”。在我的示例中,最短可能匹配不会导致“整个模式失败”:整个模式是“a+?$”,它适合 str 中的子字符串“a”没有问题。
【解决方案5】:

这里有两个问题。您在没有指定组的情况下使用了group(),我可以告诉您,正则表达式 with 显式括号组和 没有 括号组的行为之间存在混淆。您观察到的这种没有括号的行为只是Python提供的一种快捷方式,您需要阅读group()上的文档才能完全理解它。

>>> import re
>>> string = "baaa"
>>> 
>>> # Here you're searching for one or more `a`s until the end of the line.
>>> pattern = re.search(r"a+$", string)
>>> pattern.group()
'aaa'
>>> 
>>> # This means the same thing as above, since the presence of the `$`
>>> # cancels out any meaning that the `?` might have.
>>> pattern = re.search(r"a+?$", string)
>>> pattern.group()
'aaa'
>>> 
>>> # Here you remove the `$`, so it matches the least amount of `a` it can.
>>> pattern = re.search(r"a+?", string)
>>> pattern.group()
'a'

底线是字符串a+? 匹配一个a,句点。但是,a+?$ 匹配 a直到行尾。请注意,如果没有明确的分组,您将很难让? 意味着任何东西,永远。一般来说,无论如何,最好明确说明你用括号分组的内容。让我给你一个例子显式组。

>>> # This is close to the example pattern with `a+?$` and therefore `a+$`.
>>> # It matches `a`s until the end of the line. Again the `?` can't do anything.
>>> pattern = re.search(r"(a+?)$", string)
>>> pattern.group(1)
'aaa'
>>>
>>> # In order to get the `?` to work, you need something else in your pattern
>>> # and outside your group that can be matched that will allow the selection
>>> # of `a`s to be lazy. # In this case, the `.*` is greedy and will gobble up
>>> # everything that the lazy `a+?` doesn't want to.
>>> pattern = re.search(r"(a+?).*$", string)
>>> pattern.group(1)
'a'

编辑:删除了与旧版本问题相关的文本。

【讨论】:

  • 好吧...我实际上只想要开始部分,所以我使用 re.sub() 来摆脱匹配的子字符串。
  • 您是否看到匹配的大小如何随着“$”的包含/排除而变化?这是奇怪的一点。
  • @krumpelstiltskin:看我的回答
  • @arussell84:见我的 cmets 给 Fred Nurk。
  • @krumpelstiltskin 你的意思是你不明白为什么?a+?$ 中不受尊重?这是因为$ 更重要。就像我说的,a+?$ 表示匹配a 直到行尾($)。实际上,在您的任何示例中,? 都不重要。
【解决方案6】:

除非您的问题不包含一些重要信息,否则您不需要也不应该使用正则表达式来完成此任务。

>>> import os
>>> p = "/we/shant/see/this/butshouldseethis"
>>> os.path.basename(p)
butshouldseethis

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-09
    • 2011-05-15
    • 1970-01-01
    • 1970-01-01
    • 2012-01-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多