锱铢必较:程序员生存指南——正则表达式中使用断言
想让一个名词听起来特别高大上,最简单的方法就是加很多修饰语。比如多源异构群智协同负反馈深度神经网络(当然了,这是我瞎编的)。在正则表达式中,有一种东西叫断言,它的修饰语也很多:
- 零宽正向先行断言
- 零宽负向先行断言
- 零宽正向后行断言
- 零宽负向后行断言
断言之所以叫“零宽”,是因为它们不会消费字符串,因为断言匹配的是位置。
断言之所以叫“断言”,是因为它们用来产生一个TrueFalse的判定结果。
正向和负向分别指的是“应该出现”和“不应该出现”。
先行和后行分别指的是“此位置之后”和“此位置之前”。
这些东西有哪些实际的用途呢?Talk is cheap,show you the code!注意:以下例子是用scala写的,这样就避免了java字符串中“”的转义。
负向断言例子1
假设有几个文件全名:"file1.mp3","file2.bat","file3.txt",需要把英文句号之前的文件名提取出来。在这个过程中,需要忽略所有bat文件和mp3文件。
val pattern ="""(\w+)\.(?!bat|mp3)(\w+)""".r val result = List("file1.mp3", "file2.bat", "file3.txt") .flatMap(s => { s match { case pattern(name, _) => List(name) case _ => Nil } })
这个负向先行断言意思是此位置(句号后面)后面的字符串不能匹配“bat|mp3”,也就排除了bat和mp3扩展名。
负向断言例子2
例如需要在标书中需要提取采购联系人的姓名。
采购人:大连理工大学
联系人:张三
这时采购联系人可以认为是张三
采购人:大连理工大学
代理机构:大连理工代理公司
联系人:李四
这时李四不能认为是采购联系人,他应该是代理机构联系人。(别问我为什么不用如日中天、如火如荼的自然语言处理,而非要用正则表达式作茧自缚)
这时的正则表达式为
(采购人)(?!.*代理机构).*?(联系人:)(?<name>\S+)
它要求“采购人”后面出现“联系人”,但是“采购人”后面不能有“代理机构”。
事实上,严格来说应该要求“采购人”和“联系人”之间不能有“代理机构”,anyway......who cares?
正向断言例子1
提取获取标书的开始时间。
需要购买标书的投标人,请于3月15日到26日登录某某网站
招标文件下载时间:北京时间3月15日至3月22日
这个正则表达式要求后面有日期,前面要出现“购买标书”、“招标文件下载”等关键词,这些关键词是特定名词和特定动词的组合。
(标书|招标文件|购买|下载).*(?<month>\d+)月(?<day>\d+)日
这个正则表达式问题在于名词和动词没有要求同时出现。
((标书|招标文件) .*(购买|下载).*)|((购买|下载).*(标书|招标文件) .*)(?<month>\d+)月(?<day>\d+)日
这个要求倒是严格了,但是也太长了吧,增加了名词和动词时修改也不方便啊,要改两个地方呢,容易出错。
(?<=(标书|招标文件).*)(?<=(购买|下载).*)(\d+)月(\d+)日
这个正向断言则解决了以上两个问题。
正向断言例子2
常见的密码强度验证一般都要求:
- 8-12位
- 必须有大写字母
- 必须有小写字母
- 必须有数字
^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]).{8,12}
这个正则表达式还是挺有用的,说不定哪次面试就用上了呢!!!