懒惰和贪婪-正则回溯

需要一定的正则基础,并且是基于JS写的文章。

正则表达式是从左往右匹配的。在使用正则表达式的时候我们知道/.*/可以匹配一个字字符串中所有的字符,/.*?/却一个字符都匹配不到。/(.*)\d/中的.\*可以匹配除了最后一位数字的所有字符,但是之前说的/.*/不是匹配了所有字符吗为什么后面的\d还可以匹配到一个数字字符?

首先我们要知道对于贪婪模式在进行匹配的时候会首先尝试匹配。意思就是/.*/匹配”abcd”的时候可以选择匹配a和不匹配a都是可以的,但是因为是贪婪模式所以选择了匹配a,b和c和d是同样的道理,到最后匹配完了abcd正则表达式匹配完成并且匹配成功。

对于懒惰模式在进行匹配的时候会首先尝试跳过。就是/.*?/匹配”abcd”字符串的时候首先尝试跳过a的匹配,再跳过b的匹配,直到最后正则表达式匹配完成,射门都没匹配到。

通过上面的描述可以看出贪婪和懒惰只是在每一个字符是否匹配上做的选择不同,相同的是在匹配和跳过的选择中正则表达式都会记住我在这里做了选择,这里还要其他选择。记住这些选择的作用就是当前选择如果走不通了,那么还可以回退到这里选择记录下来的另一条路,这就是回溯。 开始回溯的会选择离当前位置最近的一次选择,就是一个后入先出的栈的模式。

例1:/(.*)\d/匹配”abcd1”

第一步:因为.*是贪婪模式,所以会匹配字符a(并记住也可以不匹配a),往后匹配字符b(也可不匹配)一直往后匹配了字符c,字符d,字符1。

第二步:\d匹配的时候匹配不到字符,整个正则需要回溯,到选择是否匹配字符1的时候,之前选择了匹配现在要选择不匹配,让出了字符1。

第三步:\d匹配字符1,完成整个正则的匹配。

var reg = /(.*)\d/
var str = "abcd1"
var res = str.match(reg) //  ["abcd1", "abcd"] 可以看到分组(.*)匹配到了abcd并不包括1

例2:/(.*)\d/匹配”ab1cd”

第一步:因为.*是贪婪模式,所以会匹配字符a(并记住也可以不匹配a),往后匹配字符b(也可不匹配)一直往后匹配了字符c,字符d,字符1。

第二步:\d匹配的时候匹配不到字符,整个正则需要回溯,到选择是否匹配字符d的时候,之前选择了匹配现在要选择不匹配d,让出了字符d。

第三步:\d匹配字符d,匹配失败。继续回溯.*让出字符c。

第四步:\d继续匹配字符c,匹配失败。还要继续回溯.*让出字符1.

第五步:\d匹配字符1,完成整个正则的匹配。

var reg = /(.*)\d/
var str = "ab1cd"
var res = str.match(reg) //  ["ab1", "ab"] 可以看到分组(.*)匹配到了ab并不包括1

例3:/(.*?)\d/匹配”abcd1”

第一步:因为.*?是懒惰模式,所以不会匹配字符a(并记住可以匹配a)。

第二步:\d匹配字符a,匹配失败。回溯.*?重新选择匹配字符a,并继续放弃了字符b(记住可以匹配字符b)。

第二步:\d匹配字符b,匹配失败。回溯.*?重新选择匹配字符b,并继续放弃了字符c(记住可以匹配字符c)。

第三步:\d匹配字符c,匹配继续失败。继续回溯.*?重新选择匹配了字符c,继续并放弃了匹配字符d(记住可以匹配字符d)。

第四步:\d继续匹配字符d,匹配失败。还要继续回溯.*?重新选择匹配字符d,还是放弃了字符1(记住可以匹配字符1)。

第五步:\d匹配字符1,完成整个正则的匹配。

var reg = /(.*?)\d/
var str = "abcd1"
var res = str.match(reg) //  ["abcd1", "abcd"] 可以看到分组(.*)匹配到了abcd并不包括1

小结

对比例1和例3可以发现懒惰和贪婪模式匹配的结果是相同的,但是这并不意味着这两种匹配模式匹配结果是无差别的。对于两种模式匹配的字符串如果只有一个真确的匹配结果那么确是匹配得到的结果是一样的,但是一步一步检查过来就会知道虽然匹配结果一样但是经过的步骤是不同的。

如果匹配的结果不止一种可能,那么这两种模式匹配得到的结果就不一样了。例如,将字符串“abcd1”换成”abcd11”,那么这两种模式匹配到的结果就一样了。

var reg = /(.*?)\d/
var reg2 = /(.*)\d/
var str = "abcd11"str.match(reg)  // ["abcd1", "abcd"]
str.match(reg2)  // ["abcd11", "abcd1"]

例4:/\w?(\w?)1/匹配“a1”

第一步:\w?匹配字符a(记住可以不匹配字符a)

第二步:(\w?)匹配字符1(记住可不匹配字符1)

第三步:1匹配不到任意字符,返回之前做选择的地方(\w?)从新选择放弃了字符1,什么都没有匹配

第四步:1匹配了字符1,完成整个正则表达式的匹配

var reg = /\w?(\w?)1/
var str = "a1"str.match(reg) // ["a1", ""] 数组的第二个值也就是分组1(\w?)什么都没有匹配到

通过上面的步骤可以看出当需要回溯的时候会选择当前位置最近的一次选择,在重新开始,而不是从整个表达式的第一次选择开始重新选择。这是一个后进先出的模式就像栈一样。

断言/环视中的回溯

首先说结论,断言中的备用状态在断言匹配结束后会被丢弃,整个断言只能当做一个整体存在,回溯的时候不会进入断言中的备用状态。

例5:/(?=(.*))\11/匹配”11111”

这个正则什么都匹配不到。

第一步:(?=(.*))\1匹配了11111

第二步:1并不能匹配到任何字符串,并且准备回溯的时候发现前面并没有可回溯的状态,匹配失败

第三步:以为这就结束了吗?还没有会从第二个1再开始匹配,虽然得到的结果是一样的,直到最后一个1完成匹配,匹配失败,什么都没有匹配到

var reg = /(?=(.*))\11/
var str = "11111"str.match(reg) // null

注:这是固化分组的一种模拟,固化分组指的是放弃分组中的备用状态,语法是(?…)。

分支中的回溯

首先多选分支是从左到右匹配的,并不会因为某个分支的或长或短就优先匹配,这样就会造成分支顺序不正确就导致某些分支永远不会被匹配到。

var reg = /abc|ab|abcd/
var str = "abcd"str.match(reg) // ["abc"]

匹配结果是abc并不是ab,如果将分支调换位置那么得到的结果又将不一样。

var reg = /abcd|abc|ab/
var str = "abcd"str.match(reg) // ["abcd"]
var reg = /ab|abcd|abc/
var str = "abcd"str.match(reg) // ["ab"]

从上面的例子中可以看出多选分支的匹配是从左到右来选择分支来匹配的,并且在分支之间选择的时候会记住备用的选择,以备无法匹配的时候回溯到这里选择另一个分支。

例6:/((aa?)|(aa))a/匹配”aa”

第一步:选择分支(aa?)并且记住可以选择分支(aa),开始匹配字符aa

第二步:a匹配字符a

第三步:a? 匹配第二个a(记住可以不匹配这个a)

第四步:正则中最后一个a匹配的时候发现,无字符可匹配

第五步:回溯到第三步选择不匹配a字符

第六步:正则中最后一个a完成了匹配字符串中的a,整个正则匹配完成

var reg = /((aa?)|(aa))a/
var str = "aa"str.match(reg) // ["aa", "a", "a", undefined]

例7:/((aa)|(aa?))a/匹配”aa”

第一步:选择分支(aa)并且记住可以选择分支(aa?),开始匹配字符aa

第二步:a匹配字符a

第三步:aa 匹配第二个a

第四步:正则中最后一个a匹配的时候发现,无字符可匹配

第五步:回溯到第一步选择分支(aa?)

第六步:这时就会重复例6的步骤,直到整个正则匹配成功

var reg = /((aa)|(aa?))a/
var str = "aa"str.match(reg) // ["aa", "a", undefined, "a"]

注:双引号”表示的是字符串,并不是字符串的一部分

参考

精通正则表达式(第三版)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/362163.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

简单的Java SSH客户端

可以使用jcabi-ssh在Java中通过几行代码通过SSH执行shell命令: String hello new Shell.Plain(new SSH("ssh.example.com", 22,"yegor", "-----BEGIN RSA PRIVATE KEY-----...") ).exec("echo Hello, world!");jcabi-ssh…

【JS复习笔记】00 序

作为一个前端苦手,说是复习,你就当我是重学好了。 好吧,我当然不可能抱着一个砖头去复习,所以捡了本薄的来读——《JavaScript语言精粹》。 当初带我的人说这本书挺好,就看这本书好了。我觉得他说的挺对。我喜欢这么…

高并发网络架构解决方案分析

1:html静态化2:图片服务器分离3:数据库集群4:缓存5:负载均衡大型高并发高负载网站的系统架构我在Cernet做过拨号接入平台的搭建,而后在Yahoo3721负载搜索引擎前端平台开发,又在猫扑处理过大型社…

Generator执行步骤浅析

在Generator函数出现之前JS的函数只能返回一个值,返回的方式就是return,但是Generator函数可以返回多个值,返回的方式是yield。并且Generator赋予了外部动态影响函数内部的执行顺序的能力。 基础语法 function* f () {const a yield 1cons…

使用 jQuery.Pin 垂直滚动时固定导航

ZKEACMS的导航默认是不能固定的,随着页面的滚动而滚动,为了有更好的用户体验,当页面往下滚动时,可以将导航固定在顶端,这样方便用户点击。 jQuery Pin 借助jQuery的一个插件 jQuery.Pin,这个插件可在用来…

班级名称

在Java中,每个类都有一个名称。 类位于软件包中,这使我们程序员可以一起工作,避免名称冲突。 我可以为A类命名,也可以为A类命名,只要它们位于不同的程序包中,它们就可以很好地协同工作。 如果您查看Class的…

MDK升级后的头文件冲突

////TITLE:// MDK升级后的头文件冲突//AUTHOR:// norains//DATE:// Friday 17-June-2011//Environment:// Keil MDK 4.2// .NET Micro Framework Porting 4.1// 因为在移植的时候,发现了不少MDK编译的一些问题,于是便想升级到最新版本&a…

内置假对象

尽管模拟对象是进行单元测试的理想工具,但通过模拟框架进行模拟可能会将您的单元测试变成难以维护的混乱。 这种复杂性的根本原因是我们的对象太大。 他们有很多方法,这些方法返回其他对象,这些对象也有方法。 当将此类对象的模拟版本作为参…

微信小程序面试题

小程序与原生App哪个好? 答: 小程序除了拥有公众号的低开发成本、低获客成本低以及无需下载等优势,在服务请求延时与用户使用体验是都得到了较大幅度 的提升,使得其能够承载跟复杂的服务功能以及使用户获得更好的用户体验。 简单…

阻止默认事件

在JS中经常需要阻止元素的默认事件。而阻止默认事件的方法都是使用事件对象的preventDefault()方法或者在函数中return false。在最近一次开发中使用preventDefault()方法的时候遇到一个问题&#xff0c;现在才想/猜明白原因&#xff0c;场景是这样的&#xff1a; <a href&…

MySQL之SQL优化详解(三)

目录 MySQL 之SQL优化详解&#xff08;三&#xff09; 1. 索引优化2. 剖析报告:Show ProfileMySQL 之SQL优化详解&#xff08;三&#xff09; 1. 索引优化 一旦建立索引&#xff0c;select 查询语句的where条件要尽量符合最佳左前缀的原则&#xff0c;如若能做到全值匹配最好。…

jUnit:规则

规则在测试&#xff0c;测试用例或测试套件周围增加了特殊处理。 他们可以对类中的所有测试执行通用的其他验证&#xff0c;并发运行多个测试实例&#xff0c;在每个测试或测试用例之前设置资源&#xff0c;然后将其拆除。 该规则可以完全控制将要应用到的测试方法&#xff0c…

常用浏览器内核:

浏览器内核又可以分为两部分&#xff1a;渲染引擎和JS引擎。 PC端&#xff1a;IE&#xff1a;Trident&#xff0c;沿用到IE11,即兼容模式。 IE8 的 JavaScript 引擎是 Jscript&#xff0c;IE9&#xff08;PS: JS内核&#xff09; 开始用 Chakra&#xff0c;这两个版本区别很大…

行内格式化

相对于熟知的块级格式化上下文&#xff0c;行内格式化上下文更加的复杂难明。行内元素不像块级元素那样直来直去&#xff0c;一个块级元素占据一行&#xff0c;其他块级元素在垂直方向依次向下排列即可。行内元素不同&#xff0c;多个行内元素可以在一行显示&#xff0c;那么&a…

[转载]struts+hibernate遇到的错误总结

原文地址&#xff1a;strutshibernate遇到的错误总结作者&#xff1a;畫上句號经过对strutshibernate几天的学习&#xff0c;大体上还算比较的了解机制&#xff0c;以前学习的时候都是 单个框架训练&#xff0c;没有结合2个框架做&#xff0c;所以今天就找了个网上发布租房信息…

JPA休眠替代方案。 如果JPA或Hibernate对于我的项目而言不够好,该怎么办?

你好&#xff01;你好吗&#xff1f; 今天&#xff0c;我们将讨论不建议使用JPA / Hibernate的情况。 在JPA领域之外&#xff0c;我们还有哪些选择&#xff1f; 我们将谈论的是&#xff1a; JPA /休眠问题 解决一些JPA /休眠问题的方法 选择此处描述的框架的标准 Spring J…

一个Web前端自学者的自述

想来想去还是写下这篇文章&#xff0c;先说明&#xff0c;我精通JAVA编程语言和web前端常见的技术&#xff0c;个人是做JAVA的多&#xff0c;但是更加喜欢前端。因为我从高一开始接触JAVA&#xff0c;家父是黑马的JAVA讲师&#xff0c;自己对编程很热爱&#xff0c;在大学的时候…

mongoose中的populate之多级填充,嵌套字段填充?

在mongoose中存引用的时候如果是多级&#xff0c;查询的时候填充引用字段会使用populate&#xff0c;如下&#xff1a; 定义一个User&#xff0c;有字段friends每一项是自己collection的ObjectId。 // file: user-schema.js let mongoose require(mongoose) let ObjectId m…

深度学习优化基础

1、网络优化参数 sigmoid函数&#xff1a;1/(1e^(-x))&#xff1a;&#xff1a;便于求导的平滑函数&#xff0c;但是容易出现梯度消失问题&#xff1b;函数中值不是0&#xff0c;会导致模型训练的收敛速度变慢。。。 tanh函数&#xff1a;(e^x-e^(-x))/(e^xe^(-x))::解决了zero…

稳定高效大型系统架构---集群中间件开发

那现在来说&#xff0c;稳定的中间件应该是什么样子呢&#xff1f; 对于客户端请求&#xff0c;如果发现服务停止&#xff0c;可以实现服务无缝转移&#xff0d;&#xff0d;&#xff0d;这叫不丢失任何服务. 对于多个客户端请求&#xff0c;可以讲请求轮巡到不同的服务器上&am…