经过两天的课设+考试,终于有时间继续看书了,今天记录一道面试题“正则表达式的匹配”。
问题描述:请实现一个函数用来匹配包含’.’和’‘的正则表达式。模式中的字符’.’表示任意一个字符,而’‘表示它前面的字符可以出现任意次(包括0次)。在本题中,匹配是指字符串的所有自负匹配整个模式。
例如:字符串”aaa”与模式”a.a”和”ab*ac*a”匹配,但与”aa.a”和”ab*a”均不匹配。
一开始没看懂题,后来读了好几遍发现,就是一个简单的检测字符串,意思是,给你一个模式和一个字符串,然后,看看是否匹配,字符’.’可以用任意字符代替。
比如”a.a”那么和”a&a”、”aka”、”a/a”都匹配,只要两边是a,那么中间那个字符’.’可以随意变换。同理,字符‘’前面的那个字符可以出现无数次,也可以出现0次,比如”ab*a”,那么它与”aa”(b出现0次)、”aba”(b出现1次)、”abbbbbbbbbba”(b出现10次)、”ab······ba”(b出现n次)都能匹配,因为其他字符不变,字符’‘前面的字符出现多少次都不影响字符串的匹配。那么题目意思理解了,后面就容易了。
思路:
每次从字符串中拿出一个字符和模式中的字符去匹配。先来分析如何匹配一个字符,如果模式中的字符ch是’.’,那么它可以匹配字符串中的任意字符。如果模式中的字符ch是’.’,而且字符串中的字符也是ch,那么他们相互匹配。
当模式中的第二个字符不是’*’时,如果字符串的第一个字符和模式的第一个字符相匹配,那么字符串和模式都向后移一个字符,然后匹配剩余的字符串和模式。如果字符串中的第一个字符和模式的第一个字符不匹配,则直接返回false。
当模式中的第二个字符是‘*’的时候,因为可能有多种不同的匹配方式:
方式一:在模式上向后移动两个字符,相当于前面的字符和‘’被忽略了(模式‘’前面的字符出现了0次)
方式二:当前模式的字符与字符串的字符相匹配,同时向后移一位(模式‘*’前面的字符只出现一次)
方式三:字符串向后移一位,模式上不变(模式上‘*’前面的字符出现多次)
以上,就是对于正则表达式匹配的分析,有点绕脑,看代码你会觉得清晰很多。
当然,我还进行了一系列可能的测试,在我能考虑到的范围内,都进行了测试,全部PASS,以下为通过代码与测试。
using System;
namespace 正则表达式
{
class Program
{
static void Main(string[] args)
{
string pattern1 = "ab*a.a.*a";
//测试一:两个字符'*'的前面的字符个数均为0
string str1 = "aaaaa";
char[] ch = str1.ToCharArray();
char[] pattern = pattern1.ToCharArray();
if (Match(ch, pattern))
Console.WriteLine("Test1 Pass");
else
Console.WriteLine("Test1 Error");
//测试二:字符'*'之前的字符只出现一次
string str2 = "abaaaa";
ch = str2.ToCharArray();
if (Match(ch, pattern))
Console.WriteLine("Test2 Pass");
else
Console.WriteLine("Test2 Error");
//测试三:模式字符'*'的前一个字符出现n次
string str3 = "abbbbbbaaaa";
ch = str3.ToCharArray();
if (Match(ch, pattern))
Console.WriteLine("Test3 Pass");
else
Console.WriteLine("Test3 Error");
//测试四:模式字符'*'的前一个字符为'.',匹配任意字符n次
string str4 = "abaaa222*a";
ch = str4.ToCharArray();
if (Match(ch, pattern))
Console.WriteLine("Test4 Pass");
else
Console.WriteLine("Test4 Error");
//测试五:匹配的字符串为空
ch = null;
if (Match(ch, pattern) == false)
Console.WriteLine("Test5 Pass");
else
Console.WriteLine("Test5 Error");
//测试六:模式字符串为空
ch = str1.ToCharArray();
pattern = null;
if (Match(ch, pattern) == false)
Console.WriteLine("Test6 Pass");
else
Console.WriteLine("Test6 Error");
//测试七:正则表达式为".*"且字符串中都没有字符
string str7 = "";
string pattern7 = ".*";
ch = str7.ToCharArray();
pattern = pattern7.ToCharArray();
if (Match(ch, pattern))
Console.WriteLine("Test6 Pass");
else
Console.WriteLine("Test6 Error");
}
public static bool Match(char[] str, char[] pattern)
{
//如果传入的字符数组是空,则匹配失败
if (str == null || pattern == null)
return false;
return MatchCore(str, pattern, 0, 0);
}
/// <summary>
/// 用来匹配字符串和正则表达式
/// </summary>
/// <param name="str">匹配的字符串</param>
/// <param name="pattern">匹配的正则表达式</param>
/// <param name="strIndex">字符串的匹配位置</param>
/// <param name="patternIndex">正则表达式的匹配位置</param>
/// <returns></returns>
private static bool MatchCore(char[] str, char[] pattern, int strIndex, int patternIndex)
{
//如果字符串和模式的匹配都到了最后一位,那么返回真
if (strIndex == str.Length && patternIndex == pattern.Length)
return true;
//字符串未匹配完但是正则表达式已经匹配完毕,返回假
else if (strIndex < str.Length && patternIndex == pattern.Length)
return false;
//正则表达式匹配的下一位是'*'
if (patternIndex + 1 <= pattern.Length - 1 && pattern[patternIndex + 1] == '*')
{
//如果当前字符串和模式的字符匹配,或者模式的字符为'.'且字符串还有字符没有匹配,这里需要注意的是,一定得在这里判断strIndex是否小于str.Length,因为在测试中发现,str为"",pattern为".*"的时候,如果没有这个判断,就会执行里面的if语句,虽然会返回true,但是会数组越界,下面的if同理
if (strIndex < str.Length && (pattern[patternIndex] == str[strIndex] || pattern[patternIndex] == '.'))
{
//'*'前面的字符出现了0次
return MatchCore(str, pattern, strIndex, patternIndex + 2) ||
//'*'前面的字符出现了N次
MatchCore(str, pattern, strIndex + 1, patternIndex) ||
//'*'前面的字符只出现了一次
MatchCore(str, pattern, strIndex + 1, patternIndex + 2);
}
else
{
//当前正则表达式匹配的字符不匹配,那么字符向后移两位
return MatchCore(str, pattern, strIndex, patternIndex + 2);
}
}
//如果当前模式与字符串的字符匹配,或当前模式上的字符为'.'且字符串尚未匹配完,同时向后移一位
if (strIndex < str.Length && (str[strIndex] == pattern[patternIndex] || pattern[patternIndex] == '.'))
return MatchCore(str, pattern, strIndex + 1, patternIndex + 1);
return false;
}
}
}