最前面說到,現在的正規表示式引擎一般是用有限狀態機(nfa)進行匹配。前面我們已經用parser得到了ast,現在我們來構建nfa。
乙個有限狀態機由5元組描述:
輸入字元集合不需要我們定義。狀態我們可以用乙個整型數表示,並且規定起始狀態總是狀態0,狀態轉移我們可以用乙個結構體表示。那麼現在的問題就是,如何在編譯期表示集合這樣的資料結構。
其實我們沒有必要實現乙個集合資料結構,因為不需要集合的去重特性,編譯期陣列就足夠了。
我們只需要實現乙個支援constexpr的array:
template struct array ;
constexpr int size() const
constexpr t operator(int idx) const
constexpr t& operator(int idx)
constexpr const t* begin() const
constexpr const t* end() const
};
array相比其他容器特殊的一點是,array沒有constructor,所以花括號初始化式的寫法不同,後面應用時可以看到。
nfa進行狀態轉移時要搜尋集合,所以我們另外加入乙個排序的方法便於二分搜尋。這裡偷懶,只寫插入排序。
// returns a new constexpr array in ascending order
template template constexpr auto array::sorted(cmp cmp) const else }}
return res;
}}
注意返回值是乙個新的陣列,因為編譯期計算不能修改資料,只能複製資料,所以要改變資料只能建立乙個新的物件。
先定義class nfa
,這裡只定義了狀態轉移函式集合和終止狀態集合。
idx_t
和idx_fs
用於跟蹤兩個array的資料插入位置。
state_count()
用於計算總狀態數,因為狀態使用由0開始的連續的整數表示,所以掃瞄一遍狀態轉移集合就能知道狀態數。
template class finite_automata
constexpr finite_automata(arrayt, arrayfs)
: transitions(t), final_states(fs)
constexpr finite_automata(const finite_automata& other)
: transitions(other.transitions), final_states(other.final_states), idx_t(other.idx_t), idx_fs(other.idx_fs) {}
constexpr int size_transition() const
constexpr int size_final_state() const
constexpr void add_transition(const transition& t)
constexpr void add_final_state(int fs)
constexpr int state_count() const;
// 排序兩個array
constexpr void sort();
// 二分查詢src的狀態轉移,返回在狀態轉移array中最左側項的索引
constexpr int lower_idx_in_trans(int src) const;
// 檢查fs是否在終止狀態集合中
constexpr bool is_final_state(int fs) const;
};
其中狀態轉移的定義:
這裡有乙個是否是epsilon轉移的標記,方便判斷。
struct transition
constexpr bool match(char c) const
};
從ast構建nfa的過程就是將很多小nfa連線起來,最簡單的nfa只有兩種,空或只有乙個狀態轉移。
// 有括號的狀態表示終止狀態
// (0)
static constexpr finite_automata<0, 1> fa_epsilon, };
// 0 --'a'--> (1)
template static constexpr finite_automata<1, 1> fa_char } }, };
順便解釋一下初始化式的括號:
fa_char } }, }
|<- ->| -- 傳給transition的初始化引數
|<- ->| -- 傳給array的資料成員
|<- 傳給array ->| |<->| -- 傳給array,由於資料成員是int,標準規定可以省略資料成員的一層括號
|<-finite_automata的初始化引數->|
解析ast構建nfa的實現類似於parser,也是遞迴和過載的形式。
至於為什麼要寫成fa_concat::res
這樣的形式,見下文connector的實現。
// 將concat中的fa用concat聯結器串聯起來,並遞迴構建
template constexpr auto& build_fa(concat)
// 將alter中的fa用alter聯結器連線,並遞迴構建
template constexpr auto& build_fa(alter)
template constexpr auto& build_fa(star)
// 遞迴出口,遇到字元
template constexpr auto& build_fa(ch)
// 遞迴出口,遇到空標記
constexpr auto& build_fa(epsilon)
現在再來實現真正連線nfa的部分。
注意到我沒有直接寫乙個函式,而是將函式包裝進乙個結構體裡,然後將函式的輸出儲存在乙個static成員變數中。這是為了強制編譯器在編譯期計算出函式的結果,否則可能出現編譯器將計算移到執行期的情況;計算結果同時相當於乙個快取,遇到相同的呼叫可以避免重複計算,加快編譯期計算的速度;最後還為了便於實現可變模板引數介面。
// 用乙個epsilon連線lhs終態到rhs初態
// from
// 0 --...--> (n1) 0 --...--> (n2)
// to
// 0 --...--> n1 --epsilon--> n1+1 --...--> (n2+n1+1)
template struct fa_concat
// copy rhs's transitions
for (transition t : rhs.transitions)
// connect lhs's final states to rhs
for (int fs : lhs.final_states) );
}// copy final states
for (int fs : rhs.final_states)
res.sort();
return res;
}template static constexpr auto f(t1 t1, t2 t2, ts... ts)
static constexpr auto res = f(lhs, rhs, fas...);
};
// 合併初態
// from
// 0 --...--> (n1) 0 --...--> (n2)
// to
// 0 --...--> (n1)
// |--...---> (n2+n1)
template struct fa_alter ;
// 終態增加乙個epsilon連線到初態
// from
// 0 --...--> (n1)
// to
// 0 --...--> (n1)
// ^--epsilon--|
template struct fa_star
for (int fs : fa.final_states) );
}res.sort();
return res;
}static constexpr auto res = f(fa);
};
至此,我們就可以使用build_fa(ast)
得到nfa了。 有限狀態機
有限狀態機 finite state machine,fsm 又稱有限狀態自動機,簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動作等行為的數學模型。狀態儲存關於過去的資訊,就是說 它反映從系統開始到現在時刻的輸入變化。轉移指示狀態變更,並且用必須滿足來確使轉移發生的條件來描述它。動作是在給...
有限狀態機
以前,只碰到過 陣列中所有數字只出現2次,只有乙個出現1次,找這個數的問題 每次迴圈異或陣列中元素,最後的結果就是single one。這次換作出現3次就懵逼了,主要原因,沒有使用過有限狀態機,應該說是連概念都沒有,所以這次一定要好好記錄一下 關於這道題的解釋discussion中woshidais...
有限狀態機
需要掌握的名詞 數字系統有兩大類有限狀態機 finite state machine,fsm moore狀態機和mealy狀態機。狀態機名 次態輸出 moore摩爾 f 現狀,輸入 g 現狀 mealy公尺粒 f 現狀,輸入 g 現狀,輸入 mealy型狀態機 下一狀態不但與當前狀態有關,還與當前輸...