标签:
(正则引擎已完成,Github)
最小化 DFA 是引擎中另外一个略繁琐的点(第一个是构建语法树)。
基本思路是,先对 DFA 进行重命名,然后引入一个拒绝态 0,定义所有状态经过非接受字符转到状态 0,0 接受所有字符转换为自身。也就是说我们先建立一个转换表,然后把第一行填写为:
| state | a | b | c | d | e | f | g | h | ... |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
再之后,我们讲 DFA 的其余状态从 1 开始重命名,填入状态表。代码实现如下:
// rename all states
for (Set<NFAState> nfaState : oriDFATransitionMap.keySet()) {
if (initStateAfterRenaming == -1 && nfaState.equals(initClosure)) {
initStateAfterRenaming = renamingStateID; // record init state id
}
stateRenamingMap.put(nfaState, renamingStateID++);
}
renamedDFATransitionTable.put(0, newRejectState()); // the reject state 0
finalFlags.put(0, false);
// construct renamed dfa transition table
for (Map.Entry<Set<NFAState>, Map<Character, Set<NFAState>>> entry : oriDFATransitionMap.entrySet()) {
renamingStateID = stateRenamingMap.get(entry.getKey());
int[] state = newRejectState();
for (Map.Entry<Character, Set<NFAState>> row : entry.getValue().entrySet()) {
state[row.getKey()] = stateRenamingMap.get(row.getValue());
}
renamedDFATransitionTable.put(renamingStateID, state);
if (entry.getKey().contains(finalNFAState)) {
finalFlags.put(renamingStateID, true);
} else finalFlags.put(renamingStateID, false);
}
这里有一个 finalFlags 用来记录哪些 DFA 的终态包含了 NFA 的终态 1。(这些状态都作为 DFA 的终态)
接下来,把所有状态分为终态作为一个 group,非终态作为另一个 group:
// split states to final states and non-final states
Map<Integer, Integer> groupFlags = new HashMap<>();
for (int i = 0; i < finalFlags.size(); i++) {
boolean b = finalFlags.get(i);
if (b) groupFlags.put(i, 0);
else groupFlags.put(i, 1);
}
我们定义任意 group 中的任意状态的转换规则为:接受某个字符转移到某个 group(区别于转换到某个状态)。
不停地遍历所有的 group,把同一个 group 中具有不同转换规则的状态分隔为不同的 group。
do { // splitting, group id is the final state id
preGroupTotal = groupTotal;
for (int sensitiveGroup = 0; sensitiveGroup < preGroupTotal; sensitiveGroup++) {
// <target group table, state id set>
Map<Map<Integer, Integer>, Set<Integer>> invertMap = new HashMap<>();
for (int sid = 0; sid < groupFlags.size(); sid++) { //use state id to iterate
int group = groupFlags.get(sid);
if (sensitiveGroup == group) {
Map<Integer, Integer> targetGroupTable = new HashMap<>(CommonSets.ENCODING_LENGTH);
for (char ch = 0; ch < CommonSets.ENCODING_LENGTH; ch++) {
int targetState = renamedDFATransitionTable.get(sid)[ch];
int targetGroup = groupFlags.get(targetState);
targetGroupTable.put((int) ch, targetGroup);
}
Set<Integer> stateIDSet = invertMap.get(targetGroupTable);
if (stateIDSet == null) {
stateIDSet = new HashSet<>();
invertMap.put(targetGroupTable, stateIDSet);
}
stateIDSet.add(sid); // gather all sids having the same target group table into this set
}
}
boolean first = true;
for (Set<Integer> stateIDSet : invertMap.values()) {
if (first) {
first = false;
} else {
for (int sid : stateIDSet) {
groupFlags.put(sid, groupTotal);
}
groupTotal++;
}
}
}
} while (preGroupTotal != groupTotal);
如此分到不可再分,再把每一个 group 中的所有状态合并为同一个状态,这样我们就得到了 group 个状态,就完成了 DFA 的最小化。
接着是再次确定整个 DFA 的初态、终态和拒绝态。我们只要把包含这些状态的 group 作为最小化之后的相应状态就可以了。
// determine initial group state
is = groupFlags.get(initStateAfterRenaming);
// determine reject group state
rs = groupFlags.get(0);
// determine final group states
Set<Integer> finalGroupFlags = new HashSet<>();
for (int i = 0, groupFlagsSize = groupFlags.size(); i < groupFlagsSize; i++) {
Integer groupFlag = groupFlags.get(i);
if (finalFlags.get(i)) {
finalGroupFlags.add(groupFlag);
}
}
fs = new boolean[groupTotal];
for (int i = 0; i < groupTotal; i++) {
fs[i] = finalGroupFlags.contains(i);
}
最后一步是把 DFA 转换为一个以数组形式存放的状态表,就可以用来进行快速而准确的正则匹配了。
// construct the final transition table
transitionTable = new int[groupTotal][];
for (int groupID = 0; groupID < groupTotal; groupID++) {
for (int sid = 0; sid < groupFlags.size(); sid++) {
if (groupID == groupFlags.get(sid)) {
int[] oriState = renamedDFATransitionTable.get(sid);
int[] state = new int[CommonSets.ENCODING_LENGTH];
for (char ch = 0; ch < CommonSets.ENCODING_LENGTH; ch++) {
int next = oriState[ch];
state[ch] = groupFlags.get(next);
}
transitionTable[groupID] = state;
break;
}
}
}
至此,我们的整个 DFA 正则表达式引擎就构建完成了。
之后我用一个 apache log 的正则做了一下性能测试,这个引擎的匹配速度比 JDK 自带的正则要快至少一倍,还是有一定的实用性的,所以我把它放到了 Github 上,欢迎下载源码。
个人水平有限,代码中一定有不少尚待优化的地方,如有建议请不吝赐教。
实现一个 DFA 正则表达式引擎 - 4. DFA 的最小化
标签:
原文地址:http://www.cnblogs.com/zbdzzg/p/4509326.html