正则匹配原理之——逆序环视深入
1 问题引出
前几天在CSDN论坛遇到这样一个问题:
var str="8912341253789";
需要将这个字符串中的重复的数字给去掉,也就是结果89123457。
首先需要说明的是,这种需求并不适合用正则来实现,至少,正则不是最好的实现方式。
这个问题本身不是本文讨论的重点,本文所要讨论的,主要是由这一问题的解决方案而引出的另一个正则匹配原理问题。
先看一下针对这一问题本身给出的解决方案。
string str = "8912341253789";
Regex reg = new Regex(@"((\d)\d*?)\2");
while (str != (str = reg.Replace(str, "$1"))) { }
richTextBox2.Text = str;
/*--------输出--------
89123457
*/
基于此有朋友提出另一个疑问,为什么使用下面的正则没有效果
“(?<=(?<value>\d).*?)\k<value>”
由此也引出本文所要讨论的逆序环视更深入的一些细节,涉及到逆序环视的匹配原理和匹配过程。前面的两篇博客中虽然也有介绍,但还不够深入,参考 正则基础之——环视 和 正则应用之——逆序环视探索 。本文将以逆序环视和反向引用结合这种复杂应用场景,对逆序环视进行深入探讨。
先把问题简化和抽象一下,上面的正则中用到了命名捕获组和命名捕捉组的反向引用,这在一定程度上增加了问题的复杂度,写成普通捕获组,并且用“\d”代替范围过大的“.”,如下
“(?<=(\d)\d*?)\1”
需要匹配的字符串,抽象一下,取两种典型字符串如下。
源字符串一:878
源字符串二:9878
与上面正则表达式类似,正则表达式相应的也有四种形式
正则表达式一:(?<=(\d)\d*)\1
正则表达式二:(?<=(\d)\d*?)\1
正则表达式三:(?<=(\d))\d*\1
正则表达式四:(?<=(\d))\d*?\1
先看一下匹配结果:
string[] source = new string[] {"878", "9878" };
List<Regex> regs = new List<Regex>();
regs.Add(new Regex(@"(?<=(\d)\d*)\1"));
regs.Add(new Regex(@"(?<=(\d)\d*?)\1"));
regs.Add(new Regex(@"(?<=(\d))\d*\1"));
regs.Add(new Regex(@"(?<=(\d))\d*?\1"));
foreach (string s in source)
{
foreach (Regex r in regs)
{
richTextBox2.Text += "源字符串: " + s.PadRight(8, ' ');
richTextBox2.Text += "正则表达式: " + r.ToString().PadRight(18, ' ');
richTextBox2.Text += "匹配结果: " + r.Match(s).Value + "\n------------------------\n";
}
richTextBox2.Text += "------------------------\n";
}
/*--------输出--------
源字符串: 878 正则表达式: (?<=(\d)\d*)\1 匹配结果: 8
------------------------
源字符串: 878 正则表达式: (?<=(\d)\d*?)\1 匹配结果:
------------------------
源字符串: 878 正则表达式: (?<=(\d))\d*\1 匹配结果: 78
------------------------
源字符串: 878 正则表达式: (?<=(\d))\d*?\1 匹配结果: 78
------------------------
------------------------
源字符串: 9878 正则表达式: (?<=(\d)\d*)\1 匹配结果:
------------------------
源字符串: 9878 正则表达式: (?<=(\d)\d*?)\1 匹配结果:
------------------------
源字符串: 9878 正则表达式: (?<=(\d))\d*\1 匹配结果: 78
------------------------
源字符串: 9878 正则表达式: (?<=(\d))\d*?\1 匹配结果: 78
------------------------
------------------------
*/
这个结果也许会出乎很多人的意料之外,刚开始接触这个问题时,我也一样感到迷惑,放了两天后,才灵机一触,想通了问题的关键所在,下面将展开讨论。
在此之前,可能还需要做两点说明:
1、 下面讨论的话题已经与本文开始提到的问题没有多大关联了,最初的问题主要是为了引出本文的话题,问题本身不在讨论范围之内,而本文也主要是纯理论的探讨。
2、 本文适合有一定正则基础的读者。如果您对上面几个正则的匹配结果和匹配过程感到费解,没关系,下面就将为您解惑;但是如果您对上面几个正则中元字符和语法代表的意义都不清楚的话,还是先从基础看起吧。
一些内容讲解是要通过颜色区分的,帖子编辑不是很方便,请到我的博客查看完整介绍
正则匹配原理之——逆序环视深入
194 个解决方案
#1
#2
沙发没坐到
#3
up
#4
客客
#5
ding
#6
我是地板吗?
#7
看看!!!
#8
顶~
#9
没用弄过!顶一下
#10
来学习的,mark然后常看看
#11
看着不错 有空研究下
#12
谢谢客客分享
我是找bug的。。。
我是找bug的。。。
#13
#14
up
#15
#16
#17
好东西..楼主辛辛苦 谢谢分享..解释得很透彻
#18
UP
#19
学习
#20
学习
#21
学习了~~
#22
好啊。。。。学习了。。。
#23
似乎这样也行:
Regex.Replace("8912341253789", @"(?<=\1.*)(\d)", "", RegexOptions.RightToLeft)
#24
CSDN对内容进行处理了?怎么中间会多出空格来的!
再贴一次
再贴一次
Regex.Replace("8912341253789", @"(?<=\1.*)(\d)", "", RegexOptions.RightToLeft)
#25
#26
又来学习拉,占个座~~~
#27
受益匪浅!!3ks
#28
up
#29
mark
#30
mark
#31
顶一下
#32
呵呵,0009兄太有才了
不过逆序环视已不是那么好把握了,RightToLeft就更是一个奇怪的模式,目前只有.NET支持这一模式,但它对这一模式的支持并不是很完善,有时容易让人费解,所以除非对源字符串的构成很了解,而且又不得不使用的情况,否则还是不要轻易使用这一模式。
#33
学习
#34
顶
#35
up
#36
地板、
#37
模仿熊猫背影?=_=
#38
D
#39
感觉不通用
#40
up
#41
up
#42
学习了,谢谢哦
#43
#44
正則高科技- -
學習
學習
#45
up........................................
......................................................
......................................................
#46
#47
UP
#48
普通模式之所以不能用 (?<=\1.*)(\d),是因为从左向右解析, (?<=\1.*) 中永远都不可能解析到有内容的 \1,而 RightToLeft 刚好解决了这个问题,如果可以不考虑结果的顺序,则完全可以使用普通模式下的 (\d)(?=.*?\1),只不过这样出来的结果会变为 41253789。
Regex.Replace("8912341253789", @"(\d)(?=.*?\1)", "") //41253789
#49
jf,学习
#50
mark
#1
#2
沙发没坐到
#3
up
#4
客客
#5
ding
#6
我是地板吗?
#7
看看!!!
#8
顶~
#9
没用弄过!顶一下
#10
来学习的,mark然后常看看
#11
看着不错 有空研究下
#12
谢谢客客分享
我是找bug的。。。
我是找bug的。。。
#13
#14
up
#15
#16
#17
好东西..楼主辛辛苦 谢谢分享..解释得很透彻
#18
UP
#19
学习
#20
学习
#21
学习了~~
#22
好啊。。。。学习了。。。
#23
似乎这样也行:
Regex.Replace("8912341253789", @"(?<=\1.*)(\d)", "", RegexOptions.RightToLeft)
#24
CSDN对内容进行处理了?怎么中间会多出空格来的!
再贴一次
再贴一次
Regex.Replace("8912341253789", @"(?<=\1.*)(\d)", "", RegexOptions.RightToLeft)
#25
#26
又来学习拉,占个座~~~
#27
受益匪浅!!3ks
#28
up
#29
mark
#30
mark
#31
顶一下
#32
呵呵,0009兄太有才了
不过逆序环视已不是那么好把握了,RightToLeft就更是一个奇怪的模式,目前只有.NET支持这一模式,但它对这一模式的支持并不是很完善,有时容易让人费解,所以除非对源字符串的构成很了解,而且又不得不使用的情况,否则还是不要轻易使用这一模式。
#33
学习
#34
顶
#35
up
#36
地板、
#37
模仿熊猫背影?=_=
#38
D
#39
感觉不通用
#40
up
#41
up
#42
学习了,谢谢哦
#43
#44
正則高科技- -
學習
學習
#45
up........................................
......................................................
......................................................
#46
#47
UP
#48
普通模式之所以不能用 (?<=\1.*)(\d),是因为从左向右解析, (?<=\1.*) 中永远都不可能解析到有内容的 \1,而 RightToLeft 刚好解决了这个问题,如果可以不考虑结果的顺序,则完全可以使用普通模式下的 (\d)(?=.*?\1),只不过这样出来的结果会变为 41253789。
Regex.Replace("8912341253789", @"(\d)(?=.*?\1)", "") //41253789
#49
jf,学习
#50
mark