正则表达式的可变长度lookbehind-assertion替代方案
Posted
技术标签:
【中文标题】正则表达式的可变长度lookbehind-assertion替代方案【英文标题】:Variable-length lookbehind-assertion alternatives for regular expressions 【发布时间】:2012-07-23 08:04:48 【问题描述】:在 Python/php/javascript 中是否有支持变长lookbehind-assertion的正则表达式实现?
/(?<!foo.*)bar/
如何编写具有相同含义但不使用lookbehind-assertion的正则表达式?
这种类型的断言是否有可能有一天会实现?
事情比我想象的要好得多。
更新:
(1) 已经有正则表达式实现支持变长lookbehind-assertion。
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 两次。而且你不能说直到什么时候你想“杀死”你找到的角色。一直到行首。
(感谢并 +1 ikegami)。
我的其他问题:
能不能说\K
效果的终点一定是哪一点?
Perl/Ruby/JavaScript/PHP 的增强正则表达式实现怎么样?类似于 regex
对于 Python 的东西。
【问题讨论】:
要知道如何正确地编写一个不使用后向断言的替代方案,我们需要更多的上下文。这实际上是为了什么? @minitech:没有额外的上下文。这是一个普遍的问题 不,它需要额外的上下文。目前解决你的问题最好的方法是使用indexOf
找到'foo'
,然后重复查找所有'bar'
。
@minitech:我可以去掉这个简单的例子;我提供它只是为了说明目的;问题是:“我如何(通常)避免后视否定断言以及(通常)我可以使用什么?”。为什么不喜欢ikegami的回答?我认为答案几乎是完美的。我不知道这个\K
把戏,我发现它真的很杀
@ikegami:“这是你从哪一端开始匹配的问题”,好的,我明白了。我认为这只是一个定义问题。
【参考方案1】:
大多数情况下,您可以通过使用\K
来避免可变长度的lookbehinds。
s/(?<=foo.*)bar/moo/s;
会
s/foo.*\Kbar/moo/s;
最后遇到的\K
之前的任何内容都不会被视为匹配的一部分(例如,出于替换目的,$&
等)
否定的lookbehinds有点棘手。
s/(?<!foo.*)bar/moo/s;
会
s/^(?:(?!foo).)*\Kbar/moo/s;
因为(?:(?!STRING).)*
对应于STRING
,就像[^CHAR]*
对应于CHAR
。
如果你只是匹配,你甚至可能不需要\K
。
/foo.*bar/s
/^(?:(?!foo).)*bar/s
【讨论】:
\K
这个技巧真的很酷,但是可以在一个正则表达式中指定多个\K
吗?可能,不是
否(或没用),但您可以使用捕获:s/foo.*\Kbar/moo/s;
=== s/(foo.*)bar/$1moo/s;
。
捕获很明显,但并不有趣:) \K
要好得多:)
但是你只能拥有一个。我是在指出如果您要求的不止一个,您可以做什么。 (在 5.10 之前引入 \K
时,捕获也可以工作。)
这太好了,非常感谢。但请添加关于 \K
实际含义的注释。这对 Google 来说并不容易。【参考方案2】:
对于 Python,有一个支持可变长度后视的正则表达式实现:
http://pypi.python.org/pypi/regex
它被设计为向后兼容标准 re 模块。
【讨论】:
谢谢!这确实有效,并且该模块通常非常有趣。非常感谢! +1 此答案已添加到 Stack Overflow Regular Expression FAQ 的“Lookarounds”下。 在Python 3.4.1
上流畅运行。它似乎也比re
快一点。【参考方案3】:
您可以反转字符串和模式并使用可变长度前瞻
(rab(?!\w*oof)\w*)
粗体匹配:
raboof rab7790oof raboo rabof rab rabo raboooof rabo
据我所知的原始解决方案:
Jeff 'japhy' Pinyan
【讨论】:
Benjamin,谢谢你的回答,但你确定可以反转任何模式吗? 我从来没有遇到过这种方法不起作用的情况。创建模式比“正常”模式需要更多时间。 此答案已添加到 Stack Overflow Regular Expression FAQ 的“Lookarounds”下。【参考方案4】:您显示的正则表达式将找到bar
的任何实例,not 前面有foo
。
一个简单的替代方法是首先将foo
与字符串匹配,然后找到第一次出现的索引。然后搜索bar
,看看你是否能找到出现在该索引之前的事件。
如果你想找到bar
的实例,而不是直接 前面有foo
,我也可以为此提供一个正则表达式(不使用lookbehind),但它会非常难看.基本上,颠倒/foo/
的含义——即/[^f]oo|[^o]o|[^o]|$/
。
【讨论】:
Alex,谢谢你的回答,但总的来说一切都不像你写的那么简单。我只提供了一个带有断言的正则表达式的小例子。当然,re 可能要复杂得多,并且断言可能在其中的深处。在这种情况下,您不能简单地检查字符串中的某个子字符串。 Alex,当你需要“bar
的实例不直接以foo
开头”时,你可以使用普通的lookbehind assertion (?<!foo)bar
。这样可行。但诀窍在于 foo 和 bar 之间可以是其他字符。【参考方案5】:
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.
【讨论】:
以上是关于正则表达式的可变长度lookbehind-assertion替代方案的主要内容,如果未能解决你的问题,请参考以下文章
正则表达式的可变长度lookbehind-assertion替代方案