http://blog.youkuaiyun.com/justin12zhu/article/details/3275263
字符串通配符的匹配算法
如何实现带有?*通配符的字符串(string)匹配呢,可以把问题拆开看
只含有?的模型(pattern),和只含有*的模型
只含有?的模型理解起来非常简单,因为?就等于一个字符,含有?的模型就等于一个字符串
判断是否匹配,只要在给定字符串中,查找含有?的字符串就行了,而在字符串中查找字符串就是用indexOf.
只不过现有的indexOf,不知道'?'就代表任意字符,所以需要自己动手改写一下.
程序如下
function indexOf(str: String, find: String, pos: Number): Number
{
if(undefined == str || undefined == find ) return;
if(undefined == pos || pos < 0) pos = 0;
var si=pos, slen=str.length, fi=0, flen=find.length;
while(si<slen && fi<flen)
{
if( str.charAt(si) == find.charAt(fi)
|| find.charAt(fi) == '?' ) // 碰到?号,可以继续往下比较
{
si++; fi++;
}
else
{
si = si-fi+1;
fi=0;
}
}
if( fi==flen ) return si-flen;
else return -1;
}
//-Example
var string = "abaabbabc";
var pattern = "a?babc";
trace( indexOf(string, pattern) ); // 3
可以看到,经过我们改写的indexOf,已经把'?'当作任意字符了.
而lastIndexOf的改写也是类似,不同的地方要注意字符的比较方向和indexOf相反
现在使用改写了的indexOf或者lastIndexOf就可以解决含有?的模型匹配问题了.
因为只需要在查到的同时再比较一下string和pattern的长度,看看是否相同就可以得出判断.
为了提高效率,可以把长度判断放在查询前面.
function matchWithInterrogation(str: String, pattern: String): Boolean
{
if(str.length != pattern.length ) return false;
else return indexOf(str, pattern) > -1;
}
------------------------------------------------------------------------
接着,我们来看只含有*通配符的模型匹配算法.
因为一个*代表的可以是0个或者N个的字符,所以一个含有*通配符的模型,是不能确定长度的.
不能简单的认为是pattern.length,这个长度值,只是把*看作普通字符来统计.
另外,几个连续的*的意义和一个*的意义是相同的,比如
*代表0-n个字符
**也代表0-n个字符....这个不用多解释了吧~
由于*的特殊性,我们无法确定它所代表的长度,反过来说,我们又可以认为它是任意长度.
也就说,可以把*看作是空字符,1,a,123,abcd!@$#%^%等等这样的字符或字符串.
既然这样,我们就不能把string和pattern按照逐字的形式一个一个来比较.
但可以变成一串一串来比较,而且可以不用管串与串之间是否是紧挨着的.
什么意思呢?
比如
string: Oh year.Totay is weekend!
pattern: *ye*a*e*
模型的有效字符是y,e,a,e
ye是连在一起的,可以把ye当成一个串来看,其它的也可以看作是一个串,只是这样的串长度为1.
我们先在string中找ye,找到了唯一存在的地方Oh (ye)ar....
接着找a,发现ye后面就有一个a,那么Oh (yea)r....
然后找e,Oh (yea)r.Totay is w(e)ekend!
于是找完了,但是我们发现a不一定在ye后面,在Totay中也有一个a,而e的话weekend里面就有3个,那是不是一定要按照上面的方法找呢?
是的.这里有个就近原则,如果从左向右找,就以在未查询过部分的最左边找到为匹配,从右向左找,亦然.
因为最近的被匹配掉了,还有远的可以用来再匹配.
假如不是这样,那就有可能出现应该匹配的而匹配不到
比如
weekend
*e*e*e*
如果此时不用就近原则,就无法完成3个e的匹配(前两个e间的*,可以认为是空字符)
使用就近原则,还利于方便程序的编写和方法的理解.
通过上面的解释,发现含有*通配符的模型,只是在string中不停的依次搜索而已.既然是搜索当然要用到indexOf这个函数啦,哈哈是不是很眼熟.
那怎么用呢?很自然想到,把含有*的模型,以*为分界线,分成几个包含有效字符的串.
然后以就近原则在指定的string里面,逐个的把这些串找出来,如果都找出来了,那就是匹配的,只要一个串没有找到,那就是不匹配了.
因为前面我们已经改写了indexOf这个函数,使得它可以辨别?通配符,所以在这里,就可以把?当成是一个串中的有效字符来理解.
我们还要感谢flashplayer提供了String.split这个函数,哈哈,分割的任务就靠它了.现在我们万事俱备就欠编程.
在写程序之前,有几个注意事项.
第一,注意模型的头是*,还是有效字符
如果是*,那*后的第一个串,可以在string的任意位置
如果是有效字符,那第一个串,一定也要在string的头
尾部也是如此
第二,注意几个极端现象.
a)模型没有*,那就是一个含?字符串的匹配,可以用前面的matchWithInterrogation函数来解决
b)模型只有*,前面说了连续的*意义等同于只有一个*,而只有一个*就说明string可以是任意字符,包括空
c)模型为空,也就是只有string为空时才匹配
d)由bc认为,连续的*间存在一个空字符,空字符匹配空字符,而在字符串范围内查找空字符,就返回搜索的起始位置.
第三,注意String.split的分割符在字符串的开头或者末尾时,第一或最后一个元素为空字符
好了,可以开始动手写程序了
function matchWithAsterisk(str: String, pattern: String): Boolean
{
if(undefined == str || undefined == pattern ) return;
var ps = pattern.split('*');
if( 1==ps.length ) // 没有*的模型
return matchWithInterrogation(str, ps[0]);
var si = indexOf(str, ps[0], 0); // 从string头查找第一个串
if( 0 != si ) return false; // 第一个串没找到或者不在string的头部
si += ps[0].length; // 找到了串后,按就近原则,移到未查询过的最左边
var plast=ps.length-1; // 最后一串应单独处理,为了提高效率,将它从循环中取出
var pi=0; // 跳过之前处理过的第一串
while(++pi<plast)
{
if(''==ps[pi]) continue; //连续的*号,可以忽略
si = indexOf(str, ps[pi], si); // 继续下一串的查找
if(-1==si) return false; // 没有找到
si += ps[pi].length; // 就近原则
}
if(''==ps[plast]) // 模型尾部为*,说明所有有效字符串部分已全部匹配,string后面可以是任意字符
return true;
// 从尾部查询最后一串是否存在
var last_index = lastIndexOf(str, ps[plast]);
// 如果串存在,一定要在string的尾部, 并且不能越过已查询过部分
return (last_index == str.length - ps[last].length) && (last_index >= si);
}
//-Example
var string = "Oh year.Totay is weekend!";
var pattern = "*ye*a*e*";
trace( match(string, pattern) ); // true
var string = "weekend";
var pattern = "e*e*e";
trace( match(string, pattern) ); // false