5句话,让你的正则表达式水平突飞猛进!(第3-5句)
创作者俱乐部成员
上一篇文章我们说了:
在正则表达式眼中,字符串由位置和字符组成。正则表达式就是去匹配字符或位置。
文章链接:
对于正则的初学者来说,匹配位置通常比匹配字符更难理解一些。所以,这五句话都是围绕位置来讲解的。
接上篇,我们继续
第3句
位置相当于空字符
上一篇文章我们介绍了字符串是由字符和位置组成,
比如字符串“Excel“每个字符前后都有一个位置。
我们通常说“位置”,一般认为它是有顺序的,但正则表达式中的“位置”并不能用第几个位置来指称。我们可以将正则表达式中的位置理解成空字符。
在Excel中文本需要用双引号括起来,双引号中间没有内容,就是空字符。用""表示空字符。也就是说,在正则表达式眼中“Excel”相当于:
""+E+""+x+""+c+""+e+""+l+""
来测试一下是否如此
证明1:
用REGEXP的替换模式,将位置替换为@字符看效果
=REGEXP(B1,"",2,"@")
插入字符的更多写法见文后的图片。
证明2:
提取字符串中的所有字符(含位置)
公式:
=REGEXP(B1,".*?")
正则表达式解释:
正则表达式.*?是一个非贪婪(懒惰)匹配的模式,它的含义是:
.:匹配任意单个字符(除了换行符)。
*:匹配前面的字符零次或多次,即匹配任意数量的字符。
?:问号“?”放在*星号和+加号等量词后是一个非贪婪修饰符,表示尽可能少地匹配字符。
要点:
非贪婪的 .*? 会尝试匹配最少的字符,所以在第一次应用时,它会首先去匹配零个字符(即不匹配任何字符),零个字符匹配不上,再匹配1个字符……。当匹配零个字符时,就会将位置(空字符)匹配上。
龙逸凡:这堆文字看起来比较绕、比较上头,理解后会大大提升你的正则……
偷懒读者:打住!说关键的,有什么用?
龙逸凡:我们来看一个实战案例,一个你肯定用得上的案例。用这方法可以在每个字符间插入一个你喜欢的符号,比如:长得象雪花的星号*。
公式:
=REGEXP(B3,"",2,"*")
偷懒读者:然后呢?拎瓶雪花勇闯天涯?
龙逸凡:不,这些雪花能帮你找到意中人去风花雪月年年暮暮朝朝。将上面的公式放到VLOOKUP函数的第一参数,实现你梦寐以求的查找功能:根据不连续简称查全称
偷懒读者:???
龙逸凡:看图说话
偷懒读者:哇……!太棒了,的确梦寐以求!这公式是我一直寻找的。众里寻它千百度,蓦然回首,公式却在,偷懒公号处。只是,我看不懂这公式,能否解释一下?
龙逸凡:星号在正则表达式中是量词,表示0个及多个,但在Excel函数中,它是通配符,表示任意多个任意字符(但不代表星号本身)。函数REGEXP(B3,"",2,"*")的计算结果是“*偷*懒*1*”。用它做Vlookup函数的第一参数,意思是在去查找符合这样规则的字符串:
在“偷”、“懒”,“1”三个字符中间有若干个其他字符的文本。
在E列的名称列表中,符合这种格式的文本就是“《“偷懒”的技术1:打造财务Excel达人》”。所以VLOOKUP将它做为查找结果。
偷懒读者:明白了,很巧妙。
龙逸凡:在理解“位置就是空字符”后,大家应该就能理解正则表达式专业术语“零宽断言”中的“零宽”了。位置相当于空字符,它是没有宽度(字符数)的,所以说是零宽。
偷懒读者:我好象理解“位置就是空字符”了。
龙逸凡:不,你没有。
偷懒读者:为什么?
龙逸凡:来做个小测试,公式=REGEXP("Excel","^^^",2,"@"),将字符串开始的位置(连续三个)替换为@符号,其计算结果是什么?备选答案:
A、会显示出错;B、@@@Excel;C、@Excel
偷懒读者:字符的开始处只有一个,用三个^去匹配肯定匹配不上,不够用啊。所以肯定是A,当然,不排除是B。
龙逸凡:我们看完第五句话再来做这道题。先看第四句。
第四句
正则表达式还可用字符来确定位置,比如“前面是XX(在XX后面)”、“前面不是XX(不在XX后面)”、“后面是XX(在XX前面)”、“后面不是XX(不在XX前面)”。
用此方法时,请屏蔽掉那些装逼拗口晦涩难懂的专业术语“零宽断言、正向预查、反向预查、顺序肯定环视、逆序否定环视、零宽度正预测先行断言、零宽度负回顾后发断言”。
正则表达式写法及其作用见下表:
助记方法:
? 通常用于表示是某种模式,比如上一篇文章用到的(?m)多行模式、还有(?i)忽略大小写的模式。
= 是;
! 表示排除、否定,不是;
< 指向左边,也就是前面
合起来看
“?<=”左边是,也就是“前面是,在XX后面”,
“?<!”左边不是。
龙逸凡:是不是很好理解?
偷懒读者:理解了,不会再用错了
案例:
来看个小案例深入理解一下。比如字符串"Excel"中X和C之间的那个位置,我们可以描述为“在C前面”或“在x后面”
老办法,还是用正则函数的替换模式将“位置”替换为@符号,让位置可视化,更直观。
需求:
在字母"c"之前插入一个@号:
公式:
=REGEXP(B1,"(?=c)",2,"@")
在字母"x"之后插入一个@号
公式:
=REGEXP(B1,"(?<=x)",2,"@")
字符串"Excel"的其他位置,可以描述为不在c之前,不在x后。
在“不在c前面”的位置插入一个@号
公式:
=REGEXP(B1,"(?!c)",2,"@")
在“不在x后面”的位置插入一个@号
公式:
=REGEXP(B1,"(?<!x)",2,"@")
来看几个实战案例吧
案例1:
提取“本”字前面的册数、提取“元”字前面的金额(含小数点),将其转为数值然后求和。
龙逸凡:Regexp函数是文本函数,运算结果是文本,前面放两个负号,负负得正,四则运算一下,即可将文本数字转为数值,以方便求和。
偷懒读者:这个知识点我知道。我不明白的是字符串中的“234本”,只有4后面有“本”字,2和3后面并没有“本”字,它们为什么能提取出来。
龙逸凡:“\d+(?=本)”的意思是:前面是若干个连续数字,后面跟了个“本”字,也可理解为:“本”字前面的若干个连续数字。
偷懒读者:哦……终于明白了。
案例2:
提取中文逗号和#号之间的内容
案例3:
判断是否姓“龙”
=REGEXP(B3,"^(?!龙)",1)
^ 是开始的位置
(?!龙) 后面不是“龙”字
^(?!龙) 合起来就是“开始位置后不是“龙”,也就是不姓“龙”。
案例4:
提取主办、主管人员的姓名(不带职位)
公式:
=REGEXP(B1,"[一-龟]+(?=主办|主管)")
附:如果要提取主办、主管人员的姓名(带职位)
公式:
=REGEXP(B1,"[一-龟]+(主办|主管)")
案例5:
提取B列字符串中的数量
公式:
=REGEXP(B2,"(?<=\*)\d+")
=REGEXP(B2,"\d+(?=[桶缸罐坛]|$)")
案例6:
给银行账号每4位添加一空格
看起来好象没问题,如果换成逗号就会发现有问题
从前往后添加逗号的公式:
=REGEXP(B1,"(\d{4})",2,"\1,")
从后往前添加逗号的
=REGEXP(B1,"(?=(\d{4})+$)",2,",")
那怎么解决呢?
需要用到第五句话的知识。
第五句
字符只能被匹配一次,位置可以被多次匹配。
比如下面的示例:
公式:
=REGEXP(B1,".e",2,"@")
B1单元格内容是“eel”,用正则表达式".e"去匹配,首先用"."去匹配,第一个"e"能匹配上,被消耗掉(后面的正则表达式就不能再匹配此字符了)。然后用正则表达式中的"e"去匹配字符串中的第二个"e",也能匹配上。
但是,位置被匹配后,并不会被消耗掉,还能被其他的正则表达式匹配。
比如公式:
=REGEXP(B1,"^(?=E)\A",2,"@")
首先用^去匹配,会被匹配到字符串的开头,这位置不会被消耗掉。
接着,用(?=E)去匹配,匹配到的还是"E"前面的位置,也就是字符串的开头。
接着,又去用\A去匹配,这是字符串的绝对开头,匹配的还是字符串的开始处。
也就是说字符串的“开始”这个位置被匹配了三次。
现在再来做=REGEXP("Excel","^^^",2,"@")这道题,你就知道其计算结果是“@Excel”了。
龙逸凡:现在你终于知道为什么找不到喜欢的人了吧?
偷懒读者:为啥?
龙逸凡:就三个备选答案,你都能完美地错过正确答案,你还指望能从14亿中准确地找到你喜欢的人?
偷懒读者:
注意,这个地方就不能去套第三句话“位置即空字符”。否则就会得出下面的推论:各字符中间的空字符(位置)可以是一个,也可以是任意多个。比如:“Excel”相当于:
""+E+""+x+""+c+""+e+""+l+""
也相当于
""+""+……""+E+""+x+""+……+""+c+""+e+""+l+""
这就让人理解不了,完全没法理解。
一头雾水,一面懵逼!
难道这表示位置的空字符是薛定谔家的?
所以,记住:
字符只能被匹配一次,位置可以被多次匹配。
我们来看看这个知识点的实战应用案例
案例7:
在前面的案例6给银行账号从后往前每4位添加逗号,当账号长度是4的倍数时,会在最前面多出一个逗号。如下图
如何避免这种情况呢?
我们只需在原正则表达式的基础上,再添加一个位置判断:
当是行首时,就不添加逗号。
换另一种表达:
当不是行首时,才添加逗号
完整的条件是:
从后往前,每四位所在的位置,并且不是行首的位置处,加一个逗号。
公式:
=REGEXP(B33,"(?!^)(?=(\d{4})+$)",2,",")
扩展案例1:
给字符串中的数字添加千位分隔符,但不能给年份添加。
公式:
=REGEXP(B1,"(?<=\d)(?=(?:\d{3})+($|[^\d年]))",2,",")
扩展案例2
密码校验:由数字字母下划线组成,长度不少于八位。且至少一个大写字母、一个小写字母、一个数字
公式:
=REGEXP(B5,"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)\w{8,}$",1)
知识点:
至少包含1个大写字母:
(?=.*[A-Z])
至少包含1个小写英文字母
(?=.*[a-z])
至少包含1个数字
(?=.*[0-9])
扩展案例3
判断字符串中是否不包含XXX
公式
=REGEXP(B5,"^((?!52|1234).)*$",1)
知识点:
不包含XXX的正则表达式
^((?!字符串).)*$
利用位置可以被多次匹配的特性,还能解决“前面是 (?<=...)” 和“前面不是 (?<!...)”位置匹配模式下,不能使用不确定量词的问题。
案例7:
B列是一些订单数据,现在要求在其金额后添加“元”字,但不能在编号数字后添加“元”,已有“元”的也不再能添加。
分析:
由于数据不规范,“订单编码”也可能被写成“订单编号”,更麻烦的是,“编码”或“编号”字符后的冒号可能有,也可能没有。我们能想到的正则表达式是:
(?<!编[号码][::]?)
标红的问号在此处表示[::]是0个或1个。
【小总结:问号的作用有多种,①量词,0个或1个;②在量词后表示非贪婪匹配;③还可用于表示是某种匹配模式】
由于“前面是 “(?<=...) 和”前面不是“ (?<!...)【也就是反向环视】模式下不能使用不确定量词。所以,(?<!编[号码][::]?)这种写法是错误的,我们可以利用位置可以被多次匹配的特点,写成:
(?<!编[号码])(?<!编[号码][::])
完整的公式:
=REGEXP(B3,"(?<!编[号码])(?<!编[号码][::])\b([0-9.]+)(\r|\n|$)",2,"\1元\2")
第五句不太好理解,大家慢慢消化。
附上在字符间各种花式插入星号的正则表达式
Excel偷懒的技术微信公众号还有正则表达式的更多文章,请点击下面的合集阅读:
社区管理员
创作者俱乐部成员