ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
楼主: liu-aguang

[原创] 正则表达式入门与提高---VBA平台的正则学习参考资料

    [复制链接]

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-6-14 11:03 | 显示全部楼层
本帖已被收录到知识树中,索引项:文本处理和正则
本帖最后由 liu-aguang 于 2014-6-15 08:07 编辑

二、匹配总原则
  说到原理,总让人觉得它该是若干条高度概括的结论.不幸的是对正则原理来说貌似任何概括都对理解和利用没有指导意义.如果一定概括,那么,想到的就两条匹配原则:
  1.    NFA是以正则表达式为主导从文本开始处依次匹配的
  2.    如果有必要,NFA总是穷尽所有途径,找到匹配
  事实上,只要我们把握了各元字符(序列)及组合的匹配过程,也就把握了匹配原理.

三、正则表达式匹配的基本过程
     1.    在正则制导下,引擎从目标文本的开始处,依次进行匹配尝试.
    引擎会从左向右在每一个位置上扫描目标文本,同时检查正则表达式的每一个元素。如果产生一个合法匹配,匹配过程就会停在这个位置.
    例:
   正则表达式:
    cat
   目标文本:
   He captured a catfish for his cat
  过程:
   引擎先比较<<c>>和目标文本位置0上“H”,结果失败了。于是引擎再比较<<c>>和“e”,也失败了。直到第四个字符(位置3)<<c>>匹配了“c”。<<a>>匹配了第五个字符(位置4)。到第六个字符<<t>>没能匹配“p”,失败了。于是引擎重新从位置4开始,即引擎再继续从第五个字符重新检查匹配性。直到第十五个字符开始,<<cat>>匹配上了“catfish”中的“cat.至此,引擎报告成功.引擎停在这个位置.
    这时,如果global属性为False,那么,整个匹配结束.反之,引擎从该结束位置(catfish后的位置上)开始,重复上述过程,继续前行,直至目标文本的最后一个位置.
   结论:正则表达式”cat”,并不是匹配单词cat”.它的本质意义是首先匹配一个字符”c”,紧接着一个字符”a”,再接着一个字符”t”的这样一个字符串.(你能用前面所学知识,只匹配目标文本的最后单词”cat”?)
    2.    引擎依次在目标文本的每一个位置上,尝试整个正则表达式中的所有子表达和组成元素,直到匹配失败,才移动到下一个位置.
   在目标文本中的每个字符位置会首先匹配该正则表达式的所有可能排列,如果都不能匹配,然后才会到下一个字符位置进行匹配尝试.
   例:
   正则表达式:
    fat|cat|belly|your
   目标文本:
    The drgging belly in decates that your cat is too fat
   如果global属性False,最后结果是:belly.
   为True,结果是:belly cat your cat fat
   从这个结果中看到了吗?匹配结果单词出现的顺序并不是正则表达式的分支排列顺序,而是目标文本中出现的顺序.
   分析过程:”The drgging “之前的每个位置上,匹配都是失败的.这时,引擎移动到下一位置,即目标文件”belly””b”前面,而该位置上<<f>>不能匹配b,结果失败,这时用下一个子表式的开始字母<<c>>”b”匹配,还是失败;当用再下一子表达式(belly)的开始字母”b”尝试时,匹配成功,接着测试正则后面的字符,最终在该位置上得到成功匹配”belly”,引擎在该位置上停止,它不会在此位置再去尝试下一个子表式.所以,我们得到了第一个结果是”belly”.
     例:正则表达式
    jane|janet 或 janet|jane
   分别匹配目标文本:  janet and jane
   朋友们可以自己测试两种匹配结果.并分析原因.
    结论:
       1. 正则匹配总是优先选择目标文本中最左端的匹配结果.
       2.如果在目标文本的同一位置存在两个选择分支都可匹配的时候,分支的排列顺序将影响匹配结果.你可增加单词边界或改变排列顺序来达成自己的目标.
       3.将目标文本中最可能会被找到的字符放在前面,会在性能方面有较小的提高.

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-6-14 17:17 | 显示全部楼层
本帖最后由 liu-aguang 于 2014-6-15 09:05 编辑

    3.    匹配优先量词总是匹配尽可多的字符
   
   匹配优先量词:
    ?       *    +     {n}    {n,m}
    它们存在匹配上限和下限,总是匹配尽可多的字符,直到匹配上限或没有符合要求的字符为止.
   例: 正则表达式:
       .?
  目标文本:
      Abc
   选项:globalfalse
   匹配结果是: A
   分析: ?表示匹配0个或1个除换行符的任意字符,由于它是匹配优先,所以,选择了匹配1个字符,而没有选择匹配0.
   例:正则表达式
   \d+
   目标文本:
    March 1998
   匹配结果:1998
    分析:在位置6上成功匹配字符”1”,”+”表示可以无限地继续匹配下去,所以直到字符”8”后面位置上匹配失败. ”+”的意思是只要能匹配上一个字符就算匹配成功,所以引擎最后报告匹配成功,得到上面的结果.
    例:正则表达式
    ^Subject:(.*)
   目标文本:
    Subject:
   匹配结果:Subject:
    $1变量值为空.
   分析:目标文本的开始位置直到”:”号,引擎只进行简单的逐一字符比较,每个位置上都是匹配成功的. 但正则表达式还有”.*”部分(:括号不会改变匹配过程),而目标文本已经没有字符可匹配了,”*”表示即使匹配到0个字符(即没有得到匹配)也是成功的.所以,引擎报告匹配成功,得到如上的结果.(如果正则表达式中用?代替*,是一样的结果;但如果用”+”代替”*”,则整个匹配过程失败,无匹配结果,你能分析原因吗?)
    再给一个例了:
    正则表达式:
     ^.*(\d+)
   目标文本:
    Copyright 2014
   你能推测最后括号捕获到的结果吗?也就是$1的值是什么呢?
    它不是我们想像的”2014”,而是只得到一个字符”4”.为什么是这个结果呢?
   分析: “^.*” ”*”号的贪婪性(人性?)会将目标文本的所有字符匹配,直至结尾,引擎检查”\d+”,结果没有字符可匹配了.引擎这时是否就报告整个正则表达式匹配失败呢?记得上面讲到一个匹配总原则:”引擎总是穷尽所有途径,找到匹配”. 根据这个原则,正则中,每个部分的匹配只要不越过它们的底线”,就尽可能让整个匹配成功.所以”.*”首先交还一个字符”4”,”\d+”匹配,由于”\d+”的底线是匹配一个字符就可成功,而对”.*”来说少一个字符没有导致自己匹配失败.引擎这时发现每部分都匹配成功了,赶快报告匹配结果吧.于是就有了上面的结果.
    如果这个例子中正则表达式是
     ^.*(\d{4})
    ^.*(.*)
   情况结果又如何呢?
   结论:最后的几个例子告诉我们:慎用表达式”.*”或”.+”,在编写正则表达式时,如果可能尽量用较具体的表达式代替".";如:上例,假如我们希望的结果是提取"2014",那么,可用\D+代替".*".
   
     4 .   忽略优先量词总是匹配尽可能少的字符
    忽略优先量词:
    ??  *? +?  {n}?  {n,m}?
   它们忠实贯彻总设计师的战略决策:”摸着石头过河!”.在匹配的过程中,它们总是自己先得到最少匹配,观察一下,正则表达式中,紧跟它后面的元素或子表达式,如果后面子表达式不需要(不能匹配),自己才继续匹配下一个字符.
    例:正则表达式:
      <.*?>
    目标文本:
      <em>aaa</em>
    匹配结果:<em>
     分析: 在开始位置,”<”匹配成功,接下来”.*?”部分以最少匹配0个字符(即不匹配),宣布匹配成功;让它后面紧跟的”>”部分尝试匹配目标文本中的”e”,显然”>”匹配失败,于是,”.*?”选择匹配一个字符,从而成功匹配"e";引擎移到下一位置,先让”>”又尝试,还是失败,于是”.*?”再匹配”m”,匹配成功;移到下一位置,”>”匹配目标文本在该位置上”>”,匹配成功.到此,正则表达式中的所有子表达式都得到了成功匹配,所以,引擎宣布整个匹配结束.结果<em>.
     当然,如果global属性设置为True,那么引擎将一如既往进行下去.从而,又得到一个结果</em>
    思考:用正则表达式
   .*?
   匹配文本:12d
   匹配结果是什么?
   如果用正则表达式
   .*?d
   匹配结果又是什么?
   结论:  对忽略优先量词来说,只有因为它们匹配得太少,导致整个匹配失败的时候,它们才会匹配更多的字符.这个结论的意义是:在编制正则表达式时,注意避免各子表式都是可选项的情形.

评分

1

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-6-14 18:07 | 显示全部楼层
本帖最后由 liu-aguang 于 2014-6-15 14:12 编辑

   四、穷尽所有可能途径找到匹配---回溯
     实际上NFA能够找到所有可能的匹配,要归功于正则的一个最重要原理----回溯
     如果正则表达式中,存在量词(无论匹配优先还是忽略优先)或多选结构,那么可能会产生回溯”. 我们再重新审视引擎遭遇它们时的状况:
      (1)多选结构的回溯
    用cat|car匹配文本car and cat
    根据前面知识分析,它将首先匹配到文本开始处的car, 前面说了原因:引擎会在每个位置上尝试正则表达式的所有分支,那么它是怎样来实现这一点的呢?即真正的表因是什么?
   我们再来分析它的匹配过程: 当引擎处于文本开始这个位置时(其它位置也一样),它面临两种选择:一是用cat去尝试,二是用car去尝试.我们知道多选结构是首先用前面的cat去尝试,.如果用cat尝试成功了,那么引擎宣布匹配结束;这个例子的实际匹配情况怎样的呢?在开始位置0”c”匹配成功,由于子表达式还没有匹配完,所以引擎移动到下一位置1,也匹配成功,继续移动到位置2,”t”不能匹配”r”,匹配失败.于是第一个分支在文本的位置0处匹配失败.
    到这里我们知道引擎将从位置2回退到位置0处尝试下一个分支car.事实上,要做到这一点,引擎必须在尝试第一个分支cat之前,记住还有一个子表达式可以备用. 也就是说:在位置0处引擎遭遇多选结构时,它将在该处存储所有的可能途径,称为备用状态”,以准备当前一个子表达式匹配失败,而能尝试下一个子表达式, 并能够确定回退的位置;在这同时正则表达式中也保存了一个备用状态”,即第一个子表达式尝试失败后,该从正则表达式的哪个位置开始进行第二轮尝试.
     我们把上面多选结构第一次尝试失败后,回退的过程叫回溯”.
      (2)量词”?”的回溯
    例: 用正则表达式
      ab?c
    分别匹配目标文本
      abc ac
    首先看匹配ac的情况:ac位置0处匹配成功,引擎移动到位置1,在这里,正则子表达式”b?”有两种选择,一是匹配0b(即不匹配b),二是尝试一次. 所以,引擎将分别在文本和正则的位置1处留下备用状态”;”?”是优先匹配的,所以它将首先尝试匹配一次b,结果正则中的b不能匹配文本中的c, 于是引擎回溯尝试第二个选择,即不匹配b;回溯的位置是备用状态中存储的位置1. 显然不匹配始终是成功的.于是在位置1,用正则中的下一个元素”c”来尝试,结果匹配成功.引擎停止,整个匹配报告成功.
     再看匹配abc的情况.前面的情况与上面一样的,直到首先尝试匹配一次b,结果匹配成功;引擎后移,匹配字符”c”,匹配成功.由于正则表达式的所有元素都得到成功匹配,这时将抛弃备用状态,引擎报告整个匹配成功.
      在这个例中,只有存储备用状态过程,没有回溯过程;所以,“回溯是当有多个选择,并且前面选择导致整体匹配失败时发生的.
    思考:
     1.如果改用忽略优先量词,即用正则表达式
     ab??c
    分别匹配abc ac,情况是怎样的?请自己分析.
     2.如果目标文本是abx,请分析匹配过程.

  (3)量词”*”的回溯
   下面图片展示正则表达式
   “.*”
   匹配目标文本:
     ”ab””c”
捕获.PNG
  请分析用上面的正则表达式匹配文本: ”a”bcdefghijk
  从这个例子中你得到什么启示?你能调较这个正则表达式,提高它的匹配效率吗?
  思考:
  如果改用忽略优先量词的正则表达式
  ”.*?”
  匹配文本”ab””cd” ,结果是什么?请自己分析.

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-6-14 18:14 | 显示全部楼层
本帖最后由 liu-aguang 于 2014-6-15 13:54 编辑

    五.    回溯的总结
    在匹配的过程中NFA引擎会依次处理正则各个子表达式或组成元素.当遇到量词或多选结构时,它将面临两种或多种尝试选择.这时它会存储备用状态.即选择其一,同时记住其它可能的路径.
    不论选择那一种途径,如果它能匹配成功,而且正则表达式的余下部分也成功了,整个匹配即告完成.如果正则表达式余下的部分匹配失败,引擎就会回溯到备用状态所指示的位置,选择其他的备用分支继续尝试.这样引擎最终可能尝试表达式所有可能的途径,直到余下部分匹配成功,或所有途径尝试完毕都失败,而停下来.
    回溯的几个要点:
    (1)  如果需要在进行尝试跳过尝试之间选择,对于匹配优先量词,引擎会优先选择进行尝试”,而对于忽略优先量词”,会选择跳过尝试”.
    (2)  对于多选结构,引擎从正则表达式的开始依次尝试各子表达.
    (3)  如果正则表达式中有多个量词或多选结构,就意谓着有多个备用状态”.至于引擎回溯时使用哪个备用状态”,原则是:距离当前最近存储的选项就是当本地失败时强制回溯时返回的.
    (4)  使用匹配优先还是忽略优先量词,有时会得到不同的匹配结果.如分别用<.*><.*?> 去匹配文本<em>aaa</em>. 但如果只有一条可能的路径,那么无论使用匹配优先还是忽略量词,其最终匹配结果都一样,不一样的只是引擎在达到最终匹配之前需要尝试的次数.:分别用<.*><.*?>去匹配文本<abcd>.
   (5)  在环视的内部,其子表达式也有可能包含量词或多选结构,它们的回溯原理与上面所讨论的完全一样.但要提醒一点的是当引擎退出环视时,它将抛弃内部的所有备用状态”.

评分

1

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-6-15 13:49 | 显示全部楼层
本帖最后由 liu-aguang 于 2014-6-15 22:22 编辑

    六.    回溯与效率
    NFA中的回溯是一个好东西,可以为正则表达式带来更多的功能,它也是很多正则引擎选择NFA的原因。但从上面的介绍中,我们也明显感觉到,回溯也可能带来效率的问题。所以,在我们编制正则表达式时,如果涉及量词或多选结构,就必须关注正则表达式的形式,尽可能减少回溯次数。
   例:下面三个正则表达式在效果上是等价的
    Jeffrey|Jeffery
    Jeff(rey|ery)
    Jeff(re|er)y
   这三个表达式在某些情况下,效率是不一样的.其中效率最高的是第三个表达式. 为了说明这个问题,我们假设用它们来匹配下列目标文本:
    Jeffery and Jeffrey
    分析:用第一个表达式匹配,在位置0处留下备用状态”,它的第一个分支在位置4”r”不能匹配文本中的”e”, 于是回溯选择第二个分支重新从位置0处开始尝试,最后匹配成功.找到文本的成功匹配: Jeffery
    如果用第三个表达式匹配,当匹配到位置4,留下备用状态”. 用第一分支”er”尝试失败,这时回溯,用第二分支”er”尝试匹配成功.可以看出它的回溯与第一个表达式不同:不需要重新回退到位置0. 从而减少了找到匹配的尝试次数.
   在使用量词时,也要关注回溯导致的效率问题,如果可能尽量用替代方法.
   前面多次遇到的一个例子:
   提取目标文本中引号的内容:
      本帖正则表达式入门与提高对你有用吗?
   下面三个正则表达式都能实现目标:
       “”.*””
       “”.*?””
       “”[^””]+””
    分析:
   第一个表达式是通过回溯来实现目标的.即需要回退6.
   第二个表达式在匹配引号内的字符时,每个位置都需要回溯一次,所以,共有10次回溯.
   比较第一/二个表达式的效率高低,与具体文本中,引号内字符数与右引号后字符数多少有关.
   而第三个表达式,只有一次回溯,它发生在字符匹配右双引号失败时,回退一步.
   
   七.灾难回溯
   有时不小心使用量词,则可能带来灾难性的后果.
    现在我们用下面正则表达式
    \d+
   匹配目标文本
    123456X
   显然,引擎最后将报告整个匹配失败.那么 正则表达式必须计算多少个路径才能得出此结论呢?
   它会在此字符串开头处开始计算,发现字符 1 是一个有效的数字字符,与此正则表达式匹配。然后它会移动到字符 2,该字符也匹配。因此,在此时,此正则表达式与字符串 12 匹配。接下来,它会尝试 3(匹配 123),依次类推,直到到达 X,该字符不匹配。
    但是,由于我们的引擎是回溯 NFA 引擎,它不会在此点上停止。而是从其当前的匹配 (123456) 返回到其上一个已知的匹配 (12345),然后从那里再次尝试匹配。由于 5 后面的下一个字符不是此字符串的结尾,因此,此正则表达式不是匹配项,它会返回到其上一个已知的匹配 (1234),然后再次尝试匹配。按这种方式进行所有匹配,直到此引擎返回到其第一个匹配 (1),发现 1 后面的字符不是此字符串的结尾。此时,正则表达式停止,没有找到任何匹配。
    总的说来,此引擎计算了六个路径:12345612345123412312 1。如果此输入字符串再增加一个字符,则引擎会多计算一个路径。因此,此正则表达式相对于字符串长度的是线性算法,正则对象计算速度非常快,足以迅速拆分计算大量字符串(超过 10,000 个字符)。
    现在我们把正则表达式修改为:
     (\d+)
    前面说过,括号并不会改变引擎匹配的路径,无非增加了一个处理括号的步骤.
    但如果在括号后面增加一个”+”,:
      (\d+)+
    情况会怎样呢?
    分组表达式 (\d+) 后面额外的 + 字符表明此正则表达式引擎可匹配任何数量的捕获组。此引擎按以前的方式进行计算,在到达123456 之后回溯到 12345
    这就是关键所在。此引擎不仅会检查到 5 后面的下一个字符不是此字符串的结尾,而且还会将下一个字符 6 作为新的捕获组,并从那里开始重新检查。一旦此途径失败,它会返回到 1234,将 56 作为单独的捕获组,然后将5 6 分别作为单独的捕获组。最终结果是该引擎实际上完成了 32 个不同路径的计算。
     现在,我们只要向此计算字符串再增加一个数字字符,该引擎将必须计算 64 个路径(翻了一倍)才能确定它不是匹配项。这会使正则表达式引擎执行的工作量呈指数增加.
     你可以用30位数字在自己的电脑上测试. 需要强制引擎处理数亿个路径.如果电脑性能不好怕要处理好几天吧.
     这就是传说中的灾难回溯”! 也许没有人会写出这样的正则表达式,但其形式在构建复制正则表达式中也可能不经意地出现.这种形式就是在量词上再叠加量词.
     有种网络攻击称之为”ReDoS”,就是利用这个原理瘫痪对方电脑. 现在有些语言平台对正则引擎作了优化,可以回避这个问题.VBsrcipt中正则引擎没有.


补充内容 (2016-6-8 08:30):
"灾难回溯"部分的正则书写有误, \d+修改为^\d+$ 其它地方的正则也按此方式修改一下.  

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-6-16 10:50 | 显示全部楼层
本帖最后由 liu-aguang 于 2014-6-17 07:55 编辑

第四篇   技巧篇
     文本处理归根到底是对特定串的捕捉(查找),所以我们讨论的所谓技巧聚焦于对特定字符串的描述.而现实中待处理的字符串在目标文本中的存在状态形形色色,与大多数文章以文本类型归类不同,本篇根据字符串结构特征分类进行讨论,试图从正则特性角度帮助初学者提高综合应用正则符号的能力.文中例举的是常见任务类型或任务的一部分,我们精选了一些较短小的,”天才般构建的正则表达式进行讨论,相信通过你的仔细解读,一定会心有所悟,提升编写正则表达式的思维境界.也欢迎朋友们跟贴,把你所见所写的自认为有新意的正则表达式展示出来,供大家学习讨论.本篇的正则表达式有的来自世界级专家,也有的来自网络网友,在此一并致谢!
   
一.  匹配具有多种形态结构的字符串

    在现实的世界中,有些数据类型具有多种呈现形式,HTML标签,它可能有属性也可能没有,在一些位置上可能有若干空格也可能没有等等.     
    下面来讨论几个实例:
    1.  匹配下列文本中的<HR>标签,它可能呈现的形式例举如下:
<HR>
<HR  >
<HR size = 14 >
    正则表达式:
<HR(\s+size\s*=\s*\d+)?\s*>
    字符串中,”<HR””>”是标签的标志,是肯定出现的,所以用字面字符表示; 它的size属性可能出现也可能不出现,可用”?”描述,属性是一个相对独立的整体所以用括号包围,(…)?表示要么整体出现,要么都不出现;标签与属性之间至少必须有一个空格,\+表示;而其它有些地方可以没有空格也可以有多空格用\s*表示.
    2.  匹配浮点数,它可能有下列几种呈现形式:
-56
0
+0.14
35.699
.123
    正则表达式:
[+-]?\d+(\.\d+)?|\.\d+
    +-可用字符组[+-];它们可有可无可用[+-]?;
    整数部分部分可用\d+
    小数部分可用\.\d+ ,小数部分可有可无,可用可选项元字符?,由于小数部分如果出现则小数点与后面的数据将同时出现,反之,同时不出现,所以用分组括号包围让?号整体作用即:(?:\.\d+)?
    于是整合起来就是[+-]?\d+(?:\.\d+)?
    整数部分也不是必须出现的,即实际浮点数表示法,可以存在只是小数部分也合法.能否用[+-]?\d*\(\.\d+)?表示各种情形呢?这是不行的.因为在这个表达式中所有子表达式都可以不出现,就意味着什么都不匹配也匹配成功.也意味着什么都可以匹配.
    它的解决办法是用选择符把它们作为两部分分别匹配两种可能的情况.
[+-]?\d+(\.\d+)?|\.\d+
    在这里选择分支的前后顺序很重要,不能交换,你知道为什么吗?如果不明白建议回头再研究一下原理.
     3.     匹配某范围内的数据
    有时候我们需要匹配一个数值范围,如一天内的小时数,一月()内的天数,等等.遗憾的是正则表达式的元字符(序列)里没有专门用来表示数据范围的符号.
    在正则中想要匹配包含多于一个数字的整数,就必须罗列出所有的数字组合.先看几个例子:
1-12
    正则表达式:
^(1[0-2]|[1-9])$
    1-22范围内有,1, 2, 3, ...12共有12个数字,事实上我们可以用多选结构把它们连接起来:1|2|...|12, 显然这不是我们需要的方案.
    我们把1-12分为两部分:1-910-12. 第一部分可用字符组[1-9];第二部分可用表达式1[0-2],然后用多选结构组合起来,即实现了表示1-12范围内的数值.这时也要注意子表达式在多选结构中的顺序.
1-31
    正则表达式:
^3[01]|[12][0-9]|[1-9]$
    同样的思路把数值范围分为了三个部分,然后用多选结构组合起来,朋友们可自己去分析.
0-100
^100|[1-9][0-9]?$
    在这里巧妙地用了一个可选项元素?,令第二个子表达式即可表示两位数字的数值,也可以表示只有一位数字的数值.这个表达式也可以写为
^100|[1-9][0-9]|[0-9]
    从前面原理部分知道,多选结构采用的是回溯算法,所以这个表达式的效率不如用可选项元素?的效率高.
0-255
    正则表达式:
^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d$
    这是一个IP4地址的各组数据允许的范围.我们把它分为四个独立的部分:第一25[0-5]表示250-255;第二2[0-4]\d表示200-249;第三1\d{2}表示100-199;第四[1-9]?\d表示0-910-99
    讨论:
     (1)  表示多个字符之一时用字符组,表示多个字符串之一时用多选结构,表示可出现可不出现用可选项元素,表示可出现多次时用”+””*”. 所以在表示具有多种可能呈现状态的复杂字符串时,一般方法是把它们分组,然后选择字符组和量词连结起来;事实上,当在现实的世界中有较多的问题都可以把它转化为这种匹配类型.
     (2)  一个正则表达式中,是不允许所有子表达式都是可选的情况.解决的方法是用多选结构把它们分为独立的多项.
     (3)  使用正则表达式来匹配整数区间的所有技巧:你只需要对区间进行简单拆分,直到拆分之后的所有区间都只包含固定个数的彼此无关的数字为止.这个拆分思维很重要,在以后的技巧中我们还会遇到.


补充内容 (2014-6-25 19:27):
在叙述"匹配浮点数"时,原文中,关于选择分支顺序的表述有问题.就匹配正确性来说,这个例子的选择分支前后顺序无关.但根据上下文环境,前后顺序可能会影响匹配效率.

补充内容 (2016-6-8 08:44):
0-100的正则就为^100|[1-9]?[0-9]$

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-6-16 16:50 | 显示全部楼层
本帖最后由 liu-aguang 于 2014-6-17 08:10 编辑

二、匹配特定位置上的字符串
      
      前几天看到一个正则求助的帖子,希望从一段包含汉字及数字的文本中提取所需数字数据.稍微麻烦的是文本中除了有要提取的数字数据外还有不需要提取的数字数据.很多坛友给出了正则表达式解法,但该求助者不满意,认为如果数字数据形式和在文本中位置改变了就不行了,于是坛友不断根据他的新要求修正正则表达式;有的回帖干脆直接用正则删除掉不要的数据与汉字,只留下需要的数据;事实上,坛友们的回帖解法方向都是正确的.结果求助者最后回帖十分遗憾地感叹到:”所有的回答都不尽我意,我是希望用正则表达式直接匹配要提取的数据!”
     为什么他有这个想法呢?是因为他可能认为(也是较多初学者认为的)既然正则符号可以描述所有字符,那么一定可以描述要提取的数字数据(毕竟世界上没有一片相同树叶嘛).这种想法的误区是自觉不自觉地把正则符号孤立起来.
     事实上,对于那些在特定文本中自己具有唯一数据结构特征的字符串,是可以通过直接描述它们,并加以处理的.但是如果要提取的字符串的结构特征在文本中不是唯一的,就必须以正则的思维将它们与之区别开来.而在正则中只能辅之以特定位置特征将提取字符串锁定.例如: ^\s+可锁定行首空格;\s+$可以锁定行尾空格等.
     字符串在文本中的位置,可以用锚点^$,也可以用环视或者直接用与之相邻的其它特殊字符().
     例1:从一段文本里的URL中抽取通信协议方案.
     正则表达式
     [a-z][a-z0-9+\-.]*(?=://)
      URL ”://” 部分的前面是通信方案,:http之类的.在这里用环视(?=://)锁定的特定位置上的字符串,即提取的字符串通信方案”,它的位置特征是后面紧跟特定字符串”://”.
前面的两个字符组共同表达了通信方案的数据结构特征:1个位置上必须是一个英文字母;字母后面可以是第二个字符组中列出的字符的任意组合.
     例2:在一大段文本中查找独立存在的任意十进制正整数.
     正则表达式:
       (^|\s)\d+(?=$|\s)
     表示正整数很简单,\d+ .为了能在文本中把它与其它可能存在的正整数区分开来,按题目要求,在表达式的前面增加(^|\s),后面增加(?=$|\s),它们分别描述了要提取的正整数的位置特征: 即这个数的前面必须紧挨着行开始或者是一个不可见字符;后面必须紧跟着行尾或一个不可见字符.
     例3:查找后面不跟着某个特定单词(cat)的任意单词
     正则表达式:
     \b\w+\b(?!\W+cat\b)
     单词可以用\w+表示,在一段文本中要用单词分界行描述单词存在形态. 后面否定顺序环视(?!\W+cat\b)表示如果紧跟着一个或一个以上非单词字符(如空格) 并且后面还有一个单词cat,那么否定环视将报告匹配失败,于是整个匹配失败,否则报告匹配成功.即后面不存在单词cat,则整个匹配成功.
     使用表位置的正则符号,不仅常用于锁定特定字符串,而且有时可提高匹配效率. :假如你要在一个有若干行的文本中,搜索行首的字符串”ABCD”, 那么最好加上脱字符”^”, ^ABCD. 这样对于有的引擎来说,它只会在行首查找,如果在行首搜索失败,则引擎马上报告失败,它不会继续在文本行的其它位置搜索.
     即使对没有作此优化的引擎,当在行首搜索失败进,它也只会在其它位置上开始搜索此位置是否开始位置,不是则马上放弃,移至下一位置.
     如果没有在前面加上脱字符”^”,那么当在开始处搜索失败,引擎必须在其它每一个位置上搜索. 当遇到有连续出现的AABABC等字符串,那么它会按部就班地尝试下去,直至失败.这样显然增加了报告失败结论的匹配尝试次数.

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-6-16 18:49 | 显示全部楼层
本帖最后由 liu-aguang 于 2014-6-18 07:42 编辑

三、匹配其内部由相似结构字符串构成的字符串
     例1:匹配一个IPv4地址
     正则表达式:
      25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d(?:\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d){3}
     看一个具体的IPv4地址: 61.135.136.142 我们知道IPv4地址有如下规范:
      (1)  有四组阿拉伯数字组成的数据;
      (2)  数据组之间用英文点号分隔;
      (3)  每组数据范围是:0-255
    数值范围0-255,在上面已经遇见过,我们可以用25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d表示;后面三组数据其结构相似:都是由英文句点与数据组成.所以,我们用括号包围起来,然后跟上一个表范围的量词{3},它表示出现并且只出现三次括号内的字符串.
    这时用了非捕获性括号(?:…),只起分组作用.事实上,凡是不需要存储的分组都应该有非捕获性括号.本篇中,有些地方我们用了捕获性括号,是为了让代码清晰一点,在实际应用中可修改.

    例2:验证一个URL地址
    严格按URL地址规范编制的正则表达式较复杂,下面是一个具有给出条件的正则表达式:必须包含一个域名,不允许用户名或口令
     (https?|ftp)://[a-z0-9-]+(\.[a-z0-9-]+)+([/?].+)?
    我们先来看一个具体的IPv4地址:(它就是本页面的URL地址)
    它可分为三个部分:
     (1)   通信协议.具体地址中是http:// .正则表达式中,(https?|ftp)://表示.它可以匹配三种通信协议之一: httphttpsftp . 它是http|https|ftp更聪明的写法.后面的://是字面字符匹配也可作为识别IP地址的标志之一.
      (2)   域. 具体地址中是club.excelhome.net .正则表达式中,[a-z0-9-]+(\.[a-z0-9-]+)+表示 . 第一个字符组表示域中第一个项允许出现的字母,量词”+”作用于字符组表示可以连续出现多次,但至少出现一次,在这里是不能用量词”*”.注意后面括号内表示的一个具有特定结构的字符串,该字符串的结构特点是英文句点后跟着多个连续的英文字符或短线.我们用量词”+”作用于这个括号内的字符串,表示可以连续出现一次或多次.
     (3)   最后是URL的路径或参数.正则中用 [/?].+ 表示. 这部分前面用了一个字符组规定紧跟后的必须是正斜线”/”或问号”?”之一; ”.+”表示抓取直到换行符之前的一切字符.正则表达式中,用量词”?”作用于 [/?].+ 部分,表示整个正则表达式也可以匹配没有参数或路径的URL.

    例3:验证一个电子邮箱
   下面给出一个最严格的电子邮箱正则表达式:
     ^[\w!#$%&’*+/=?{|}~^-]+(?:\.[!#$%&”*+/=?{|}~^-]+)*@(?:[a-z0-9-]+\.)+[a-z]{2,6}$
    它看起来够复杂的,但其实整个结构很简单.共分为五个部分:
    第一部分: 也就是第一个字符组[….]+ 表示邮箱开始允许的字符及组合, 注意并没有包含句点;
    第二部分: 是非捕获性括号内的内容,其结构为:(\.[…]+)*. 括号的作用量词是”*”,表示可以不出现的.为什么要这部分呢?是因为英文名句点是可以作为邮箱用户名中字符的,但它不能在用户名开头或结尾,也不能出现连续的两个或两个以上句点. 我们通过\.[...]+形式做到了这一点,即英文句点前或后必须至少一个其它字符,通过第一/二部分来表示邮箱用户名,可以严格验证用户名输入是否规范.
    第三部分:”@”,这是邮箱的标志符了.
    第四部分:”@”符号后括号内容.它表示子邮箱组织名,其结构为 […]\. 量词”+”作用于分组,表示至少出现一次;
    第五部分:[a-z]{2-6} . 是邮箱中的顶级域名.该表达式表示了它必须是由2-6个字母组成的字符.比如com
    讨论:
    我们看到,有些字符串其组成的内部,有相似结构,相似结构块的个数有可能是固定的如IP4地址,更多的是结构块的数目不定, 如URL的域部分和电子邮箱的组织名.这时我们可以用分组结合量词来表达它们.
    要指出的是,正则表达式编写的严格程度,总是与上下文环境联系在一起的,在实际应用中,应因地制宜. 比如明明是去匹配一个规范的电子邮箱,你就没有必要用上面的表达式了,而直接用^\S+@\S+$,也许更加快捷.

评分

1

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2014-6-16 20:30 | 显示全部楼层
本帖最后由 liu-aguang 于 2014-6-18 12:21 编辑

    四、匹配一段文本,这段文本中不能包含特定字符串

     我们曾遇到过匹配一对尖括号之间的字符串的例子.可以用<[^>]*>来表达.但如果这一对特定的字符串不是一个字符而是多个字符组成的呢?肯定是不能用否定字符组了,因为字符组内没有词组概念,只表达单个字符之一.这时我们可环视来解决这一问题.看下面的例子.
     例1 有一目标文本,它也许是一大段文本中的一部分:
       ….cccc<B>aaa/bbb</B>dddd…..
     要求: 提取标签<B>及标签内的内容.即要求结果为:<B>aaa/bbb</B>
     正则表达式:
       <B>((?!</B>).)*</B>
     引擎找到连续字符串<B>,将在其后的每个位置上尝试匹配否定顺序环视:(?!</B>), 它的意义是如果当前位置的后面没有连续字符串</B>,那么匹配成功,并接着继续扫描正则的下一部分即”.”; 英文句点可以匹配除换行符处的所有字符.我们把它们用括号包围后让量词”*”作用,它的意思就是可以无限次重复((?!</B>).)部分,直到它匹配失败.在上面目标文本中,显然”aaa/bbb”中每个字符之前位置上(?!</B>)都是成功的.所以每个字符都会得到成功匹配.
     而当引擎移到</B>”<”之前位置时,情况发生了变化:因为在该位置上环视的子表达式得到了成功匹配,而否定环视就会让引擎报告匹配失败.但这并不意味((?!</B>).)*部分匹配失败,因为量词”*”表达的是即使括号()内匹配到0个字符(即不匹配),整个子表达式也算匹配成功.于是引擎继续依次扫描正则的最后部分</B>,虽然目标文本中的</B>已经被(?!</B>)测试过,但它是不会消耗它们的,所以正则中的最后部分也得到成功匹配.这时引擎发现正则表达式中已经没有元素了,并且各子表达式或元素都匹配成功,于是报告整体匹配成功.
    假如标签</B>的结束标签</B>不在同一行中怎么办呢? 元字符”.”是不能匹配换行符的.这时我们可以把”.”换为[\s\S].
      <B>((?!</B>)[\s\S])*</B>
    字符组[\s\S]可以匹配任意字符之一. 同样也可以用[\w\W][\d\D],它们都是等价的.这没有什么好解读的,它是常用技巧之一. 可以理解为不可见字符与它的补集就构成了全部.

    例2: 查找不包含另一单词(如cat)的任意单词
     \b(?:(?!cat)\W)+\b
    它的原理与例1是一样的.单词分界符”\b”,限定了连续字符必须在两个字边界之内;这时用了\W,即非英文单词字符代替”.”,目的是明显. 如果用”.”[\s\S],那么它将匹配到文本中的所有字符.用了非捕获性括号是不想把匹配的单词放入特殊变量$.因为匹配集合中就是所需要的单词,不必须用Submatches方法提取了.
      
     例3 匹配不包含某个单词(如Cat)的整行
     正则表达式
      ^(?:(?!\bcat\b).)*$
      注意例2,例3中单词边界符的应用差别.
    讨论:
     用上面的方法,其匹配效率是很低的.因为它将在每个位置上尝试(?!</B>)或(?!cat).所以,如果可能,比如例2,3,我们可以用VBAsplit方法生成单词数组,然后逐一检查.

评分

1

查看全部评分

TA的精华主题

TA的得分主题

发表于 2014-6-17 08:56 | 显示全部楼层
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-12-25 15:27 , Processed in 0.056498 second(s), 6 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表