热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

正则表达式——详细讲解平衡组

这篇文章主要介绍了正则表达式——详细讲解平衡组,需要的朋友可以参考下

这篇文章适合你吗?

要读懂这篇文章的精髓,你最好要有一点正则匹配原理的基础。比如".*?"匹配文本内容"asp163",稍懂正则表达式的人都知道可以匹配,但是你知道他的匹配过程吗?如果你不太清楚,那么下面的内容,对你来说可能不太适合,或许,看的太吃力且无法领悟平衡组的用法。因此,我建议你先了解正则表达式NFA引擎的匹配原理。想要整理一份易懂易描述的话,的确要费些时间,但不知道这篇内容会不会达到我预期的效果。慢慢完善吧~(注:这是我2010年写的,现在拿过来,有时间将自己做为读者来看本篇文章,修改有问题的地方,并增加些实例,尽量做到通俗易懂。)

一般正则教程中对平衡组的介绍

如果想要匹配可嵌套的层次性结构的话,就得使用平衡组了。举个例子吧,如何把“xx aa> yy”这样的字符串里,最长的尖括号内的内容捕获出来?

这里需要用到以下的语法构造:
(?) 把捕获的内容命名为group,并压入堆栈
(&#63;<-group>) 从堆栈上弹出最后压入堆栈的名为group的捕获内容,如果堆栈本来为空,则本分组的匹配失败
(&#63;(group)yes|no) 如果堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,否则继续匹配no部分
(&#63;!) 顺序否定环视,由于没有后缀表达式,试图匹配总是失败

如果你不是一个程序员(或者你是一个对堆栈的概念不熟的程序员),你就这样理解上面的三种语法吧:第一个就是在黑板上写一个(或再写一个)"group",第二个就是从黑板上擦掉一个"group",第三个就是看黑板上写的还有没有"group",如果有就继续匹配yes部分,否则就匹配no部分。
我们需要做的是每碰到了左括号,就在黑板上写一个"group",每碰到一个右括号,就擦掉一个,到了最后就看看黑板上还有没有-如果有那就证明左括号比右括号多,那匹配就应该失败(为了能看得更清楚一点,我用了(&#63;'group')的语法):

<#最外层的左括号
 [^<>]*     #最外层的左括号后面的不是括号的内容
 (
  (
   (&#63;'Open'<) #碰到了左括号,在黑板上写一个"Open"
   [^<>>]*   #匹配左括号后面的不是括号的内容
  )+
  (
   (&#63;'-Open'>) #碰到了右括号,擦掉一个"Open"
   [^<>]*   #匹配右括号后面不是括号的内容
  )+
 )*
 (&#63;(Open)(&#63;!))  #在遇到最外层的右括号前面,判断黑板上还有没有没擦掉的"Open";如果有,则匹配失败
>         #最外层的右括号

我为什么写这篇文章

看了上面的介绍,你明白了吗?在我未理解正则表达式匹配原理之前,看上面对于平衡组的介绍,似懂非懂,且只能当做模板记住,而不能灵活运用。因此查阅大量有关正则方面的资料,这里尤其感谢lxcnn的技术文档及《精通正则表达式》这本书,让我对正则表达式有了更深入、更系统的理解,因此,在它们的基础之上,我就结合自己的学习经历做个小结,一来做为学习笔记存档,另外,如果能解决你的疑惑,也是件让人高兴的事。
我先暂不分析上面的代码,先讲解一下关于平衡组相关的概念及知识。
下面表达式匹配测试工具为:Expresso,本站也提供它的完美破解版下载。

平衡组的概念及作用

平衡组,故名思义,平衡即对称,主要是结合几种正则语法规则,提供对配对出现的嵌套结构的匹配。平衡组有狭义与广义两种定义,狭义平衡组指(&#63;Expression) 语法,而广义平衡组并不是固定的语法规则,而是几种语法规则的综合运用,我们平时所说的平衡组通常指的是广义平衡组。本文中如无特殊说明,平衡组这种简写指的是广义平衡组。
平衡组的匹配原理
平衡组的匹配原理可以用堆栈来解释,先举个例子,再根据例子进行解释。

源字符串:a+(b*(c+d))/e+f-(g/(h-i))*j
正则表达式:((&#63;\()|(&#63;<&#8722;Open>)|[^()])*(&#63;(Open)(&#63;!))\)
需求说明:匹配成对出现的()中的内容
输出:(b*(c+d)) 和 (g/(h-i))
我将上面正则表达式代码分行写,并加上注释,这样看起来有层次,而且方便

 \(        #普通字符“(”
  (       #分组构造,用来限定量词“*”修饰范围
   (&#63;\() #命名捕获组,遇到开括弧“Open”计数加1
   |      #分支结构
   (&#63;<-Open>\)) #狭义平衡组,遇到闭括弧“Open”计数减1
   |      #分支结构
   [^()]+    #非括弧的其它任意字符
  )*       #以上子串出现0次或任意多次
  (&#63;(Open)(&#63;!)) #判断是否还有“Open”,有则说明不配对,什么都不匹配
 \)       #普通闭括弧

对于一个嵌套结构而言,开始和结束标记都是确定的,对于本例开始为“(”,结束为“)”,那么接下来就是考察中间的结构,中间的字符可以划分为三类,一类是“(”,一类是“)”,其余的就是除这两个字符以外的任意字符。

那么平衡组的匹配原理就是这样的

1、先找到第一个“(”,作为匹配的开始。即上面的第1行,匹配了:a+(b*(c+d))/e+f-(g/(h-i))*j (红色显示部分)

2、在第1步以后,每匹配到一个“(”,就入栈一个Open捕获组,计数加1

3、在第1步以后,每匹配到一个“)”,就出栈最近入栈的Open捕获组,计数减1

也就是讲,上面的第一行正则“\(”匹配了:a+(b*(c+d))/e+f-(g/(h-i))*j (红色显示部分)
然后,匹配到c前面的“(”,此时,计数加1;继续匹配,匹配到d后面的“)”,计算减1;——注意喽:此时堆栈中的计数是0,正则还是会向前继续匹配的,但是,如果匹配到“)”的话,比如,这个例子中d))(红色显示的括号)——引擎此时将控制权交给(&#63;(Open)(&#63;!)),判断堆栈中是否为0,如果为0,则执行匹配“no”分支,由于这个条件判断结构中没有“no”分支,所以什么都不做,把控制权交给接下来的“\)”
这个正则表达式“\)”可匹配接下来的),即b))(红色显示的括号)

4、后面的 (&#63;(Open)(&#63;!))用来保证堆栈中Open捕获组计数是否为0,也就是“(”和“)”是配对出现的

5、最后的“)”,作为匹配的结束

匹配过程

首先匹配第一个“(”,然后一直匹配,直到出现以下两种情况之一时,把控制权交给(&#63;(Open)(&#63;!)):
a)堆栈中Open计数已为0,此时再遇到“)”
b)匹配到字符串结束符
这时控制权交给(&#63;(Open)(&#63;!)),判断Open是否有匹配,由于此时计数为0,没有匹配,那么就匹配“no”分支,由于这个条件判断结构中没有“no”分支,所以什么都不做,把控制权交给接下来的“\)”
如果上面遇到的是情况a),那么此时“\)”可以匹配接下来的“)”,匹配成功;
如果上面遇到的是情况b),那么此时会进行回溯,直到“\)”匹配成功为止,否则报告整个表达式匹配失败。
由于.NET中的狭义平衡组“(&#63;Expression)”结构,可以动态的对堆栈中捕获组进行计数,匹配到一个开始标记,入栈,计数加1,匹配到一个结束标记,出栈,计数减1,最后再判断堆栈中是否还有Open,有则说明开始和结束标记不配对出现,不匹配,进行回溯或报告匹配失败;如果没有,则说明开始和结束标记配对出现,继续进行后面子表达式的匹配。
需要对“(&#63;!)”进行一下说明,它属于顺序否定环视,完整的语法是“(&#63;!Expression)”。由于这里的“Expression”不存在,表示这里不是一个位置,所以试图尝试匹配总是失败的,作用就是在Open不配对出现时,报告匹配失败。

下面在看个例子:

snhame f

以上为部分的HTML代码.现在我们的问题是要提取出其的标签并将其删除掉,以往我们惯用的方法都是直接去取,像[\s\S]+&#63;\,不过问题出来了,我们提取到的不是我们想要的内容,而是


原因也很简单,它和离他最近的标签匹配上了,不过它不知道这个标签不是它的-_-,是不是就是&#63;符号的原因呢,我们去掉让他无限制贪婪,可这下问题更大了,什么乱七八糟的东东它都匹配到了

snhame

这个结果也不是我们想要的。那么我就用“平衡组”来解决吧。

]*>((&#63;]*>)+|(&#63;<-mm>)|[\s\S])*&#63;(&#63;(mm)(&#63;!))

匹配的结果是

这正是我们想要的
注意,我开始写成这样的方式

]*>((&#63;]*>)+|(&#63;<-mm>)|[\s\S])*(&#63;(mm)(&#63;!)) 

匹配的结果是

一个问题
以下代码只是做为一个问题探讨
文本内容:e+f(-(g/(h-i))*j

正则表达式:

\(
 (
  (&#63;\()
  |
  (&#63;<-mm>\))
  |
  .
 )*&#63;
 (&#63;(mm)(&#63;!))
\)

匹配的结果是:(-(g/(h-i))


推荐阅读
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文介绍了求解gcdexgcd斐蜀定理的迭代法和递归法,并解释了exgcd的概念和应用。exgcd是指对于不完全为0的非负整数a和b,gcd(a,b)表示a和b的最大公约数,必然存在整数对x和y,使得gcd(a,b)=ax+by。此外,本文还给出了相应的代码示例。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 电销机器人作为一种人工智能技术载体,可以帮助企业提升电销效率并节省人工成本。然而,电销机器人市场缺乏统一的市场准入标准,产品品质良莠不齐。创业者在代理或购买电销机器人时应注意谨防用录音冒充真人语音通话以及宣传技术与实际效果不符的情况。选择电销机器人时需要考察公司资质和产品品质,尤其要关注语音识别率。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 如何去除Win7快捷方式的箭头
    本文介绍了如何去除Win7快捷方式的箭头的方法,通过生成一个透明的ico图标并将其命名为Empty.ico,将图标复制到windows目录下,并导入注册表,即可去除箭头。这样做可以改善默认快捷方式的外观,提升桌面整洁度。 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • Java实战之电影在线观看系统的实现
    本文介绍了Java实战之电影在线观看系统的实现过程。首先对项目进行了简述,然后展示了系统的效果图。接着介绍了系统的核心代码,包括后台用户管理控制器、电影管理控制器和前台电影控制器。最后对项目的环境配置和使用的技术进行了说明,包括JSP、Spring、SpringMVC、MyBatis、html、css、JavaScript、JQuery、Ajax、layui和maven等。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
author-avatar
王静芸平桂
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有
     
snhame f
snhame f
snhame f