首先需要对词典创建一个DFA(如果对于DFA不熟悉的话,可以看看形式语言和自动机方面的书),构造DFA的过程如下:
对于每一个词a1 a2 … an,依次按该词中每个字的顺序,遍历DFA的状态跳转表,直到遇到该DFA不能接受某个输入时,假设这个字为ai,那么从ai … an将依次建立新的状态以及状态跳转,同时需要对ai进行编码。DFA的实现需要一个数组去保存所有的状态,每个数组元素是一个集合,该集合包含了该状态所能接受的输入以及对应的下一个状态,因而会占用比较大的内存。
其次是把DFA转化成双数组Trie(如果对于双数组Trie不熟悉的话,可以参考http://linux.thai.net/~thep/datrie/datrie.html)转化过程如下:
base值从1开始,双数组Trie的第一个元素的base值为1,check为-1,状态si从0开始,如果双数组Trie的大小不能够容纳状态si的所有下一个状态,那么需要申请更大的内存,之后遍历双数组Trie,找到合适的base值i,设置好Trie.base和Trie.check的值,如果该状态也是一个终止状态,那么需要把Trie.base的最高位设置为1,之后遍历si状态的所有的下一个状态,假设si的输入为ai,ai对应的编码为idx,对应的下一个状态为sj,那么设置Trie[idx].check=i,Trie[idx].base=-1,并把(idx,sj)插入到一个队列中,作为下依次要扩展的状态。所有的状态处理完后,就得到了双数组Trie。为了使双数组Trie尽量占用小的内存,在插入队列时,需要根据sj的所有下一个状态的个数以及空隙的密度进行权衡,比如空隙越大就越靠近队列的前面,使得扩展下一个状态时,尽量能够在sj的状态空隙中来保存。如果插入队列的时间越长,那么构造双数组Trie的时间就越长,目前我采用的是状态空隙比较,即谁的空隙越多,那么就插入到队列的队首,并且如果发现在找base值,如果找了很多次才找到合适的值,那么将适当增加下一次扩展时的base值得初始值,这样就能减少查找base值的时间。
双数组Trie查询词典构造算法由于有些长,暂时未贴出,之后会贴一个带有完整源代码的附件,欢迎大家讨论更好的优化算法。
该双数组Trie查询词典构造算法对Firtex的影响:
由于表示终止状态和以及没有下一个状态的终止状态有些变化,那么Analyzer中需要修改的地方是:
头文件需要修改的地方:
typedef unsigned int int_t;
typedef unsigned short short_t;
struct state
{//state information in double-array trie
int_t base;//base value
int_t check;//check value
int_t handle;//handle for dictionary entry
};
short_t m_charset[_CHARSET_SIZE];
cpp文件需要修改的地方:
#define FINAL_TAG 0x80000000
nextTokensInternal(CReader* reader,CTokens* pInput)
{
int i = 0,nWordLen = 0,nCharLen = 0,j = 0,nStart = 0;
size_t nLen;
char* sLine = NULL;
int_t check,base, nPos;
short_t code;
int nWordHandle=0;
termid_t* tokenBuff = (termid_t*)pInput->getBuffer();
CTokenXs<termid_t>* pTokenXs = (CTokenXs<termid_t>*)pInput->asTokensX();
int buffbase = 0;
char lastwordbuf[50];
int lastwordlen = 0;
#define INIT_STATE() /
base=1; /
check=0; /
nWordLen=0; /
nStart=i; /
nWordHandle=0;
INIT_STATE();
bool bIsEof = false;
do
{
if(nWordLen > 0)
{
strncpy(lastwordbuf,sLine + nStart - buffbase,nWordLen);
lastwordlen = nWordLen;
}
sLine = reader->readWithoutCopy(nLen);
bIsEof = reader->isEof();
buffbase = i;
while( (i-buffbase < (int)nLen) || bIsEof && ( (nStart-buffbase) < ((int)nLen-1) && nWordLen>0) )
//while (i-buffbase < (int)nLen)
{
if (i-buffbase >= (int)nLen) //( (nStart-buffbase) < ((int)nLen-1) && nWordLen>0)
{//Not complete word in the last part
if(!pTokenXs->addTokenX(nWordHandle))
{
return pInput;
}
i=nStart+nWordLen;
INIT_STATE();
continue;
}
if(i - buffbase < 0)//has back off
{
int c = lastwordlen + i - buffbase;
if ( (lastwordbuf[c] > 0))
{//Single byte character
code = (unsigned char)lastwordbuf[c];
nCharLen = 1;//Character Length
}
else//2-byte character
{
if( (c+1) == lastwordlen )
{
code = ((unsigned char)lastwordbuf[c] << 8) | (unsigned char)sLine[0];
}
else
{
code = ((unsigned char)lastwordbuf[c] << 8) | (unsigned char)lastwordbuf[c+1];//Get code
nCharLen = 2;//Character Length
}
}
}
else
{
if ( (sLine[i-buffbase] > 0) || ( (i+1-buffbase == nLen) && reader->isEof()) )
{//Single byte character
code = (unsigned char)sLine[i-buffbase];
nCharLen = 1;//Character Length
}
else//2-byte character
{
if( (i+1-buffbase) == nLen )
{
//code = 256*(uint8_t)sLine[i-buffbase];
code = ((unsigned char)sLine[i-buffbase]) << 8;
strncpy(lastwordbuf,sLine + nStart - buffbase,nWordLen + 1);
lastwordlen = nWordLen + 1;
sLine = reader->readWithoutCopy(nLen);
code |= (unsigned char)sLine[0];
nCharLen = 2;
buffbase = i + 1;
}
else
{
code = ((unsigned char)sLine[i - buffbase] << 8) | (unsigned char)sLine[i+1-buffbase];//Get code
nCharLen = 2;//Character Length
}
}
}
i += nCharLen;
if ( !m_charset[code] )
{//Invalid Character
if (nWordLen>0)
{
if( nWordHandle != 0 || (j>0 && (tokenBuff[j-1] != 0) ))
{
if(!pTokenXs->addTokenX(nWordHandle))
{
return pInput;
}
j++;
}
i=nStart+nWordLen;//added 06.5.12
}
else
{
if( j>0 && (tokenBuff[j-1] != 0) )
{
if(code != ' ')
{
if(!pTokenXs->addTokenX(0))
{
return pInput;
}
j++;
}
//m_pResultID[j++]=0;
}
}
INIT_STATE();
continue;
}
nPos = base + m_charset[code];//current position
if (nPos>m_nLowerBound||m_pData[nPos].check!=check)
{//Not exists
if (nWordLen>0)
{//Have a word
if( nWordHandle != 0 || (j>0 && (tokenBuff[j-1] != 0) ))
{
if(!pTokenXs->addTokenX(nWordHandle))
{
return pInput;
}
j++;
}
i = nStart+nWordLen;//Back off
}
else
{//First Character, not exists
if( (j>0 && (tokenBuff[j-1] != 0) ))
{
if(!pTokenXs->addTokenX(0))
{
return pInput;
}
j++;
}
}
INIT_STATE();
continue;
}
if (m_pData[nPos].base&FINAL_TAG)
{
check=nPos;
nWordLen = i - nStart;
nWordHandle = m_pData[nPos].handle;//Record Handle
if ( m_pData[nPos].base == -1 )//Leaf
{
//if (j==0&&lastID!=0||j>0&&m_pResultID[j-1]!=0||nWordHandle!=0)
if( nWordHandle != 0 || (j>0 && (tokenBuff[j-1] != 0) ))
{
if(!pTokenXs->addTokenX(nWordHandle))
{
return pInput;
}
j++;
//m_pResultID[j++]=(unsigned short)nWordHandle;
}
INIT_STATE();
continue;
}
else
{
base = m_pData[nPos].base & (~FINAL_TAG);
}
}
else
{
base=m_pData[nPos].base;
if (nWordLen==0)//Single Char being a word
{
nWordLen=nCharLen;
}
check=nPos;
}
}//end while
if(reader->isEof())
{
//if (j==0&&lastID!=0||j>0&&m_pResultID[j-1]!=0||nWordHandle!=0)
if( nWordHandle != 0 )
{
if(!pTokenXs->addTokenX(nWordHandle))
{
return pInput;
}
i = nStart+nWordLen;
INIT_STATE();
j++;
continue;
//m_pResultID[j++]=(unsigned short)nWordHandle;
}
}
}while (!reader->isEof());
return pInput;
}
Load(const tchar *sFilename)
{
FILE *fp;
fp=_tfopen(sFilename,_T("rb"));
if (fp==NULL)
{//Open file fail.
return false;
}
fread(m_charset,_CHARSET_SIZE,sizeof(short_t),fp);
//Read charset
fread(&m_nLowerBound,1,sizeof(int_t),fp);
//read lower bound
if (m_pData)
{
free(m_pData);
}
m_nLength=m_nLowerBound;
--m_nLowerBound;
m_pData=0;
m_pData=(PSTATE)malloc(sizeof(STATE)*m_nLength);
fread(m_pData,m_nLength,sizeof(STATE),fp);
//read data
fclose(fp);
return true;
}
双数组trie构造总结
最新推荐文章于 2024-04-21 11:36:12 发布