关于双数组Trie查询词典构造总结

原贴:http://www.firtex.org/firtex_forum/archiver/?tid-241.html

FirteX开发论坛 » FirteX开发者专区 » 关于双数组Trie查询词典构造总结


2007-5-12 12:25 AM firtexer
关于双数组Trie查询词典构造总结

[b]首先需要对词典创建一个DFA[/b](如果对于DFA不熟悉的话,可以看看形式语言和自动机方面的书),构造DFA的过程如下:
对于每一个词a1 a2 … an,依次按该词中每个字的顺序,遍历DFA的状态跳转表,直到遇到该DFA不能接受某个输入时,假设这个字为ai,那么从ai … an将依次建立新的状态以及状态跳转,同时需要对ai进行编码。DFA的实现需要一个数组去保存所有的状态,每个数组元素是一个集合,该集合包含了该状态 所能接受的输入以及对应的下一个状态,因而会占用比较大的内存。

[b]其次是把DFA转化成双数组Trie[/b](如果对于双数组Trie不熟悉的话,可以参考[url]http://linux.thai.net/~thep/datrie/datrie.html[/url])转化过程如下:
base值从1开始,双数组Trie的第一个元素的base值为1,check为-1,状态si从0开始,如果双数组Trie的大小不能够容纳状态si的 所有下一个状态,那么需要申请更大的内存,之后遍历双数组Trie,找到合适的base值i,设置好Trie[i].base和Trie[i]. check的值,如果该状态也是一个终止状态,那么需要把Trie[i].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;
}

[[i] 本帖最后由 firtexer 于 2007-5-13 10:55 PM 编辑 [/i]]

2007-5-14 09:19 AM admin
双数组TRIE树词典的构建源代码和分词部分相应的修改会和FirteX的下一个版本一起发布。可以先使用附件中的可执行程序生成你自己的词典,替换 data目录下的coredict.pdat 文件,再根据上贴的说明修改CChineseAnalyzer.h 和CChineseAnalyzer.cpp相应部分的代码即可。有什么问题请向我们报告。

PS:firtexer是FirteX开发组的主要成员,这部分代码主要由他贡献。

2007-5-14 10:28 AM firtexer
忘记说一点了,原始词典的格式是每行一个词。

2007-6-6 02:37 PM nonego
最近在学习双数组Trie,所以看了一下Firtex的代码,在下面的代码中fread的用法好像和该函数的说明不太一致(size 和count位置互换了),MSDN上fread的函数原型是: size_t fread(    void* buffer,    size_t size,    size_t count,    FILE* stream );
是有什么特别的原因吗?

[i]            fread(m_charset,_CHARSET_SIZE,sizeof(short_t),fp);
            //Read charset
            fread(&m_nLowerBound,1,sizeof(int_t),fp);[/i]

2007-6-6 02:56 PM admin
没什么特殊用意,是写代码时没有注意,不过并不影响结果。多谢你的细心

2007-6-6 07:59 PM yxg_80
admin :论坛的一个缺点是一次发表的字多了就成了斜题了,影响阅读啊!看看能不能修改一下论坛设置!

2007-7-9 05:16 PM bg1011
谢谢

结合代码看有收获~

2007-7-12 10:29 AM stickyman
Dictionary.rar 里的exe是否也可以开放源代码?

2007-7-12 10:31 AM admin
[quote]原帖由 [i]stickyman[/i] 于 2007-7-12 10:29 AM 发表
Dictionary.rar 里的exe是否也可以开放源代码? [/quote]
可以,待整理整合后会和FirteX代码一起发布

2007-8-1 09:17 AM phinix
上面的代码中CTokenXs<termid_t>* pTokenXs = (CTokenXs<termid_t>*)pInput->asTokensX();
怎么在源程序中找不到CTokenXs这个类
Thank you!

2007-8-1 09:35 AM admin
在analyzer目录下的Tokens.h中

2007-8-1 10:18 AM phinix
我是从Firtex网站上本地下载的firtex-1.0.2_beta3_src.zip,在Tokens.h中没有CTokenXs这个类,CTokens也没有asTokensX()函数。
还有一个问题,我下载的代码,在执行完CTokens* CChineseAnalyzer::nextTokensInternal(CReader* reader,CTokens* pInput)之后,查看 pInput->getTokenNum()的值,好像这个值是按单字分词的个数,是词典的原因还是专门这样设计的?
谢谢!

2007-8-1 10:22 AM admin
是词典的缘故。
看看sourceforge上的SVN代码吧,要是来得及,这几天就发布

2007-8-12 11:34 PM admin
这部分代码已整合并更新至SVN,见contrib/DoubleArrayTrieDict和contrib/app/Dictionary,暂时还没有包含在发布的1.0.3_RC版中

2007-8-13 12:11 AM stickyman
admin辛苦了。 // bow
拉下来看看先~

2007-8-15 12:02 AM stickyman
经确认,虽然部分地方考虑到了,不过这部分的代码也不支持_UNICODE编译,呼呼

2007-8-15 07:56 AM stickyman
CTrieDictCreator::CreateDict函数的
pTrie[idx].handle = m_wordHanleTable[pState->m_pState[k].state];
应该是
pTrie[idx].handle = m_wordHanleTable[pState->m_pState[k].state-1];吧?

2007-8-15 11:52 AM firtexer
没必要支持_UNICODE编译,词典生成跟unicode没任何关系。

请仔细看看CreateStates().

谢谢你对算法提出的问题,个人有个建议,当你在遇到问题的时候,应该多去思考,这样对你的进步会更大:)

2007-8-15 12:13 PM stickyman
谢谢firtexer的建议。
因为早上debug模式运行到这一步就出数组越界的错误,减1就正常了。
恩,晚上回去再看看。

2007-8-16 09:54 AM admin
[quote]原帖由 [i]firtexer[/i] 于 2007-8-15 11:52 AM 发表
没必要支持_UNICODE编译,词典生成跟unicode没任何关系。

请仔细看看CreateStates().

谢谢你对算法提出的问题,个人有个建议,当你在遇到问题的时候,应该多去思考,这样对你的进步会更大:) [/quote]


stickyman 的意思应该是程序不支持编译成UNICODE版,而不是程序支持UNICODE编码。目前FirteX的核心代码也是,虽然很多地方考虑UNICODE编译的支持,但是目前还不能直接编译成UNICODE版

2007-8-16 10:11 AM stickyman

算法部分还没来得及看,有个小问题先report一下, DictionaryDlg.cpp的OnBnClickedButtonQuery函数,最后应该需要调用一下close吧?否则会有内存泄漏。当然,这只是测试代码中的小问题,不影响字典的生成

2007-8-16 10:59 AM firtexer
哦,那应该是CTrieDictCreator::TestDict出的问题,里面的字符串加上 _T 宏就应该可以了。

最近搬家还没收拾好东西,没法在家用电脑,晚上应该能看看。

2007-8-16 11:57 PM firtexer
[quote]原帖由 [i]stickyman[/i] 于 2007-8-15 12:13 PM 发表
谢谢firtexer的建议。
因为早上debug模式运行到这一步就出数组越界的错误,减1就正常了。
恩,晚上回去再看看。 [/quote]

目前没有发现数组越界的异常,你可以把你的词库发给我,这样我可以调试一下程序

2007-8-19 12:05 AM stickyman
奇怪了,state 是从1开始计数的,vector的m_wordHanleTable,vector本身是从0开始计数的,到最后一位的时候应该是会越界才对啊。
不能上传附件,不过我测试的时候就在一个文件里放了三个单词:
live
look
man

2007-8-20 06:40 PM firtexer
[quote]原帖由 [i]stickyman[/i] 于 2007-8-19 12:05 AM 发表
奇怪了,state 是从1开始计数的,vector的m_wordHanleTable,vector本身是从0开始计数的,到最后一位的时候应该是会越界才对啊。
不能上传附件,不过我测试的时候就在一个文件里放了三个单词:
live
look
man [/quote]

state 从0开始编号

2007-8-20 08:05 PM stickyman
初始值是0,
pStateSet->push_back(sInput, total_state, final); 这里用的是total_state,第一次是1

2007-8-20 09:51 PM stickyman
因为最近需要用到DAT……不过现在已经搞清楚算法了。上面的问题和算法本身没有关系,浪费了firtexer 的不少时间,谢谢firtexer和admin~:)

2007-8-20 10:05 PM firtexer
[quote]原帖由 [i]stickyman[/i] 于 2007-8-20 09:51 PM 发表
因为最近需要用到DAT……不过现在已经搞清楚算法了。上面的问题和算法本身没有关系,浪费了firtexer 的不少时间,谢谢firtexer和admin~:) [/quote]

弄懂了就好了。不过还是提醒你一下,这里的state从0开始编号,当然你也可以从1开始,不过对写程序没什么好处。
 

你可能感兴趣的:(算法,vector,扩展,character,FP,Dictionary)