正则表达式的可变长度回顾断言替代方案

53

Python/PHP/JavaScript中是否有支持可变长度向后断言的正则表达式实现?

/(?<!foo.*)bar/

如何编写一个正则表达式,它具有相同的含义,但不使用回顾后发断言呢?

这种类型的断言会在将来实现吗?

事情比我想象的要好得多。

更新:

(1) 已经有支持可变长度回顾后发断言的正则表达式实现。

Python模块regex(不是标准的re模块,而是额外的regex模块)支持这些断言(还有许多其他很酷的功能)。

>>> import regex
>>> m = regex.search('(?<!foo.*)bar', 'f00bar')
>>> print m.group()
bar
>>> m = regex.search('(?<!foo.*)bar', 'foobar')
>>> print m
None

对我来说,真的很惊讶的是正则表达式中Perl不能做到而Python可以做到某些事情。可能,Perl也有“增强正则表达式”实现吗?

(感谢MRAB并点赞+1)。

(2) 现代正则表达式中有一个很酷的功能\K

这个符号表示当您进行替换(从我的角度来看,断言最有趣的用例是替换)时,在\K之前找到的所有字符都不得更改。

s/unchanged-part\Kchanged-part/new-part/x

这几乎就像一个向后查找断言,但当然不那么灵活。

关于 \K 更多信息:

据我所知,在同一正则表达式中不能两次使用 \K。而且你无法确定要“杀死”找到的字符的终止点。这总是到行的开头。

(感谢 ikegami 提供解答并进行点赞 +1)。

我的额外问题:

  • 是否可以指定 \K 生效的最终位置?
  • 对于 Perl/Ruby/JavaScript/PHP 的增强正则表达式实现,有什么类似 Python 的 regex 的东西吗?

1
@minitech:没有额外的上下文。这是一个一般性的问题。 - Igor Chubin
1
@minitech:我可以删除这个简单的例子;我只是提供它作为说明目的;问题是:“如何(一般地)避免负回顾断言,以及我可以使用什么(一般地)代替?”你为什么不喜欢ikegami的答案?我认为这个答案几乎完美。我之前不知道这个\K技巧,我觉得它真的很厉害。 - Igor Chubin
@ikegamiпјҡдҪ зҡ„ж„ҸжҖқжҳҜ(?:(?!foo).)*дјҡжӣҙжңүж•ҲпјҲжҲ–иҮіе°‘дёҚдјҡжӣҙе·®пјүеҗ—пјҹ - Igor Chubin
“Effective”在编程中是什么意思? - ikegami
1
@ikegami:“这是一个从哪一端开始匹配的问题”,好的,我明白了。我认为这只是一个定义问题。 - Igor Chubin
显示剩余11条评论
5个回答

54

大多数情况下,您可以通过使用\K来避免变量长度回顾。

s/(?<=foo.*)bar/moo/s;

将是

s/foo.*\Kbar/moo/s;

任何一个匹配中最后一个遇到的\K之前的内容都不会被视为匹配结果的一部分(例如在替换或使用$&等功能时)。

负向预查则稍微棘手一些。

s/(?<!foo.*)bar/moo/s;

可能是

s/^(?:(?!foo).)*\Kbar/moo/s;

因为(?:(?!STRING).)*对于STRING的作用,就像[^CHAR]*对于CHAR的作用一样。


如果你只是在匹配,甚至可能不需要使用\K

/foo.*bar/s

/^(?:(?!foo).)*bar/s

这个\K技巧非常酷,但是在一个正则表达式中是否可能指定多个\K?可能不行。 - Igor Chubin
但是你只能有一个。我指出了如果你需要超过一个的情况下可以做些什么,正如你所问的那样。(在引入“\K”之前,在5.10之前,也可以使用捕获。) - ikegami
此答案已添加到Stack Overflow正则表达式FAQ,位于“环视”一节下。 - aliteralmind
10
非常好,非常感谢。但请添加一条关于\K是什么的说明。这并不容易在Google上搜索到。 - tremby
Perl的正则表达式在perlre中有详细的文档记录。 - ikegami
显示剩余4条评论

14

1
此答案已添加到Stack Overflow正则表达式FAQ,位于“环视”一节下。 - aliteralmind
在Python 3.4.1上运行得非常顺畅。它似乎比“re”快一点。 - Navin

6
你可以反转字符串和模式,并使用可变长度的向前查找。 (rab(?!\w*oof)\w*) 加粗匹配结果:
raboof rab7790oof raboo rabof rab rabo raboooof rabo 据我所知,最初的解决方案来自Jeff 'japhy' Pinyan。

Benjamin,谢谢你的回答,但是你确定可以反转任何模式吗? - Igor Chubin
我从未遇到过这种情况不起作用的情况。然而,创建该模式所需的时间比“正常”模式要长。 - Ben
此答案已添加到Stack Overflow正则表达式FAQ,位于“环视”一节下。 - aliteralmind

3
foo.*|(bar)

如果字符串中包含foo,则正则表达式将匹配,但不会有任何分组。
否则,它会找到bar并将其分配给一个分组。
因此,您可以使用这个正则表达式,并在找到的分组中查找结果:
>>> import re
>>> m = re.search('foo.*|(bar)', 'f00bar')
>>> if m: print(m.group(1))
bar
>>> m = re.search('foo.*|(bar)', 'foobar')
>>> if m: print(m.group(1))
None
>>> m = re.search('foo.*|(bar)', 'fobas')
>>> if m: print(m.group(1))
>>> 

Source.


3
你展示的正则表达式将找到任何一个不以foo为前导的bar实例。
一个简单的替代方法是先匹配字符串中的foo,并找到第一次出现的索引。然后搜索bar,看看能否找到在该索引之前出现的实例。
如果你想找到不是直接以foo为前导的bar实例,我也可以提供一个正则表达式(不使用向后查找),但它会非常丑陋。基本上,反转/foo/的意义 - 即/[^f]oo|[^o]o|[^o]|$/

Alex,感谢你的回答,但总的来说,并不像你所写的那样简单。我只提供了一个带有断言的正则表达式的小例子。当然,re可能会更加复杂,而且断言可能深藏其中。在这种情况下,你不能简单地检查一个字符串是否包含某个子字符串。 - Igor Chubin
1
Alex,当你需要“不是直接在foo之前的bar实例”时,你可以使用正常的反向断言(?<!foo)bar。这很有效。但是诀窍在于foobar之间可能有其他字符。 - Igor Chubin

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