原贴: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开始,不过对写程序没什么好处。

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

  1. 双数组trie树的基本构造及简单优化

    一 基本构造 Trie树是搜索树的一种,来自英文单词"Retrieval"的简写,可以建立有效的数据检索组织结构,是中文匹配分词算法中词典的一种常见实现.它本质上是一个确定的有限状 ...

  2. Ansj分词双数组Trie树实现与arrays.dic词典格式

    http://www.hankcs.com/nlp/ansj-word-pairs-array-tire-tree-achieved-with-arrays-dic-dictionary-format ...

  3. 双数组Trie树(DoubleArrayTrie)Java实现

    http://www.hankcs.com/program/java/%E5%8F%8C%E6%95%B0%E7%BB%84trie%E6%A0%91doublearraytriejava%E5%AE ...

  4. python Trie树和双数组TRIE树的实现. 拥有3个功能:插入,删除,给前缀智能找到所有能匹配的单词...

    #coding=utf-8 #字典嵌套牛逼,别人写的,这样每一层非常多的东西,搜索就快了,树高26.所以整体搜索一个不关多大的单词表 #还是O(1). ''' Python 字典 setdefault ...

  5. 双数组Trie的一种实现

    An Implementation of Double-Array Trie 双数组Trie的一种实现 原文:http://linux.thai.net/~thep/datrie/datrie.htm ...

  6. 双数组trie树详解

    目录 双数组trie树的构建 构建base array 构建check array 双数组trie树的查询 双数组trie树的构建 NLP中trie树常用于做快速查询,但普通的trie树由于要保存大量 ...

  7. 数据结构-----基于双数组的Trie树

    Trie树简介 Trie树也称字典树,在字符串的查找中优势比较明显,适用于在海量数据中查找某个数据.因为Trie树的查找时间和数据总量没有关系,只和要查找的数据长度有关.比如搜索引擎中热度词语的统计. ...

  8. 双数组 实现 Trie

    NewSMTH zhjin (sweptAway): 在开发中文分词器的时候, 一个高效的词典结构尤其重要. 词典 的一种常见高效的实现方式就是使用 Trie 结构, 但是传统的 Trie 结构的实现 ...

  9. 用Python实现字典树(Trie)与双数组字典树(DATrie)

    1. 字典树(Trie) 假如我们把字典中的词以记录的形式(无序)存入数据库中.现给定一串字符,要查找该字符串是否为字典中的词.因为数据库中的记录是无序的,所以,最朴素的方法就逐记录匹配.此方法简单, ...

最新文章

  1. css网页布局中文字排版的属性和用法
  2. 2021年8月最新sci-hub可用网址,高速稳定
  3. IOS 9人机界面指南(1):UI设计基础
  4. rman备份恢复命令之switch
  5. 修复 Xcode 错误 “The identity used to sign the executable is no longer valid”
  6. 陶瓷移动在飞信版C++女程序员
  7. 浅显易懂的GMM模型及其训练过程
  8. R语言 相关分析和典型相关分析
  9. mac OS X 10.9.2 gdb codesign
  10. jsoniter与原生json对比
  11. MaxDOS 网刻服务端网刻教程。
  12. python 绝对值_Python绝对值– abs()
  13. 5个很少被提到但能提高NLP工作效率的Python库
  14. 鸿蒙手机卸载内置应用 adb连接
  15. 第八天 Python爬虫之Rquests库打码平台的简单使用
  16. 小说里的编程 【连载之十七】元宇宙里月亮弯弯
  17. 导入多段落文档排版计算机作业,2011级计算机基础操作Word作业说明_论文排版.pdf...
  18. python第二周基本图形绘制
  19. php富友接口对接http请求
  20. oppok5和荣耀play4tpro哪个好

热门文章

  1. k3调拨单中新增一级菜单及二级菜单
  2. 疫情加剧,线上需求暴涨,杭州海赢科技解读东南亚家居生活热销潜力商品
  3. Android开发 指纹识别
  4. Android studio历史版本下载
  5. python的ppt报告_看完这篇Python操作PPT总结,从此使用Python玩转Office全家桶就没有压力了!...
  6. STM32CubeMX+HAL库定时器介绍
  7. 电大2019计算机基础客观题,2019电大计算机应用基础试题及答案必考重点
  8. Android获取手机的设备识别码IMEI
  9. 盘点一个JS逆向过程中中文编解码的小案例
  10. 哲学思想对软件开发的启示