正则表达式正向预搜索的问题

Dic4000 2008-12-24 08:40:19

string str ="aaa bbbb ffffff 999999999";
Regex r = new Regex(@"(\w)((?=\1\1\1)(\1))+");
foreach (Match m in r.Matches(str))
{
Console.WriteLine("Match:Value={0},Index={1},Length={2}", m.Value, m.Index, m.Length);
}

结果如下:
Match:Value=bb,Index=4,Length=2
Match:Value=ffff,Index=9,Length=4
Match:Value=9999999,Index=16,Length=7

表达式 "(\w)((?=\1\1\1)(\1))+" 在匹配字符串 "aaa ffffff 999999999" 时,将可以匹配4个"b"的前2个,可以匹配6个"f"的前4个,可以匹配9个"9"的前7个。我想问的是为什么漏掉的总是最后两个字母?它们为什么不能匹配成功?哪个规则导致了这种影响?能详细说说好吗?
...全文
1430 23 打赏 收藏 转发到动态 举报
写回复
用AI写文章
23 条回复
切换为时间正序
请发表友善的回复…
发表回复
sharpe 2009-01-09
  • 打赏
  • 举报
回复
结这么早,感觉上面的人解释得不很满意,,,自己试了一下,有一些见解
sprc_lcl 2008-12-25
  • 打赏
  • 举报
回复
我被绕糊涂了,看了楼上的才明白过来,回了言后才发现m.Index就第一位的
-过客- 2008-12-25
  • 打赏
  • 举报
回复
那个啥,我还没睡zzzZZZ~~~

楼主的正则,(\w)((?=\1\1\1)(\1))+,其实就等价于
(\w)(\1)*(?=\1\1\1)(\1)

这个会相对好理解一些

说下分析过程,因为+表示{1,},下面的“次数”表示((?=\1\1\1)(\1))+匹配成功的次数
次数 楼主的正则等价于
1 (\w)((?=\1\1\1)(\1))
2 (\w)((?=\1\1\1)(\1))((?=\1\1\1)(\1))
3 (\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 (\w)((?=\1\1\1)(\1))
2 (\w)(\1)((?=\1\1\1)(\1))
3 (\w)(\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)这个条件的
sprc_lcl 2008-12-25
  • 打赏
  • 举报
回复
(?=\1\1\1) 是判断位置1 2 3上的9,不在结果内
(?=\1\1\1)\1 如果匹配9999的话结果是第四位的9

\w(?=\1\1\1)\1 == 99 9(\w) 9(\1)
wackyboy 2008-12-25
  • 打赏
  • 举报
回复
Regex r = new Regex(@"(\w)((?=\1\1\1)(\1))+");

先找到一个字符 看他后面时否有三个相同的字符, 有 取一个, 之后再去看后面有没有三个相同的字符 有 取一个…… 知道倒数第四个 看后面有三个相同的字符吗 有 取一个这样就匹配到倒数第三个字符 看倒数第三个后面时候有三个相同的字符 因为倒数第三个后面不够三个字符 匹配失败 如果其中遇到不匹配的情况 同样 匹配失败 然后把前面匹配的字符取道 这样 九个9 就只取到七个 因为后面不够三个
止戈而立 2008-12-25
  • 打赏
  • 举报
回复
[Quote=引用 20 楼 Dic4000 的回复:]
引用 19 楼 min_jie 的回复:
你可以试一下用(?=3)去匹配,看看结果是什么,这样你马上就会明白。


试了,运行后只有一条结果是:Match:Value=,Index=0,Length=0
我认为这个结果匹配了字符串的开端"^",不知道这样理解是否正确?
如果这个结论成立的话,那么它还应该有另外个结果,匹配字符串的终端"$",Match:Value=,Index=1,Length=0
但却没这条结果.
用(?!3)的话,就可以有这条结果,却没第一个结果.
那么这种现象如何…
[/Quote]

应该这样理解:(?=3)在字符串中预搜索匹配的值,字符串0位置的字符是3,匹配成功,由于整个表达式是0宽度的,
那么index=预搜索匹配成功的位置-length=0-0=0

(?!3)也是一样的,在字符串位置1匹配,index=位置1-0宽度=1
可以再比较一下(?=$)
sprc_lcl 2008-12-25
  • 打赏
  • 举报
回复
(?!3)是判断型的
你的new Regex(@"(?!3)"); 和new Regex(@""); 意思差不多吧..
你要改为new Regex(@"\w(?!3)"); 就是你说的效果了


-过客- 2008-12-25
  • 打赏
  • 举报
回复
首先楼主要明白一点,这不是错误结果,而是正确结果


顺道BS一下CSDN,引用图片真麻烦。。。



可以这样认为
string s= "3";
这样一个字符串,包含两个位置,一个字符,两位位置分别是开始的位置0和结束的位置1,一个字符就是“3”

一般(?Exp)这样语法的表达式,是零宽度的,也就是说匹配的结果是不匹配任何字符的,只匹配位置
说得更通俗点,也就是这样的表达式,是在某一位置加了一个附加条件,在这个位置的前或后要满足Exp表达式,这样整个正则表达式才能匹配成功

位置是不互斥的,也就是一个位置,同时可以由多个零宽度的表达式来匹配,而字符同时就只能由一个表达式来匹配

知道了这些,就来看一下楼主这个问题

一个正则表达式,匹配一个源字符串,首先从开始位置,也就是位置0开始尝试匹配,因为(?!3)表示所在位置的右侧不能是字符“3”,而实际上位置0的右侧就是字符“3”,所以位置0是匹配失败的

此时正则引擎会引导正则向前传动,从下一个位置开始尝试匹配,也就是在位置1尝试匹配,因为位置1的右侧不是字符“3”,所以匹配成功,此时整个表达式匹配成功,匹配结果是空字符串,长度当然是0,Index是1

因为(?!3)这样一个正则表达式是零宽度的,所以无论源字符是什么样的,它都会有匹配成功的结果的,用来学习语法规则,写这样的正则还可以,项目应用就千万不要写这样的表达式了

扩展一下,如果正则换成^(?!3),那结果会如何?是没有任何输出,因为没有匹配成功
首先^从位置0尝试匹配,匹配成功,这时正则表达式的控制权交给(?!3),在位置0匹配,匹配失败。
因为^只能匹配开始位置,所以稍微做了一点优化的正则引擎,都不会尝试去匹配下一个位置,这时会报告整个正则表达式都匹配失败

Dic4000 2008-12-25
  • 打赏
  • 举报
回复
[Quote=引用 19 楼 min_jie 的回复:]
你可以试一下用(?=3)去匹配,看看结果是什么,这样你马上就会明白。
[/Quote]

试了,运行后只有一条结果是:Match:Value=,Index=0,Length=0
我认为这个结果匹配了字符串的开端"^",不知道这样理解是否正确?
如果这个结论成立的话,那么它还应该有另外个结果,匹配字符串的终端"$",Match:Value=,Index=1,Length=0
但却没这条结果.
用(?!3)的话,就可以有这条结果,却没第一个结果.
那么这种现象如何解释呢?
Dic4000 2008-12-25
  • 打赏
  • 举报
回复
明白了.谢谢lxcnn 和sprc_lcl
还想问一个正向预搜索的另一种形式(?!)的问题.

string s= "3";
Regex r=new Regex(@"(?!3)");
foreach (Match m in r.Matches(str))
{
Console.WriteLine("Match:Value={0},Index={1},Length={2}", m.Value, m.Index, m.Length);
}


结果: Match:Value=,Index=1,Length=0

对于这个结果我不能理解.
我的理解是:当刚开始匹配时,是从开始边界"^"进行匹配,发现后面有字符"3",不符合规则,然后从3开始匹配.因为字符"3"后面是结束边界"$",符合规则,所以这时候结果应为Value=3,Index=0,Length=1.
请问我哪里理解错了?
止戈而立 2008-12-25
  • 打赏
  • 举报
回复
[Quote=引用 12 楼 Dic4000 的回复:]
string s= "3";
Regex r=new Regex(@"(?!3)");
foreach (Match m in r.Matches(str))
{
Console.WriteLine("Match:Value={0},Index={1},Length={2}", m.Value, m.Index, m.Length);
}




结果: Match:Value=,Index=1,Length=0

对于这个结果我不能理解.
我的理解是:当刚开始匹配时,是从开始边界"^"进行匹配,发现后面有字符"3",不符合规则,然后从3开始匹配.因为字符"3"后面是结束边界"$",符合规则,所以这时候结果应为Value=3,Index=0,Length=1.
请问我哪里理解错了?
[/Quote]

value不可能是3,你的正则表达式是零宽度的,即使匹配成功,value也是空字符串。
结束边界$满足(?!3)的匹配,因此匹配是成功的,$的index是1。

你可以试一下用(?=3)去匹配,看看结果是什么,这样你马上就会明白。
Dic4000 2008-12-25
  • 打赏
  • 举报
回复
自己顶一下
止戈而立 2008-12-25
  • 打赏
  • 举报
回复
(?!3)是零宽度的。
sprc_lcl 2008-12-25
  • 打赏
  • 举报
回复
不知道,可能是走到1走不下去了吧
Dic4000 2008-12-25
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 sprc_lcl 的回复:]
你要改为new Regex(@"\w(?!3)"); 就是你说的效果了
[/Quote]

这个效果我知道是怎么来的,我想问的这个Match:Value=,Index=1,Length=0 错误的效果怎么来的.明明只有一个字符"3",为什么Index显示的是1?
Dic4000 2008-12-24
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 sprc_lcl 的回复:]
(?=\1\1\1)匹配前面的三位(判断),不是后面.............
所以\w就匹配了第三个9.
[/Quote]
你的意思是,刚开始的时候(?=\1\1\1) 是匹配位置1 2 3上的9吗?但这三个位置上的9应该不会被吃进吧?那这样的话,前面的\w应该匹配位置1上面的9啊,怎么你说\w就匹配了位置3上的9了呢?
sprc_lcl 2008-12-24
  • 打赏
  • 举报
回复
你强,这么晚了,睡了
sprc_lcl 2008-12-24
  • 打赏
  • 举报
回复
(?=\1\1\1)匹配前面的三位(判断),不是后面.............
所以\w就匹配了第三个9.。前面的两个不在匹配范围内,只用来判断了
Dic4000 2008-12-24
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 wuyi8808 的回复:]

(\w) # 匹配一个单词字符
( # 分组开始
(?=\1\1\1) # 紧接着必须是三个和前面的一样的字符,但不吃进字符
(\1) # 匹配一个和前面一样的字符
)+# 匹配一个或多个这样的分组

[/Quote]

谢谢大家,我说说我的理解吧:
以999999999为例,这里有9个9。(\w)匹配第一个9。然后根据规则(?=\1\1\1)发现后面有连续的3个9,但不吃进字符,因此紧接着这个(?=\1\1\1)表达式后面的(\1)匹配第2个9,这个9被吃进。又由于((?=\1\1\1)(\1))是个组,+表示匹配一个或多个这样的分组,所以接着从第3个9查看后面是否又有连续的3个9(即查看3 4 5位置上是否都是9),如果发现了,那么(?=\1\1\1)表达式后面的(\1)匹配第3个9,且吃进这个9,所以接着从第4个9查看后面是否又有连续的3个9(即查看4 5 6位置上是否都是9),行为同上以此类推.

我知道我理解有误,但不知道是哪里出了问题?
烈火蜓蜻 2008-12-24
  • 打赏
  • 举报
回复
string str ="aaa bbbb ffffff 999999999";

第一个匹配字符a 然后他开始判断,除了第一个a的字符串"aa bbbb ffffff 999999999"这个时候,他没有三个a,因此,a没有被匹配,
直到匹配到第1个b的时候, 后面三个连着都是b,因此又匹配到一个b,当他开始判断时,这时的字符串,变成了bb ffffff 999999999,只有2个没有三个,因此b只被匹配到2个,
以此类推,所以才会得出你的那个结果

((?=\1\1\1)) 这个判断是不消耗字符的,他只是判断,
加载更多回复(3)
正则表达式教程
 正则表达式(regular expression)描述了一种字符串匹配的模式,可以用来检查一个串是否含有某种子串、将匹配的子串做替换或者从某个串中取出符合某个条件的子串等。

列目录时, dir *.txt或ls *.txt中的*.txt就不是一个正则表达式,因为这里*与正则式的*的含义是不同的。
  正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为元字符)组成的文字模式。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。

3.1 普通字符
  由所有那些未显式指定为元字符的打印和非打印字符组成。这包括所有的大写和小写字母字符,所有数字,所有标点符号以及一些符号。

3.2 非打印字符 字符 含义
\cx 匹配由x指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 'c' 字符。
\f 匹配一个换页符。等价于 \x0c 和 \cL。
\n 匹配一个换行符。等价于 \x0a 和 \cJ。
\r 匹配一个回车符。等价于 \x0d 和 \cM。
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。
\t 匹配一个制表符。等价于 \x09 和 \cI。
\v 匹配一个垂直制表符。等价于 \x0b 和 \cK。


3.3 特殊字符

   所谓特殊字符,就是一些有特殊含义的字符,如上面说的"*.txt"中的*,简单的说就是表示任何字符串的意思。如果要查找文件名中有*的文件,则需要对*进行转义,即在其前加一个\。ls \*.txt。正则表达式有以下特殊字符。

 

特别字符 说明
$ 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 '\n' 或 '\r'。要匹配 $ 字符本身,请使用 \$。
( ) 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 \( 和 \)。
* 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。
+ 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。
. 匹配除换行符 \n之外的任何单字符。要匹配 .,请使用 \。
[ 标记一个中括号表达式的开始。要匹配 [,请使用 \[。
? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。
\ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, 'n' 匹配字符 'n'。'\n' 匹配换行符。序列 '\\' 匹配 "\",而 '\(' 则匹配 "("。
^ 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 \^。
{ 标记限定符表达式的开始。要匹配 {,请使用 \{。
| 指明两项之间的一个选择。要匹配 |,请使用 \|。

  构造正则表达式的方法和创建数学表达式的方法一样。也就是用多种元字符与操作符将小的表达式结合在一起来创建更大的表达式。正则表达式的组件可以是单个的字符、字符集合、字符范围、字符间的选择或者所有这些组件的任意组合。
 

3.4 限定符

   限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有*或+或?或{n}或{n,}或{n,m}共6种。
*、+和?限定符都是贪婪的,因为它们会尽可能多的匹配文字,只有在它们的后面加上一个?就可以实现非贪婪或最小匹配。
   正则表达式的限定符有:
 

字符 描述
* 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。
? 匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 中的"do" 。? 等价于 {0,1}。
{n} n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。
{n,} n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。

3.5 定位符

   用来描述字符串或单词的边界,^和$分别指字符串的开始与结束,\b描述单词的前或后边界,\B表示非单词边界。不能对定位符使用限定符。

3.6 选择

   用圆括号将所有选择项括起来,相邻的选择项之间用|分隔。但用圆括号会有一个副作用,是相关的匹配会被缓存,此时可用?:放在第一个选项前来消除这种副作用。
   其中?:是非捕获元之一,还有两个非捕获元是?=和?!,这两个还有更多的含义,前者为正向查,在任何开始匹配圆括号内的正则表达式模式的位置来匹配搜索字符串,后者为负向查,在任何开始不匹配该正则表达式模式的位置来匹配搜索字符串。

3.7 后向引用

   对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左至右所遇到的内容存储。存储子匹配的缓冲区编号从 1 开始,连续编号直至最大 99 个子表达式。每个缓冲区都可以使用 '\n' 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。
   可以使用非捕获元字符 '?:', '?=', or '?!' 来忽略对相关匹配的保存。

110,549

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧