实现一个 DFA 正则表达式引擎 - 3. NFA 的确定化
(正则引擎已完成,Github)
我们上一节已经将 NFA 构建出来了,我们的 NFAState 对象的结构实际上是这样的:
NFAState {
private Set<NFAState> directTable;
private Map<Character, Set<NFAState>> transitionMap;
private int id;
}
其中 transitionMap 是该状态接受 Character 后可以转换的状态的映射表。
那么以初始状态 0 为例,我们是不是可以认为状态 0 接受字符 a 后能去的状态全部包含在 transitionMap.get('a') 里面了呢?答案是否定的,因为我们需要考虑状态的闭包。正确答案应该是:
某状态 S 接受字符 c 可以转换到的状态集合为 S 的闭包接受字符 c 转换的状态集合的闭包。
通过这一条规则我们可以对 NFA 进行处理,构造一个考虑过闭包的 transitionMap:
Map<NFAState, Set<NFAState>> closureMap = calculateClosure(nfaStateList);
// construct a NFA first
Map<NFAState, Map<Character, Set<NFAState>>> nfaTransitionMap = new HashMap<>();
for (NFAState state : nfaStateList) {
Map<Character, Set<NFAState>> subMap = new HashMap<>();
for (char ch = 0; ch < CommonSets.ENCODING_LENGTH; ch++) {
Set<NFAState> closure = closureMap.get(state);
Set<NFAState> reachable = traceReachable(closure, ch, closureMap);
if (!reachable.isEmpty()) {
subMap.put(ch, reachable);
}
}
nfaTransitionMap.put(state, subMap);
}
之后就是对 NFA 的确定化。
确定化的思想即是把一个状态接受某字符所能到达的所有状态的集合作为一个状态,就是相当于并行地处理 NFA,原理部分没有找到非常通俗易懂的图,就先省略掉,可能以后会补上,大家可以参考编译原理或者自动机相关的书籍和网站。
private void constructOriginalDFA(Set<NFAState> stateSet, Map<NFAState, Map<Character, Set<NFAState>>> nfaTransitionMap, Map<Set<NFAState>, Map<Character, Set<NFAState>>> originalDFATransitionMap) {
Map<Character, Set<NFAState>> subMap = originalDFATransitionMap.get(stateSet);
if (subMap == null) {
subMap = new HashMap<>();
originalDFATransitionMap.put(stateSet, subMap);
}
for (char ch = 0; ch < CommonSets.ENCODING_LENGTH; ch++) {
Set<NFAState> union = new HashSet<>();
for (NFAState state : stateSet) {
Set<NFAState> nfaSet = nfaTransitionMap.get(state).get(ch);
if (nfaSet != null) {
union.addAll(nfaSet);
}
}
if (!union.isEmpty()) {
subMap.put(ch, union);
if (!originalDFATransitionMap.containsKey(union)) {
constructOriginalDFA(union, nfaTransitionMap, originalDFATransitionMap);
}
}
}
}
到这里,我们就完成了 DFA 正则引擎的第三步:NFA 的确定化。