【问题标题】:Can't make non-greedy match work不能使非贪婪匹配工作
【发布时间】:2018-10-22 23:46:32
【问题描述】:

在 Python3.4 中,我使用了 re 库(regex 库给出了相同的结果),我得到了一个我没想到的结果。

我有一个字符串 s = 'abc'。我期望以下正则表达式:

re.match(r"^(.*?)(b?)(.*?)$", s).groups()

..匹配三个非空组,即:

('a', 'b', 'c')

--因为模式的中间部分是贪婪的(b?)。相反,只有最后一组是非空的:

('', '', 'abc')

我得到以下两个相同的结果:

re.match(r"^(.*?)(b?)(.*?)$", s).groups()   #overt ^ and #
re.fullmatch("(.*?)(b?)(.*?)", s).groups()  #fullmatch()

如果我让第一组成为一个贪心匹配,那么结果是:

('abc', '', '')

我想我会预料到的,因为贪婪的 .* 在其他组看到它之前就消耗了整个字符串。

我正在尝试构建的正则表达式当然比这更复杂,否则,我可以从左右组中排除 b

re.match(r"^([^b]*?)(b?)([^b]*?)$", s).groups()

但在我的实际用例中,中间组是一个长几个字符的字符串,其中任何一个都可能单独出现在左侧或右侧组中,所以我不能只从左侧或右侧排除这些字符组。

我查看了标记为 的其他问题,但似乎没有人回答这个问题,尽管我怀疑 ctwheels 在python non-greedy match 中的回复是我的问题的原因(前两组的可选性阻止了正则表达式引擎从实际失败直到它到达字符串的末尾,然后它只需要回溯一些方法来获得非失败匹配)。

【问题讨论】:

  • (.*?) 将匹配下一个可能匹配的内容。 ` (b?)` 将不匹配任何内容,这足以在第一个字符之前终止惰性匹配。这没什么。
  • 谢谢,感谢艾哈迈德的回答,我现在更好地理解了这一点,并且我已经实现了 s.t.这类似于 tdelaney 的答案(主要是因为我想避免前瞻,我可能直觉不到我直觉的懒惰/贪婪搜索)。答案选哪个?我选择艾哈迈德的,因为虽然两种解决方案都有效,艾哈迈德更好地解释了问题。但是感谢你们俩!

标签: regex-greedy regex python-3.x regex-greedy


【解决方案1】:

我希望下面的正则表达式

re.match(r"^(.*?)(b?)(.*?)$", s).groups()

匹配三个非空组..因为模式的中间部分是贪婪的

不,你不应该期待。实际上,这种行为是非常值得期待的,原因如下:

您明确指示第一组中的正则表达式为 lazy,这意味着它将接受尽可能少的字符 (在这种情况) 因为没有其他东西迫使它寻找更多。所以,虽然第二组的正则表达式是贪婪的(即b?),但它仍然无法匹配b,因为位置仍然在0。

您可以通过将您的第二组替换为 (.?) 来确认,在这种情况下将匹配 a而不是 b,就像您所期望的那样。这是a demo 代表^(.*?)(.?)(.*?)$

现在,如果您的规则不允许缺少b,您可以轻松地将您的正则表达式更改为^(.*?)(b)(.*?)$,但是由于您希望第一组继续匹配如果b存在 但同时,允许b不存在(即第二组实际上可以为空),那么这个方案并不能解决问题。

目前我想到的满足这两个条件的唯一解决方案是使用Lookahead 来确定@​​987654335@ 是否存在。这是一个例子:

^((?:.*?(?=b))|.*?)(b?)(.*?)$

Try it online.

这将继续匹配任何字符(使用.),直到找到b,然后停止,否则(即,如果没有b),只要字符数量最少,它将停止匹配可能被发现(这是原始行为)。换言之,只要b 存在,它将保证第二组不为空

如果这不符合您的任何条件,请告诉我。

【讨论】:

  • 干得好。尽管如此,还有其他使用交替、空捕获等的选项,例如^(?|(.*?)(b)|(.*?)())(.*?)$ (PCRE) 但建议的模式很好。
【解决方案2】:

由于目标是根据中间的模式将字符串拆分为三部分,因此您可以搜索该模式并使用其开始和结束索引自行拆分字符串。

import re

def combo_finder(line):
    try:
        search = re.search("(foo|bar|baz)", line)
        start, end = search.start(1), search.end(1)
        return (line[:start], line[start:end], line[end:])
    except AttributeError:
        return (line, '', '')

test = ("afoob", "abarb", "afoo", "ab")

for s in test:
    print(s, combo_finder(s))

这个测试运行给出了

afoob ('a', 'foo', 'b')
abarb ('a', 'bar', 'b')
afoo ('a', 'foo', '')
ab ('ab', '', '')

【讨论】:

    【解决方案3】:

    回答自己(尽管正如我在评论中所说,我选择艾哈迈德的答案作为答案)。可能这会帮助其他人。我的解决方案类似于 tdelaney 的,但使用 if/else 而不是 try/except,并得到不同的答案。代码如下:

    rxRX = re.compile("^(.*)(foo|bar|baz)(.*)$")
    Match = rxRX.match(sLine)
    if Match:
         return [G for G in Match.groups()]
    else: #rxRX didn't match, so just return the input:
         return [sLine]
    

    【讨论】:

    • 我喜欢这个解决方案。顺便说一句,由于 .*.
    【解决方案4】:

    您的答案很好,但我将更具体地说明此要求:

    但在我的实际用例中,中间组是一个字符串 字符长,其中任何一个都可能在左侧单独显示 或右组,所以我不能只从左边排除这些字符或 正确的组。

    无论中间组是什么,您都可以在查找时使用模式来允许/禁止匹配的内容:

    ^((?:(?!GROUP2).)*)(GROUP2)((?:!GROUP2).)*)$
    

    所以如果GROUP2b 它是:

    ^((?:(?!b).)*)(b)((?:(?!b).)*)$
    

    在正则表达式世界中,它被称为tempered dot

    Live demo

    【讨论】:

      猜你喜欢
      • 2017-05-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-16
      • 1970-01-01
      • 2015-02-11
      • 2011-12-10
      • 1970-01-01
      相关资源
      最近更新 更多