nestjs 字符串匹配 js字符串匹配方法_字符串

//3个字符串匹配算法
var j1=require('./1')
var char2int=j1.char2int
var R=4                                                //包含a,b,c,d
var pattern='abababac'

var M=pattern.length
var N=300
var txt=j1.getWords(N,N,N,R).join('')                 //随机生成一个长度N*N的字符串
console.log('indexOf-->',ind=txt.indexOf(pattern));



/*KMP算法:
txt[i]和pattern[j]比较后,不管匹配成功还是失败,下一次都比较txt[i+1]和pattern[jj]。而jj=dfa[char2int(txt[i])-97][j](为了表达方便用jj代替j)。所有核心是构造二维数组dfa。

dfa[?,j](j>2)这一列表示某字符串str的前j-1个字符与pattern前j-1个字符匹配成功后,
再用?(字母表任何一个)和pattern[j]比较后,应该用pattern[dfa[?,j]]和str[j+1]比较。

dfa[?,j]和dfa[?,Y]应该一样(Y是pattern前j-1个字符的最长公共前缀缀长度,简称公共长度),
除了dfa[char2int(pattern[j])-97][j]这个特殊值。因为pattern[:Y]和pattern[j-Y:j]一样,
令Z=pattern前Y个字符的公共长度,则pattern[:Z]和pattern[Y-Z:Y]一样,所以pattern[:Z]和pattern[j-Z:j]一样,
所以pattern[:j]+?的公共长度大于等于pattern[:Y]+?的公共长度,注意这里不是pattern[:j+1]和pattern[:Y+1],
但pattern[:j]+?的公共长度大于pattern[:Y]+?的公共长度显然会导致矛盾,这就是代码dfa[i][j]=dfa[i][X]的含义。
令k=char2int(pattern[j])-97,则dfa[k][j]=dfa[k][X]。而dfa[k][j]正是pattern[:j+1]的公共长度,
这就是代码X=dfa[char2int(pattern[j])-97][X]的含义。遇到特殊值的证明类似。
*/

function KMPSearch(){
    let dfa=[]
    
    //构造确定有限状态自动机dfa
    for(let i=0;i<R;i++){dfa[i]=[0]}
    dfa[char2int(pattern[0])-97][0]=1
    let X=0
    for(let j=1;j<M;j++){
        for(var i=0;i<R;i++){dfa[i][j]=dfa[i][X]}
        dfa[char2int(pattern[j])-97][j]=1+j
        X=dfa[char2int(pattern[j])-97][X]
    }
    //console.log(JSON.stringify(dfa))
    
    for(let i=0,j=0;i<txt.length;i++){
        j=dfa[char2int(txt[i])-97][j]
        if(j==M){return i-M+1}
    }
    return -1
}



/*Boyer-Moore算法:
找到字母表里每个字符在pattern里最右边的索引,匹配失败后,
比如在txt的'c'失败,则向右移动pattern使这个'c'与pattern最右边的'c'对齐,再从右边第一个字符开始匹配。
如果对齐过程需要将pattern向左移动,则不用对齐,只需将pattern向右移动一位再重新开始匹配。
时间复杂度:一般情况N/M,最坏情况NM,如txt是'abab****',pattern是'aabababab'。
空间复杂度R,因为使用了right数组。*/

function BoyerMooreSearch(){
    let right=[]
    for(let i=0;i<R;i++){right[i]=-1}
    for(let i=0;i<M;i++){
        let j=char2int(pattern[i])-97
        if(j>-1){right[j]=i}
    }
    //console.log(right)
    var skip=0
    for(let i=0;i<txt.length-(M-1);i=i+skip){
        skip=0
        for(let j=M-1;j>=0;j--){
            let currentChar=txt[i+j]
            if(currentChar!=pattern[j]){
                skip=j-right[char2int(currentChar)-97]
//每次都应该向右移动,但可能出现currentChar在j右边的情况(此时一定有多个currentChar),所以特殊处理
                skip=skip<0?1:skip
                break
            }
        }
        if(skip==0){
            return i
        }
    }
    return -1
}



/*Rabin-Karp(指纹字符串查找)算法:
设pattern长度是len。计算出pattern的散列值patternHash和txt前len位的散列值txtHash,
相等的情况调用check;从txt[1]开始,对每一个txt[i]用txtHash减去txt[i-1]*RM,作用是去掉这个字符的散列值,
结果乘以R再加上txt[i+len-1]的散列值,这样就从txt[i-1:i+len-2]的散列值得到了txt[i:i+len-1]的散列值。
关于check有两种情况。
1:使用拉斯维加斯算法,正常调用check,从第i位开始遍历检查完整个pattern。
2:使用蒙特卡洛算法,取一个非常大的Q,使得散列值冲突的概率极低,这样不需要check。
时间复杂度:一般情况7N,最坏情况7N。空间复杂度1。*/

function check(i){return true}
function RabinKarpSearch(){
    let Q=Math.pow(2,31)-1        //取一个大素数即可。这里随便取的,应该不是素数
    var RM=Math.pow(R,M-1)
    var patternHash=0
    var txtHash=0
    for(let i=0;i<M;i++){
        patternHash=(patternHash*R+char2int(pattern[i])+Q)%Q
        txtHash=(txtHash*R+char2int(txt[i])+Q)%Q
    }
    if(txtHash==patternHash&&check(0)){return 0}
    for(let i=1;i<txt.length-(M-1);i++){
        //一开始写成txtDeleteFirstHash=(patternHash-××××××,找了半个小时bug
        txtDeleteFirstHash=(txtHash-(char2int(txt[i-1])*RM)%Q+Q)%Q
        txtHash=(txtDeleteFirstHash*R+char2int(txt[i+M-1])+Q)%Q
        if(txtHash==patternHash&&check(i)){return i}
    }
    return -1
}

/*Sunday算法:
用txt的i和pattern[0]开始一一匹配,匹配失败用txt[i+M]去right里查找,找到的值即指针(当前还在i)需要移动的长度,
即如果找到j,则用txt[i+j]和pattern[0]开始一一匹配。
令c=txt[i+M],如果c不在pattern里需要移动M+1,c在就使txt[i+M]和pattern最右边的c对齐,然后从头开始匹配。
*/
function SundaySearch(){
    let right=[]
    for(let i=0;i<R;i++){right[i]=M+1}
    for(let i=0;i<M;i++){
        let j=char2int(pattern[i])-97
        if(j>-1){right[j]=M-i}
    }
    //console.log(right)
    let i=0
    while(i<=txt.length-M){
        let j=0
        while(txt[i+j]==pattern[j]&&j++<M){
        }
        if(j>=M){return i}
        else if(i+M>=txt.length){break}				//txt[i+M]会越界
        else{i=i+right[char2int(txt[i+M])-97]}
    }
    return -1
}



console.log('BoyerMooreSearch-->',BoyerMooreSearch())
console.log('KMPSearch-->',KMPSearch())
console.log('RabinKarpSearch-->',RabinKarpSearch())
console.log('SundaySearch-->',SundaySearch())



//以下1.js,修改了getWords,可以控制字幕表的长度
exports.getWords=function getWords(maxcount=20,minlen=0,maxlen=11,R=26){
    let words=[]
    for (let j=0;j<maxcount;j++){
        let arr=''
        let len_=minlen+Math.floor(Math.random()*(maxlen-minlen+1))
        for(let l=0;l<len_;l++){
            arr = arr + String.fromCharCode(97+Math.floor(Math.random()*R))
        }
        words.push(arr)
    }
    return words
}
exports.char2int=(a)=>{
    return a==''?-1:a.charCodeAt()
}