ICTCLAS代码学习笔记
中科院计算技术研究所多语言交互技术评测实验室
黄瑾  huangjin@ict.ac
为了练手,打算写一个新的ICTCLAS,用STL的东东,尽量避免预定义BUFFER造成的种种不确定性因素,同时了解一下整个分词的相关算法。
下面是学习笔记,看到哪记到哪。所有自己对代码的注释都用//!来表示,水平有限,写得不一定对@@。
2006-7-27的学习笔记
关于ICTCLAS词典的组织
词典相关的操作都在自定义的类CDictionary里,相关文件为CDictionary.h和CDictionary.cpp;涉及几个结构体变量,只注明了变量没有写构造函数。
struct tagWordResult{
char sWord[WORD_MAXLENGTH]; //!可以用string代替
//The word
int nHandle;
//the POS of the word
double  dValue;
//The -log(frequency/MAX)
};
typedef struct tagWordResult WORD_RESULT,*PWORD_RESULT;
/*data structure for word item*/
struct tagWordItem{
int nWordLen; //!可省略
char *sWord; //!可以用string代替,有写拷贝功能性能损失不大
/
/The word
int nHandle;
//the process or information handle of the word
int  nFrequency;
//The count which it appear
};
typedef struct tagWordItem WORD_ITEM,*PWORD_ITEM;
/*data structure for dictionary index table item*/
struct tagIndexTable{ //!索引表,一次性读入应该可以用一个vector<WORD_ITEM>代替int nCount;
//The count number of words which initial letter is sInit
PWORD_ITEM pWordItemHead;
/
/The  head of word items
};
typedef struct tagIndexTable INDEX_TABLE;
/*data structure for word item chain*/
struct tagWordChain{  //!词链,可以用一个list<WORD_ITEM>代替?
WORD_ITEM data;
struct tagWordChain *next;
/*----Added By Huangjin@ict.ac 2006-5-30----*/
struct tagWordChain()
{
next=NULL;
}
/*-----------------------------------------------*/
};
typedef struct tagWordChain WORD_CHAIN,*PWORD_CHAIN;
/*data structure for dictionary index table item*/
struct tagModifyTable{
int nCount;
//The count number of words which initial letter is sInit
int nDelete;
//The number of deleted items in the index table
PWORD_CHAIN pWordItemHead;
/
/The  head of word items
};
typedef struct tagModifyTable MODIFY_TABLE,*PMODIFY_TABLE;
词典类的声明如下:
class CDictionary
{//!所有的输入参数为char*者都可以使用const string&来代替,至少应该是const char*
//!对返回值所用的char*使用string&来代替
//!对参数为int*的根据情况改为int&或者vector<int>&
//!其他int,double或者char等内置类型一律传值,其他复杂类型如果不涉及修改一律const &
public:
bool Optimum();
bool Merge(CDictionary dict2,int nRatio); //!这里应该用const CDictionary&
bool OutputChars(char *sFilename);
bool Output(char *sFilename);
int GetFrequency(char *sWord,  int nHandle);
bool GetPOSString(int nPOS,char *sPOSRet);
int GetPOSValue(char *sPOS);
bool GetMaxMatch(char *sWord, char *sWordRet, int *npHandleRet);
bool MergePOS(int nHandle);
bool GetHandle(char *sWord,int *pnCount,int *pnHandle,int *pnFrequency);
bool IsExist(char *sWord,int nHandle);
bool AddItem(char *sWord,int nHandle,int nFrequency=0);
merge函数
bool DelItem(char *sWord,int nHandle);
bool Save(char *sFilename);
bool Load(char *sFilename,bool bReset=false);
int  GetWordType(char *sWord);
bool PreProcessing(char *sWord,int *nId,char *sWordRet,bool bAdd=false);
CDictionary();
virtual ~CDictionary();
INDEX_TABLE  m_IndexTable[CC_NUM]; //!这个换成vector<WORD_ITEM>在构造函数时大小赋为CC_NUM
PMODIFY_TABLE m_pModifyTable;
//The data for modify
protected:
bool DelModified();
bool FindInOriginalTable(int nInnerCode,char *sWord,int nHandle,int *nPosRet=0);
bool FindInModifyTable(int nInnerCode,char *sWord,int nHandle,PWORD_CHAIN
*pFindRet=0);
/*----Added By huangjin@ict.ac 2006-5-29----*/
void ClearDictionary(void);
/*-----------------------------------------------*/
};
下面是有必要说明的类成员函数
CDictionary::Load和CDictionary::Save
主要说明内容为词典本身的存储结果,文件为二进制读写格式,存储CC_NUM个索引及相关值。
共有CC_NUM个索引项,对于每个索引的内容:
第一个int型数据的值为当前索引项的词个数m_IndexTable[i].nCount,如果大于0表示该索引项后面有m_IndexTable[i].nCount个词,对于每个词:
前3个int型的数据分别表示每个词的出现频率nFrequency、词条字符串长度nWordLen
及其nHandle。如果词条长度大于0则后面连续nWordLen个char型字符为该词的字符
表示。
Load函数另外一个参数bReset主要是控制nFrequency,如果该值为true则nFrequency的值为0
词典的存储过程与读入过程正好相反,需要注意的一点是,存储时要考虑是否有修改词典的部分,即m_pModifyTable的相关内容。主要规则如下:
每个索引的词条个数应该为原有个数+修改表个数-修改表删除个数
写入的顺序为(只针对有修改表的情况)
将修改表中的数据按词条字母序写入,如果词条字符串相等则优先写入hHandle值较小的。2006-7-31的
学习笔记
在家里看代码,心静不下来啊,呵呵。
接着说CDictionary类的相关操作,save函数原来没看太清楚,还得仔细看。
m_pModifyTable的大小要么为0要么与m_IndexTable相同,即CC_NUM大小。如果m_pModifyTable 非空则在保存时需要判断。对于第i个索引,写入的个数nCount等于原始的大小m_IndexTable[i].nCount加上新表中的个数并减去新表中删除的个数。后面两个怎么算见后。
如果还没有写够原始的个数m_IndexTable[i].nCount且修改表中还没有写完,则重复下面的操作
如果修改表中的当前词条
2006-8-2 的学习笔记
Save函数不太容易看,先看一下AddItem和DelItem等会对m_pModifyTable有修改的部分再理解Save 就容易得多了。
AddItem需要三个参数,分别是要新添的词,其handle和频度。首先进行预处理,如果第一个字是汉字,
则返回的是这个汉字的handle及传入词的剩余部分(即要新添的词);如果是分割符则返回的是handle固定为3755且传入词的全部被返回(即要新添的词)(//?3755的含义未知)。如果不是这两种情况则返回false。预处理函数中有一个参数bAdd在当前版本中没有使用。只有预处理成功的词串才会被添加。首先会在原始表即m_IndexTable中查是否已经有这个词了,如果到,则对m_IndexTable 及m_pModifyTable进行适当的修改并返回表示成功添加的true。这里的修改主要根据在原始表中是否已经删除来区别操作,如果原始表中已删除,则重置这项的频度值,并在修改表中相应位置记录删除个数减1,如果原始表中仍然有词,只要添加相应的频度即可。原始表中没有的情况会相对复杂一些。首先会在修改表中查,如果到了则添加相应的位置的频度值并返回true。如果修改表中也没
有,那就要新建一个元素,并将其赋值到修改表的相应项的词串后面了,同时记录该记录的个数加1。其实简单的说就是,如果在原始表中有该项,则设置相应的频率,否则如果在修改表中有该项,则设置相应的频度。如果两者都没有,则在修改表中的新添一个元素代表该项并插入到相应的位置中去。
DelItem的过程可以说是AddItem的逆操作,其参数只需要待删除的词及其handle值。经过同样的预处理之后首先在原始表中查,如果到了将原始表中该项的频率值置为-1,在修改表中记录删除个数加一。需要注意的是,如果传入的handle的值为-1即要求忽略handle删除所有相应词的项,需要遍历并修改m_IndexTable和m_pModifyTabale的值。如果原始中没有到,那么在修改表中做类似的操作。在修改表中的操作会删除所有词相同且handle值相同或者词相同且传入handle值为-1的项。如果在两个表中
都没有到则删除失败返回false。注意,在修改表中做删除操作时并未对修改表中的nCount成员做任何的修改,怀疑为bug//!。
DelModified函数其实就是清空修改表,主要是要释放其中每个项的sWord所占用的buffer。如果使用string及相应的stl类表示则这个函数只需要一条clear语句即可。
IsExist函数用来查给出的词条及相应的handle是否在原始表和修改表中的存在。
GetHandle函数是一个比较重要的函数,用来查给定词条相关信息,不仅仅是handle值。对于同一个词,可能有多个handle及相应的频率值。也是先在原始表中查,如果没有到在修改表中查。
FindInOriginalTable是在原始表中查相应词条信息。传入的参数分别为汉字的内码,词以及handle,返回值为到的词匹配的位置,就是索引。用的是二分法进行查,因为m_IndexTable中的词条本身是有序的。对于已删除的词条会做进一步的判断,会到第一个匹配当前词的项的索引。
FindInModifyTable是类似的一个操作,在修改表中查相应的词条信息,不同的是返回值的类型为到词条的前一个位置的指针,而且查过程中是顺序查的。
GetWordType用于简单判断输入字的类型,如果输入字符串全为汉字则返回汉字类型,如果第一个是分割符则返回分割符类型,否则返回其他。只有汉字类型和分割符类型是合法的词典项类型。
预处理函数PreProcessing的功能前面已提过,这里需要注意的是,在预处理这个函数中会删除传入词串的首末空格,所以输入参数sWord不能是const string&型。
MergePOS函数是用于将词的词性信息压缩保存在handle里面。在目前的版本中未使用,会同时处理原始表和修改表。对于每一个索引项中的若干词条,如果某个词条与“前一个词”一样且当前词未被删除(即频率值不为-1)则么会标记该词为删除同时在删除表中的相应索引处的delete值加一。如果当前词条为第一个或者比“前一个词”小且未被删除,则置该词条的handle值为传入的参数。对修改

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。