`
ksdq75ksdq
  • 浏览: 12139 次
最近访客 更多访客>>
社区版块
存档分类
最新评论

正则基础之

 
阅读更多

  捕获组捕获到的内容,不仅可以在正则表达式外部通过程序进行引用,也可以在正则表达式内部进行引用,这种引用方式就是反向引用。要了解反向引用,首先要了解捕获组,关于捕获组,参考正则基础之--捕获组(capture group)。
  反向引用的作用通常是用来查找或限定重复、查找或限定指定标识配对出现等等。
  对于普通捕获组和命名捕获组的引用,语法如下:
  普通捕获组反向引用:\k,通常简写为\number
  命名捕获组反向引用:\k或者\k'name'
  普通捕获组反向引用中number是十进制的数字,即捕获组的编号;命名捕获组反向引用中的name为命名捕获组的组名。 捕获组(Expression)在匹配成功时,会将子表达式匹配到的内容,保存到内存中一个以数字编号的组里,可以简单的认为是对一个局部变量进行了赋值,这时就可以通过反向引用方式,引用这个局部变量的值。一个捕获组(Expression)在匹配成功之前,它的内容可以是不确定的,一旦匹配成功,它的内容就确定了,反向引用的内容也就是确定的了。
  反向引用必然要与捕获组一同使用的,如果没有捕获组,而使用了反向引用的语法,不同语言的处理方式不一致,有的语言会抛异常,有的语言会当作普通的转义处理。 源字符串:abcdebbcde
  正则表达式:([ab])\1
  对于正则表达式"([ab])\1",捕获组中的子表达式"[ab]"虽然可以匹配"a"或者"b",但是捕获组一旦匹配成功,反向引用的内容也就确定了。如果捕获组匹配到"a",那么反向引用也就只能匹配"a",同理,如果捕获组匹配到的是"b",那么反向引用也就只能匹配"b"。由于后面反向引用"\1"的限制,要求必须是两个相同的字符,在这里也就是"aa"或者"bb"才能匹配成功。
  考察一下这个正则表达式的匹配过程,在位置0处,由"([ab])"匹配"a"成功,将捕获的内容保存在编号为1的组中,然后把控制权交给"\1",由于此时捕获组已记录了捕获内容为"a","\1"也就确定只有匹配到"a"才能匹配成功,这里显然不满足,"\1"匹配失败,由于没有可供回溯的状态,整个表达式在位置0处匹配失败。
  正则引擎向前传动,在位置5之前,"([ab])"一直匹配失败。传动到位置5处时,,"([ab])"匹配到"b",匹配成功,将捕获的内容保存在编号为1的组中,然后把控制权交给"\1",由于此时捕获组已记录了捕获内容为"b","\1"也就确定只有匹配到"b"才能匹配成功,满足条件,"\1"匹配成功,整个表达式匹配成功,匹配结果为"bb",匹配开始位置为5,结束位置为7。
  扩展一下,正则表达式"([a-z])\1{2}"也就表达连续三个相同的小写字母。 详细的分析讨论参考:正则表达式正向预搜索的问题。
  源字符串:aaa bbbb ffffff 999999999
  正则表达式:(\w)((?=\1\1\1)(\1))+
  测试代码:
  string test = "aaa bbbb ffffff 999999999" ;
  Regex reg = newRegex (@"(\w)((?=\1\1\1)(\1))+" );
  MatchCollection mc = reg.Matches(test);
  foreach (Match m in mc)
  {
  richTextBox2.Text += "匹配结果:" + m.Value.PadRight(12, ' ' ) + "匹配开始位置:" + m.Index + "\n" ;
  }
  //输出
  匹配结果:bb          匹配开始位置:4
  匹配结果:ffff         匹配开始位置:9
  匹配结果:9999999      匹配开始位置:16
  匹配结果分析:
  正则表达式(\w)((?=\1\1\1)(\1))+从匹配结果上分析,其实就等价于(\w)(\1)*(?=\1\1\1)(\1) ,这个会相对好理解一些,下面讨论下分析过程。
  因为"+"等价于"{1,}",表示至少匹配1次,下面把子表达式"((?=\1\1\1)(\1))+"展开来看下规律,下表中的"次数"表示子表达式"((?=\1\1\1)(\1))+"匹配成功的次数。 如果最后一个"((?=\1\1\1)(\1))"匹配成功,那么中间的"((?=\1\1\1)(\1))"一定可以匹配成功,所以中间的限制条件(?=\1\1\1)就没有意义了,这时就可以简写为"(\1)",也就是 可以归纳为等价于
  (\w)(\1)*((?=\1\1\1)(\1)) 
  因为"((?=\1\1\1)(\1))"开始和结尾的()原来是用作量词+修饰范围的,这里已经没有什么意义了,所以表达式最后可以归纳为等价于
  (\w)(\1)*(?=\1\1\1)(\1) 
  分析这个表达式就容易多了。"(\w)"匹配一个字符,占一位,"\1"是对"\w"匹配内容的引用,"(\1)*"可以匹配0到无穷多个"(\w)"匹配到的字符,"(?=\1\1\1)(\1)"只占一位,但是"(?=\1\1\1)"要求所在位置右侧有三个连续相同的"(\w)"匹配到的字符,所以在"(?=\1\1\1)"这个位置右侧应该有三个字符,不过只有这个位置右侧的一个字符计入最后的匹配结果,最后两个只作为限制条件,不计入最后的匹配结果。
  以"999999999"为例,第一个"9"由"(\w)"匹配,第二到第六个"9"由"(\1)*"来匹配,第七个"9"由"(?=\1\1\1)(\1)"中最后的"(\1)"来匹配,而第七、八、九这三个"9"是用来保证满足"(?=\1\1\1)"这个条件的。 对于普通捕获组的反向引用,是通过捕获组的编号来实现的,那么对于一些可能存在歧义的语法又是如何解析的呢?对于正则表达式
  ([ab])\10
  这里的"\10"会被解析成第10个捕获组的反向引用,还是第1个捕获组的反向引用加一个普通字符"0"呢?不同语言的处理方式是不一样的。
  string test = "ab0cdebb0cde" ;
  richTextBox2.Text = Regex .Match(test, @"([ab])\10" ).Value;
  在.NET中,以上测试代码输出为空,说明这里的"\10"被解析成第10个捕获组的反向引用,而这个表达式中是不存在第10个捕获组的,所以匹配结果为空。 /*--------输出-------- bb0 */ 而在JavaScript中,由于浏览器解析引擎的不同,得到的结果也不一样,以上为IE下是可以得到匹配结果"bb0",说明在IE的浏览器引擎中,"\10"被解析成第1个捕获组的反向引用加一个普通字符"0"。而在Firefox、Opera等浏览器中,得到的结果为空,说明"\10"被解析成第10个捕获组的反向引用,而这个表达式中是不存在第10个捕获组的。
  string test = "ab0cdebb0cde" ;
  richTextBox2.Text = Regex .Match(test, @"([ab])\10" , RegexOptions .ECMAScript).Value;
  /*--------输出--------
  bb0
  */
  而在.NET中,如果正则表达式加了RegexOptions.ECMAScript参数,则这里的"\10"被解析成第1个捕获组的反向引用加一个普通字符"0"。
  至于正则表达式中确实有10个以上的捕获组时,"\10"的具体意义留给有兴趣的读者去测试了,因为在实际应用当中,如果你的正则表达式中用到了10个以上捕获组,而同时又用到了第10个以上捕获组的反向引用时,就要注意分析一下,你的正则是否需要进行优化,甚至于这里是否适合使用正则表达式了。
  出于对现实应用场景的分析,第10个以上捕获组的反向引用几乎不存在,对它的研究通常仅存在于理论上。而对于10个以内捕获组反向引用后面还有数字,容易造成混淆的情况,可以通过非捕获组来解决。
  ([ab])\1(?:0)
  这样就可以明确,是对第1个捕获组的反向引用,后面跟一个普通字符"0"。也就不会产生混淆了。
  string test = "ab0cdebb0cde" ;
  richTextBox2.Text = Regex .Match(test, @"([ab])\1(?:0)" ).Value;
  /*--------输出--------
  bb0
  */
  而事实上,即使是这样用的场景也非常少,至今为止,只在日期正则表达式中用到过。
  ^(?:(?!0000)[0-9]{4}([-/.]?) (?:(?:0?[1-9]|1[0-2])\1(?:0?[1-9]|1[0-9]|2[0-8]) |(?:0?[13-9]|1[0-2])\1(?:29|30) |(?:0?[13578]|1[02])\1(?:31) )|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48] |[2468][048]|[13579][26])00)([-/.]?)0?2\2(?:29) )$
  这一节讨论的内容,了解一下就可以了,在实际应用当中,如果遇到,注意一下不要出现混淆而导致匹配结果错误就可以了。 反向引用的作用通常是用来查找或限定重复、查找或限定指定标识配对出现等等。以下以实例进行场景分析及应用讲解。 查找重复通常的应用场景是查找或验证源字符串中,是否有重复单词、重复项等等。 需求描述:
  验证源字符串中以","分隔的数字是否有重复项。
  代码实现:
  string[] test = newstring [] { "1,2,3,123,32,13" , "12,56,89,123,56,98" , "8,2,9,10,38,29,2,9" , "8,3,9,238,93,23" };
  Regex reg = newRegex (@"\b(\d+)\b.*?\b\1\b" );
  foreach (string s in test)
  {
  richTextBox2.Text += "源字符串: " + s.PadRight(20, ' ' ) + "验证结果: " + reg.IsMatch(s) + "\n" ;
  }
  /*--------输出--------
  源字符串: 1,2,3,123,32,13      验证结果: False
  源字符串: 12,56,89,123,56,98   验证结果: True
  源字符串: 8,2,9,10,38,29,2,9   验证结果: True
  源字符串: 8,3,9,238,93,23      验证结果: False
  */
  源字符串的规则比较明确,就是用","分隔的数字,类似于这种查找是否有重复的需求,最简单的就是用反向引用来解决了。
  由于要验证的是用","分隔的元素的整体是否有重复,所以"(\d+)"两侧的"\b"就是必须的,用来保证取到的数字子串是一个元素整体,而不是"123"中的"1",当然,这里前后两个"\b"分别换成"(?正则表达式。
  需求描述:
  数据: 
  1985aaa1985bb 
  bcae1958fiefadf1955fef 
  atijc1944cvkd 
  df2564isdjfef2564d 
  实现1: 匹配第一次出现的四个数字. 然后后面也存在这四个数字的 
  如: 
  1985aaa1985bb 
  第一次出现的四个数字是1985. 然后后面也存在这四个数字, 所以这个匹配 
  bcae1958fiefadf1955fef 
  第一次出现的四个数字是1958. 然后后面不存在这四个数字. 所以不匹配 
  ----- 
  所以实现1. 应该匹配 
  1985aaa1985bb 
  df2564isdjfef2564d
  代码实现:
  //如果是验证第一个出现的连续4 个数字是否有重复
  string[] test = newstring [] { "1985aaa1985bb" , "bcae1958fiefadf1955fef" , "atijc1944cvkd" , "df2564isdjfef2564d" , "abc1234def5678ghi5678jkl" };
  Regex reg = newRegex (@"^(?:(?!\d{4}).)*(\d{4})(?:(?!\1).)*\1" );
  foreach (string s in test)
  {
  richTextBox2.Text += "源字符串:   " + s.PadRight(25, ' ' ) + "验证结果:   " + reg.IsMatch(s) + "\n" ;
  }
  /*--------输出--------
  源字符串:   1985aaa1985bb             验证结果:   True
  源字符串:   bcae1958fiefadf1955fef    验证结果:   False
  源字符串:   atijc1944cvkd             验证结果:   False
  源字符串:   df2564isdjfef2564d        验证结果:   True
  源字符串:   abc1234def5678ghi5678jkl  验证结果:   False
  */
  由于需求要求验证第一次出现的四个数字是否有重复,所以这里需要用"^(?:(?!\d{4}).)*(\d{4})"来保证捕获组取得的是第一次出现的四个数字。
  这样写可能有些复杂,可读性较差,但这里需要用这种顺序环视结合贪婪模式,来达到匹配第一次出现的四个数字的目的,而不能使用非贪婪模式.
  对于使用非贪婪模式的正则"^.*?(\d{4})(?:(?!\1).)*\1",可以看一下它匹配的结果。
  string[] test = newstring [] { "1985aaa1985bb" , "bcae1958fiefadf1955fef" , "atijc1944cvkd" , "df2564isdjfef2564d" , "abc1234def5678ghi5678jkl" };
  Regex reg = newRegex (@"^.*?(\d{4})(?:(?!\1).)*\1" );
  foreach (string s in test)
  {
  richTextBox2.Text += "源字符串:   " + s.PadRight(25, ' ' ) + "验证结果:   " + reg.IsMatch(s) + "\n" ;
  }
  /*--------输出--------
  源字符串:   1985aaa1985bb             验证结果:   True
  源字符串:   bcae1958fiefadf1955fef    验证结果:   False
  源字符串:   atijc1944cvkd             验证结果:   False
  源字符串:   df2564isdjfef2564d        验证结果:   True
  源字符串:   abc1234def5678ghi5678jkl  验证结果:   True
  */
  是的,最后一项的验证结果也是"True",为什么会这样?当捕获组"(\d{4})"匹配到"1234"时,由于"1234"没有重复,所以后面的子表达式匹配失败,此时".*?"会进行回溯,放弃当前状态,继续向前匹配,直到它匹配到"5678"前的"f",由捕获组"(\d{4})"匹配到"5678",后面的子表达式可以匹配成功,报告整个表达式匹配成功。
  NFA引擎在有可供回溯的状态时,会一直尝试直到所有可能都尝试失败后才报告失败。上例中非贪婪模式在继续尝试时是可以找到匹配成功的位置的,而采用贪婪模式的正则"^(?:(?!\d{4}).)*(\d{4})",由于"^(?:(?!\d{4}).)*"匹配到的内容不可能是连续的四个数字,所以无论怎么回溯,接下来的"(\d{4})"都不可能匹配成功,一直回溯到起始位置"^",报告整个表达式匹配失败。
  而后面的顺序环视+贪婪模式子表达式"(?:(?!\1).)*"则不存在以上问题,所以在源字符串比较简单时可以写作".*?",不会影响匹配结果。
  而对于验证任意位置是否存在四个重复数字,则不需要加起始位置的限定。
  //如果是验证任意位置出现的连续4 个数字是否有重复,可以用我38 楼的正则
  string[] test = newstring [] { "1985aaa1985bb" , "bcae1958fiefadf1955fef" , "atijc1944cvkd" , "df2564isdjfef2564d" , "abc1234def5678ghi5678jkl" };
  Regex reg = newRegex (@"(\d{4})(?:(?!\1).)*\1" );
  foreach (string s in test)
  {
  richTextBox2.Text += "源字符串:   " + s.PadRight(25, ' ' ) + "验证结果:   " + reg.IsMatch(s) + "\n" ;
  }
  /*--------输出--------
  源字符串:   1985aaa1985bb             验证结果:   True 源字符串:   bcae1958fiefadf1955fef    验证结果:   False
  源字符串:   atijc1944cvkd             验证结果:   False
  源字符串:   df2564isdjfef2564d        验证结果:   True
  源字符串:   abc1234def5678ghi5678jkl  验证结果:   True
  */ 相对于查找重复来说,查找或指定标识配对出现这种应用场景要更多一些。尤其是对于HTML的处理中,这种应用更普遍。 由于HTML语言的不规范性,导致以下三种写法可以被解析。 1.   CSDN 2.   CSDN 3.   CSDN 而这对于一些需要进行字符串解析的应用,造成很大的麻烦。在提取链接时,虽然两侧都用"['"]?"通常也可以得到正确结果,却不如用反向引用来得严谨、方便。
  Regex reg = newRegex (@"(?is)[^""'\s>]* )\1[^>]*>(?(?:(?!).)*)" );
  MatchCollection mc = reg.Matches(yourStr);
  foreach (Match m in mc)
  {
  richTextBox2.Text += m.Groups["url" ].Value + "\n" ;
  richTextBox2.Text += m.Groups["text" ].Value + "\n" ;
  }
  /*--------输出--------
  www.csdn.net
  CSDN
  www.csdn.net
  CSDN
  www.csdn.net
  CSDN
  */
  以下可以正确解析出三种形式的HTML代码中的链接和文本,下面把正则改一下
  Regex reg = newRegex (@"(?is)[^""'\s>]* )\1[^>]*>(?(?:(?!).)*)" );
  看到区别了吗?只是把"(['""]?)"改成了"(['""])?",结果会怎么样呢?
  Regex reg = newRegex (@"(?is)[^""'\s>]* )\1[^>]*>(?(?:(?!).)*)" );
  MatchCollection mc = reg.Matches(yourStr);
  foreach (Match m in mc)
  {
  richTextBox2.Text += m.Groups["url" ].Value + "\n" ;
  richTextBox2.Text += m.Groups["text" ].Value + "\n" ;
  }
  /*--------输出--------
  www.csdn.net
  CSDN
  www.csdn.net
  CSDN
  */
  结果只取到了两组数据。这是因为对于情况1的HTML字符串,在"(['""]?)"这种情况下,捕获组虽然匹配到的只是一个位置,但毕竟是匹配成功了,所以可以用"\1"进行反向引用;而改成"(['""])?",捕获组根本就没有进行匹配,所以也就无法进行反向引用。
  当然,对于HTML来说,还有一些比较复杂的情况,如
   2)"/>
  这种复杂情况涉及到的场景比较少,通常应用可以不予以考虑,否则考虑的场景太复杂,会影响匹配效率。写正则的一个一般原则就是,适用就好。这种场景如果遇到,需求根据具体情况,是否需要提取等进行分析,根据分析结果不同,写出的正则也是不一样的。 这种应用一般是在取某几个特定标签,或是动态生成正则表达式时用到。
  需求描述:
  删除…与…标签及其中间的内容。
  代码实现:
  Regex reg = newRegex (@"(?is)]*>(?(?!\1\b).)*" );
  string result = reg.Replace(yourStr, "" );
  因为这里要删除的标签不止一个,所以事先无法确定是哪个标签,需要用到反向引用来限定标签的配对。
  当然,对于标签有嵌套的情况,就要用到平衡组了。可以参考.NET正则基础之--平衡组。 需求描述:
  [id]5554323[id!][destid]10657302023180404[destid!] [srcterminalid]13518841197[srcterminalid!][msgconte nt]好的[msgcontent!][receivetime]20090409165217[receiveti me!] 
  源字符串中标签成对出现,无嵌套,分别提取标签和对应的内容。
  代码实现:
  string test = "[id]5554323[id!][destid]10657302023180404[destid! ][srcterminalid]13518841197[srcterminalid!][msgcont ent]好的[msgcontent!][receivetime]20090409165217[receiveti me!]";
  Regex reg = newRegex (@"(?s)\[([^\]]+)\]((?:(?!\[\1).)*)\[\1!\]" );
  MatchCollection mc = reg.Matches(test);
  foreach (Match m in mc)
  {
  richTextBox2.Text += "Tag: " + m.Groups[1].Value.PadRight(20, ' ' ) + "Content: " + m.Groups[2].Value + "\n" ;
  }
  /*--------输出--------
  Tag: id                   Content: 5554323
  Tag: destid               Content: 10657302023180404
  Tag: srcterminalid        Content: 13518841197
  Tag: msgcontent           Content: 好的
  Tag: receivetime          Content: 20090409165217
  */
  这种需求通常是由捕获组匹配到一个标签,然后向后匹配,直到与之配对的标签外为止,根据源字符串的特点,中间可以使用非贪婪模式,也可以使用顺序否定环视+贪婪模式。 需求描述:
  只允许12 位数字,并且其中不能出现6 位连续相同数字。
  例如,123456789012 是允许的,而123333334567 是不允许的。
  正则表达式:^(?:([0-9])(?!\1{5})){12}$
  类似这种需要判定是否有连续相同元素的需求,其实也是验证重复,也要用到反向引用。
  说下分析过程,需求分解一下: 1、一个数字 
  2、它后面不能连续出现5 个与它相同的数字 
  3、满足以上两条的字符一共12 个 那么根据需求分解写出相应的正则:
  1、([0-9])
  2、(?!\1{5})
  3、.{12}
  将以上三个分解后得出的正则,按需求逻辑关系,组合一下:
  (([0-9])(?!\1{5})){12}
  由于是验证整个字符串的规则,所以开始和结束标识"^"和"$"是少不了的,不需要用捕获组的地方,用非捕获代替,也就成了最后满足需求的正则:
  ^(?:([0-9])(?!\1{5})){12}$
  其实这个例子的分析过程,也是一些正则问题解析的通用过程,先把复杂的需求由整到零的分解,再各个实现,然后把实现的正则由零到整,考虑一下相互间的逻辑关系,基本上就可以得出正确的正则表达式了。 需求描述:A-Z以内不重复的10个字母
  正则表达式1:^(?:([A-Z])(?!.*?\1)){10}$
  正则表达式2:^(?:([A-Z])(?=((?!\1).)*$)){10}$
  这个需求与上一个需求类似,分析过程也差不多。其实这个问题如果用正则来实现,思路是非常清晰的。
  首先因为是验证规则,所以"^"和"$"是必不可少的,分别匹配开始和结束的位置。
  然后是10个字母,那么([A-Z]){10},合起来就是^([A-Z]){10}$ 。
  最后就是加一个规则,字母不能重复。
  如何保证不能重复,必然是用到反向引用,(一个字母)后面任意一个字母不能与这个字母重复,这样实现起来就有两种方式,当然,实质都是一样的
  实现方式一:^(?:([A-Z])(?!.*?\1)){10}$
  实现方式二:^(?:([A-Z])(?=(?:(?!\1).)*$)){10}$
  在这个需求当中,由于可能出现的源字符串不会太长,也不会太复杂,所以这两个正则表达式在匹配效率上不会有明显的差异。
  解释一下正则的含义,先解释一下方式一的正则:
  ^(?:([A-Z])(?!.*?\1)){10}$
  "^"和"$"分别匹配开始和结束位置,"{10}"为量词,表示修饰的子串重复10次。
  "(?:Expression)"是非捕获组,目的是不将"()"内的"Expression"匹配的内容保存到内存中,之所以要这样用,是因为后面的反向引用使用的是"\1",如果不用非捕获组,那么"([A-Z])"就是编号为2的捕获组,后面的"\1"就要换成"\2",来引用第二个捕获组,替换后对匹配结果当然不会有什么影响,但由于由"(([A-Z])(?!.*?\1))"捕获的内容我们并不关心,所以还是用非捕获组,可以提升匹配效率。
  "([A-Z])"就是匹配A到Z之间的任意一个字母,并保存匹配结果到捕获组中。
  "(?!.*?\1)"顺序环视,它是零宽度的,虽然进行匹配,但不保存匹配结果,可以理解为它就是在所在位置的右侧附加了一个条件,用在这里表示,它所在位置的右侧,不管间隔多少个字符,都不能出现之前匹配到的那个字符,也就是不能有重复的字母出现。
  "(?:([A-Z])(?!.*?\1)){10}"就是匹配到这样一个字符:
  1、它首先是一个字母;
  2、然后这个字母的右侧间隔任意多个字符,不能再出现同样的字母;
  3、最后,符合以上两条规则的字符,一共有10个。
  加上首尾限定字符"^"和"$",就是满足需求的正则。
  接下来讨论一下方式二的正则:
  ^(?:([A-Z])(?=(?:(?!\1).)*$)){10}$
  思路和以及其余部分子表达式与方式一完全一样,只有"(?=(?:(?!\1).)*$)"这里不同,这个子表达式表示,它所在位置右侧,一直到结尾,都不能是之前匹配到的那个字符。方式一是非贪婪模式的实现,而这个就是贪婪模式的实现。
  这里需要用到顺序肯定环视"(?=Expression)",而不能用非捕获组"(?:(?:(?!\1).)*$)",是因为这里的表达式不能占有字符,只能作为条件存在,由量词"{10}"修饰的子表达式最终只能匹配一个字符,否则就无法限定长度了。 需求描述参考求一正则表达式(c# ):
  例如有字符串 string str = "w1w2w3w2w3w1w3w2w4w5w4w5w4w4w5w4w2w4w3w4w3w2w6w5w6 w5w6w4w7",找出有且仅有两个单元(w+数字作为一个单元,例如:w1,w2)组成的长度大于等于4个单元的字串(必须包括这两个单元),这个例子,应输出:"w2w3w2w3","w4w5w4w5w4w4w5w4","w4w3w4w3","w6w5w6w5w6"。
  如果找出有且仅有三个单元长度大于等于6个单元的字串,该如何写正则表达式?
  代码实现:
  //第一个需求,两单元的
  string str = "w7w7w7w5w7w1w2w3w2w3w1w3w2w4w5w4w5w4w4w5w4w2w4w3w 4w3w2w6w5w6w5w6w4w7w7w7w5w7" ;
  MatchCollection mc = Regex .Matches(str, @"(?i)(?=(w\d)\1*(w\d))(?:\1|\2){4,}" );
  foreach (Match m in mc)
  {
  richTextBox2.Text += m.Value + "\n" ;
  }
  /*--------输出--------
  bb0w7w7w7w5w7
  w2w3w2w3
  w4w5w4w5w4w4w5w4
  w4w3w4w3
  w6w5w6w5w6
  w4w7w7w7
  */
  //第二个需求,三单元的
  string str = "w7w7w7w5w7w1w2w3w2w3w1w3w2w4w5w4w5w4w4w5w4w2w4w3w 4w3w2w6w5w6w5w6w4w7w7w7w5w7" ;
  MatchCollection mc = Regex .Matches(str, @"(?i)(?=(w\d)\1*(w\d)(?:\1|\2)*(w\d))(?:\1|\2|\3) {6,}" );
  foreach (Match m in mc)
  {
  richTextBox2.Text += m.Value + "\n" ;
  }
  /*--------输出--------
  bb0w7w7w7w5w7w1
  w2w3w2w3w1w3w2
  w4w5w4w5w4w4w5w4w2w4
  w2w6w5w6w5w6
  w4w7w7w7w5w7
  */ 这个实例可以认为是环视和反向引用综合运用的一个经典实例。主要是用到了环视零宽度,不占有字符的特性,先由环视来取得规定单元的捕获组的内容,再通过反向引用来进行实际的匹配。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics