Python正则表达式:查找不包含特定子字符串的子字符串

5

以下是例子:

a = "one two three four five six one three four seven two"
m = re.search("one.*four", a)

我想要的是找到从“one”到“four”的子字符串,其中不含有子字符串“two”。答案应该为:m.group(0) = “one three four”,m.start() = 28,m.end() = 41。 有没有一种方法可以用一行搜索完成这个任务?
4个回答

8
您可以使用这个模式:
one(?:(?!two).)*four

在匹配任何其他字符之前,我们要检查是否开始匹配了 "two"。
工作示例:http://regex101.com/r/yY2gG8

1
所以我们可以像使用 ^ 的多字符版本一样使用 (?:(?!two).)*,对吗? - satoru
@Satoru.Logic - 没错。另一个选项是 (?:[^t]|t[^w]|tw[^o])*,它与不带高级功能(lookahead)的正则表达式兼容。 - Kobi
在这种情况下,我会很高兴选择 lookahead ;) - satoru
1
@Kobi,“(?:[^t]|t[^w]|tw[^o])*”不太正确,因为它可能会消耗应该由后面的内容匹配的字符。例如,“one(?:[^t]|t[^w]|tw[^o])*four”无法匹配“onetwfour”——字符串中的“f”被“[^o]”消耗了。 - Tim Peters
1
@TimPeters - 很好的观点。我想不出一个好的解决方案,除了也禁止 four,但这会太麻烦了。让我们像 Satoru 建议的那样继续使用前瞻... - Kobi
显示剩余3条评论

2
您可以使用负向先行断言(?!...)
re.findall("one(?!.*two).*four", a)

它适用于他特定的字符串,但如果您将"two"附加到他的特定字符串上,则不起作用-前瞻适用于整个剩余字符串,而不仅仅是找到最右侧的"four"。 - Tim Peters
太棒了!找这些东西有点尴尬。我甚至不知道如何正确地搜索答案。谢谢! - Solaris
1
@user2948379,注意Satoru编辑了你的问题,使它更难(在你的字符串末尾添加了“two”),现在答案找不到任何匹配项(原因我在上面的评论中解释了)。这仍然比看起来要难一些;-) - Tim Peters
@TimPeters 我想知道是否有一种简单的方法可以使用正则表达式来检查模式是否可解决 ;p - satoru
@user2948379,你的输入中是否可能出现末尾的“two”?如果不是这种情况,请随意删除我在你的示例中添加的“two”,对此我很抱歉。 - satoru

1
使用更难的字符串Satoru添加后,这个程序可以正常工作:

>>> import re
>>> a = "one two three four five six one three four seven two"
>>> re.findall("one(?!.*two.*four).*four", a)
['one three four']

但是,总有一天你会后悔写复杂的正则表达式。如果我需要解决这个问题,我会这样做:

for m in re.finditer("one.*?four", a):
    if "two" not in m.group():
        break

这里使用了最小匹配(.*?),因为正则表达式本身就很棘手。它们可能会让人头痛 :-(

编辑:哈哈!但是如果让字符串变得更加复杂,顶部的混乱正则表达式仍然会失败:

a = "one two three four five six one three four seven two four"

最终:这里是一个正确的解决方案:

>>> a = 'one two three four five six one three four seven two four'
>>> m = re.search("one([^t]|t(?!wo))*four", a)
>>> m.group()
'one three four'
>>> m.span()
(28, 42)

我知道你说你希望m.end()是41,但那是不正确的。

1
第二个版本 - one.*?four 带有一个过滤器,将无法处理 "one two one five four"。必须有一种优雅的解决方案,只捕获 onefourtwo,并取正确的配对。Python 应该是这样的解决方案的好语言,但我不太了解它... - Kobi

0

另一个只有非常简单模式的一行代码

import re
line = "one two three four five six one three four seven two"

print [X for X in [a.split()[1:-1] for a in 
                     re.findall('one.*?four', line, re.DOTALL)] if 'two' not in X]

给我

>>> 
[['three']]

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