正则里的零宽断言——(?=)
创作者俱乐部成员
一句话简介:零宽断言的精髓是零宽。
昨天看到论坛里的一个问题,从单元格里找出重复出现的词,就想着用正则怎么实现?
🔔 | 一开始写的是这样:(.{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?)*$ |
通过零宽断言预查整个单词,再反向引用的结果是一个整体(公式里的正则似乎支持原子组,一个意思),而整体不会被排列组合式的分成不同组而灾难性回溯,所以避免了卡住
又是想到哪说哪的一天,尤其是写正则,经常脑子短路不知道自己刚才写了啥,也不知道是不是老年痴呆了,哈哈,就当有个模糊的印象吧,具体的学习还是要搜索大佬们的教程😁
创作者俱乐部成员
创作者俱乐部成员