细说正则表达式之断言

xiaoxiao2021-02-28  97

最近遇到一个问题: 截取一篇文章中包含指定关键词的一句话(即前后可能有逗号或者句号分割)。方法有很多,单从实现的角度甚至可以用字符串的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,断言的讲解到此为止~鞠躬~~

转载请注明原文地址: https://www.6miu.com/read-46246.html

最新回复(0)