细说正则表达式之断言

来源:互联网 发布:微信业务域名 编辑:程序博客网 时间:2024/06/06 01:36

最近遇到一个问题: 截取一篇文章中包含指定关键词的一句话(即前后可能有逗号或者句号分割)。方法有很多,单从实现的角度甚至可以用字符串的split方法先按照逗号和句号分割文章再遍历各个字符串检索关键词,但是实在是不太优雅^_^。当时脑中的第一反应就是正则表达式,那进入正题之前就先用这道小题热个身。
(注: 本文代码为C#代码)

假设要检索的关键词是keyword,则正则表达式为:
(^([^,\.])*?|(,|\.)[^,\.]*?)keyword.*?((,|\.)|$)
应该没什么问题吧。大概就是如果从头匹配,则keyword前需要匹配不含,.的任意字符;如果不是从头匹配的则keyword前必须有一个,或者.且keyword和该符号之间不能再包含,.。后半部分的意思是一样的就不多说了。
(注:\.为C#中匹配半角句号的写法;*?为非贪婪匹配)


进入正题

话说这道题貌似就到此为止了,那跟断言有什么关系呢?各位可以试一下,用这个方法匹配的结果前后基本都会带上逗号句号,也就是说虽然我们只要关键句,但是正则表达式把符号也匹配进来了。或许有人说替换一下就好啦,但是在这里我要强力推荐使用断言。理由有二,先说第一点: 断言更优雅简洁,逼格更高^_^

好啦,先让我们先来看看断言在这道题的写法吧。
(^[^,\.]*|(?<=,|\.)[^,\.]*?)keyword.*?(?=(,|\.)|$)

吼了,看看和上面的有什么不同吧。总共只有两部分:(?<=,|\.)以及(?=(,|\.),很像似不似。那为什么这两句话能达到我们想要的效果呢,先岔开话题说一下断言的分类:

(?=pattern)  零宽正向先行断言(zero-width positive lookahead assertion) (?!pattern)  零宽负向先行断言(zero-width negative lookahead assertion) (?<=pattern) 零宽正向后行断言(zero-width positive lookbehind assertion) (?<!pattern) 零宽负向后行断言(zero-width negative lookbehind assertion)

解释一下:

(?=pattern)  要求当前位置开始向后的字符串能匹配pattern。比如hello(?=world)匹配helloworld能够成功(?!pattern)  要求当前位置开始向后的字符串不能匹配pattern。比如hello(?!world)匹配helloworld则失败(?<=pattern) 要求当前位置开始向前的字符串能匹配pattern。比如(?<=hello)world匹配helloworld能够成功(?<!pattern) 要求当前位置开始向前的字符串不能匹配pattern。比如(?<!hello)world匹配helloworld则失败

而除了对于前后字符串的限定之外,有没有注意到他们都是零宽的,换句话说断言只是条件,本身并不占位匹配。那么回头看看上面的小题。
我将前后部分的(,|\.)分别用(?<=,|\.)以及(?=(,|\.)进行替代。相当于把原本的匹配行为换成了条件判断行为: 前后包含逗号或句号则结果为true。这样一来,最终匹配结果就不会包含前后的符号了^O^ 。


那说到这里就引出了断言的第二点好处: 断言的条件判断可以允许我们匹配不包含指定关键词的句子↖(^ω^)↗
匹配包含指定关键词的正则表达式都见得多了,那不包含指定关键词呢。一般方法基本实现不了,或者比较麻烦,但是用断言的话就容易多了。借用网上的例子:

例如判断一句话中包含this,但不包含that。
包含this比较好办,一句话中不包含that,可以认为这句话中每个字符的前面都不是that或每个字符的后面都不是that。正则表达式如下:
^((?<!that).)*this((?<!that).)*$^(.(?!that))*this(.(?!that))*$
对于”this is the case”这句话,两个表达式都能够匹配成功,而”note that this is the case”都匹配失败。
在一般情况下,这两个表达式基本上都能够满足要求了。考虑极端情况,如一句话以that开头、以that结尾、that和this连在一起时,上述表达式就可能不胜任了。
如”note thatthis is the case”或者”this is the case, not that”或者”thatthis is the case”等。
只要灵活运用这几个断言,就很容易解决:
^(.(?<!that))*this(.(?<!that))*$
^(.(?<!that))*this((?!that).)*$
^((?!that).)*this(.(?<!that))*$
^((?!that).)*this((?!that).)*$
这4个正则表达式测试上述的几句话,结果都能够满足要求。

那么上面最后的几个正则表达式为什么能够成功呢,或者有人说这么复杂怎么记啊。其实很简单,上面四个表达式都是一个原理。先看看为什么^((?<!that).)*this((?<!that).)*$无法通过”note thatthis is the case”这句话的测试,因为^((?<!that).)*this只会判断每个字符的前面都不是that,但是由于判断时没有包括本字符,所以会出现判断thatthis这样的字符串时出错,倘若换成thatQthis就又没有问题了。包括^(.(?!that))*this(.(?!that))*$ 这个正则表达式也有类似的问题,就是以that开头和thisthat的模式就会出错。解决的思路就是把当前字符串也考虑在内,而这也是上面那四个正则表达式之所以没问题的原因啦


OK,断言的讲解到此为止~鞠躬~~

1 0
原创粉丝点击