关于双数组Trie查询词典构造总结
原贴:http://www.firtex.org/firtex_forum/archiver/?tid-241.html
[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]]
PS:firtexer是FirteX开发组的主要成员,这部分代码主要由他贡献。
是有什么特别的原因吗?
[i] fread(m_charset,_CHARSET_SIZE,sizeof(short_t),fp);
//Read charset
fread(&m_nLowerBound,1,sizeof(int_t),fp);[/i]
结合代码看有收获~
Dictionary.rar 里的exe是否也可以开放源代码? [/quote]
可以,待整理整合后会和FirteX代码一起发布
怎么在源程序中找不到CTokenXs这个类
Thank you!
还有一个问题,我下载的代码,在执行完CTokens* CChineseAnalyzer::nextTokensInternal(CReader* reader,CTokens* pInput)之后,查看 pInput->getTokenNum()的值,好像这个值是按单字分词的个数,是词典的原因还是专门这样设计的?
谢谢!
看看sourceforge上的SVN代码吧,要是来得及,这几天就发布
拉下来看看先~
pTrie[idx].handle = m_wordHanleTable[pState->m_pState[k].state];
应该是
pTrie[idx].handle = m_wordHanleTable[pState->m_pState[k].state-1];吧?
请仔细看看CreateStates().
谢谢你对算法提出的问题,个人有个建议,当你在遇到问题的时候,应该多去思考,这样对你的进步会更大:)
因为早上debug模式运行到这一步就出数组越界的错误,减1就正常了。
恩,晚上回去再看看。
没必要支持_UNICODE编译,词典生成跟unicode没任何关系。
请仔细看看CreateStates().
谢谢你对算法提出的问题,个人有个建议,当你在遇到问题的时候,应该多去思考,这样对你的进步会更大:) [/quote]
stickyman 的意思应该是程序不支持编译成UNICODE版,而不是程序支持UNICODE编码。目前FirteX的核心代码也是,虽然很多地方考虑UNICODE编译的支持,但是目前还不能直接编译成UNICODE版
算法部分还没来得及看,有个小问题先report一下, DictionaryDlg.cpp的OnBnClickedButtonQuery函数,最后应该需要调用一下close吧?否则会有内存泄漏。当然,这只是测试代码中的小问题,不影响字典的生成
最近搬家还没收拾好东西,没法在家用电脑,晚上应该能看看。
谢谢firtexer的建议。
因为早上debug模式运行到这一步就出数组越界的错误,减1就正常了。
恩,晚上回去再看看。 [/quote]
目前没有发现数组越界的异常,你可以把你的词库发给我,这样我可以调试一下程序
不能上传附件,不过我测试的时候就在一个文件里放了三个单词:
live
look
man
奇怪了,state 是从1开始计数的,vector的m_wordHanleTable,vector本身是从0开始计数的,到最后一位的时候应该是会越界才对啊。
不能上传附件,不过我测试的时候就在一个文件里放了三个单词:
live
look
man [/quote]
state 从0开始编号
pStateSet->push_back(sInput, total_state, final); 这里用的是total_state,第一次是1
因为最近需要用到DAT……不过现在已经搞清楚算法了。上面的问题和算法本身没有关系,浪费了firtexer 的不少时间,谢谢firtexer和admin~:) [/quote]
弄懂了就好了。不过还是提醒你一下,这里的state从0开始编号,当然你也可以从1开始,不过对写程序没什么好处。
关于双数组Trie查询词典构造总结相关推荐
- 双数组trie树的基本构造及简单优化
一 基本构造 Trie树是搜索树的一种,来自英文单词"Retrieval"的简写,可以建立有效的数据检索组织结构,是中文匹配分词算法中词典的一种常见实现.它本质上是一个确定的有限状 ...
- Ansj分词双数组Trie树实现与arrays.dic词典格式
http://www.hankcs.com/nlp/ansj-word-pairs-array-tire-tree-achieved-with-arrays-dic-dictionary-format ...
- 双数组Trie树(DoubleArrayTrie)Java实现
http://www.hankcs.com/program/java/%E5%8F%8C%E6%95%B0%E7%BB%84trie%E6%A0%91doublearraytriejava%E5%AE ...
- python Trie树和双数组TRIE树的实现. 拥有3个功能:插入,删除,给前缀智能找到所有能匹配的单词...
#coding=utf-8 #字典嵌套牛逼,别人写的,这样每一层非常多的东西,搜索就快了,树高26.所以整体搜索一个不关多大的单词表 #还是O(1). ''' Python 字典 setdefault ...
- 双数组Trie的一种实现
An Implementation of Double-Array Trie 双数组Trie的一种实现 原文:http://linux.thai.net/~thep/datrie/datrie.htm ...
- 双数组trie树详解
目录 双数组trie树的构建 构建base array 构建check array 双数组trie树的查询 双数组trie树的构建 NLP中trie树常用于做快速查询,但普通的trie树由于要保存大量 ...
- 数据结构-----基于双数组的Trie树
Trie树简介 Trie树也称字典树,在字符串的查找中优势比较明显,适用于在海量数据中查找某个数据.因为Trie树的查找时间和数据总量没有关系,只和要查找的数据长度有关.比如搜索引擎中热度词语的统计. ...
- 双数组 实现 Trie
NewSMTH zhjin (sweptAway): 在开发中文分词器的时候, 一个高效的词典结构尤其重要. 词典 的一种常见高效的实现方式就是使用 Trie 结构, 但是传统的 Trie 结构的实现 ...
- 用Python实现字典树(Trie)与双数组字典树(DATrie)
1. 字典树(Trie) 假如我们把字典中的词以记录的形式(无序)存入数据库中.现给定一串字符,要查找该字符串是否为字典中的词.因为数据库中的记录是无序的,所以,最朴素的方法就逐记录匹配.此方法简单, ...
最新文章
- css网页布局中文字排版的属性和用法
- 2021年8月最新sci-hub可用网址,高速稳定
- IOS 9人机界面指南(1):UI设计基础
- rman备份恢复命令之switch
- 修复 Xcode 错误 “The identity used to sign the executable is no longer valid”
- 陶瓷移动在飞信版C++女程序员
- 浅显易懂的GMM模型及其训练过程
- R语言 相关分析和典型相关分析
- mac OS X 10.9.2 gdb codesign
- jsoniter与原生json对比
- MaxDOS 网刻服务端网刻教程。
- python 绝对值_Python绝对值– abs()
- 5个很少被提到但能提高NLP工作效率的Python库
- 鸿蒙手机卸载内置应用 adb连接
- 第八天 Python爬虫之Rquests库打码平台的简单使用
- 小说里的编程 【连载之十七】元宇宙里月亮弯弯
- 导入多段落文档排版计算机作业,2011级计算机基础操作Word作业说明_论文排版.pdf...
- python第二周基本图形绘制
- php富友接口对接http请求
- oppok5和荣耀play4tpro哪个好
热门文章
- k3调拨单中新增一级菜单及二级菜单
- 疫情加剧,线上需求暴涨,杭州海赢科技解读东南亚家居生活热销潜力商品
- Android开发 指纹识别
- Android studio历史版本下载
- python的ppt报告_看完这篇Python操作PPT总结,从此使用Python玩转Office全家桶就没有压力了!...
- STM32CubeMX+HAL库定时器介绍
- 电大2019计算机基础客观题,2019电大计算机应用基础试题及答案必考重点
- Android获取手机的设备识别码IMEI
- 盘点一个JS逆向过程中中文编解码的小案例
- 哲学思想对软件开发的启示