正则里的零宽断言——(?=)

wils
wils

创作者俱乐部成员

一句话简介:零宽断言的精髓是零宽。

昨天看到论坛里的一个问题,从单元格里找出重复出现的词,就想着用正则怎么实现?

🔔

一开始写的是这样:(.{2,})(?=.*\1)

看起来挺好用,但对于"abcxabxbc"这样的字符串,就只找到了ab

💡

于是改成:(.)(?=(.).*\1\2)

找倒是找到了bc,但因为第2组不捕获,只获得了第1组

📌

于是改成:.*?(.)(?=(.).*\1\2)|.*用替换

但结果还是需要按逗号分隔,现在还没想出简洁的写法。。。

说了一通废话,想表达的是:

  • 过去一直把(?=)这种写法,理解成"后面跟着xxx的,但不捕获xxx",直到了解到这东西的名字叫零宽断言,而零宽是指不消耗字符的匹配,关键就在这个零宽

要理解这一点,b站搜索"渡一教育 使用正则前瞻检查密码强度"的视频(不让发链接),这个讲的非常清晰明了!

简单介绍一下就是:

当你要求密码中必须同时出现,小写、大写、下划线三者时,才满足条件,这个正则可以写成

🔔

=REGEXP(A3,"(?=.*[a-z]+)(?=.*[A-Z]+)(?=.*_+)[a-zA-Z_]+",1)

因为如果只是[a-zA-Z_]+,那表达的是,小写、大写、下划线只要出现一种就满足条件

所以前面用(?=.*[a-z]+)先不消耗字符的预查了后面的内容,再进行消耗字符的匹配

从这里就可以看出,如果把(?=)理解成"后面跟着的",会限制自己的思路,想不到这种用法,而如果理解成零宽断言,也就是说(?=)只是不消耗字符的匹配,这样,就可以理解先用(?=.*[a-z]+)这个预先查找后面的内容,满足一定条件,再进行匹配的思路了

零宽断言的另一个用处,是避免正则"回溯地狱"或者叫"灾难性回溯"

根据"梁飞宇"老师博客里的例子(搜索正则表达式之灾难性回溯

比如,字符串需要匹配"由多个可选空格分隔的字符组成"时,容易写成

👋

^(\w+\s?)*$

但这会造成在遇到不匹配的符号时,前面匹配的内容,按字符的排列组合全部尝试匹配一遍,最终卡死进程。。。

经过测试,公式里的正则不会卡住,正常处理(果然是第一梯队的正则引擎,赞)

但在JS宏里,用这个正则匹配字符串,"An input string that takes a long time or even makes this regexp hang!",会卡死wps。。。

类似的情况还有

通用的解决办法是把\w+改成(?=(\w+))\1

💡

^((?=(\w+))\1\s?)*$

通过零宽断言预查整个单词,再反向引用的结果是一个整体(公式里的正则似乎支持原子组,一个意思),而整体不会被排列组合式的分成不同组而灾难性回溯,所以避免了卡住

又是想到哪说哪的一天,尤其是写正则,经常脑子短路不知道自己刚才写了啥,也不知道是不是老年痴呆了,哈哈,就当有个模糊的印象吧,具体的学习还是要搜索大佬们的教程😁

海南省
浏览 531
1
6
分享
6 +1
2
1 +1
全部评论 2
 
懒得批爆
懒得批爆

创作者俱乐部成员

写的挺好,简单易懂,不过熬夜对身体不好。
· 四川省
1
回复
wils
wils

创作者俱乐部成员

谢谢!看来还是得听劝,调一下生物钟୧꒰•̀ᴗ•́꒱୨
· 海南省
回复