正则表达式
--- Look around
在正则表达式中有一组特殊的通配符
---- Look around
通配符,其包含四个子类通配符:
Table 2. Four Types of Look around
| ||
Type
|
Regex
|
Successful if the enclosed subexpression . . .
|
Positive lookbehind Negative Lookbehind |
(?<=......) (?<!......) |
successful if can match to the left successful if can not match to the left |
Positive Lookahead Negative Lookahead |
(?=......) (?!......) |
successful if can match to the right successful if can not match to the right |
在一般的资料中我们经常可以看见
Lookahead
的介绍:
(?=pattern)
|
正向预查,在任何匹配
pattern
的字符串开始处匹配查找字符串。这是一个
非获取匹配
,也就是说,该匹配不需要获取供以后使用。例如,
'Windows (?=95|98|NT|2000)'
能匹配
"Windows 2000"
中的
"Windows"
,但不能匹配
"Windows 3.1"
中的
"Windows"
。
预查不消耗字符
,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
|
(?!pattern)
|
负向预查,在任何不匹配
pattern
的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如
'Windows (?!95|98|NT|2000)'
能匹配
"Windows 3.1"
中的
"Windows"
,但不能匹配
"Windows 2000"
中的
"Windows"
。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始
|
这比较详细的介绍了
Look ahead
通配符,从以上的一段描述中我们可以看见
Look ahead
通配符的特点:
1.
该通配符是一个非获取匹配;就是正则表达式引擎在处理该类通配符时不会把匹配项存储起来,不会供以后使用。
2.
该通配符是预查操作符,不消耗字符;这个可能不是很好理解,我们还是结合实例说明吧:比如有一串字符
a1b2c3
,在匹配的开始,我们可以想象有一个指针,所指的位置在
a
的前面,我们用
\w
来匹配该字符串可匹配到
a
,然后下一个匹配应该从
1
之前的位置开始;但如果我们用
(?=)
来匹配时,我们匹配到
a,
可是这时候指针还是在
a
的前面;也就是说,指针的位置并没有改变。这就是所谓的
不消耗字符
。
以上介绍的
”look ahead”
通配符,这两个通配符被解释为正向预查和负向预查,而
”look behind”
的两个通配符我一直没有找到其对应的中文翻译,但是其性质和特点和
”Look ahead”
非常相似,唯一的不同就是
”look ahead”
是预查(向右),
” look behind”
是
”
复查?
”
(向左)。
我们看看以上四个通配符都在什么情况下可以匹配;
string
str23 = "abc9defg";
string
strRegular26 = @"[a-z]{3}";
string
strRegular27 = @"[a-z]{3}(?=\d)"; //lookahead
string
strRegular271 = @"(?=\d)[a-z]{3}"; //lookahead
string
strRegular28 = @"(?<=\d)[a-z]{3}"; //lookbehind
string
strRegular281 = @"[a-z]{3}(?<=\d)"; //lookbehind
Console.WriteLine(Regex.Matches(str23,strRegular26).Count.ToString()); // 2
Console.WriteLine(Regex.Matches(str23,strRegular26)[0].Captures[0].ToString()); // abc
Console.WriteLine(Regex.Matches(str23,strRegular26)[1].Captures[0].ToString()); // def
Console.WriteLine(Regex.Matches(str23,strRegular27).Count.ToString()); // 1
Console.WriteLine(Regex.Matches(str23,strRegular27)[0].Captures[0].ToString());
// abc
Console.WriteLine(Regex.Matches(str23,strRegular271).Count.ToString()); // 0
// Console.WriteLine(Regex.Matches(str23,strRegular271)[0].Captures[0].ToString()); //
前向预查不消耗字符,所以这个匹配永远不可能出现
Console.WriteLine(Regex.Matches(str23,strRegular28).Count.ToString());//1
Console.WriteLine(Regex.Matches(str23,strRegular28)[0].Captures[0].ToString());//def
以上是一段.net环境下c#实现的代码,每句话的后面给出了输出结果,和常规表达式@"[a-z]{3}"相比较,在表达式变为: @"[a-z]{3}(?=\d)"时不匹配原有的
”
def
”
因为在向右看时,
”
def
”
后面不是数字。在表达式变为@"(?<=\d)[a-z]{3}"后不匹配原有的
”
abc
”
,因为在向左看时,
”
abc
”
前面没有数字。
让我们来看一段示例:
通过常用通配符和这四个通配符(不一定全用到)的结合,让我们试着将一串夹在字符串中的数字用千分号分隔开:
如:
The value is 123456789 dollars.
通过正则表达式变为:
The value is 123,456,789 dollars.
或者将:
The result is 123456789
通过正则表达式变为:
The result is 123,456,789
让我们分析这个问题:
首先分析我们应该如何分割
123456789
毫无疑问,我们应该从右边不是数字的字符或者位置开始,每隔
3
个数字且该位置之前还是数字(如
4
前面和
7
前面是应该找到了,而
1
前面不是应该匹配的。)
那么让我们逐步完善匹配正则表达式:
首先是匹配模式:在
.net
中应该用
RegexOptions.RightToLeft.
是一点首先明确,然后是左边不是数字,如果用的是
(?=[^0-9]+)
这已经很接近了,可是一个问题是不能匹配数字后什么都没有的东西了(因为用了一个
+
)所以我们可以完善为
"(?=(\D|$))"
且右边有
3
个一组的数字一组或者多组
:"(?=(\d{3})+(\D|$)
)
",
而且左边至少有一位数字
:"\d(?=(\d{3})+(\D|$)
)
"
这样我们通过这个正则表达式去匹配,我们就可以找到
123456789
中的
3
和
6
了,这离
123
,
456
,
789
已经非常接近了。
我们需要将
3
替换为
3
,
而
6
替换为
6
,所以我们需要将
3
和
6
捕获到
:"(\d)(?=(\d{3})+(\D|$)
)
"
这时候我们可以采用
Timmy
的方式用
Regex.Replace(strTest,strPattern,new atchEvaluator ReplaceNumber));
或者我们在正则表达式的替换中我们可以用
$1
来获取我们的第一组捕获,这样我们就可以得到一种解决方案:
string str37 = "123456789";
string strRegular47 = @"(\d)(?=(\d{3})+(\D|$))";
Console.WriteLine(Regex.Replace(str37,strRegular47,"$1,",RegexOptions.RightToLeft ));
这是一种替换
"3" -> "3," ; "6" -> "6,"
的一种方式。这里我们就到一个
Positive Lookahead
,那么其实我们的实际操作就是希望在
"3"
和
"6"
的位置后插入一个
","
,我们可不可以只找出这个位置,然后替换为
","
呢?
我想我们通过
(?<=......)
和
(?<!......)
是可以达到只找到这个位置的目的的
:)
这里我也将表达式给出:
(?<=\d+)(?=(\d{3})+(\D|$))
这样以上的表达式就只会匹配一个位置,而不对应任何字符
,
用
string str37 = "123456789";
string strRegular47 = @"(?<=\d+)(?=(\d{3})+(\D|$))";
Console.WriteLine(Regex.Replace(str37,strRegular47,",",RegexOptions.RightToLeft ));
这样就可以不用捕获,也能完成插入 "," 了。
string str37 = "123456789";
string strRegular47 = @"(?<=\d+)(?=(\d{3})+(\D|$))";
Console.WriteLine(Regex.Replace(str37,strRegular47,",",RegexOptions.RightToLeft ));
这样就可以不用捕获,也能完成插入 "," 了。
我们还可以对这个问题进行扩展,比如
123456789.123456
如果采用以上的方式处理就会获得
123,456,789.123,456
这显然不太合适的,我们应该获得的是 "123,456,789.123456" ,即小数点后不加以格式化。那么这个正则表达式又应该怎么写呢?
这显然不太合适的,我们应该获得的是 "123,456,789.123456" ,即小数点后不加以格式化。那么这个正则表达式又应该怎么写呢?
这还是需要大家多多考虑。
要熟悉正则,就是要多多练习,尤其是我们进行搜索的时候,还是很有用处的。:)
现对关于正则表达式中
look around
的总结如下:
1.
是非捕获匹配,不消耗字符。
2.
有
look ahead (
向右
)
和
look behind
(向后)两种。其下又分正匹配和负匹配两种,共
4
种。
3.
属于特殊通配符,比较不容易通过其他通配符替代。
4
.合理运用可以大大提到字符处理效率。