我之前打过这个,但因为忙碌(现在还是),所以可能需要一段时间才能回复。我没有发布它,如果您仍然愿意得到答案...
有没有任何教学资源在推广它们?
我觉得没有,我相信这只是巧合。
But, for example, this query from a recent question has a simple solution to capturing the .*
, but why would you use a look behind?
(?<=<td><a href="\/xxx\.html\?n=[0-9]{0, 5}">).*(?=<\/a><span
这很可能是一个C#正则表达式,因为许多正则表达式引擎都不支持可变宽度回溯。嗯,可以肯定地避免使用回溯,因为我认为对于这个问题来说,使用捕获组会更简单(并且在这里将
.*
设置为懒惰模式):
(<td><a href="\/xxx\.html\?n=[0-9]{0,5}">).*?(<\/a><span)
如果是用于替换,或者
<td><a href="\/xxx\.html\?n=[0-9]{0,5}">(.*?)<\/a><span
对于匹配而言,尽管使用HTML解析器更加明智,但是正则表达式也可以实现。
在这种情况下,我认为使用回顾后发现速度较慢。请参见regex101 demo,其中捕获组的匹配需要64个步骤,而回顾后发现需要94+19 = 1-3个步骤。
什么时候真正适合使用正向回顾后发现? 你能举些例子吗?
好吧,回顾后发现具有零宽度断言的属性,这意味着它们并不真正对匹配做出贡献,而是对决定要匹配什么以及允许重叠匹配做出贡献。
稍微思考一下,我认为负回顾后发现的使用频率更高,但这并不意味着正向回顾后发现没有用处!
以下是我在浏览我的一些旧回答时找到的一些“漏洞”(下面的链接将是来自regex101的演示)。如果您看到不熟悉的内容,我可能不会在这里解释,因为问题集中在正向先行断言上,但您可以随时查看我提供的演示链接,在那里有关于正则表达式的描述,如果您仍然需要一些解释,请告诉我,我会尽力解释。
获取特定字符之间的匹配项:
在某些匹配中,正向先行断言使事情更容易,其中一个先行断言也可以做到同样的效果,或者当使用没有先行断言不太实际时:
Dog sighed. "I'm no super dog, nor special dog," said Dog, "I'm an ordinary dog, now leave me alone!" Dog pushed him away and made his way to the other dog.
我们想要获取所有引号外的dog
(不区分大小写)。通过使用正向先行断言,我们可以做到 this:
\bdog\b(?=(?:[^"]*"[^"]*")*[^"]*$)
为了确保引号数目是偶数,可以使用负向先行断言,代码如下
this:
\bdog\b(?!(?:[^"]*"[^"]*")*[^"]*"[^"]*$)
为了确保前面没有奇数个引号,请使用类似于this的内容,如果您不想要先行断言,但是您需要提取第1组匹配项:
(?:"[^"]+"[^"]+?)?(\bdog\b)
好的,现在假设我们想要相反的情况;在引号内查找“dog”。具有环视的正则表达式只需要将符号取反,
first和
second:
\bdog\b(?!(?:[^"]*"[^"]*")*[^"]*$)
\bdog\b(?=(?:[^"]*"[^"]*")*[^"]*"[^"]*$)
但是如果没有向前查看,这是不可能的。你能接近的最好的方法可能是this:
"[^"]*(\bdog\b)[^"]*"
但这并不能获取所有的匹配项,或者你可以使用
this:
"[^"]*?(\bdog\b)[^"]*?(?:(\bdog\b)[^"]*?)?"
但是对于更多出现的
dog
,并且您得到的结果是具有递增数字的变量...这就是使用先行断言更容易的原因,因为它们是零宽度断言,您不必担心lookaround内部的表达式是否匹配
dog
或者正则表达式是否能够在引号中获取所有
dog
的出现次数。
当然,现在这个逻辑可以扩展到字符组,例如获取特定模式的单词之间的内容,例如
start
和
end
之间的内容。
重叠匹配
如果您有一个字符串,例如:
abcdefghijkl
如果你想提取其中所有可能的连续三个字符,可以使用 this:
(?=(...))
如果你有类似这样的东西:
1A Line1 Detail1 Detail2 Detail3 2A Line2 Detail 3A Line3 Detail Detail
我希望您能提取这些内容,知道每行开头是
#A Line#
(其中
#
是数字):
1A Line1 Detail1 Detail2 Detail3
2A Line2 Detail
3A Line3 Detail Detail
你可以尝试使用
this,但由于贪婪性而失败...
[0-9]+A Line[0-9]+(?: \w+)+
或者this,但是当它变得懒惰时就不再起作用了...
[0-9]+A Line[0-9]+(?: \w+)+?
但是使用正向预查,你可以得到this:
[0-9]+A Line[0-9]+(?: \w+)+?(?= [0-9]+A Line[0-9]+|$)
我希望你能适当地提取所需内容。
另一个可能的情况是这样的:
你想转换为三组变量(每组的第一个是带#和一些十六进制值(6)以及后面的任何字符):
如果“单词”中没有哈希标记,那么使用
(#[0-9a-f]{6})([^#]+)
就足够了,但不幸的是,情况并非如此,你必须使用
.*?
代替
[^#]+
,这还不能完全解决杂散哈希的问题。然而,正向预查使这成为
可能。
(#[0-9a-f]{6})(.+?)(?=#[0-9a-f]{6}|$)
验证和格式化
虽然不建议使用,但您可以使用正向先行断言进行快速验证。例如,以下正则表达式允许输入包含至少1个数字和1个小写字母的字符串。
^(?=[^0-9]*[0-9])(?=[^a-z]*[a-z])
这在检查具有不同长度模式的字符串字符长度时非常有用,例如,一个4个字符长的字符串,其中有效格式为
#
表示数字,连字符/破折号/减号
-
必须位于中间。
一个像
this 这样的正则表达式就能做到:
^(?=.{4}$)\d+-\d+
如果没有其他方式,你会使用^(?:[0-9]{2}-[0-9]|[0-9]-[0-9]{2})$
,现在假设最大长度为15,则需要进行的更改次数将增加。
如果您想要一种快速而简单的方法来重新排列一些日期,使其格式更加统一,例如mmm-yyyy
和yyyy-mm
,可以使用this:
(?=.*(\b\w{3}\b))(?=.*(\b\d{4}\b)).*
输入:
Oct-2013
2013-Oct
输出:
Oct-2013
Oct-2013
另一种选择是使用正则表达式(普通匹配),并单独处理所有不符合格式的内容。
我在SO上遇到的另一件事是印度货币格式,它是##,##,###.###
(小数点左侧有3位数字,其他数字以一对为一组)。如果您输入122123123456.764244
,则期望得到1,22,12,31,23,456.764244
,如果要使用正则表达式,这个可以实现:
\G\d{1,2}\K\B(?=(?:\d{2})*\d{3}(?!\d))
链接中的(?:\G|^)
仅用于\G
仅在字符串开头和匹配后匹配,我认为这不可能在没有正向先行断言的情况下工作,因为它可以向前查看而不移动替换点。
修剪
假设你有:
this is a sentence
我想用一个正则表达式来去除所有空格,你可能会尝试在空格上进行全局替换:
\s+
但这会产生
thisisasentence
。那么,也许用一个空格替换?现在它输出 " this is a sentence "(双引号用于因反引号而吃掉空格)。你可以做的一件事是
this:
^\s*|\s$|\s+(?=\s)
Which makes sure to leave one space behind so that you can replace with nothing and get "this is a sentence".
分割
好的,正向先行断言可能有用的另一个地方是,当你有一个字符串ABC12DE3456FGHI789
并想要将字母和数字分开时,也就是你想要得到ABC12
,DE3456
和FGHI789
。你可以轻松使用正则表达式:
(?<=[0-9])(?=[A-Z])
如果您使用
([A-Z]+[0-9]+)
(即捕获组被放置在结果列表/数组等中),您将得到空元素。
请注意,这也可以使用匹配来完成,使用[A-Z]+[0-9]+
如果我要提到负向先行断言,这篇文章会更长 :)
(?>...)
变成了(?=(...))\1
。我不确定匹配两次并使用捕获是否值得收益,但有时原子分组也可以极大地简化模式。 - Martin Ender