整个引擎代码在github上,地址为:https://github.com/sun2043430/RegularExpression_Engine.git
接上篇《正则表达式引擎的构建——基于编译原理DFA(龙书第三章)——3 计算4个函数》
本章将介绍如何使用followpos集合来构建DFA。相关算法和例子在龙书中文第二版的3.9.5节(根据正则表达式构建DFA),算法3.36和例3.37。
添加结尾END符号表示接受状态的语法树和函数表
我们把上一篇文章中所举的例子——正则表达式(a|b)*abb,其对应的语法树和计算得到的4个函数列表再展示一下,我们需要借助followpos集合来构建DFA。下面展示的图形和表格中,我们在正则表达式的末尾添加了表示接受状态的END符号。
正则表达式(a|b)*abb对应的抽象语法树(注意最后的END标志):
正则表达式(a|b)*abb对应的nullable值、firstpos集合、lastpos集合和followpos集合(在结尾添加了END标志):
根据followpos集合创建DFA的实例说明
在构建DFA时,我们需要followpos集合。按照龙书的算法3.36,首先我们从根节点的firstpos集合入手。也就是cat11的firstpos集合{a1,b2,a5},将其放入队列Q中(此时Q中只有这一个集合)。
接下来我们的工作将转入处理队列Q。具体处理方法如下:
从头开始遍历队列Q,对其中的每一个集合S考察每一个输入字符(例如a)可匹配的节点p,所有p的followpos集合再求并集U,如果集合U在队列Q中存在则不添加到队列中,如果集合U在队列Q中不存在,则将其添加到队列Q中,最后不管U是否存在于队列Q中,我们都要记录对应的转换——集合S遇到输入字符a到达集合U。
上面讲的很抽象,我们结合例子来看,队列Q中首先放入了集合{a1,a5},对于此集合存在输入字符a和b上的转换,我们逐一来处理,首先处理输入字符为a的情况,a匹配的节点是a1和a5,所以将followpos(a1)和followpos(a5)求并集,也就是{a1,a5}并上{b7},得到的集合是{a1,a5,b7},此集合不存在于队列Q中,所以我们需要添加此集合。这样队列Q变成下图所示:
(队列图1:2个集合)
同时我们需要记录转换关系:
队列Q中的第1个集合在输入为a时,会转换到队列Q的第2个集合。
接下来看输入字符为b的情况,字符b匹配的节点在集合{a1,a5}中只有b2,所以看b2的followpos集合,followpos(b2)为{a1,a5},此集合在Q中已经存在(队列Q的第一个对象),所以无需加入到队列Q中,但转换关系还是要记录的:
队列Q中的第1个集合在输入为b时,会转换到队列Q的第1个集合。
现在队列Q中的第1个集合处理完了,接下来处理第2个集合{a1,b7},还是一样此集合存在输入字符a,b。所以逐一处理,先看a,a匹配的节点是a1,a5,所以对followpos(a1)和followpos(a5)求并集,得到的集合是{a1,b7}。在队列Q中存在。只记录转换关系:
队列Q中的第2个集合在输入为a时,会转换到队列Q的第2个集合。
再来看字符b,匹配的是b2,b7两个节点,followpos(b2)和followpos(b7)求并集,得到的集合是{a1,b9},此集合在Q中没有,所以需要添加,这样队列变成下图所示:
(队列图2:3个集合)
同时要记录转换关系:
队列Q中的第2个集合在输入为b时,会转换到队列Q的第3个集合。
第2个集合处理完之后,接下来处理第3个集合{a1,b9},和前面分析的一样,分别考虑输入字符a、b,对于a,匹配a1和a5节点,求这两个节点的followpos集合的并集为{a1,b7},此集合在队列Q中已经存在,所以只记录转换关系:
队列Q中的第3个集合在输入为a时,会转换到队列Q的第2个集合。
再来看字符b,匹配的是b2,b9两个节点,followpos(b2)和followpos(b9)求并集,得到的集合是{a1,END},此集合在Q中没有,所以需要添加,这样队列变成下图所示:
(队列图3:4个集合)
同时要记录转换关系:
队列Q中的第3个集合在输入为b时,会转换到队列Q的第4个集合。
队列Q中的第4个集合在输入为a时,会转换到队列Q的第2个集合。
当遇到的输入字符为b时,followpos(b2)到的集合是{a1,a5}。在队列Q中已存在,所以只记录转换关系为:
队列Q中的第4个集合在输入为b时,会转换到队列Q的第1个集合。
至此,队列Q中已经没有未处理的集合了。遍历队列Q的过程结束。
我们整理上面的转换关系(上面红色字体部分),将队列Q中的每个集合看成一个状态,则转换关系为下表:
状态1,2,3,4分别对应队列Q中的每一个集合,表中的状态4是接受状态,因为状态4对应的集合为{a1,END},其中包含了END标志。根据此表建立DFA,如下图:
算法总结
至此,根据根节点的firstpos集合和其他节点的followpos集合,构建DFA的过程就讲解完毕了。最后我们总结一下算法:
1 将根节点的firstpos集合放入队列Q中。
2 当队列Q中有未处理的对象S(也就是集合、状态)时,我们处理S中的每个输入符号(例如a),令U是S中和输入符号a匹配的所有节点的followpos集合的并集。
3 如果U在不在队列Q中,则将U加入到队列尾部,如果U在队列Q中已经存在,则无需添加。
4 不管U是否已经出现在队列Q中,都需要记录转换关系:S在输入符号为a时转换到U。
5 如果队列Q中还有未处理的对象回到步骤2。否则处理结束。
6 如果队列Q中的某个对象(集合、状态)包含了END标志,则该对象(集合、状态)为接受状态。
下图为龙书中的算法介绍,我上面的算法步骤思想与之相同,只是在表述上有一些差异。
关键代码
下面是创建DFA的关键代码:
BOOL CDFA::CreateDFA(CNodeInTree *pNode) { int nIdxCur = 0; int nIdxNext= 0; BOOL bRet = FALSE; vector<CNodeInTree*>::iterator itNode = pNode->m_vecFirstPos.begin(); set<CNodeInTree*> setNode; map<unsigned char,set<CNodeInTree*>> mapSet; m_lstSet.clear(); m_lstNodeRelation.clear(); m_setAcceptingIdx.clear(); try { // prepare start state for ( ; itNode != pNode->m_vecFirstPos.end(); itNode++ ) { setNode.insert(*itNode); } m_lstSet.push_back(setNode); if (IsContainAcceptingState(setNode)) { m_setAcceptingIdx.insert(nIdxCur);// the set is an accepting state } // end prepare start state // Construct DFA set,translation relation. list<set<CNodeInTree*>>::iterator it = m_lstSet.begin(); for ( ; it != m_lstSet.end(); it++,nIdxCur++) { mapSet.clear(); setNode.clear(); setNode = *it; // collect all translation char into mapSet set<CNodeInTree*>::iterator itSet = setNode.begin(); for ( ; itSet != setNode.end(); itSet++ ) { unsigned char ch = (*itSet)->m_pToken->GetChar(); mapSet[ch].insert(*itSet); } // end collect // for each translation char,get the set that correspond to translation char map<unsigned char,set<CNodeInTree*>>::iterator itMap = mapSet.begin(); for ( ; itMap != mapSet.end(); itMap++ ) { unsigned char ch = itMap->first; set<CNodeInTree*> & setNodeTemp = itMap->second; set<CNodeInTree*> setNodeNext; // get the union of followpos(p) for all p in the setNodeTemp CHECK_BOOL ( GetNextSet(setNodeTemp,setNodeNext) ); if (setNodeNext.empty()) continue; if (!IsNodeSetInList(setNodeNext,nIdxNext)) { //if the set not in list,add it into list m_lstSet.push_back(setNodeNext); if (IsContainAcceptingState(setNodeNext)) { m_setAcceptingIdx.insert(nIdxNext);// the set is an accepting state } } DFANodeRelation stNodeRelation(nIdxCur,ch,nIdxNext); m_lstNodeRelation.push_back(stNodeRelation); } // end for } } catch (...) { goto Exit0; } bRet = TRUE; Exit0: return bRet; } BOOL CDFA::GetNextSet(set<CNodeInTree*> & setNodeTemp,set<CNodeInTree*> & setNodeNext) { BOOL bRet = FALSE; set<CNodeInTree*>::iterator it = setNodeTemp.begin(); for ( ; it != setNodeTemp.end(); it++ ) { vector<CNodeInTree*> & vecFollowPos = (*it)->m_vecFollowPos; vector<CNodeInTree*>::iterator itVec = vecFollowPos.begin(); for ( ; itVec != vecFollowPos.end(); itVec++ ) { try { setNodeNext.insert(*itVec); } catch (...) { goto Exit0; } } } bRet = TRUE; Exit0: return bRet; } BOOL CDFA::IsNodeSetInList(set<CNodeInTree*> &setNodeNext,int &nIdx) { list<set<CNodeInTree*>>::iterator it = m_lstSet.begin(); for (nIdx = 0; it != m_lstSet.end(); it++,nIdx++) { if (*it == setNodeNext) { return TRUE; } } return FALSE; } BOOL CDFA::IsContainAcceptingState(set<CNodeInTree*> &setNode) { for (set<CNodeInTree*>::iterator it = setNode.begin(); it != setNode.end(); it++) { if (eType_END == (*it)->m_pToken->GetType()) { return TRUE; } } return FALSE; }原文链接:https://www.f2er.com/regex/362807.html