正则表达式之原理篇

Python012

正则表达式之原理篇,第1张

背景

       最近公司规范出来后,关于字符串不提倡用 “ + ”  进行拼接,于是自己写了个function,利用正则表达式来进行匹配。对于正则表达式,之前不了解原理,每次要用的时候查一下,很浪费时间。

内容

基础知识;

正则表达式引擎;

贪婪与非贪婪模式;

DFA与NFA引擎;

回溯机制及常见的回溯形式

基础知识

1. 占有字符:正则表达式匹配过程中,如果子表达式匹配到东西,而并非是一个位置,并最终保存到匹配的结果当中

2. 零宽度:只匹配一个位置,或者是匹配的内容并不保存到匹配结果中

一个字符,同一时间只能由一个子表达式匹配,而一个位置,却可以同时由多个零宽度的子表达式匹配

3.控制权:正则表达式由左到右依次进行匹配,通常情况下是由一个表达式取得控制权,从字符串的的某个位置进行匹配,一个子表达式开始尝试匹配的位置,是从前一子表达匹配成功的结束位置开始的(例如:(表达式一)(表达式二)意思就是表达式一匹配完成后才能匹配表达式二,而匹配表达式二的位置是从表达式一的位置匹配结束后的位置开始)。如果表达式一是零宽度,那表达式一匹配完成后,表达式二匹配的位置还是原来表达式以匹配的位置。也就是说它匹配开始和结束的位置是同一个

4. 元字符 

5. 反义元字符

6. 转义字符:\  使元字符失去它的意义,仅代表其输入中字符的意义

需要转义的字符列表 \ * + ? | { [ ( ) ^ $ . # 和 空白

7. 重复限定符:匹配优先量词,忽略优先量词,即:贪婪与非贪婪

{n,}、 {n, m}、 {, m}、 ’+’ 、‘?’、 '*'

8. 字符类:[ ],区分大小写

9. 分支条件: |

10. 分组 :()指定子表达式,可限制多选项的范围、将若干字符组合为一个单元、受问号或星号之类的量词作用,例:(\d{1,3}){3}\d{3}

断言;(?

11. 括号及反向引用:(子表达式一)(子表达式二)\1      此时括号作用为分组,它具有记忆的功能,即在正则表达式内部仍然能回忆上次匹配到的是什么;\1、\2、\n 是用在正则表达式的匹配环节。在正则表达式的替换环节,则要使用像 $1、$2、$n 这样的语法

12. 平衡组 参考

正则表达式引擎

有两个主要特点:

1. 默认贪婪匹配;( 贪婪匹配与非贪婪匹配 )

2. 返回最先匹配到的结果

针对简单的正则匹配进行分析,例:

        当把cat应用到“He captured a catfish for his cat”,引擎先比较c和“H”,结果失败了。于是引擎再比较c和“e”,也失败了。直到第四个字符,c匹配了“c”。a匹配了第五个字符。到第六个字符t没能匹配“p”,也失败了。引擎再继续从第五个字符重新检查匹配性。直到第十五个字符开始,cat匹配上了“catfish”中的“cat”,正则表达式引擎急切的返回第一个匹配的结果,而不会再继续查找是否有其他更好的匹配

Rubular: 基于 Web 的 Ruby 正则表达式编辑器

贪婪与非贪婪(又称惰性、懒惰等)模式

两者影响的是被量词修饰的子表达式的行为。

贪婪模式在整个表达式匹配成功的前提下,尽可能多的匹配;而非贪婪模式(只被部分NFA引擎支持)在整个表达式匹配成功的前提下,尽可能少的匹配。

匹配优先量词(属于贪婪模式的量词):

“{m,n}”、“{m,}”、“?”、“*”和“+”。

忽略优先量词(匹配优先量词后加上“?”:非贪婪模式的量词):

“{m,n}?”、“{m,}?”、“??”、“*?”和“+?”

例:

源字符串:aa

正则表达式一:

正则表达式二:

DFA与NFA引擎(JS的正则引擎是NFA:非确定型有限自动机)

参考: 正则表达式引擎及其分类

DFA引擎:在线性时状态下执行,不要求回溯(因此永远不测试相同的字符两次);确保匹配最长的可能的字符串;因为只包含有限的状态(?),所以它不能匹配具有反向引用的模式;并且因为它不构造显示扩展,所以它不可以捕获子表达式

传统的NFA引擎:运行匹配回溯算法——以指定顺序测试正则表达式的所有可能的扩展并接受第一个匹配项。因为传统的 NFA 构造正则表达式的特定扩展以获得成功的匹配,所以它可以捕获子表达式匹配和匹配的反向引用。但传统 NFA的 回溯使它可以访问完全相同的状态多次(如果通过不同的路径到达该状态)。因此,在最坏情况下,它的执行速度可能非常慢。因为传统的 NFA 接受它找到的第一个匹配,所以它还可能会导致其他(可能更长)匹配未被发现

POSIX NFA 引擎:与传统 NFA 引擎类似,不同点:在可以确保已找到了可能的最长的匹配之前,它们将继续回溯(更慢);并且在使用 POSIX NFA 时,您恐怕不会愿意在更改回溯搜索的顺序的情况下来支持较短的匹配搜索,而非较长的匹配搜索

例:

字符串: this is yansen’s dog

正则表达式: /ya(msen|nsen|nsem)/

NFA工作方式:先在字符串中查找 y, 然后匹配其后是否为 a;  如果是 a 则继续查找其后是否为 m; 如果不是则匹配其后是否为 n (此时淘汰 msen 支分支); 然后继续看其后是否依次为 s,e; 接着测试是否为 n ,是 n 则匹配成功,不是则测试是否为 m 。为什么是 m ?因为 NFA 工作方式是以正则表达式为标准,反复测试字符串,这样同样一个字符串有可能被反复测试了很多次!

DFA:从 this 中 t 开始依次查找 y ,定位到 y ,已知其后为 a ,则查看表达式是否有 a ,此处正好有 a; 然后字符串 a 后为 n ,DFA依次测试表达式,此时 msen 不符合要求淘汰。 nsen 和 nsem 符合要求,然后DFA依次检查字符串,检测到 sen 中的 n 时只有 nsen 分支符合,则匹配成功!

由此两种引擎是完全不同的工作方式:NFA以表达式为主导,更容易操纵;DFA以文本为主导(搜索更快)

回溯机制

引擎是如何来处理那些模糊的条件匹配?

从问题的某一种状态(初始状态)出发,搜索从这种状态出发所能达到的所有“状态”,当一条路走到“尽头”的时候(不能再前进),再后退一步或若干步,从另一种可能“状态”出发,继续搜索,直到所有的“路径”(状态)都试探过。这种不断“前进”、不断“回溯”寻找解的方法,就称作“回溯法”

--来自百度百科

本质上就是深度优先搜索算法:尝试匹配失败时的下一步通常就是回溯

JS中正则表达式会产生回溯的地方都有哪些呢?

常见的回溯形式

1.贪婪量词

例:正则:/ab{1,3}c/

可视化形式

1. 没有回溯的匹配:当目标字符串是"abbbc"时

匹配过程

2. 有回溯的匹配:当目标字符串是“abbc”时

匹配过程

上图第5步有红颜色(仅表示匹配不成功):此时b{1,3}已经匹配到了2个字符“b”,准备尝试第三个时,结果发现接下来的字符是“c”。那么就认为b{1,3}就已经匹配完毕。然后状态又回到之前的状态(即第6步,与第4步一样),最后再用子表达式c,去匹配字符“c”。当然,此时整个表达式匹配成功了;上图的第6步,就是“回溯”

即:尝试可能的顺序是“从多往少”的方向去尝试:首先会尝试"bbb",然后再看整个正则是否能匹配。不能匹配时,吐出一个"b",即在"bb"的基础上,再继续尝试。如果还不行,再吐出一个,再试。如果还不行呢?只能说明匹配失败了

另一个清晰的回溯:

正则:/".*"/

目标字符串:"acd"ef

省略了尝试匹配双引号失败的匹配过程

其实“.*”最简单但也是非常影响效率的

2.惰性量词

虽然惰性量词不贪,但也会有回溯的现象(为了整体匹配成)

正则表达式

目标字符串:"12345"

匹配过程

3.分支结构

分支也是惰性的,比如/Java|JavaScript/,去匹配字符串"JavaScript",得到的结果是"Java",因为分支会一个一个尝试,如果前面的满足了,后面就不会再试验了。

分支结构中可能前面的子模式会形成了局部匹配,如果接下来表达式整体不匹配时,仍会继续尝试剩下的分支。这种尝试也可以看成一种回溯:

正则表达式

匹配过程

虽然第五步没有回到之前的状态,但仍然回到了分支结构,尝试下一种可能

总结:有回溯的过程,那么匹配效率肯定比DFA相对低一些;别看匹配慢,但是编译快而且还挺有趣

参考: 正则表达式的回溯机制

技术类

后端开发

Java Python PHP .NET C# C++ C VB Delphi Perl Ruby Hadoop Node.js 数据挖掘 自然语言处理 搜索算法 精准推荐 全栈工程师 Go ASP Shell 其它

移动开发

HTML5 Android iOS WP 移动开发其它

前端开发

web前端 Flash html5 JavaScript U3D COCOS2D-X 前端开发其它

测试

测试工程师 自动化测试 功能测试 性能测试 测试开发 游戏测试 白盒测试 灰盒测试 黑盒测试 手机测试 硬件测试 测试经理 测试其它

运维

运维工程师 运维开发工程师 网络工程师 系统工程师 IT支持 IDC CDN F5 系统管理员 病毒分析 WEB安全 网络安全 系统安全 运维经理 运维其它

DBA

数据库

MySQL SQLServer Oracle DB2 MongoDB ETL Hive 数据仓库 DBA其它

高端职位

技术经理 技术总监 架构师 CTO 运维总监 技术合伙人 项目总监 测试总监 安全专家 高端技术职位其它

非技术类

项目管理

项目经理 项目助理

产品经理

网页产品经理 移动产品经理 产品助理 数据产品经理 电商产品经理 游戏策划 产品实习生

产品设计师

网页产品设计师 无线产品设计师

高端职位

产品部经理 产品总监 游戏制作人

视觉设计

网页设计师 Flash设计师 APP设计师 UI设计师 平面设计师 美术设计师(2D/3D) 广告设计师 多媒体设计师 原画师 游戏特效 游戏界面设计师 视觉设计师 游戏场景 游戏角色 游戏动作

用户研究

数据分析师 用户研究员 游戏数值策划

高端职位

设计经理/主管 设计总监 视觉设计经理/主管 视觉设计总监 交互设计经理/主管 交互设计总监 用户研究经理/主管 用户研究总监

交互设计

网页交互设计师 交互设计师 无线交互设计师 硬件交互设计师

运营

内容运营 产品运营 数据运营 用户运营 活动运营 商家运营 品类运营 游戏运营 网络推广 运营专员 网店运营 新媒体运营 海外运营 运营经理

编辑

副主编 内容编辑 文案策划 记者

客服

售前咨询 售后客服 淘宝客服 客服经理

高端职位

主编 运营总监 COO 客服总监

市场/营销

市场策划 市场顾问 市场营销 市场推广 SEO SEM 商务渠道 商业数据分析 活动策划 网络营销 海外市场 政府关系

公关

媒介经理 广告协调 品牌公关

销售

销售专员 销售经理 客户代表 大客户代表 BD经理 商务渠道 渠道销售 代理商销售 销售助理 电话销售 销售顾问 商品经理

高端职位

市场总监 销售总监 商务总监 CMO 公关总监 采购总监 投资总监

供应链

物流 仓储

采购

采购专员 采购经理 商品经理

投资

分析师 投资顾问 投资经理

人力资源

人事/HR 培训经理 薪资福利经理 绩效考核经理 人力资源 招聘 HRBP 员工关系

行政

助理 前台 行政 总助 文秘

财务

会计 出纳 财务 结算 税务 审计 风控

高端职位

行政总监/经理 财务总监/经理 HRD/HRM CFO CEO

法务

法务 律师 专利

投融资

投资经理 分析师 投资助理 融资 并购 行业研究 投资者关系 资产管理 理财顾问 交易员

风控

风控 资信评估 合规稽查 律师

审计税务

审计 法务 会计 清算

高端职位

投资总监 融资总监 并购总监 风控总监 副总裁