正则回溯原理

回溯

Backtracking

NFA引擎最重要的性质是,它会依次处理各个子表达式或组成元素,遇到需要在两个可能成功的可能中进行选择的时候,它会选择其一,同时记住另一个,以备稍后可能的需要。

要做出选择的情形包括量词(决定是否尝试另一次匹配)和多选结构(决定选择哪个多选分支,留下哪个稍后尝试)。

不论选择那一种途径,如果它能匹配成功,而且正则表达式的余下部分也成功了,匹配即告完成。如果正则表达式中余下的部分最终匹配失败,引擎会知道需要回溯到之前做出选择的地方,选择其他的备用分支继续尝试。这样,引擎最终会尝试表达式的所有可能途径(或者是匹配完成之前需要的所有途径)。

真实世界中的例子:面包屑

A Really Crummy Analogy

回溯就像是在道路的每个分岔口留下一小堆面包屑。如果走了死路,就可以照原路返回,直到遇见面包屑标示的尚未尝试过的道路。如果那条路也走不通,你可以继续返回,找到下一堆面包屑,如此重复,直到找到出路,或者走完所有没有尝试过的路。

在许多情况下,正则引擎必须在两个(或更多)选项中做出选择——我们之前看到的分支的情况就是一例。另一个例子是,在遇到「…x?…」时,引擎必须决定是否尝试匹配「x」。对于「…x+…」的情况,毫无疑问,「x」至少尝试匹配一次——因为加号要求必须匹配至少一次。第一个「x」匹配之后,此要求已经满足,需要决定是否尝试下一个「x」。如果决定进行,还要决定是否匹配第三个「x」,第四个「x」,如此继续。每次选择,其实就是洒下一堆“面包屑”,用于提示此处还有另一个可能的选择(目前还不能确定它能否匹配),保留起来以备用。

一个简单的例子

现在来看个完整的例子,用先前的「to(nite|knight|night)」匹配字符串‘hot–tonic– tonight! ’(看起来有点无聊,但是个好例子)。第一个元素「t」从字符串的最左端开始尝试,因为无法匹配‘h’,所以在这个位置匹配失败。传动装置于是驱动引擎向后移动,从第二个位置开始匹配(同样也会失败),然后是第三个。这时候「t」能够匹配,接下来的「o」无法匹配,因为字符串中对应位置是一个空格。至此,本轮尝试宣告失败。

继续下去,从…?tonic…开始的尝试则很有意思。to匹配成功之后,剩下的3个多选分支都成为可能。引擎选取其中之一进行尝试,留下其他的备用(也就是洒下一些面包屑)。在讨论中,我们假定引擎首先选择的是「nite」。这个表达式被分解为“「n」+「i」+「t」+「e」”,在…toni?c…遭遇失败。但此时的情况与之前不同,这种失败并不意味着整个表达式匹配失败——因为仍然存在没有尝试过的多选分支(就好像是,我们仍然可以找到先前留下的面包屑)。假设引擎然后选择「knight」,那么马上就会遭遇失败,因为「k」不能匹配‘n’。现在只剩下最后的选项「night」,但它不能失败。因为「night」是最后尝试的选项,它的失败也就意味着整个表达式在…?tonic…的位置匹配失败,所以传动机构会驱动引擎继续前进。

直到引擎开始从…?tonight!处开始匹配,情况又变得有趣了。这一次,多选分支「night」终于可以匹配字符串的结尾部分了(于是整体匹配成功,现在引擎可以报告匹配成功了)。

回溯的两个要点

Two Important Points on Backtracking

回溯机制的基本原理并不难理解,还是有些细节对实际应用很重要。它们是,面对众多选择时,哪个分支应当首先选择?回溯进行时,应该选取哪个保存的状态?一个问题的答案是下面这条重要原则:

如果需要在“进行尝试”和“跳过尝试”之间选择,对于匹配优先量词,引擎会优先选择“进行尝试”,而对于忽略优先量词,会选择“跳过尝试”。

此原则影响深远。对于新手来说,它有助于解释为什么匹配优先的量词是“匹配优先”的,但还不完整。要想彻底弄清楚这一点,我们需要了解回溯时使用的是哪个(或者是哪些个)之前保存的分支,答案是:

距离当前最近储存的选项就是当本地失败强制回溯时返回的。使用的原则是LIFO(last in first out,后进先出)

用面包屑比喻就很好理解——如果前面是死路,你只需要沿原路返回,直到找到一堆面包屑为止。你会遇到的第一堆面包屑就是最近洒下的。传统的LIFO比喻也是这样:就像堆叠盘子一样,最后叠上去的盘子肯定是最先拿下来的。

备用状态

Saved States

用NFA正则表达式的术语来说,那些面包屑相当于“备用状态(saved state)”。它们用来标记:在需要的时候,匹配可以从这里重新开始尝试。它们保存了两个位置:正则表达式中的位置,和未尝试的分支在字符串中的位置。因为它是NFA匹配的基础,我们需要再看一遍某些已经出现过的简单但详细的例子,说明这些状态的意义。

未进行回溯的匹配

来看个简单的例子,用「ab?c」匹配abc。「a」匹配之后,匹配的当前状态如下:

“a↑bc” ------------- a↑b?c

现在轮到「b?」了,正则引擎需要决定:是需要尝试「b」呢,还是跳过?因为?是匹配优先的,它会尝试匹配。但是,为了确保在这个尝试最终失败之后能够恢复,引擎会把:

‘abc’ ------------- ab?c

添加到备用状态序列中。也就是说,稍后引擎可以从下面的位置继续匹配:从正则表达式中的「b?」之后,字符串的c之前(也就是当前的位置)匹配。这实际上就是跳过「b」的匹配,而问号容许这样做。

引擎放下面包屑之后,就会继续向前,检查「b」。在示例文本中,它能够匹配,所以新的当前状态变为:

‘abc’ ------------- 「ab?c」

最终的「c」也能成功匹配,所以整个匹配完成。备用状态不再需要了,所以不再保存它们。

进行了回溯的匹配

如果需要匹配的文本是‘ac’,在尝试「b」之前,一切都与之前的过程相同。显然,这次「b」无法匹配。也就是说,对「…?」进行尝试的路走不通。因为有一个备用状态,这个“局部匹配失败”并不会导致整体匹配失败。引擎会进行回溯,也就是说,把“当前状态”切换为最近保存的状态。在本例中,情况就是:

‘a在「b」尝试之前保存的尚未尝试的选项。这时候,「c」可以匹配c,所以整个匹配宣告完成。

不成功的匹配

现在,我们用同样的表达式匹配‘abX’。在尝试「b」以前,因为存在问号,保存了这个备用状态:

↑bX’ ------------- 「b」能够匹配,但这条路往下却走不通了,因为「c」无法匹配X。于是引擎会回溯到之前的状态,“交还”b给「c」来匹配。显然,这次测试也失败了。如果还有其他保存的状态,回溯会继续进行,但是此时不存在其他状态,在字符串中当前位置开始的整个匹配也就宣告失败。

事情到此结束了吗?没有。传动装置会继续“在字符串中前行,再次尝试正则表达式”,这可能被想象为一个伪回溯(pseudo-backtrack)。匹配重新开始于:

ab?c」

从这里重新开始整个匹配,如同之前一样,所有的道路都走不通。接下来的两次(从ab?X到abX?)都告失败,所以最终会报告匹配失败。

忽略优先的匹配

现在来看最开始的例子,使用忽略优先匹配量词,用「ab??c」来匹配‘abc’。「a」匹配之后的状态如下:

bc’ ------------- 「ab??c」

接下来轮到「b??」,引擎需要进行选择:尝试匹配「b」,还是忽略?因为??是忽略优先的,它会首先尝试忽略,但是,为了能够从失败的分支中恢复,引擎会保存下面的状态:

↑bc」

到备用状态列表中。于是,引擎稍后能够用正则表达式中的「b」来尝试匹配文本中的b(我们知道这能够匹配,但是正则引擎不知道,它甚至都不知道是否会要用到这个备用状态)。状态保存之后,它会继续向前,沿着忽略匹配的路走下去:

↑bc’ ------------- 「ab??c」

「c」无法匹配‘b’,所以引擎必须回溯到之前保存的状态:

显然,此时匹配可以成功,接下来的「c」匹配‘c’。于是我们得到了与使用匹配优先的「ab?c」同样的结果,虽然两者所走的路不相同。

回溯与匹配优先

Backtracking and Greediness

如果工具软件使用的是NFA正则表达式主导的回溯引擎,理解正则表达式的回溯原理就成了高效完成任务的关键。我们已经看到「?」的匹配优先和「??」的忽略优先是如何工作的,现在来看星号和加号。

星号、加号及其回溯

如果认为「x*」基本等同于「x?x?x?x?x?x?…」(或者更确切地说是「(x(x(x(x…?)?)?)?)?)」)(注5),那么情况与之前没有大的差别。每次测试星号作用的元素之前,引擎都会保存一个状态,这样,如果测试失败(或者测试进行下去遭遇失败),还能够从保存的状态开始匹配。这个过程会不断重复,直到包含星号的尝试完全失败为止。

所以,如果用「[0-9]+」来匹配‘a–1234–num’,「[0-9]」遇到4之后的空格无法匹配,而此时加号能够回溯的位置对应了四个保存的状态:

a 1?234 num

a 12?34 num

a 123?4 num

a 1234? num

也就是说,在每个位置,「[0-9]」的尝试都代表一种可能。在「[0-9]」遇到空格匹配失败时,引擎回溯到最近保存的状态(也就是最下面的位置),选择正则表达式中的「[0-9]+ ?」和文本中的‘a–1234?–num’。当然,到此整个正则表达式已经结束,所以我们知道,整个匹配宣告完成。

请注意,‘a–?1234–num’并不在列表中,因为加号限定的元素至少要匹配一次,这是必要条件。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


正则替换html代码中img标签的src值在开发富文本信息在移动端展示的项目中,难免会遇到后台返回的标签文本信息中img标签src属性按照相对或者绝对路径返回的形式,类似:<img src="qinhancity/v1.0.0/ima
正则表达式
AWK是一种处理文本文件的语言,是一个强大的文件分析工具。它是专门为文本处理设计的编程语言,也是行处理软件,通常用于扫描,过滤,统计汇总等工作,数据可以来自标准输入也可以是管道或文件。当读到第一行时,匹配条件,然后执行指定动作,在接着读取第二行数据处理,不会默认输出。如果没有定义匹配条件,则是默认匹配所有数据行,awk隐含循环,条件匹配多少次,动作就会执行多少次。逐行读取文本,默认以空格或tab键为分割符进行分割,将分割所得的各个字段,保存到内建变量中,并按模式或或条件执行编辑命令。与sed工作原理相比:s
正则表达式是特殊的字符序列,利用事先定义好的特定字符以及他们的组合组成了一个规则,然后检查一个字符串是否与这种规则匹配来实现对字符的过滤或匹配。我们刚才在学习正则表达式的时候,我们表示数字,字母下划线的时候是用w表示的,为什么我们在书写的时候用的是w?我们可以发现我们分割空格的话,并没有达到我们预期的效果,这里我们可以使用正则表达式的方式进行分割。我们可以发现,我们和上面得到的结果不一致,既然出错了,肯定是我们的使用方式不对。看到这里我们就能感受到正则表达式的作用了,正则表达式是字符串处理的有力工具。
Python界一名小学生,热心分享编程学习。
收集整理每周优质开发者内容,包括、、等方面。每周五定期发布,同步更新到和。欢迎大家投稿,,推荐或者自荐开源项目/资源/工具/文章~
本文涉及Shell函数,Shell中的echo、printf、test命令等。
常用正则表达,包括: 密码、 手机号、 身份证、 邮箱、 中文、 车牌号、 微信号、 日期 YYYY-MM-DD hh:mm:ss、 日期 YYY-MM-DD、 十六进制颜色、 邮政编号、 用户名、 QQ号
一、python【re】的用法1、re.match函数·单一匹配-推荐指数【★★】2、re.search函数·单一匹配-推荐指数【★★★★★】3、re.findall函数·多项匹配-推荐指数【★★★★★】4、re.finditer函数·多项匹配-推荐指数【★★★★】5、re.sub函数·替换函数-推荐指数【★★★★】二、正则表达式示例·总有一款适合你1、正则表达式匹配HTML指定id/class的标签2、正则表达式匹配HTML中所有a标签中的各类属性值3、获取标签的文本值
1.借助词法分析工具Flex或Lex完成(参考网络资源)2.输入:高级语言源代码(如helloworld.c)3.输出:以二元组表示的单词符号序列。通过设计、编制、调试一个具体的词法分析程序,加深对词法分析原理的理解,并掌握在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法。由于各种不同的高级程序语言中单词总体结构大致相同,基本上都可用一组正则表达式描述,所以构造这样的自动生成系统:只要给出某高级语言各类单词词法结构的一组正则表达式以及识别各类单词时词法分析程序应采取的语义动作,该系统
正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。例如:我们在写登录注册功能的时候使用的表单验证(对用户名、密码进行一些字符或长度进行限制) ===> (`匹配`) - 正则表达式还常用于过滤掉页面内容的一些敏感词汇。例如:我们平常在打游戏时候的口吐芬芳被换成了***:full_moon_with_face: ===> (`替换`) - 正则表达式从字符串中获取我们想要的特定部分。例如:我们在逛淘宝的时候在搜索框中搜索内容,会弹出很多与搜索相关的提示内容 ===> (`提取`) etc..
通过上面几个简单的示例,可以了解到常见的基础正则表达式的元字符主要包括以下几个^ 匹配输入字符串的开始位置。除非在方括号表达式中使用,表示不包含该字符集合。要匹配”^”字符本身,请使用"^"$ 匹配输入字符串的结尾位置。如果设置了RegExp对象的 Multiline属性,则"$”也匹配'n'或'r’,。要匹配”$"字符本身,请使用”$". 匹配除"rn"之外的任何单个字符 反斜杠,又叫转义字符,去除其后紧跟的元字符或通配符的特殊意义* 匹配前面的子表达式零次或多次。...
给出补充后描述 C 语言子集单词符号的正则文法,设计并实现其词法分析程序。
正则表达式(Regular Expression),又称规则表达式,它不是某个编程语言所特有的,是计算机科学的一个概念,通常被用来检索和替换符合某些规则的文本。
Python Re 正则表达式 数据匹配提取 基本使用
正则表达式:是用来描述字符串内容格式,使用它通常用于匹配一个字符串的内容是否符合格式要求
python的学习还是要多以练习为主,想要练习python的同学,推荐可以去牛客网看看,他们现在的IT题库内容很丰富,属于国内做的很好的了,而且是课程+刷题+面经+求职+讨论区分享,一站式求职学习网站,最最最重要的里面的资源全部免费!