看看背后:正则表达式中的风靡一时?

时间:2013-09-30 22:58:30

标签: regex regex-lookarounds lookaround

最近很多正则表达式问题在查询中都有某种环视元素,对我来说并不是比赛成功所必需的。是否有一些教学资源正在推广它们?我试图找出哪种情况下你会更好地使用积极的前瞻/后退。我可以看到的主要应用是在尝试匹配元素时。但是,例如,来自最近一个问题的查询有一个简单的解决方案来捕获.*,但为什么要使用后面的外观?

(?<=<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

8 个答案:

答案 0 :(得分:8)

  1. 您可以捕捉重叠的匹配,并且您可以找到可能位于其他比赛的外观中的匹配。
  2. 您可以表达关于您的匹配的复杂逻辑断言(因为许多引擎允许您使用多个 lookbehind / lookahead断言,所有这些断言必须匹配才能使匹配成功)。
  3. Lookaround是一种表达公共约束的自然方式“匹配X,如果后面是/后面是Y”。 (可以说)添加额外的“匹配”部分是不太自然的,这些部分必须通过后处理抛出。
  4. 当然,否定的外观断言更有用。结合#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有两件好事:

  • 它们是零宽度断言。它们需要匹配,但它们不会消耗任何输入字符串。这允许描述不包含在匹配结果中的字符串部分。通过在外观表达式中使用捕获组,它们是多次捕获部分输入的唯一方法。
  • 他们简化了很多事情。 While they do not extend regular languages,他们可以轻松地组合(交叉)多个表达式以匹配字符串的相同部分。

答案 2 :(得分:1)

一个简单的例子就是当你将模式锚定到一行的开头或结尾时,只是想确保某些东西正好在你匹配的模式的前面或后面。

答案 3 :(得分:1)

我试着解决你的观点:

  • 查询中的某种环视元素对我而言并不是比赛成功所必需的

    当然他们对比赛是必要的。一旦外观断言失败,就没有匹配。它们可用于确保模式周围的条件,这些条件另外也是如此。整个正则表达式只匹配,如果:

    1. 该模式符合

    2. 外观断言属实。

    3. ==&GT;但返回的匹配只是模式。

    4. 什么时候使用积极的外观更好?

      简单回答:当你想要东西在那里时,但你不想匹配它!

      作为Bergi mentioned in his answer,它们是零宽度断言,这意味着它们不匹配一个字符序列,它们只是确保它在那里。因此,环绕表达式中的字符不会被“消耗”,正则表达式引擎会在最后一个“消耗”字符后继续。

    5. 关于你的第一个例子:

      (?<=<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匹配的内容。

    6. 第二个例子

      \d+(?<=id\/[\d])
      

      很有趣。它匹配一个数字序列(\d+),在序列之后,lookbehind断言检查是否有一个数字,前面带有“id /”。意味着如果有多个数字或者数字之前的文本“id /”丢失,它将失败。意味着此正则表达式只匹配一个数字,如果之前有适合的文本。

    7. 教学资源

答案 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。在这场比赛之前的一切都是令牌。
    • 匹配的内容将被丢弃,但是:
    • 在大多数风格中,匹配中的每个捕获组也是一个标记(特别是不是Java)。
  • 当没有更多匹配时,文本的其余部分是最后一个标记。

在这里,外观是至关重要的。匹配字符意味着将其从结果中删除,或至少将其与其令牌分开 示例:我们有逗号分隔的引用字符串列表:"Hello","Hi, I'm Jim."
用逗号/,/拆分是错误的:{"Hello""HiI'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; 里面引号。具有外观的正则表达式只需要反转符号firstsecond

\bdog\b(?!(?:[^"]*"[^"]*")*[^"]*$)

\bdog\b(?=(?:[^"]*"[^"]*")*[^"]*"[^"]*$)

但没有前瞻,这是不可能的。最接近的可能是this

"[^"]*(\bdog\b)[^"]*"

但是这并没有得到所有的匹配,或者你可以使用this

"[^"]*?(\bdog\b)[^"]*?(?:(\bdog\b)[^"]*?)?"

但是对于更多dog的出现而言它是不切实际的,你会得到数字越来越多的变量结果......这看起来确实更容易,因为它们是零宽度断言,你不必担心环视中的表达式是否与dog匹配,或者正则表达式不会在引号中获得dog的所有匹配项。

当然,现在,此逻辑可以扩展到字符组,例如在startend之类的单词之间获取特定模式。

重叠比赛

如果你有一个字符串:

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

[^#]+


验证&amp;格式

不推荐,但您可以使用正向前瞻来快速验证。例如,以下正则表达式允许输入包含至少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

(您使用捕获组获得结果。)