正则表达式(四)——匹配原理

正则引擎分为两种:DFA(确定型有穷自动机)和NFA(非确定型有穷自动机)。两类引擎要顺利工作,都必须有一个正则式和一个文本串。DFA捏着文本串去比较正则式,看到一个子正则式,就把可能的匹配串全标注出来,然后再看正则式的下一个部分,根据新的匹配结果更新标注。而NFA是捏着正则式去比文本,吃掉一个字符,就把它跟正则式比较,匹配就记下来:“某年某月某日在某处匹配上了!”,然后接着往下匹配。一旦不匹配,就把刚吃的这个字符吐出来,一个个的吐,直到回到上一次匹配的地方。
这里写图片描述

一、两种正则引擎有两条普适规则:

  1. 优先选择最左端(最靠开头)的匹配结果。
  2. 标准的匹配量词*、+、?、{m,n}是匹配优先的。

规则1举例:如果用cat去匹配The drading belly indicates that your cat is too fat.

  • indicates中的cat会优先被匹配到,而不会匹配cat单词。

规则2举例:用[0-9]+匹配字符串March19985

  • 该字符串中的数字19985全部都会被匹配。

[0-9]+的意思是“匹配数字,且数字出现的次数至少为一次。其实匹配到March1时就已经算是匹配通过了,但是引擎还是继续匹配一直到数字结束,这就是"匹配优先"的意思。换句话说,这些匹配优先的元字符都有一个特点,就是尽可能多的匹配字符。

二、过度匹配优先

(.*)(.*)

由于匹配优先的规则,第二个括号是不会匹配到任何字符的。如果用perl的$2来取值,$2是取不到任何值的。

用^.*([0-9][0-9])去匹配字符串about24characters

整个匹配流程是:

  • .*先将整个字符串都匹配到,匹配第一个[0-9]时要求.*释放一个字符’s’,但是字符’s’并不能让[0-9]匹配,所以还会继续释放,直到释放到字符’4’为止。
  • 但是此时,第二个[0-9]还无法匹配,为了匹配整个正则表达式,表达式.*必须再释放一个字符,这次是’2’,由第一个[0-9]匹配,现在’4’能够由第二个[0-9]匹配。

三、表达式主导和文本主导

以to(nite|knight|night)匹配文本…tonight…的过程为例:

  • 表达式主导:表达式的第一个部分是t,它会重复尝试,直到在字符串中找到t,之后就检查随后的o,如果能匹配就继续检查下面的元素。这个例子中,下面的元素是(nite|knight|night),意思是nite或者knight或者night,引擎会依次尝试这三种可能。
  • 文本主导:当引擎读入文本t时,记录匹配的位置是to(nite|knight|night);接着读入o,匹配位置to(nite|knight|night);读入n,匹配位置to(nite|knight|night),两个位置,knight被淘汰出局;读到i时,就不会再尝试匹配knight了。

整个过程,控制权在表达式的元素之间转换,因此被称之为“表达式主导”。“表达式主导”的特点是每个子表达式都是独立的,不存在内在联系。“文本主导”被叫做文本主导是因为被扫描的字符串,控制了引擎的执行过程。
        NFA具有表达式主导的特性,引擎的匹配原理就非常重要。通过改变表达式的编写方式,会有不同的效果。NFA为创造性思维提供了丰富的施展空间。

四、回溯一些相关概念

  • 回溯
  • 匹配优先
  • 忽略优先

以下分别进行解释:

1.回溯:NFA引擎会依次处理各个子表达式或组成元素,遇到需要再两个可能成功的可能中进行选择的时候,它会选择其一,同时记住另一个,以备稍后可能的需要。无论选择哪一种途径,如果它能匹配成功,而且正则表达式的余下部分也成功了,匹配即告完成。如果正则表达式余下部分最终匹配失败,引擎会知道需要回溯到之前做出选择的地方,选择其他的备用分支继续尝试。

  • 如果在“进行尝试”和“跳过尝试”之间选择,对于匹配优先量词,引擎会优先选择“进行尝试”,而对于忽略优先量词,会选择“跳过尝试”。
  • 引擎会把可能回溯的位置状态保存在备用状态序列里。我们需要了解回溯使用的是哪个之前保存的分支,答案是距离当前最近存储的选项就是本地失败强制回溯时返回的。使用的原则是LIFO(last in first out,后进先出)。

2.匹配优先:?是匹配优先的。以ab?c为例匹配ac为例。

  • 当字符串abc中的a被匹配时,引擎匹配的位置是:a ↑ \uparrow b?c(此时备用状态序列中没有备用的位置)
  • 此时轮到用正则中的b?去匹配字符,由于?是匹配优先的,此时引擎直接用正则中的b去匹配字符串中的c。同时将ab? ↑ \uparrow c存储到备用状态序列里。(此时备用状态序列中的备用位置是ab? ↑ \uparrow c)
  • 用b?去匹配c,结果必然失败,然后引擎会返回状态序列中的备用位置ab? ↑ \uparrow c,然后从此位置开始(忽略了b),用正则里面的c匹配字符串里面的c。匹配成功。
  • 匹配成功后,引擎删除备用状态里面存储的全部备用位置。

3.忽略优先:??是忽略优先的。以ab??c为例匹配abc为例。

  • 当字符串abc中的a被匹配时,引擎匹配的位置是:a ↑ \uparrow b??c(此时备用状态序列中没有备用的位置)
  • a匹配完之后,轮到匹配b??了,由于??是忽略优先的,此时引擎匹配的位置变为ab?? ↑ \uparrow c。但是会将a ↑ \uparrow b??c存储到备用状态序列里。(此时备用状态序列中的备用位置是a ↑ \uparrow b??c)
  • 此时引擎会用正则的c去匹配字符串的b,匹配失败。
  • 然后引擎会回到在备用状态中保存的a ↑ \uparrow b??c,尝试用正则中的b匹配字符串中的b。匹配成功。
  • 直到字符串所有的字符都被匹配成功后,引擎会删除备用状态里面存储的全部备用位置。

4.补充

  • DFA不支持忽略优先。
  • 正则x*相当于x?x?x?x?……,其中x?的数量正好是要匹配的字符串的长度。在每次测试星号作用的元素之前,如果测试失败,还能从保存的状态开始匹配,这个过程不断重复,直到包含星号的尝试完全失败为止。
  • 如果用正则表达式[0-9]+来匹配字符串a1234num,[0-9]遇到4之后无法匹配,此时+能够回溯的位置有四个备用状态:a1$\uparrow 234 n u m 、 a 12 234num、a12 234numa12\uparrow 34 n u m 、 a 123 34num、a123 34numa123\uparrow 4 n u m 、 a 1234 4num、a1234 4numa1234\uparrow n u m , 也 就 是 说 , 每 个 位 置 , [ 0 − 9 ] 的 尝 试 都 代 表 一 种 可 能 。 在 [ 0 − 9 ] 匹 配 失 败 时 , 引 擎 会 回 溯 到 最 近 保 存 的 状 态 ( 满 足 后 进 先 出 ) a 1234 num,也就是说,每个位置,[0-9]的尝试都代表一种可能。在[0-9]匹配失败时,引擎会回溯到最近保存的状态(满足后进先出)a1234 num[09][09]a1234\uparrow$num,此时匹配结束。
  • 前面提到的过度匹配优先的第二个例子^.*([0-9][0-9]),中提到,当匹配到第一个[0-9]时,释放字符的过程就是回溯的体现。
  • 回溯就是引擎匹配失败后的退路。

五、固化分组

  • 语法:(?>……)
  • 意义:使用固化分组的正则匹配与正常的匹配并无差别,但是如果匹配能够进行到此结构之后(闭括号结束之后),那么此结构中的所有备用状态都会被抛弃(也可以理解为被锁定,成为不可用状态)。换句话说,在固化分组结束以时,他已经匹配的文本已经固化为一个单元,只能作为整体而保留或者放弃。括号内的子表达式中未尝试过的备用状态都不复存在了,所以回溯永远也不能选择其中的状态。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值