正则中的回溯定义与用法分析【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 相关文章推荐
JQuery下的Live方法和$.browser方法使用代码
Jun 02 Javascript
js/ajax跨越访问-jsonp的原理和实例(javascript和jquery实现代码)
Dec 27 Javascript
JS完整获取IE浏览器信息包括类型、版本、语言等等
May 22 Javascript
js在IE与firefox的差异集锦
Nov 11 Javascript
javascript自定义滚动条实现代码
Apr 20 Javascript
JavaScript实现时钟滴答声效果
Jan 29 Javascript
Bootstrap下拉菜单样式
Feb 07 Javascript
通过jquery.cookie.js实现记住用户名、密码登录功能
Jun 20 jQuery
vue使用Font Awesome的方法步骤
Feb 26 Javascript
layui表单验证select下拉框实现验证的方法
Sep 05 Javascript
Vue项目中使用flow做类型检测的方法
Mar 18 Javascript
echarts实现晶体球面投影的实例教程
Oct 10 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
php disk_free_space 返回目录可用空间
2010/05/10 PHP
PHP获取表单所有复选框的值的方法
2014/08/28 PHP
Yii框架数据模型的验证规则rules()被执行的方法
2016/12/02 PHP
centos7上编译安装php7以php-fpm方式连接apache
2018/11/08 PHP
javascript 学习之旅 (2)
2009/02/05 Javascript
jquery 最简单易用的表单验证插件
2010/02/27 Javascript
js压缩工具 yuicompressor 使用教程
2010/03/31 Javascript
一段非常简单的js判断浏览器的内核
2014/08/17 Javascript
JS运动框架之分享侧边栏动画实例
2015/03/03 Javascript
js完美实现@提到好友特效(兼容各大浏览器)
2015/03/16 Javascript
jquery实现全选和全不选功能效果的实现代码【推荐】
2016/05/05 Javascript
AngularJS 与百度地图的结合实例
2016/10/20 Javascript
JavaScript模板引擎Template.js使用详解
2016/12/15 Javascript
Jquery Easyui对话框组件Dialog使用详解(14)
2016/12/19 Javascript
nodejs 搭建简易服务器的图文教程(推荐)
2017/07/18 NodeJs
jQuery实现的回车触发按钮事件功能示例
2018/03/25 jQuery
浅谈Redux中间件的实践
2018/07/27 Javascript
vue中tab选项卡的实现思路
2018/11/25 Javascript
[47:06]DOTA2上海特级锦标赛主赛事日 - 4 败者组第五轮 MVP.Phx VS EG第一局
2016/03/05 DOTA
[01:18:36]LGD vs VP Supermajor 败者组决赛 BO3 第一场 6.10
2018/07/04 DOTA
Python类的基础入门知识
2008/11/24 Python
全面了解python字符串和字典
2016/07/07 Python
TensorFlow安装及jupyter notebook配置方法
2017/09/08 Python
Python线程同步的实现代码
2018/10/03 Python
对pytorch中的梯度更新方法详解
2019/08/20 Python
如何分离django中的媒体、静态文件和网页
2019/11/12 Python
Python利用Xpath选择器爬取京东网商品信息
2020/06/01 Python
浅谈Python中的生成器和迭代器
2020/06/19 Python
基于tensorflow __init__、build 和call的使用小结
2021/02/26 Python
美国最大的高尔夫发球时间预订网站:TeeOff.com
2018/03/28 全球购物
MATCHESFASHION.COM美国官网:英国奢侈品零售商
2018/10/29 全球购物
惠普香港官方商店:HP香港
2019/04/30 全球购物
英文导游欢迎词
2014/01/11 职场文书
无财产无子女离婚协议书范文
2014/09/14 职场文书
先进学校事迹材料
2014/12/30 职场文书
详解OpenCV曝光融合
2022/04/29 Python