20行代碼實現一個簡單的Regex Engine

項目地址git

npm install or yarn //安裝依賴

npm run test // 運行測試文件

複製代碼

咱們將會實現一個簡單正則引擎,它的規則以下github

語法 含義 example 匹配
a 匹配文本字面量 "a" "a"
* 匹配0或多個前字符 "a*" "","a","aa"
? 匹配0或1個前字符 "a?" "","a"
. 匹配任意字符 "." "a","b"
^ 起始匹配符 "^a" "a","aa","ab"
$ 結尾匹配符 "a$" "aaa","bba"

咱們用Typescript編寫Codetypescript

先考慮單個字符匹配

單個字符,狀況簡單的多。它有如下五種狀況:shell

/** * * @param pattern 匹配的字符 * @param char 須要被匹配的字符 * * case 1 : matchOneChar('','a')->true * case 2 : matchOneChar('a','')->false * case 3 : matchOneChar('.','b')->true * case 4 : matchOneChar('a','b')->false * case 5 : matchOneChar('a','a')->true * * 單個字符匹配有以上狀況 */
const matchOneChar =(pattern:string,char:string):boolean=>{
    if(!pattern) return true;   //case 1
    if(!char) return false; //case 2
    if(pattern === ".") return true; //case 3
    return pattern === char;  // case 4,5
}
複製代碼

配上測試

import {search,matchOneChar} from './regex';

describe("匹配單個字符",()=>{
    it("匹配字符爲空",()=>{
        expect(matchOneChar('','a')).toBe(true)
    });
    it("匹配內容爲空",()=>{
        expect(matchOneChar('a','')).toBe(false);
    })
    it("特殊字符 . 匹配",()=>{
        expect(matchOneChar('.','a')).toBe(true);
        expect(matchOneChar('.','b')).toBe(true);
    })
    it("匹配字符是否相同",()=>{
        expect(matchOneChar('a','a')).toBe(true);
        expect(matchOneChar('a','b')).toBe(false);
    })
})
複製代碼

多個字符的匹配

先考慮頭部對齊匹配

僅考慮文本匹配npm

/** * * @param pattern 匹配字符 * @param text 匹配文本 */
const match =(pattern:string,text:string):boolean=>{
    if (pattern === "") return true;
    if (!text) return false;
    return matchOneChar(pattern[0],text[0]) &&match(pattern.slice(1),text.slice(1));
}
複製代碼

添加 「$」匹配符

/**
 * 
 * @param pattern 匹配字符
 * @param text    匹配文本
 */
const match =(pattern:string,text:string):boolean=>{
    if (pattern === "") return true;
    if (pattern === "$"&& text==="") return true;
    if (!text) return false;
    return matchOneChar(pattern[0],text[0]) &&match(pattern.slice(1),text.slice(1));
}
複製代碼

添加 「?」匹配符

const match =(pattern:string,text:string):boolean=>{
    if (pattern === "") return true;
    if (pattern === "$"&& text==="") return true;
    if (!text) return false;
    //  添加 「?」匹配
    if (pattern[1] === '?'){
        return matchOneChar(pattern[0],text[0])&&match(pattern.slice(2),text.slice(1))  || match(pattern.slice(2),text);
    }
    return matchOneChar(pattern[0],text[0]) &&match(pattern.slice(1),text.slice(1));
}
複製代碼

添加 「*」匹配符

const match =(pattern:string,text:string):boolean=>{
    if (pattern === "") return true;
    if (pattern === "$"&& text==="") return true;
    if (!text) return false;
    if (pattern[1] === '?'){
        return matchOneChar(pattern[0],text[0])&&match(pattern.slice(2),text.slice(1))  || match(pattern.slice(2),text);
    }
    if (pattern[1] === '*'){
        return matchOneChar(pattern[0],text[0])&&match(pattern,text.slice(1))  || match(pattern.slice(2),text);
    }

    return matchOneChar(pattern[0],text[0]) &&match(pattern.slice(1),text.slice(1));
}
複製代碼

頭部不對齊狀況

能夠經過轉換匹配符或是匹配文本轉換成頭部對齊狀況,有兩種處理方案:bash

  • 匹配符前面加上「.*」
const search =(pattern:string,text:string):boolean=>{
    if (pattern[0] === '^') {
        return match(pattern.slice(1), text);
      } else {
        return match('.*' + pattern, text);
      }
}
複製代碼
  • 拆分匹配文本,只要又一個匹配上就OK
const search =(pattern:string,text:string):boolean=>{
    if (pattern[0] === '^') {
        return match(pattern.slice(1), text);
      } else {
        return text.split('').some((_, index) => {
            return match(pattern, text.slice(index));
          });
      }
}
複製代碼

總結:

思考過程由頂層開始測試

頭部不對齊 -> 頭部對齊 -> 單個字符匹配 將大的問題一步一步根據條件拆分紅小問題,如此往復。 整個實現代碼在20行左右。ui

相關文章
相關標籤/搜索