从代码的视角看DOS时代的通配符
- MD DocuMenT: 3/9/2016 5:44:39 PM by Jimbowhy
- PDF版下载: http://download.youkuaiyun.com/detail/winsenjiansbomber/9459188
听着大佑的现场版的《现象II》,感觉我的生命的就一个计时器,每嘀嗒一下都有可能是最后那一下,每多嘀嗒一下就更近最后那一下。在内心深处,始终渴望有一片田园,种花种草种春梦,养情养神养身心。如果可以一觉睡到未来,假设我在王朝建立前就存在,相信此刻的心情从来都未曾改变。如果还能有心跳的感觉,为何不能好好享受此刻,做喜欢做的人和事,不必计较认真,任何人任何事!—— by Jimbowhy 3/12/2016 7:11:05 PM
有趣的匹配模式
在 LeetCode 上看到第二个有趣的问题,是关于字符串匹配的,在接触过正则表达式后就一直想着自己实现一个实用版的正则工具,像 editplus 那样,不用做得功能齐全,实用就好。LeetCode 原题内容如下:
LeetCode 44. Wildcard Matching
Total Accepted: 52145 Total Submissions: 305251 Difficulty: Hard
Implement wildcard pattern matching with support for ‘?’ and ‘*’.
‘?’ Matches any single character.
‘*’ Matches any sequence of characters (including the empty sequence).The matching should cover the entire input string (not partial).
The function prototype should be:
bool isMatch(const char *s, const char *p)Some examples:
isMatch(“aa”,”a”) → false
isMatch(“aa”,”aa”) → true
isMatch(“aaa”,”aa”) → false
isMatch(“aa”, “*”) → true
isMatch(“aa”, “a*”) → true
isMatch(“ab”, “?*”) → true
isMatch(“aab”, “c*a*b”) → false
这是一道难度比正则要容易实现的题目,通过它来预练实现正则表达需要的技能是非常好的。和这道题相类似的是”10. Regular Expression Matching”,只要求实现正则表达的 . * 两个匹配符号。正则点号 . 意义等价于 ? 号,而正则 * 号则有所不同,它含意为匹配符号的数量可以为任意个,和点号 . 结合使用就是题中的幻数 * 的意义。两题的难度等级同样是 Hard,正则那道题显示提交的数量要稍为多一点点,接收率也要高那么点点。
编码遇到的问题
在一拿到这道题目时,因为只有 ? * 两个通用符号,我就想通过线性扫描的方式来时行匹配。线性扫描也就是通过对输入的字符串进行一次循环,在循环的过程中同时取出匹配模板进行比对。按这种思路,我写了一个方法:
bool isMatch(string s, string p) {
int i = 0, m = 0, k = 0, l = s.length(), n = p.length();
int o = 0, q=0, wm[1024];
bool c, wildMode = false;
if(p=="*") return true;
if(l==0){
c = true;
for(k=0; k<n; k++){ c &= p[k]=='*'; }
if( c ) return true;
return n==0;
}
if(p.length()==0) return false;
for(i=0; i<l; i++){
if( (s[i]==p[k] || p[k]=='?' || p[k]=='*') ){
if( wildMode && s[i]==p[k] ){
if( s[i+1]==p[k] ) continue;
if( q==0 ){ wm[o] = i+1, wm[o+1] = k; o+=2; }
wildMode = false;
}
if( p[k]=='*' ) wildMode = true;
c = ( p[k]=='?' )? true:false;
k++;
for(k=k; k<n; k++){
if( p[k]=='*' ) wildMode = true;
if( wildMode && p[k]=='?'&& !c ){
c=true;
continue;
}
if( wildMode && s[i]==p[k] ){
if( q==0 ){ wm[o] = i+1, wm[o+1] = k + 1; o+=2; }
k++;
wildMode=false;
break;
}
if( p[k]!='*' ) break;
}
if( l==i+1 && n==k ) return true;
continue;
} else if ( wildMode ){
if( l==i+1 && n==k ) return true;
continue;
}
if( o>0 && q<o){ // reset ik & retry in wildmode
i = wm[q]; k = wm[q+1];
o = 0; q = 0;
wildMode = true;
continue;
}
return false;
}
return false;
}
代码是一边编写规则一边进行修改的,进行测试时,可以说是没什么组织可言。可以匹配大部分情况,但是由于代码对可能出现在输入数据的逻辑状态检测不完整,所以修改代码的过程中不断会出现异常情况,如其中运行的一次测试结果:
OK! matched
OK! * matched
OK! ? not matched
OK! ** matched
OK! a a* matched
OK! aa * matched
OK! aa *aa matched
OK! ab *a not matched
Failed! aa *a not matched
OK! a *?* matched
OK! a ?*? not matched
OK! a *?*? not matched
OK! aa ?*? matched
OK! a **?** matched
OK! a ? matched
OK! a *a matched
OK! aa ? not matched
OK! aa a not matched
OK! aa aa matched
OK! aaa aa not matched
OK! aa a* matched
OK! aa a** matched
OK! aab a**b matched
OK! aabb a**b matched
OK! ab ?* matched
OK! aab c*a*b not matched
OK! abcdefdefg abc*def not matched
OK! abefcdgiescdfimde ab*cd?i*de matched
写到这里,发现不是这种办法不好,没代码没有好好组织,而逻辑状态会随着代码的增加而变得混乱起来。鉴于上面代码的逻辑,还可以构造出另几组测试数据来验证代码的错漏:
assert(solve, "abecdgiscdfidecde", "ab*cd?i*de", true);
assert(solve, "abcdgisdecdfideide","ab*cd?i*de", true);
以上这条测试前面的代码也是不能通过的,因为代码中对于 * 幻数的匹配是从短到长的尝试,而对于后面的 * 幻数却没有这样做,所以产生逻辑错误,这里称之为幻数陷阱。现在倒是想重写代码,其实真的应该好好想想如何组织代码逻辑,下面开始重构代码,只是部分逻辑功能:
bool isMatch(string s, string p) {
int m[512], k = 0, is = 0, ip = 0, ls = s.length(), lp = p.length();
unsigned short max_loop = -1, max_m = sizeof(m)/sizeof(int);
while(max_loop--){
if( ip<lp && ( p[ip]=='?' || is<ls && s[is]==p[ip] ) ){
ip++;
is++;
}else if( ip<lp && p[ip] == '*' ){
if( k>= max_m ) cout << "max_m " << __FILE__ << __LINE__ << endl;
m[k++] = is;
ip ++;
}else if( is==ls && ip==lp ){
return true;
}else return false;
}
if(!max_loop) cout << "max_loop " << __FILE__ << __LINE__ << endl;
}
尽管只是部分功能的实现,但测试效果来看,是有作用的,虽然从通过次数来讲还不乐观,但从代码的结构上来看已经简洁了一个层次,这是最好的消息:
>g++ -o wildcard wildcard.cpp && wildcard.exe
OK! matched
OK! * matched
OK! ? not matched
OK! ** matched
OK! a a* matched
Failed! aa * not matched
OK! aa *aa matched
OK! ab *a not matched
Failed! aa *a not matched
OK! a *?* matched
OK! a ?*? not matched
OK! a *?*? not matched
OK! aa ?*? matched
OK! a **?** matched
OK! a ? matched
OK! a *a matched
OK! aa ? not matched
OK! aa a not matched
OK! aa aa matched
OK! aaa aa not matched
Failed! aa a* not matched
Failed! aa a** not matched
Failed! aab a**b not matched
Failed! aabb a**b not matched
Failed! ab ?* not matched
OK! aab c*a*b not matched
OK! abcdefdefg abc*def not matched
Failed! abcdefdef abc*def not matched
Failed! abefcdgiescdfimde ab*cd?i*de not matched
Failed! abecdgiscdfidecde ab*cd?i*de not matched
Failed! abcdgisdecdfideide ab*cd?i*de not matched
而上面这些一大片的 Failed! 其实只要两条语句就可以干掉一大片,定义一个 bool sm 并在第一条 else 中设置为 true:
bool sm = false;
sm = true;
这样就会发现 Failed! 神奇地消失了一大片,连前面的幻数陷阱都不存在了,只余下两条:
Failed! ab *a matched
Failed! abcdefdefg abc*def matched
未完待续… 2016/3/10 2:57:12 AM
一觉醒来已经是午后,天气很差,所以睡得很好,这是真有因果联系的,因为天气差所周围会安静一点所以睡得会较好。继续完成昨夜末完成的工作,说的是添加 sm 超级匹配模式后,还有点小问题卡住了。*匹配是可以往前也可以往后的灵活匹配模式,由于这样的灵活性,在匹配不同的内容时代码就需要具有支持这种灵活性的能力。最后的代码如下,LeetCode 给出的性能评价是
CPP组的前 36.33%,还不算太差的性能:
// :)Accepted
bool isMatch(string s, string p) {
int k = 0, is = 0, ip = 0, ls = s.length(), lp = p.length();
int *m = (int *)malloc(2 * lp * sizeof(int));
unsigned short max_loop = -1, max_m = lp*2;
bool sm = false; // Super Magic not Sadism & Masochism
while(max_loop--){
if( p[ip] == '*' ){
if( k>= max_m ) cout << "max_m " << __FILE__ << __LINE__ << endl;
m[k] = is;
m[k+1] = ip;
ip ++;
k += 2;
sm = true;
}else if( ip<lp && ( p[ip]=='?' || is<ls && s[is]==p[ip] ) ){
ip++;
is++;
sm = false;
}else if( is<ls && sm ){
is++;
}else if( is==ls && ip==lp ){
return true;
}else if( k>0 ){
k -= 2;
is = m[k] + 1;
ip = m[k+1];
sm = true;
}else return false;
}
if(!max_loop) cout << "max_loop " << __FILE__ << __LINE__ << endl;
}
在本机测试代码中,都是几个毫秒级别的时差看不出来性能问题,也添加了一些对性能进行测试的数据,如:
assert(solve, string(3072,'a'), string(1024,'a')+'*', true);
试着修改了一下 max_loop,提交后发现性能一下上升到 53.06%,看来代码有问题。原来是有死循环的情况,max_loop 被冲破了,自动返回 false。到这也再次验证软件是不能百分百地正确运行的,只能说在有限制的条件下正常运行,上面的程序虽然可以被 LeetCode 确认,但并不意味着它总是正确的,因为在开发软件的时候就已经限制了它的适用范围。同时代码中最后一条用来提示死循环的语句也没发生作用,因为 while 的条件中自减是在右则的,当 max_loop 的值为 0 时循环就被预置条件终结了,但是自减的作用还在,那么离开 while 后 max_loop 的值总是不为 0 的。以下这几条测试中都是会产生死循环的数据:
max_loop 65535
OK! elapse 5ms ab *a not matched
max_loop 65535
OK! elapse 4ms a ?*? not matched
max_loop 65535
OK! elapse 5ms a *?*? not matched
max_loop 65535
OK! elapse 5ms b *?*?* not matched
max_loop 65535
OK! elapse 5ms abcdefdefg abc*def not matched
也就是说代码还缺失终结循环的功能,虽然预先设置了最大循环数有效地避免的死循环的情况,但只有添加终结循环的代码才是正确的:
...
}else if( is>ls || ip>lp ){
return false;
}else if( k>0 ){
...
修改后提交接收了 1801 个测试样本并通过,运行时间为 20 ms,成绩为 54.56%,方法复杂度为 O(N),以下是两次修改后提交的成绩汇总:
其它选择
第二条思路,匹配模板中,* ? 是相对灵活的,这里暂且称之为幻数。可不可以将模板的字符串进行分割,以幻数为分割点,然后再以普通的字符串为一个小的匹配模板对输入进行比对,完成普通的字符串比对后,要产生一组通过比对后得到的边界数值。最后,再将幻数对应的边界对待比对,这样通过两轮的比对都成功,那么结果就是匹配的,返回true。就思路说来,这种方法比前一种要好,因为实现过程中的逻辑会更清晰。而且这种方法还容易保存搜索结果,方便返回给客户方进行二次利用,像正则表达式,它的反向引用功能就非常强大,因此可以完成复杂的字符串处理工作。
这种方法可以抽象为一维数组结构,数组每个节点用四个元素表示分隔点的起点和长度还有匹配字符的左右两个终止点,用特定的值来标记幻数,幻数的起止点则可以依两相领节点的进行调整。在这个基本思想上,分割法更接近思维的模式。分析到这里,我又忍不住想要动手了!
……
// :)Accepted 2016/3/11 17:37:05
bool isMatch(string s, string p) {
int i=0,j=0,k=0,l=0, magic = -1, gl = 0, sl = s.length(), pl = p.length();
int cl = 4, ml = pl*cl*sizeof(int);
bool seq = false;
char pc;
int * g = (int *)malloc(ml);
if( g!=memset(g, magic, ml) ) cout << "memset failed!\n";
for(i=0; i<pl; i++){
if( p[i]=='*' ) {
if( gl>3 && g[gl-cl]==magic ) continue;
//g[gl] = magic; set by memset
gl += cl;
}else{
if(gl==0||g[gl-cl]==magic){
g[gl]=i; g[gl+1] = 1; gl += cl;
}else g[gl-3] ++;
}
}
for(i=0; i<gl; i+=cl){
if( g[i]==magic ) continue;
for(j=k; j<sl; j++){
if( !seq ) k = j;
pc = p[g[i]+j-k];
seq = s[j]==pc || pc=='?';
if( seq && j-k+1<g[i+1]){
continue;
}else if( seq && j-k+1==g[i+1] ){
seq = false;
if( i>0 && g[i-cl]==magic && i+cl>=gl && k+g[i+1]<sl){
j = k; continue;
}
g[i+2] = j; k = j+1; break;
}else if( !seq ){
if( j>=sl-1 || i<=0 || g[i-cl]!=magic ) return false;
j = k;
}else return false;
}
if( g[i+2]<0 ) return false;
// the mase old one condition: i+cl<gl && k>=sl && ( i+8<gl || g[gl-cl]!=magic
}
if( sl==0 && gl>=8 ) return false; // for test: a, a*
if( sl==0 && sl==pl ) return true;
if( (g[0]==magic || g[0]==0) && (g[gl-cl]==magic || g[gl-2]+1==sl && g[gl-2]>=0) ) return true;
return false;
}
注意上面使用了3个for循环,其中有两个还是嵌套的,那么这段代码提交后会有会成绩呢?很期待……看下图吧:
以上两个实现的代码例子中,时间复杂度都是O(N)级别的,空间复杂度分别是O(2N),O(3N),这是针对匹配模板的空间分配,还可以进行优化。因为现在使用的是32-bit的整形,大大超出了输入字符串的长度范围,如果按输入字符串的长度来优化,对于255的输入长度使用char来保存比对结果、对于65535长度的输入使用short来保存结果空间至少可以缩减为一半。
另外,在模板分割后,进行匹配的过程代码逻辑还是比较混乱的,如果可以再组织清晰一点,那么代码就称得上优质了。
还有闲蛋版的DP
第三条思路,动态规划 dynamic programming,参考算法概论第三版 Introduction to Algorithms, Third Edition,书中有木材分割的例子 Rod cutting。这个概念有点新奇,需要使用矩阵工具来记录比对状态,然后通过类似于递归的方法进行比对,把结果保存在矩阵中,下步需要用到之前的结果时直接查找矩阵的状态来判断匹配结果。说实话这是我最不喜欢的一个方法,一难懂,二代码难读懂,三费时,四费空间,基本上所有缺点它都沾边!DP这种方案和递归是相类似的,算法上是以暴力的穷举为基础的,因此前面这些缺点基本上递归方法也是全沾的。而LeetCode 上面却是充斥着大量相似的答案,几乎是同一盤母带拷贝出来的一般。
DP解题的基本过程如下,假设输入的数据为 aab、c*a*b,按两个字符串的长度建立矩阵,以匹配模板长度为行,并增加一行一列。矩阵开头直接用黑块填充,第一行的其它列按匹配模板中开头出现的 * 号连续出现的位置用黑色块表示匹配状态,没有连续的就不管了,这主要是处理匹配模板中连续出现 * 号的情况,其余位置用白色块填充表示不匹配。第二行、第二列到最后一行最后一列这个部分保存比对结果用,这样数量刚好对应输入的字符串的长度乘积。取第一个输入字符和匹配模板比对,结果保存在矩阵的第二行第二列开始的地方,其它字符比较的结果依次存放到后面的行中。如果匹配并且矩阵中的上一行的前一列的状态为匹配状态,那么结果就是匹配。当比对到 * 号时,就查找前一列整列是不出过匹配情况,如果出现就匹配成立。那么第一个字符 a 和模板匹配的结果就会产生图(2);第二个字符比较后产生图(3),依次进行,最后得到矩阵右下角的状态就是综合了所有匹配状态的答案:
◆◇◇◇◇◇ ◆◇◇◇◇◇ ◆◇◇◇◇◇ ◆◇◇◇◇◇
◇◎◎◎◎◎ ◇◇◇◇◇◇ ◇◇◇◇◇◇ ◇◇◇◇◇◇
◇◎◎◎◎◎ ◇◎◎◎◎◎ ◇◇◇◇◇◇ ◇◇◇◇◇◇
◇◎◎◎◎◎ ◇◎◎◎◎◎ ◇◎◎◎◎◎ ◇◇◇◇◇◇ <== Not matched
(1) (2) (3) (4)
又以 aab、a**b 为例:
◆◇◇◇◇ ◆◇◇◇◇ ◆◇◇◇◇ ◆◇◇◇◇
◇◎◎◎◎ ◇◆◆◆◇ ◇◆◆◆◇ ◇◆◆◆◇
◇◎◎◎◎ ◇◎◎◎◎ ◇◇◆◆◇ ◇◇◆◆◇
◇◎◎◎◎ ◇◎◎◎◎ ◇◎◎◎◎ ◇◇◇◇◆ <== Matched
(1) (2) (3) (4)
在DP的代码中,存在大量重复的运算,对于DP算法本身来讲这又是必要的重复,因为只有重复才能将有效的匹配通过一步步的迭代传递到最终的位置上,形成了左上角到右下角的真值传递路线。假如,上面的匹配模板不是 a*a*b,而是a*bb,那么,在矩阵的第四列表现就会天差地别,最重要的一点,它会产生一个间断不连续,这个不连续就匹配失败现象,从而导致相反的结果,如下图所示。要优化DP算法就可以从这点作为一个突破口:
◆◇◇◇◇ ◆◇◇◇◇ ◆◇◇◇◇ ◆◇◇◇◇
◇◎◎◎◎ ◇◆◆◇◇ ◇◆◆◇◇ ◇◆◆◇◇
◇◎◎◎◎ ◇◎◎◎◎ ◇◇◆◇◇ ◇◇◆◇◇
◇◎◎◎◎ ◇◎◎◎◎ ◇◎◎◎◎ ◇◇◇◇◇ <== Not matched
(1) (2) (3) (4)
如果数据变为abb、a*bb 呢,那么匹配过程就会变成:
◆◇◇◇◇ ◆◇◇◇◇ ◆◇◇◇◇ ◆◇◇◇◇
◇◎◎◎◎ ◇◆◆◇◇ ◇◆◆◇◇ ◇◆◆◇◇
◇◎◎◎◎ ◇◎◎◎◎ ◇◇◆◆◇ ◇◇◆◆◇
◇◎◎◎◎ ◇◎◎◎◎ ◇◎◎◎◎ ◇◇◆◆◆ <== Matched
(1) (2) (3) (4)
在分析动态规划的过程中,时常遇到思维短路的情况,脑子转不起来,真纠结!如果思维深度能再加一个层次,那问题解决起来是相当地轻松流畅。以下为一个DP解题的样版和测试数据:
bool isMatch(string s, string p) {
int m = s.length(), n = p.length(), msize = (m+1)*(n+1)*sizeof(bool);
bool (*matrix)[n+1] = (bool (*)[n+1])malloc( msize );
if( matrix != memset( matrix, false, msize ) ) cout << "memset failed!\n";
//s="" p="" is true
matrix[0][0] = true;
//Handle cases like s="" p="****"
for(int i=1;i<=n;i++)
{
if(p[(i-1)] == '*')
matrix[0][i] = true;
else
break;
}
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
char c = p[(j-1)];
if(c != '*')
matrix[i][j] = matrix[i-1][j-1] && (s[(i-1)] == c || c == '?');
else
{
matrix[i][j] = matrix[i][j-1] || matrix[i-1][j];
}
}
}
return matrix[m][n];
}
OK! elapse 0ms abcdefdefg abc*def not matched
OK! elapse 0ms abcdefdef abc*def matched
OK! elapse 0ms ab*efcdgiescdfimde ab*cd?i*de matched
OK! elapse 0ms abefcdgiescdfimde ab*cd?i*de matched
OK! elapse 0ms abecdgiscdfidecde ab*cd?i*de matched
OK! elapse 0ms abcdgisdecdfideide ab*cd?i*de matched
OK! elapse 0ms abbbaaaaaaaabbbaba... ab**b*bb*ab**ab***... matched
OK! elapse 111ms aaaaaaaaaaaaaaaaaa... aaaaaaaaaaaaaaaaaa... matched
对比第二例采用分割幻数方法实现的代码表现,档次是差几个级别的:
OK! elapse 0ms ab*efcdgiescdfimde ab*cd?i*de matched
OK! elapse 0ms abefcdgiescdfimde ab*cd?i*de matched
OK! elapse 0ms abecdgiscdfidecde ab*cd?i*de matched
OK! elapse 0ms abcdgisdecdfideide ab*cd?i*de matched
OK! elapse 0ms abbbaaaaaaaabbbaba... ab**b*bb*ab**ab***... matched
OK! elapse 1ms aaaaaaaaaaaaaaaaaa... aaaaaaaaaaaaaaaaaa... matched
因为DP使用的循环是完全枚举的,如果在循环体中使用 vector 之类的对象则对性能影响更是巨大。要说使用 vector 会给DP表现带来多大影响,给个测试对比就清楚了,vector 与不恰当的算法搭配可以直接接低整体性能,以下DP代码通过逆向比对来进行性能和空间上双重优化,性能如何参考后面的测试数据:
bool isMatch(string s, string p) {
int i, j, sl = s.length(), pl = p.length();
vector<bool> dp;
dp.push_back(1);
for(i=0; i<sl; i++) dp.push_back(0);
for(i=0; i<pl; i++)
{
// char pc = p.at(i); not a good idea to redefine variable looping
if(p.at(i) != '*')
{
for(j=0; j<sl; j++)
dp[sl-j] = dp[sl-j-1] && ( p.at(i)=='?' || s.at(sl-j-1)==p.at(i));
dp[0] = 0;
}
else
{
for(j=0; j<sl; j++)
dp[j+1] = dp[j+1] || dp[j];
}
}
return dp[sl];
}
OK! elapse 0ms ab*efcdgiescdfimde ab*cd?i*de matched
OK! elapse 0ms abefcdgiescdfimde ab*cd?i*de matched
OK! elapse 0ms abecdgiscdfidecde ab*cd?i*de matched
OK! elapse 0ms abcdgisdecdfideide ab*cd?i*de matched
OK! elapse 2ms abbbaaaaaaaabbbaba... ab**b*bb*ab**ab***... matched
OK! elapse 790ms aaaaaaaaaaaaaaaaaa... aaaaaaaaaaaaaaaaaa... matched
差距还是挺明显的吧!我尝试将重复多次的 p.at(i) 用一个变量来替代,即注解部分,但这真的不是个好主意,使用重复定义的并没能提高效率,相反还可能降低效率。所以代码还是简单点好,还要引入不必要的变量加重系统负担!
总结
本次解题中使用了动态内存分配,如果使用固定的数组肯定在兼容性上要下档次,平衡不好也会浪费大量空间,而使用动态数组就可以有效避免这种问题,这里将一些用到的方法罗列一下:
bool (*dp)[pL+1] = (bool (*)[pL+1]) malloc( (sL+1)*(pL+1)*sizeof(bool) );
for( int k=0; k<=sL; k++) dp[k][0] = false;
vector< vector<bool> > dp(sL+1, vector<bool>(sL+1, false));
int *m = (int *)malloc(2 * lp * sizeof(int));
int * g = (int *)malloc(ml);
if( g!=memset(g, magic, ml) ) cout << "memset failed!\n";
注意 vector 向量对象,两个左箭括号不能码到一起,这会引起编译器误认为是移位操作。通过 malloc 分配的内存一般都会需要进行初始化,如果需要特定的值可以考虑用循环,否则用memset是最简单快速的。
完整程序源代码
/*
* LeetCode #44 Wildcard Matching Demo by Jimbowhy
* compile: g++ -o wildcard wildcard.cpp && wildcard.exe
*/
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <ctime>
using namespace std;
// Accepted
class Solution {
public:
bool isMatch_N1(string s, string p) {
int k = 0, is = 0, ip = 0, ls = s.length(), lp = p.length();
int *m = (int *)malloc(2 * lp * sizeof(int));
unsigned short max_loop = -1, max_m = lp*2;
bool sm = false; // Super Magic not Sadism & Masochism
while(--max_loop){
if( p[ip] == '*' ){
if( k>= max_m ) cout << "max_m " << __FILE__ << __LINE__ << endl;
m[k] = is;
m[k+1] = ip;
ip ++;
k += 2;
sm = true;
}else if( ip<lp && ( p[ip]=='?' || is<ls && s[is]==p[ip] ) ){
ip++;
is++;
sm = false;
}else if( is<ls && sm ){
is++;
}else if( is==ls && ip==lp ){
return true;
}else if( is>ls || ip>lp ){
return false;
}else if( k>0 ){
k -= 2;
is = m[k] + 1;
ip = m[k+1];
sm = true;
}else return false;
}
if(!max_loop) cout << "max_loop " << __FILE__ << __LINE__ << endl;
return false;
}
// : Accepted 2016/3/11 17:37:05
bool isMatch(string s, string p) {
int i=0,j=0,k=0,l=0, magic = -1, gl = 0, sl = s.length(), pl = p.length();
int cl = 4, ml = pl*cl*sizeof(int);
bool seq = false;
char pc;
int * g = (int *)malloc(ml);
if( g!=memset(g, magic, ml) ) cout << "memset failed!\n";
for(i=0; i<pl; i++){
if( p[i]=='*' ) {
if( gl>3 && g[gl-cl]==magic ) continue;
//g[gl] = magic; set by memset
gl += cl;
}else{
if(gl==0||g[gl-cl]==magic){
g[gl]=i; g[gl+1] = 1; gl += cl;
}else g[gl-3] ++;
}
}
for(i=0; i<gl; i+=cl){
if( g[i]==magic ) continue;
for(j=k; j<sl; j++){
if( !seq ) k = j;
pc = p[g[i]+j-k];
seq = s[j]==pc || pc=='?';
if( seq && j-k+1<g[i+1]){
continue;
}else if( seq && j-k+1==g[i+1] ){
seq = false;
if( i>0 && g[i-cl]==magic && i+cl>=gl && k+g[i+1]<sl){
j = k; continue;
}
g[i+2] = j; k = j+1; break;
}else if( !seq ){
if( j>=sl-1 || i<=0 || g[i-cl]!=magic ) return false;
j = k;
}else return false;
}
if( g[i+2]<0 ) return false;
// the mase old one condition: i+cl<gl && k>=sl && ( i+8<gl || g[gl-cl]!=magic
}
if( sl==0 && gl>=8 ) return false; // for test: a, a*
if( sl==0 && sl==pl ) return true;
if( (g[0]==magic || g[0]==0) && (g[gl-cl]==magic || g[gl-2]+1==sl && g[gl-2]>=0) ) return true;
return false;
}
};
bool assert(Solution solve, string i, string p, bool a){
unsigned long st = time(NULL) * CLOCKS_PER_SEC + clock();
bool b = solve.isMatch(i, p);
st = (time(NULL) * CLOCKS_PER_SEC + clock())-st;
b==a? system("color 0a"):system("color 6a");
cout << (b!=a? "Failed!":" OK!") << "\t";
cout << "elapse " << st << "ms\t";
if(st>1000) cout << "time's up\t";
cout << (i.length()>22? i.substr(0,18)+"...":i) << "\t";
cout << (p.length()>22? p.substr(0,18)+"...":p) << "\t";
cout << (b? "matched":"not matched") << endl;
return b == a;
}
int main(){
/* pointer to class member. Nothing much useful!
typedef bool (Solution::*ToMatch)(string, string);
Solution sol;
ToMatch fun = &Solution::isMatch;
bool b = (sol.*fun)("a","b");
int a = 10, b = 10;
int (*p)[10] = (int (*)[10])malloc(sizeof(int) * a * b);
free(a);
*/
Solution solve;
assert(solve, "", "", true);
assert(solve, "a", "b", false);
assert(solve, "a", "b*", false);
assert(solve, "a", "*b", false);
assert(solve, "", "*", true);
assert(solve, "", "a*", false);
assert(solve, "", "?", false);
assert(solve, "", "**", true); //what for this case
assert(solve, "a", "a*", true); //leetcode say it is true!
assert(solve, "aa", "*", true);
assert(solve, "aa", "*aa", true);
assert(solve, "ab", "*a", false);
assert(solve, "aa", "*a", true);
assert(solve, "a", "*?*", true);
assert(solve, "a", "?*?", false);
assert(solve, "a", "*?*?", false);
assert(solve, "aa", "?*?", true);
assert(solve, "a", "**?**",true);
assert(solve, "a", "?", true);
assert(solve, "a", "*a", true);
assert(solve, "aa", "?", false);
assert(solve, "aa", "a", false);
assert(solve, "aa", "aa", true);
assert(solve, "aaa", "aa", false);
assert(solve, "aa", "a*", true);
assert(solve, "a", "ab*", false);
assert(solve, "aa", "a**", true);
assert(solve, "abcd","abc*d", true);
assert(solve, "aab", "a**b", true);
assert(solve, "aabb","a**b", true);
assert(solve, "ab", "?*", true);
assert(solve, "aab", "c*a*b", false);
assert(solve, "b", "*?*?*", false);
assert(solve, "aaab","b**", false);
assert(solve, "aabb","**??", true);
assert(solve, "abb", "**??", true);
assert(solve, "abcdefdefg", "abc*def", false); // Submission Result: Runtime Error
assert(solve, "abcdefdef", "abc*def", true);
assert(solve, "ab*efcdgiescdfimde","ab*cd?i*de", true);
assert(solve, "abefcdgiescdfimde", "ab*cd?i*de", true);
assert(solve, "abecdgiscdfidecde", "ab*cd?i*de", true);
assert(solve, "abcdgisdecdfideide","ab*cd?i*de", true);
assert(solve, "abbbaaaaaaaabbbabaaabbabbbaabaabbbbaabaabbabaabba
bbaabbbaabaabbabaabaabbbbaabbbaabaaababbbbabaaababbaaa",
"ab**b*bb*ab**ab***b*abaa**b*a*aaa**bba*aa*a*abb*a*a", true);
assert(solve, string(3072,'a'), string(1024,'a')+'*', true);
return 0;
}
参考资源
- LeetCode 44. Wildcard Matching
- Wildcard Matching O(N) by Jimbowhy
- Introduction to Algorithms, Third Edition