无法实现非贪婪匹配

4
在Python3.4中,我正在使用re库(正则表达式库产生相同的结果),但我得到了一个意外的结果。
我有一个字符串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?) 不匹配任何内容,这足以在第一个字符之前终止惰性匹配。第一个字符是空白。 - tdelaney
谢谢,由于Ahmed的回答,我现在更好地理解了这个问题,并且我已经实现了类似tdelaney答案的东西(主要是因为我想避免前瞻,我可能比懒惰/贪婪搜索更少地直觉)。该选择哪一个答案呢?我选择Ahmed的,因为虽然两种解决方案都可以工作,但Ahmed更好地解释了问题。但感谢你们俩! - Mike Maxwell
4个回答

2

I would expect the following regex

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

to match with three non-empty groups.. because the middle part of the pattern is greedy

不,你不应该期望那样。实际上,这种行为是非常符合预期的,原因如下:
你在第一组中明确指示正则表达式应该是“懒惰”的,这意味着它将尽可能接受最少量的字符(在这种情况下是零),因为没有其他东西强制它寻找更多。因此,尽管第二组中的正则表达式是贪婪的(即b?),但它仍然无法匹配b,因为位置仍然在0。
你可以通过用(.?)替换第二组来确认这一点,在这种情况下它将匹配a,而不是像你可能期望的那样匹配b。 这里有一个关于^(.*?)(.?)(.*?)$演示
现在,如果您的规则不允许缺少 b,您可以轻松地将正则表达式更改为 ^(.*?)(b)(.*?)$,但由于您希望第一组在存在 b 的情况下继续匹配同时,允许 b 不存在(即第二组实际上可以为空),因此这个解决方案无法解决问题。
目前我能想到的唯一满足这两个条件的解决方案是使用Lookahead来确定是否存在 b。以下是一个例子:
^((?:.*?(?=b))|.*?)(b?)(.*?)$

在线尝试

这将继续匹配任何字符(使用.),直到找到b,然后停止,否则(即如果没有b),它将在找到最少数量的字符时停止匹配(这是原始行为)。换句话说,只要b存在,它就保证第二个组不为空。

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


干得好。尽管有其他选项可使用交替,空捕获等,例如^(?|(.*?)(b)|(.*?)())(.*?)$(PCRE),但建议的模式很好。 - wp78de

0

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

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', '', '')

0

回答自己(尽管我在评论中说过,我选择了Ahmed的答案作为答案)。也许这会帮助其他人。我的解决方案类似于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]

我喜欢这个解决方案。顺便说一下,由于有 .*,所以在使用 re.match 时不需要在开头加上 ^ 或在结尾加上 $。 - tdelaney

0

你已经得到了很好的答案,但我将更具体地说明这个要求:

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

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

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

所以如果 GROUP2b 的话,那就是:

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

在正则表达式世界中,它被称为防备点实时演示

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