想理解零宽断言只需要记住一句口诀:零宽断言只『占位』,不『消费』。


没错,零宽断言就是个去麦当劳写作业的小学生~

之所以想写这篇博客是因为昨天看了@玉伯也叫射雕 的最新博文:《正则表达式中的向后匹配》。在这篇文章中,他提到了零宽断言的相关知识,但最核心的知识点却没涉及,即:零宽断言的用法类似普通的正则子表达式(也叫做分组),但不『消费』字符串。

何为『消费』呢?先来看一道题:

设:有字符串var s = 'aaalllsss0tAAAnnn999';

问:请找出所有在 3个连续相同字符 前的相邻 3个连续相同字符

(题目是有点绕口…主要是因为 JS 支持的正则太弱了,所以为了适应它,只好这样出题。)

答案:

目测是:aaa,lll,AAA,nnn。用 JS 完成的话:

var re2 = /(\w)\1{2}(?=(\w)\2{2})/g;  // 用到了 *零宽度正预测先行断言*
console.log(s.match(re2));  // 输出 => [ 'aaa','lll','AAA','nnn' ]

零宽度正预测先行断言这名字比较绕,忽略就好,理解了零宽断言之后,也没人去记住这些名字。

之所以要使用 零宽度正预测先行断言 来完成这题是因为字符串 s 中存在重叠的 3个连续相同字符,如果使用普通分组来做的话,就会不小心『消费』了那些字符而导致对他们的匹配被忽略。

不熟悉零宽断言的朋友,可能会使用如下的正则来完成这题:

var re1 = /((\w)\2{2})(\w)\3{2}/g;

这个正则输出的结果是:[‘aaa’,'AAA’],是不合我们预期的。

我们来看看用 re1 进行匹配时,re1 是如何『消费』我们的字符串 s 的:

var s = 'aaalllsss0tAAAnnn999';
var re1 = /((\w)\2{2})(\w)\3{2}/g;

console.log("s is: " + s);
console.log();

var res;
while(res=re1.exec(s)) {
  console.log("match result: " + res[1] + ".","re1 comsumed: " + res[0],"re1.lastindex: " + re1.lastIndex,"remain string: " + s.slice(re1.lastIndex));
}

输出的结果是:

s is: aaalllsss0tAAAnnn999

re1 result: aaa. re1 comsumed: aaalll,re1.lastindex: 6,remain string: sss0tAAAnnn999
re1 result: AAA. re1 comsumed: AAAnnn,re1.lastindex: 17,remain string: 999

什么是 lastIndex 呢?在 JS 中,我们可以使用 RegExp#exec 对某个字符串进行多次匹配,为了多次匹配的结果不重复,RegExp 每匹配完一次后,就记录好下一次匹配的起始位置,这个位置就是 RegExp#lastIndex。

从上面输出结果我们可以看到,re1 在匹配到了 'aaa’ 后,它就『消费』了 'aaalll’ 这个字符串,到了索引 6 这个位置。

我们再来看看用 re2 进行匹配时,re2 是如何『消费』字符串的:

re2 result: aaa. re2 comsumed: aaa,re2.lastindex: 3,remain string: lllsss0tAAAnnn999
re2 result: lll. re2 comsumed: lll,re2.lastindex: 6,remain string: sss0tAAAnnn999
re2 result: AAA. re2 comsumed: AAA,re2.lastindex: 14,remain string: nnn999
re2 result: nnn. re2 comsumed: nnn,re2.lastindex: 17,242)"> 代码就不贴了,我们直接来看输出。由于零宽断言不『消费』字符串,所以当我们第一次匹配到了 'aaa’ 后,re2 的 lastIndex 是在 3 这个位置,在其后的匹配中,它从 3 开始,又到 6 暂停。 

引入了『消费』的概念后,我相信是可以帮助大家更好理解零宽断言这个概念的,只是不知上面的栗子举得好不好。

好了,let me 再介绍介绍零宽断言。零宽断言一共有四种,而且他们的名字一个比一个酷,不过关键是没有人能记得住(-_-||):

声明:以下内容引用自《正则表达式30分钟入门教程》,我谨代表大陆人民向原作者致以深深的谢意!

(?=exp)也叫零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp。比如\b\w+(?=ing\b),匹配以ing结尾的单词的前面部分(除了ing以外的部分),如查找I’m singing while you’re dancing.时,它会匹配sing和danc。

(?<=exp)也叫零宽度正回顾后发断言,它断言自身出现的位置的前面能匹配表达式exp。比如(?<=\bre)\w+\b会匹配以re开头的单词的后半部分(除了re以外的部分),例如在查找reading a book时,它匹配ading。

零宽度负预测先行断言(?!exp),断言此位置的后面不能匹配表达式exp。例如:\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字;\b((?!abc)\w)+\b匹配不包含连续字符串abc的单词。

同理,我们可以用(?<!exp),零宽度负回顾后发断言来断言此位置的前面不能匹配表达式exp:(?<![a-z])\d{7}匹配前面不是小写字母的七位数字。


那么有同学要问:什么时候使用普通正则分组?什么时候使用零宽断言呢?

答:根据在下 Alsotang 本科三年编篡无数野代码的深厚经验来说,大部分时间使用普通正则分组;在上下文必要时,在你的队友也理解零宽断言时,才使用零宽断言。

好了,文章到此就结束了,再考大家一题。

问:有字符串

var web_development = "python PHP ruby javascript jsonp perhapsPHPisoutdated";

,如何找出其中 包含 'p’ 但不包含 'ph’ 的所有单词?

答案:

# coffeescript
web_development.split(' ').filter (s) ->
  (s.contains('p') and not s.contains('ph'))
.join(' ')

正则表达式之:零宽断言不『消费』的更多相关文章

  1. swift tableview cell自适应高度

    自适应高度达到的效果实现方法:1.xcode新建个项目,选择singleViewApplication2.打开main.storyboard将tableview拖到viewcontroller中,并给tableview拖入tableviewcell3.给tableviewcell拖入imageview、label等控件4.给控件添加autoLayout约束选中“控件Imageview或Label”

  2. swift 中cell 自适应label高度

    自适应高度达到的效果实现方法:xcode新建个项目,选择singleViewApplication打开main.storyboard将tableview拖到viewcontroller中,并给tableview拖入tableviewcell给tableviewcell拖入imageview、label等控件给控件添加autoLayout约束选中“控件Imageview或Label”,选择Edito

  3. Swift闭包是否保留捕获的变量?

    我发现Swift闭包并不像我期望的那样保留捕获的变量.我对此非常困惑,因为我一直认为默认情况下会保留捕获的变量.但是,如果我使用捕获列表显式捕获它,它将保留.我重新阅读了Swift手册,但我找不到相关说明.捕获列表用于明确设置无主,我仍然感到困惑.什么是正确的行为,为什么会发生这种情况?

  4. Java正则表达式API边界匹配

    这篇文章主要介绍了Java正则表达式API边界匹配,文章围绕主题展开相应的相关资料,具有一定的参考价值,需要的朋友可以参考一下

  5. PHP正则替换函数preg_replace()报错:Notice Use of undefined constant的解决方法分析

    这篇文章主要介绍了PHP正则替换函数preg_replace()报错:Notice Use of undefined constant的解决方法,结合具体实例形式分析了preg_replace()报错的原因与相关解决技巧,需要的朋友可以参考下

  6. PHP正则删除html代码中a标签并保留标签内容的方法 原创

    这篇文章主要介绍了PHP正则删除html代码中a标签并保留标签内容的方法,涉及php基于正则的字符串匹配与子表达式操作相关技巧,需要的朋友可以参考下

  7. php正则删除html代码中class样式属性的方法 原创

    这篇文章主要介绍了php正则删除html代码中class样式属性的方法,涉及php字符串正则匹配相关操作技巧,需要的朋友可以参考下

  8. PHP正则匹配到2个字符串之间的内容方法

    今天小编就为大家分享一篇PHP正则匹配到2个字符串之间的内容方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  9. IOS正则表达式之验证密码身份证手机号

    这篇文章主要介绍了IOS正则表达式之验证密码身份证手机号的相关资料,希望通过本文能帮助到大家,需要的朋友可以参考下

  10. 正则表达式在js前端的15个使用场景梳理总结

    本篇带来15个正则使用场景,按需索取,收藏恒等于学会!!有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

随机推荐

  1. 法国电话号码的正则表达式

    我正在尝试实施一个正则表达式,允许我检查一个号码是否是一个有效的法国电话号码.一定是这样的:要么:这是我实施的但是错了……

  2. 正则表达式 – perl分裂奇怪的行为

    PSperl是5.18.0问题是量词*允许零空间,你必须使用,这意味着1或更多.请注意,F和O之间的空间正好为零.

  3. 正则表达式 – 正则表达式大于和小于

    我想匹配以下任何一个字符:或=或=.这个似乎不起作用:[/]试试这个:它匹配可选地后跟=,或者只是=自身.

  4. 如何使用正则表达式用空格替换字符之间的短划线

    我想用正则表达式替换出现在带空格的字母之间的短划线.例如,用abcd替换ab-cd以下匹配字符–字符序列,但也替换字符[即ab-cd导致d,而不是abcd,因为我希望]我如何适应以上只能取代–部分?

  5. 正则表达式 – /bb | [^ b] {2} /它是如何工作的?

    有人可以解释一下吗?我在t-shirt上看到了这个:它似乎在说:“成为或不成为”怎么样?我好像没找到’e’?

  6. 正则表达式 – 在Scala中验证电子邮件一行

    在我的代码中添加简单的电子邮件验证,我创建了以下函数:这将传递像bob@testmymail.com这样的电子邮件和bobtestmymail.com之类的失败邮件,但是带有空格字符的邮件会漏掉,就像bob@testmymail也会返回true.我可能在这里很傻……当我测试你的正则表达式并且它正在捕捉简单的电子邮件时,我检查了你的代码并看到你正在使用findFirstIn.我相信这是你的问题.findFirstIn将跳转所有空格,直到它匹配字符串中任何位置的某个序列.我相信在你的情况下,最好使用unapp

  7. 正则表达式对小字符串的暴力

    在测试小字符串时,使用正则表达式会带来性能上的好处,还是会强制它们更快?不会通过检查给定字符串的字符是否在指定范围内比使用正则表达式更快来强制它们吗?

  8. 正则表达式 – 为什么`stoutest`不是有效的正则表达式?

    isthedelimiter,thenthematch-only-onceruleof?PATTERN?

  9. 正则表达式 – 替换..与.在R

    我怎样才能替换..我尝试过类似的东西:但它并不像我希望的那样有效.尝试添加fixed=T.

  10. 正则表达式 – 如何在字符串中的特定位置添加字符?

    我正在使用记事本,并希望使用正则表达式替换在字符串中的特定位置插入一个字符.例如,在每行的第6位插入一个逗号是什么意思?如果要在第六个字符后添加字符,请使用搜索和更换从技术上讲,这将用MatchGroup1替换每行的前6个字符,后跟逗号.

返回
顶部