获取所有匹配项,包括重叠部分。

4

我希望以这样的方式解析文本(text),即将带数字的括号添加到前后子字符串中。就我所了解的正则表达式而言,它通常会消耗字符串,这意味着默认情况下不能有匹配重叠,对吧?我该如何修改pattern_3才能获得所需的输出结果?

import re

text = 'a(1)a(2)a(1)a'
pattern = '(a(?:\((\d+)\))?)'
re.findall(pattern, text)
>>> [('a(1)', '1'), ('a(2)', '2'), ('a(1)', '1'), ('a', '')]


pattern_2 = '((?:\((\d+)\))?a(?:\((\d+)\))?)'
re.findall(pattern_2, text)
>>> [('a(1)', '', '1'), ('a(2)', '', '2'), ('a(1)', '', '1'), ('a', '', '')]


pattern_3 = pattern = '((?:\((\d+)\))?a(?=(?:\((\d+)\)))?)'
re.findall(pattern_3, text)
>>> [('a', '', '1'), ('(1)a', '1', '2'), ('(2)a', '2', '1'), ('(1)a', '1', '')]


# desired output:
>>> [('a(1)', '', '1'), ('(1)a(2)', '1', '2'), ('(2)a(1)', '2', '1'), ('(1)a', '1', '')]

更新

寻找一种仅使用 re 的解决方案。


1
我的代码的最后一行是期望的输出。 - RandomDude
你想使用 re 模块还是愿意改用 regex 模块呢? - Paolo
需要使用 re,我清楚如何在正则表达式中实现它。 - RandomDude
2
也许这个可以帮助你:(?:^|(?=\())(?=((?:\((\d+)\))?a?(?:\((\d+)\))?)) - bobble bubble
@bobblebubble 那个可以!如果你把它作为答案发布,我会接受它。你能给我一些提示,告诉我它是如何工作的 - 特别是正则表达式的第一部分看起来很奇怪 :) - RandomDude
@RandomDude 很高兴它对你有用! - bobble bubble
4个回答

1

您可以使用:

re.findall(r'(?=\(\d+\)a|a\(\d+\))(?=((?:\((\d+)\))?a(?:\((\d+)\))?)).*?a', s)

解释:

第一个预查检查圆括号中是否至少有一个数字,且其周围有a

第二个预查仅用于捕获所需内容,但由于两个\(\d+\)是可选的,因此需要第一个预查。

然后使用.*?a消耗字符,直到匹配到a来避免重复匹配。

demo


1

若要得到重叠的匹配,使用前瞻中的捕获组是正确的思路。

首先定义起始点(零宽度)。它应该是字符串的开头或括号前面:(?:^|(?=\())。因为我们只需要在开头找到a(...或者在中间或结尾找到(...

在这些点上触发前瞻。在前瞻内捕获模式(?=...)的模式可以像这样:((?:\((\d+)\))?a?(?:\((\d+)\))?)通过使每个部分都是可选的,并在内部添加第二个组以提取数字。也可以通过交替不同的选项来完成。

(?:^|(?=\())(?=((?:\((\d+)\))?a?(?:\((\d+)\))?))

这里有一个在regex101上的演示


1
你可以尝试使用这个模式 (?=(\(\d+?\)[a-z]\(\d+?\)|[a-z]\(\d+?\)|\(\d+?\)[a-z])),它使用正向先行断言解决了问题。
由于前瞻是断言,它们匹配但不消耗字符串,所以将捕获组放在其中就足够了。然后,您可以多次匹配相同的字符串部分,并使用捕获组访问这些匹配项。
在我的解决方案中,始终存在一个捕获组。
参考此处:如何使用正则表达式查找重叠的匹配项? 演示

你的正则表达式模式在text中只有4个匹配项,但对于a却返回了6个。唯一应该重叠的部分是两个a之间的括号。 - RandomDude

0

为了使解决方案更简洁,括号被省略了,但您可以将它们放回去:

text='a1a2a1a'

你必须从结果中过滤掉空字符串:

re.findall(r"(?=^(a(\d)))|(?=((\d)a)$)|(?=((\d)a(\d)))",text)
     Out:
    [('a1', '1', '', '', '', '', ''),
     ('', '', '', '', '1a2', '1', '2'),
     ('', '', '', '', '2a1', '2', '1'),
     ('', '', '1a', '1', '', '', '')]

编辑: 根据 @Michał Turczyn 的说法,一个单一的预测就可以做到:

re.findall(r"(?=^(a(\d))|((\d)a)$|((\d)a(\d)))",text)

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