最近很多正则表达式问题在查询中都有某种环视元素,对我来说并不是比赛成功所必需的。是否有一些教学资源正在推广它们?我试图找出哪种情况下你会更好地使用积极的前瞻/后退。我可以看到的主要应用是在尝试不匹配元素时。但是,例如,来自最近一个问题的查询有一个简单的解决方案来捕获.*
,但为什么要使用后面的外观?
(?<=<td><a href="\/xxx\.html\?n=[0-9]{0, 5}">).*(?=<\/a><span
这是另一个问题:
$url = "www.example.com/id/1234";
preg_match("/\d+(?<=id\/[\d])/",$url,$matches);
什么时候使用积极的外观真的更好?你能举一些例子吗?
我意识到这与基于意见的问题接近,但我认为答案真的很有启发性。正则表达式令人困惑,没有让事情变得更复杂......我已经阅读了this page,并且对于何时使用它们而不是它们的工作原理更感兴趣。
感谢所有回复。除了以下内容之外,我建议您在此处查看m.buettner's great answer。
答案 0 :(得分:8)
当然,否定的外观断言更有用。结合#2,它们可以让你做一些漂亮的向导技巧,甚至可能很难在通常的程序逻辑中表达。
示例,按流行要求:
重叠匹配:假设您想在给定的基因序列中找到所有候选基因。基因通常以ATG开始,以TAG,TAA或TGA结束。但是,候选人可能会重叠:可能存在错误的开始。所以,你可以使用这样的正则表达式:
ATG(?=((?:...)*(?:TAG|TAA|TGA)))
这个简单的正则表达式寻找ATG起始密码子,然后是一些密码子,然后是终止密码子。它可以提取看起来像基因的所有内容(没有开始密码子),并且即使它们重叠也能正确输出基因。
零宽度匹配:假设您希望在计算机生成的HTML页面中找到具有特定类的每个tr
。你可能会这样做:
<tr class="TableRow">.*?</tr>(?=<tr class="TableRow">|</table>)
这涉及行中出现裸</tr>
的情况。 (当然,一般来说,HTML解析器是更好的选择,但有时你只需要快速而又脏的东西)。
多个约束:假设您有一个包含id:tag1,tag2,tag3,tag4
等数据的文件,其中包含任意顺序的标记,并且您希望查找标记为“green”和“egg”的所有行。这可以通过两个前瞻轻松完成:
(.*):(?=.*\bgreen\b)(?=.*\begg\b)
答案 1 :(得分:4)
lookaround expressions有两件好事:
答案 2 :(得分:1)
一个简单的例子就是当你将模式锚定到一行的开头或结尾时,只是想确保某些东西正好在你匹配的模式的前面或后面。
答案 3 :(得分:1)
我试着解决你的观点:
查询中的某种环视元素对我而言并不是比赛成功所必需的
当然他们对比赛是必要的。一旦外观断言失败,就没有匹配。它们可用于确保模式周围的条件,这些条件另外也是如此。整个正则表达式只匹配,如果:
该模式符合和
外观断言属实。
==&GT;但返回的匹配只是模式。
什么时候使用积极的外观更好?
简单回答:当你想要东西在那里时,但你不想匹配它!
作为Bergi mentioned in his answer,它们是零宽度断言,这意味着它们不匹配一个字符序列,它们只是确保它在那里。因此,环绕表达式中的字符不会被“消耗”,正则表达式引擎会在最后一个“消耗”字符后继续。
关于你的第一个例子:
(?<=<td><a href="\/xxx\.html\?n=[0-9]{0, 5}">).*(?=<\/a><span
我认为当你写“有一个简单的解决方案来捕获.*
”时,你会产生误解。 .*
未被“捕获”,它是表达式唯一匹配的东西。但只有那些之前匹配“<td><a href="\/xxx\.html\?n=[0-9]{0, 5}">
”和之后为“<\/a><span
”的字符匹配(这两个不属于匹配的一部分!)。
“捕获”只是与capturing group匹配的内容。
第二个例子
\d+(?<=id\/[\d])
很有趣。它匹配一个数字序列(\d+
),在序列之后,lookbehind断言检查是否有一个数字,前面带有“id /”。意味着如果有多个数字或者数字之前的文本“id /”丢失,它将失败。意味着此正则表达式只匹配一个数字,如果之前有适合的文本。
教学资源
答案 4 :(得分:1)
我假设您了解了外观的良好用法,并且在没有明显原因的情况下询问为什么使用它们。
我认为人们如何使用正则表达式有四个主要类别:
<强>验证
验证通常在整个文本上完成。你所描述的外观是不可能的。
<强>匹配
提取文本的部分。 Lookarounds的使用主要是由于开发人员的懒惰:避免捕获
例如,如果我们在包含Index=5
行的设置文件中,我们可以匹配/^Index=(\d+)/
并获取第一个组,或者匹配/(?<=^Index=)\d+/
并获取所有内容。
正如其他答案所说,有时你需要在比赛之间重叠,但这些比较少见。
<强>替换
这类似于匹配一个区别:整个匹配被删除并被替换为新字符串(以及一些捕获的组)。
示例:我们要在"Hi, my name is Bob!"
中突出显示该名称
我们可以将/(name is )(\w+)/
替换为$1<b>$2</b>
,
但是用/(?<=name is )\w+/
替换<b>$&</b>
更简洁,而且根本没有捕获。
<强>分割
split
获取文本并将其分解为一个标记数组,您的模式是分隔符。这可以通过以下方式完成:
match
。在这场比赛之前的一切都是令牌。
在这里,外观是至关重要的。匹配字符意味着将其从结果中删除,或至少将其与其令牌分开
示例:我们有逗号分隔的引用字符串列表:"Hello","Hi, I'm Jim."
用逗号/,/
拆分是错误的:{"Hello"
,"Hi
,I'm Jim."
}
我们无法添加引号,/",/
:{"Hello
,"Hi, I'm Jim."
}
唯一不错的选择是lookbehind,/(?<="),/
:{"Hello"
,"Hi, I'm Jim."
}
就个人而言,只要有可能,我更愿意匹配令牌而不是分隔符。
回答主要问题 - 使用这些外观是因为:
答案 5 :(得分:1)
Lookaround assertions
也可用于减少backtracking
,这可能是正则表达式中性能不佳的主要原因。
例如:正则表达式^[0-9A-Z]([-.\w]*[0-9A-Z])*@
(1)也可以使用正面背面(在电子邮件地址中简单验证用户名)来编写^[0-9A-Z][-.\w]*(?<=[0-9A-Z])@
(2)。
正则表达式(1)可能导致大量回溯,因为[0-9A-Z]
是[-.\w]
的子集和嵌套量词。正则表达式(2)减少了过多的回溯,更多信息Backtracking,控制回溯&gt;部分Lookbehind Assertions 。
有关backtracking
答案 6 :(得分:1)
我输了一会儿却忙着(仍然是,所以我可能需要一段时间才能回复)而且还没有发布它。如果您仍然愿意接听答案......
是否有一些教学资源正在推广它们?
我不这么认为,我相信这只是一个巧合。
但是,例如,来自最近一个问题的查询有一个简单的解决方案来捕获
.*
,但为什么要使用后面的外观?(?<=<td><a href="\/xxx\.html\?n=[0-9]{0, 5}">).*(?=<\/a><span
这很可能是C#正则表达式,因为我的许多正则表达式引擎不支持可变宽度的lookbehinds。好吧,这里肯定可以避免这些看法,因为对于这一点,我认为拥有捕获组(并使.*
懒惰,因为我们在它上面)非常简单:
(<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个步骤用于外观。
什么时候使用积极的外观真的更好?你能举一些例子吗?
嗯,外观具有零宽度断言的特性,这意味着它们不会真正为匹配做出贡献,同时它们有助于决定匹配什么,也允许重叠匹配。
我想也是如此,我认为,负面的外观会更频繁地被使用,但这并不能使正面看法变得不那么有用!
一些&#39;漏洞&#39;我可以找到浏览我的一些旧答案(下面的链接将是来自regex101的演示)。当/如果你看到一些你不熟悉的东西,我可能不会在这里解释它,因为问题集中在积极的外观,但你总是可以看看我提供的演示链接哪里有正则表达式的描述,如果你还想要一些解释,请告诉我,我会尽可能多地解释。
在某些比赛中,积极的前瞻使事情变得更容易,前瞻也可以做到,或者当它不那么实用时不使用外观:
狗叹了口气。 &#34;我不是超级狗,也不是特殊的狗,&#34;狗说,&#34;我是一只普通的狗,现在让我独自一人!&#34;狗把他推开,然后走向另一只狗。
我们希望得到所有dog
(无论如何)引号外。如果有一个积极的向前看,我们可以this:
\bdog\b(?=(?:[^"]*"[^"]*")*[^"]*$)
确保前面有偶数引号。使用负向前瞻,它看起来像this:
\bdog\b(?!(?:[^"]*"[^"]*")*[^"]*"[^"]*$)
确保前面没有奇数引号。如果您不想要前瞻,请使用this之类的内容,但是您必须提取第1组匹配项:
(?:"[^"]+"[^"]+?)?(\bdog\b)
好的,现在说我们想要相反;找到狗#39; 里面引号。具有外观的正则表达式只需要反转符号first和second:
\bdog\b(?!(?:[^"]*"[^"]*")*[^"]*$)
\bdog\b(?=(?:[^"]*"[^"]*")*[^"]*"[^"]*$)
但没有前瞻,这是不可能的。最接近的可能是this:
"[^"]*(\bdog\b)[^"]*"
但是这并没有得到所有的匹配,或者你可以使用this:
"[^"]*?(\bdog\b)[^"]*?(?:(\bdog\b)[^"]*?)?"
但是对于更多dog
的出现而言它是不切实际的,你会得到数字越来越多的变量结果......这看起来确实更容易,因为它们是零宽度断言,你不必担心环视中的表达式是否与dog
匹配,或者正则表达式不会在引号中获得dog
的所有匹配项。
当然,现在,此逻辑可以扩展到字符组,例如在start
和end
之类的单词之间获取特定模式。
如果你有一个字符串:
abcdefghijkl
想要在内部提取所有连续的3个字符,您可以使用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]+|$)
并适当地提取所需的内容。
另一种可能的情况是你有这样的事情:
#ff00fffirstword#445533secondword##008877thi#rdword#
您想要转换为三对变量(对中的第一个是#和一些十六进制值(6)以及它们之后的任何字符):
#ff00ff and firstword
#445533 and secondword#
#008877 and thi#rdword#
如果“&#39;”字段中没有哈希值,那么使用(#[0-9a-f]{6})([^#]+)
就足够了,但不幸的是,事实并非如此,你必须诉诸{ {1}}而不是.*?
,它还没有解决杂散哈希的问题。然而,积极的前瞻使possible:
[^#]+
不推荐,但您可以使用正向前瞻来快速验证。例如,以下正则表达式允许输入包含至少1位和1个小写字母的字符串。
(#[0-9a-f]{6})(.+?)(?=#[0-9a-f]{6}|$)
当您检查字符长度但在字符串中具有不同长度的模式时,这可能很有用,例如,4字符长的字符串,其中有效格式^(?=[^0-9]*[0-9])(?=[^a-z]*[a-z])
表示数字和连字符/短划线/减号#
必须位于中间位置:
-
像this这样的正则表达式可以解决问题:
##-#
#-##
否则,你做^(?=.{4}$)\d+-\d+
并想象现在最大长度是15;您需要的改动次数。
如果你想快速而又肮脏的方式重新排列“混乱”中的某些日期。将格式^(?:[0-9]{2}-[0-9]|[0-9]-[0-9]{2})$
和mmm-yyyy
格式化为更统一的格式yyyy-mm
,您可以使用this:
mmm-yyyy
输入:
(?=.*(\b\w{3}\b))(?=.*(\b\d{4}\b)).*
输出:
Oct-2013
2013-Oct
另一种方法可能是使用正则表达式(正常匹配)并分别处理所有不符合格式。
我在SO上遇到的其他内容是indian currency format,即Oct-2013
Oct-2013
(小数点左边3位数,所有其他数字成对分组)。如果您输入##,##,###.###
,则需要122123123456.764244
,如果您想使用正则表达式,this one会执行此操作:
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+
。那么,也许用一个空间替换?它现在产生&#34;这是一句话&#34; (使用双引号,因为反引号占用空格)。你可以做的事情是this:
thisisasentence
这确保留下一个空间,以便你可以替换任何东西并获得&#34;这是一个句子&#34;。
好吧,在其他正面看起来可能有用的地方就是在哪里,比方说你有一个字符串^\s*|\s$|\s+(?=\s)
,并希望将字母+数字分开,那就是你想获得ABC12DE3456FGHI789
,{{ 1}}和ABC12
。您可以轻松地使用正则表达式:
DE3456
如果您使用FGHI789
(即将捕获的组放回到结果列表/数组/等中,您也将获得空元素。
请注意,这也可以通过匹配来完成,(?<=[0-9])(?=[A-Z])
如果我不得不提到负面的看法,那么这个帖子会更长一些:)
答案 7 :(得分:0)
请记住,正面/负面的外观与正则表达式引擎相同。 lookarounds的目标是在“正则表达式”中的某处执行检查。
主要兴趣之一是在不使用捕获括号的情况下捕获某些东西(捕获整个模式),例如:
字符串:aaabbbccc
正则表达式:(?<=aaa)bbb(?=ccc)
(您获得整个模式的结果)
代替:aaa(bbb)ccc
(您使用捕获组获得结果。)