数据结构和算法 – 5.模式匹配和文本处理
了使用正则表达式,需要把 RegEx 类引入程序。大家可以在 System.Text.RegularExpression 名字域中找到这种类。一旦把这种类导入了程序,就需要决定想要用 RegEx 类来做什么事情了。
如果想要进行匹配,就需要使用 Match类。
如果打算做替换,则不需要 Match 类了。取而代之的是要用到 RegEx 类的 Replace 方法。
5.1.使用正则表达式
Regex reg = new Regex("quick"); string str1 = "the quick brown fox jumped over the lazy dog"; Match matchSet; int matchPos; matchSet = reg.Match(str1); if (matchSet.Success) { matchPos = matchSet.Index; Console.WriteLine("found match at position:" + matchPos); } //查看是否匹配 bool istrue = Regex.IsMatch(str1, "the"); Console.WriteLine(istrue); //为了处理所有找到的匹配可以把匹配存储到 MatchCollection 对象中 MatchCollection matchSets = reg.Matches(str1); if (matchSets.Count > 0) { foreach (Match item in matchSets) { Console.WriteLine("found a match at: " + item.Index); } } Console.Read();
5.2.数量词
代码/语法 | 说明 |
* | 重复0次或多次 |
+ | 重复一次或多次 |
? | 重复零次或1次 |
{n} | 重复n次 |
{n,} | 重复至少n次 |
{n,m} | 重复至少n次,但不多于m次 |
这里要看到的第一个数量词就是加号( +)。这个数量词说明正则表达式应该匹配一个或多个紧接的字符。
Console.WriteLine("ba+: "); String[] words = new String[] { "bad", "boy", "baaad", "bear", "bend" }; foreach (string item in words) { if (Regex.IsMatch(item,"ba+")) { Console.Write(item+" "); } } Console.WriteLine(); Console.WriteLine("ba*: "); foreach (string item in words) { if (Regex.IsMatch(item, "ba*")) { Console.Write(item + " "); } } Console.WriteLine(); Console.WriteLine("ba?: "); foreach (string item in words) { if (Regex.IsMatch(item, "ba?d")) { Console.Write(item + " "); } }
原本期望这个程序就返回两个标签: <b>和</b>。但是由于贪心,正则表达式匹配了<b>字符串</b>。利用懒惰数量词:问号(?)就可以解决这个问题。当问号直接放在原有数量词后边时,数量词就变懒惰了。这里的懒惰是指在正则表达式中用到的懒惰数量词将试图做尽可能少的匹配,而不是尽可能多的匹配了
string[] words = new string[] { "Part", "of", "this", "<b>string</b>", "is", "bold" }; // 限定符“.” 匹配出换行符以外的任意字符 string regex = "<.*>"; //<b>string</b> //regex = "<.*?>"; //<b> </b> MatchCollection collection; foreach (var item in words) { if(Regex.IsMatch(item,regex)) { collection = Regex.Matches(item, regex); foreach (var value in collection) { Console.WriteLine(value); } } }
5.3.使用字符类
这里第一个要讨论的字符类就是句号( .)。这是一种非常非常容易使用的字符类,但是它也确实是有问题的。句点与字符串中任意字符匹配。
较好利用句点的方法就是用它在字符串内部定义字符范围,也就是用来限制字符串的开始或/和结束字符。
string str1 = "the quick brown fox jumped over the lazy dog one time"; MatchCollection matchSet; matchSet = Regex.Matches(str1, "t.e"); foreach (Match aMatch in matchSet) Console.WriteLine("Matches at: " + aMatch.Index);
在使用正则表达式的时候经常希望检查包含字符组的模式。大家可以编写用一组闭合的方括号( [ ])包裹着的正则表达式。在方括号内的字符被称为是字符类。如果想要编写的正则表达式匹配任何小写的字母字符,可以写成如下这样的表达式: [abcdefghijklmnopqrstuvwxyz]。但是这样是很难书写的,所以通过连字号: [a-z]来表示字母范围的方式可以编写简写版本。</SP< span>
string str1 = "THE quick BROWN fox JUMPED over THE lazy DOG"; MatchCollection matchSet; matchSet = Regex.Matches(str1, "[a-z]"); foreach (Match aMatch in matchSet) Console.WriteLine("Matches at: " + aMatch);
5.4.断言修改正则表达式
1.^
C#语言包含一系列可以添加给正则表达式的运算符。这些运算符可以在不导致正则表达式引擎遍历字符串的情况下改变表达式的行为。这些运算符被称为断言。
第一个要研究的断言会导致正则表达式只能在字符串或行的开始处找到匹配。这个断言由脱字符号( ^)产生。
在下面这段程序中,正则表达式只与第一个字符为字母“ h”的字符串相匹配,而忽略掉字符串中其他位置上的“ h”。
string[] words = new string[] { "heal", "heel", "noah", "techno" }; string regExp = "^h"; Match aMatch; foreach (string word in words) if (Regex.IsMatch(word, regExp)) { aMatch = Regex.Match(word, regExp); Console.WriteLine("Matched: " + word + " at position: " + aMatch.Index); }
这段代码的输出就只有字符串“ heal”和“ heel”匹配。
2.$
这里还有一个断言会导致正则表达式只在行的末尾找到匹配。这个断言就是美元符号( $)。
string regExp = "h$";
那么“ noah”就是唯一能找到的匹配
3.\b
指定所有匹配只能发生在单词的边缘。这就意味着匹配只能发生在用空格分隔的单词的开始或结束处。此断言用\b 表示。
例子:查找以h或H开头的字符
string words = "hark, what doth thou say, Harold? "; string regExp = @"(\bh|\bH)\w+"; MatchCollection aMatch; if (Regex.IsMatch(words, regExp)) { aMatch = Regex.Matches(words, regExp); foreach (Match item in aMatch) { Console.WriteLine("Matched: " + item + " at position: " + item.Index); } }
5.5.分组构造
5.5.1 匿名组
"08/14/57 46 02/25/59 45 06/05/85 18 03/12/88 16 09/09/90 13"
这个字符串就是由生日和年龄组成的。如果只需要匹配年龄而不要生日,
string words = "08/14/57 46 02/25/59 45 06/05/85 18 03/12/88 16 09/09/90 13"; string regExp1 = @"(\s\d{2}\s|\s\d{2}\b)";
MatchCollection matchSet = Regex.Matches(words, regExp1);
foreach (Match aMatch in matchSet)
Console.WriteLine(aMatch.Groups[0].Captures[0]);
5.5.2 命名组
(?<ages>\s\d{2}\s|\s\d{2}\b)
5.5.3 零度正向和反向预搜索
5.5.3.1.正向预搜索断言
(?= reg-exp-char)
reg-exp-char 是正则表达式或元字符
只要搜索到匹配的当前子表达式在指定位置的右侧,那么匹配就继续。
string strwords = "lions lion tigers tiger bears,bear"; string regexp2 = @"\w+(?=\s)"; MatchCollection matchSet1 = Regex.Matches(strwords, regexp2); foreach (Match aMatch in matchSet1) //Console.WriteLine(aMatch.Groups[0].Captures[0]); Console.WriteLine(aMatch.Captures[0]);
正则表达式匹配单词,但是不匹配空格。记住这一点是非常重要的。
5.5.3.2.负的正向预搜索断言
string words = "subroutine routine subprocedure procedure";
string regExp1 = \\b(?!sub)\\w+\\b;
5.5.3.3.反向预搜索断言
这些断言会正向左或反向左搜索,而不是向右了
//这些断言会正向左或反向左搜索,而不是向右了 string strwords3 = "subroutines routine subprocedures procedure"; string regexp3 = @"\b\w+(?<=s)\b"; MatchCollection matchSet3 = Regex.Matches(strwords3, regexp3); foreach (Match aMatch in matchSet3) //Console.WriteLine(aMatch.Groups[0].Captures[0]); Console.WriteLine(aMatch.Captures[0]);
匹配的单词有“ subroutines”和“ subprocedures”。
5.6.CaptureCollection类
当正则表达式匹配子表达式的时候,产生了一个被称为是 Capture 的对象,而且会把此对象添加到名为 CaptureCollection 的集合里面。当在正则表达式中使用命名组的时候,这个组就由自己的捕获集合。为了重新得到用了命名组的正则表达式所收集的捕获,就要调用来自 Match 对象 Group 属性的 Captures 属性。这在实例中是很容易理解的。
string words4 = "08/14/57 46 02/25/59 45 06/05/85 18 03/12/88 16 09/09/90 13"; string regExp4 = @"(?<dates>\d{2}/\d{2}/\d{2})(?<ages>\s\d{2}\s)"; MatchCollection matchSet4 = Regex.Matches(words4, regExp4); foreach (Match item in matchSet4) { foreach (Capture ac in item.Groups["dates"].Captures) { Console.WriteLine("dates:" + ac.Value); } foreach (Capture ac in item.Groups["ages"].Captures) { Console.WriteLine("ages:" + ac.Value); } }