正则中的回溯定义与用法分析【JS与java实现】


Posted in Javascript onDecember 27, 2016

本文实例分析了正则中的回溯定义与用法。分享给大家供大家参考,具体如下:

关于“回溯”我也是第一次接触,对它也不算很了解。下面就把我所了解的做为一个心德记录下来,以备查看。

我们所使用的正则表达式的匹配基础大概分为:优先选择最左端(最靠开头)的匹配结果和标准的匹配量词(*、+、?和{m, n})是匹配优先的。

“优先选择最左端的匹配”顾名思义就是从字符串的起始位置开始匹配直到匹配结束这是基础;“标准匹配量词”又分为“非确定型有穷自动机(NFA)”也可以叫做“表达式主导”;另外一种是“确定型有穷自动机(DFA)”也可以叫做“文本主导”。我们目前在JavaScript中所使用的正则表达式为“表达式主导”。表达式主导和文本主导解释起来有些麻烦,先看来一个例子可能会清楚些。

// 使用正则表达式匹配文本
var reg = /to(nite|knight|night)/;
var str = 'doing tonight';
reg.test(str);

在上面的这个例子中,第一个元素[t],它将会重复尝试,直到目标字符串中找到‘t'为止。之后,就检查紧随其后的字符是否能由[o]匹配,如果能,就检查下面的元素(nite|knight|night)。它的真正含义是“nite”或者“knight”或者“night”。引擎会依次尝试这3种可能。尝试[nite]的过程是先尝试[n],然后[i],然后[t],最后是[e]。如果这种尝试失败,引擎会尝试另一种可能,如此继续下去,直到匹配成功或是报告失败。表达式中的控制权在不同的元素之间转换,所以称为“表达式主导”。

同样是上面的例子“文本主导”在扫描字符串时,会记录当前有效的所有匹配可。当引擎移动到t时,它会在当前处理的匹配可能中添加一个潜在的可能:

字符串中的位置 正则表达中的位置
……doing tonight 可能的匹配位置:/t↑o(nite|knight|nigth)/

接下来扫描的每个字符,都会更新当前的可能匹配序列。继续扫描两个字符以后的情况是:

字符串中的位置 正则表达中的位置
……doing tonight 可能的匹配位置:/to(ni↑te|knight|ni↑gth)/

有效的可能匹配变为两个(knight被淘汰出局)。扫描到g时,就只剩下一个可能匹配了。当h和t匹配完成后,引擎发现匹配已经完成,报告成功。“文本主导”是因为它扫描的字符串中的每个字符都对引擎进行了控制。

如果想要弄明白“表达式主导”是如何工作的,那就要看一下我们今天的主题“回溯(backtracking)”。回溯就像是在走岔路口,当遇到岔路的时候就先在每个路口做一个标记。如果走了死路,就可以照原路返回,直到遇见之前所做过的标记,标记着还未尝试过的道路。如果那条路也走不能,可以继续返回,找到下一个标记,如此重复,直到找到出路,或者直到完成所有没有尝试过的路。

在许多情况下,正则引擎必须在两个(或更多)选项中做出选择。当遇到/……x?……/时,引擎必须是否尝试匹配X。对于/……X+……/的情况,毫无疑问,X至少尝试匹配一次——因为加号要求必须匹配至少一次。第一个X匹配之后,此要求已经满足,需要决定是否尝试下一个X。如果决定进行,还要决定是否匹配第三个X,第四个X,如此继续。每次选择,其实就是做一个标记,用于提示此处还有另一个可能的选择,保留起来以备用。在回溯的过程中要考虑两个要点:哪个分支应当首先选择?回溯的时候使用的是哪个(或者是哪些个)之前保存的分支?

第一个问题是按下面这条重要原则来选择的:

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

第二个问题是按以下这条原则:

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

我们先来看几个在道路中做标记的例子:

1、未进行回溯的匹配

用[ab?c]来匹配“abc”。[a]匹配之后,匹配的当前状态如下:

“a↑bc” a↑b?c

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

“a↑bc” ab?↑c

添加到备用状态序列中。也就是说,稍后引擎可能从下面的位置继续匹配:从正则表达式中的[b?]之后,字符串的c之前(也就是说当前的位置)匹配。这实际上就是跳过[b]的匹配,而问题容许这样做。引擎做好标记后,就会继续向前检查[b]。在示例中,它能够匹配,所以新的当前状态变为:
“ab↑c” ab?↑c

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

2、进行了回溯的匹配

下面要匹配的文本是“ac”,在尝试[b]之前,一切都与之前的过程相同。显然,这次[b]无法匹配。也就是说,对[……?]进行尝试的路走不通了。因为有一个备用状态,这个“局部匹配失败”产工会导致整体匹配失败。引擎会进行回溯,也就是说,把“当前状态”切换为最近保存的状态。

“a↑c” ab?↑c

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

3、不成功的匹配

现在要匹配的文本是“abx”。在尝试[b]以前,因为存在问号,保存了这个备用状态:

“a↑bx” ab?↑c

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

例子1: 提取字符串   提取 da12bka3434bdca4343bdca234bm   提取包含在字符a和b之间的数字,但是这个a之前的字符不能是c,b后面的字符必须是d才能提取。

例如这里就只有3434这个数字满足要求。那么我们怎么提取呢?

首先我们写出提取这个字符串的表达式: (?<!c)a(/d+)bd  这里就只有一个捕获组(/d+)

Java代码片段如下:

Pattern p = Pattern.compile( "(?<!c)a(//d+)bd " );
Matcher m = p.matcher( "da12bka3434bdca4343bdca234bm" );
 while (m.find()){
 System.out.println(m.group( 1 )); //我们只要捕获组1的数字即可。结果 3434
 System.out.println(m.group(0)); // 0组是整个表达式,看这里,并没有提炼出(?<!c)的字符 。结果 a3434bd
}

例子2: 将一些多位的小数截短到三位小数:\d+\.\d\d[1-9]?\d+

在这种条件下 6.625 能进行匹配,这样做没有必要,因为它本身就是三位小数。最后一个“5”本来是给 [1-9] 匹配的,但是后面还有一个 \d+ 所以,[1-9] 由于是“?”可以不匹配所以只能放弃当前的匹配,将这个“5”送给 \d+ 去匹配,如果改为:

\d+\.\d\d[1-9]?+\d+

的侵占形式,在“5”匹配到 [1-9] 时,由于是侵占式的,所以不会进行回溯,后面的 \d+ 就匹配不到任东西了,所以导致 6.625 匹配失败。

这种情况,在替换时就有效了,比如把数字截短到小数点后三位,如果正好是三位小数的,就可以不用替换了,可以提高效率,侵占量词基本上就是用来提高匹配效率的。

把 \d+\.\d\d[1-9]?+\d+ 改为 \d+\.\d\d(?>[1-9]?)\d+ 这样是一样的。

Javascript 相关文章推荐
JavaScript 计算当天是本年本月的第几周
Mar 22 Javascript
Javascript的构造函数和constructor属性
Jan 09 Javascript
JavaScript插入动态样式实现代码
Feb 22 Javascript
jQuery使用prepend()方法在元素前添加内容用法实例
Mar 26 Javascript
jQuery  ready方法实现原理详解
Oct 19 Javascript
JS中with的替代方法与String中的正则方法详解
Dec 23 Javascript
用director.js实现前端路由使用实例
Jan 27 Javascript
javascript设计模式之策略模式学习笔记
Feb 15 Javascript
JS常用正则表达式总结【经典】
May 12 Javascript
react开发教程之React 组件之间的通信方式
Aug 12 Javascript
解决Angular.js中使用Swiper插件不能滑动的问题
Feb 26 Javascript
Vue iview-admin框架二级菜单改为三级菜单的方法
Jul 03 Javascript
Vue.js双向绑定操作技巧(初级入门)
Dec 27 #Javascript
JS实现页面中所有img对象添加onclick事件及新窗口查看图片的方法
Dec 27 #Javascript
JS使用正则实现去掉字符串左右空格的方法
Dec 27 #Javascript
js使用Replace结合正则替换重复出现的字符串功能示例
Dec 27 #Javascript
easyUI实现类似搜索框关键词自动提示功能示例代码
Dec 27 #Javascript
jQuery获取选中单选按钮radio的值
Dec 27 #Javascript
JS前向后瞻正则表达式定义与用法示例
Dec 27 #Javascript
You might like
Zerg兵种介绍
2020/03/14 星际争霸
星际争霸 Starcraft 发展史
2020/03/14 星际争霸
用Flash图形化数据(一)
2006/10/09 PHP
php微信开发之百度天气预报
2016/11/18 PHP
解决PHP curl或file_get_contents下载图片损坏或无法打开的问题
2019/10/11 PHP
php判断数组是否为空的实例方法
2020/05/10 PHP
JS 实现获取打开一个界面中输入的值
2013/03/19 Javascript
JQuery+Ajax无刷新分页的实例代码
2014/02/08 Javascript
JS/jQ实现免费获取手机验证码倒计时效果
2016/06/13 Javascript
JS控制TreeView的结点选择
2016/11/11 Javascript
Jquery on绑定的事件 触发多次实例代码
2016/12/08 Javascript
JS表格组件神器bootstrap table使用指南详解
2017/04/12 Javascript
在ABP框架中使用BootstrapTable组件的方法
2017/07/31 Javascript
删除table表格行的实例讲解
2017/09/21 Javascript
实例讲解Vue.js中router传参
2018/04/22 Javascript
vue2中使用sass并配置全局的sass样式变量的方法
2018/09/04 Javascript
浅谈从React渲染流程分析Diff算法
2018/09/08 Javascript
浅谈vue加载优化策略
2019/03/19 Javascript
从0到1搭建Element的后台框架的方法步骤
2019/04/10 Javascript
vue-element-admin 菜单标签失效的解决方式
2019/11/12 Javascript
深入了解JS之作用域和闭包
2020/06/16 Javascript
利用webpack理解CommonJS和ES Modules的差异区别
2020/06/16 Javascript
对numpy中的数组条件筛选功能详解
2018/07/02 Python
使用Python实现分别输出每个数组
2019/12/06 Python
如何理解python中数字列表
2020/05/29 Python
python3.6.8 + pycharm + PyQt5 环境搭建的图文教程
2020/06/11 Python
Python基于pillow库实现生成图片水印
2020/09/14 Python
甜品店的创业计划书范文
2014/01/02 职场文书
法务专员岗位职责
2014/01/02 职场文书
商场消防管理制度
2014/01/12 职场文书
化工专业自荐书
2014/06/16 职场文书
学校安全管理责任书
2014/07/23 职场文书
考试作弊检讨书怎么写?
2014/12/21 职场文书
技术员岗位职责
2015/02/04 职场文书
初中生入团申请书范文(五篇)
2019/10/16 职场文书
vue整合百度地图显示指定地点信息
2022/04/06 Vue.js