ICTCLAS分词系统是由中科院计算所的张华平、刘群所开发的一套获得广泛好评的分词系统,该版的Free版开放了源代码,为初学者提供了宝贵的学习材料。我们可以在“http://sewm.pku.edu.cn/QA/”找到FreeICTCLASLinux.tar的C++代码。
可是目前该版本的ICTCLAS并没有提供完善的文档,所以阅读起来有一定的难度,所幸网上可以找到一些对ICTCLAS进行代码分析的文章,对理解分词系统的内部运行机制提供了很大的帮助。这些文章包括:
1)http://blog.csdn.net/group/ictclas4j/;《ICTCLAS分词系统研究(一)~(六)》作者:sinboy。
2)http://qxred.yculblog.com/post.1204714.html;《ICTCLAS 中科院分词系统 代码 注释 中文分词 词性标注》作者:风暴红QxRed 。
按照上面这些文章的思路去读ICTCLAS的代码,可以比较容易的理顺思路。然而在我阅读代码的过程中,越来越对ICTCLAS天书般的代码感到厌烦。我不得不佩服中科院计算所的人思维缜密,头脑清晰,能写出滴水不漏而又让那些“头脑简单”的人百思不得其解的代码。将一件本来很简单的事情做得无比复杂…………
ICTCLAS中有一个名为CDynamicArray的类,存放在DynamicArray.cpp与DynamicArray.h两个文件中,这个DynamicArray是干什么用的?经过一番研究后终于明白是一个经过排序的链表。为了表达的更明白些,我们不妨看下面这张图:
(图一)
上面这张图是一个按照index值进行了排序的链表,当插入新结点时必须确保index值的有序性。DynamicArray类完成的功能基本上与上面这个链表差不多,只是排序规则不是index,而是row和col两个数据,如下图:
(图二)
大家可以看到,这个有序链表的排序规则是先按row排序,row相同的按照col排序。当然排序规则是可以改变的,如果先按col排,再按row排,则上面的链表必须表述成:
(图三)
在了解了这些内容的基础上,不妨让我们看看ICTCLAS中DynamicArray.cpp中的代码实现(这里我们只看GetElement方法的实现,其基本功能为给出row与col,然后将对应的元素取出来)。
DynamicArray.cpp
ELEMENT_TYPE CDynamicArray::GetElement(int nRow, int nCol, PARRAY_CHAIN pStart, PARRAY_CHAIN *pRet) { PARRAY_CHAIN pCur = pStart; if (pStart == 0) pCur = m_pHead; if (pRet != 0) *pRet = NULL; if (nRow > (int)m_nRow || nCol > (int)m_nCol) //Judge if the row and col is overflow return INFINITE_VALUE; if (m_bRowFirst) { while (pCur != NULL && (nRow != - 1 && (int)pCur->row < nRow || (nCol != - 1 && (int)pCur->row == nRow && (int)pCur->col < nCol))) { if (pRet != 0) *pRet = pCur; pCur = pCur->next; } } else { while (pCur != NULL && (nCol != - 1 && (int)pCur->col < nCol || ((int)pCur ->col == nCol && nRow != - 1 && (int)pCur->row < nRow))) { if (pRet != 0) *pRet = pCur; pCur = pCur->next; } } if (pCur != NULL && ((int)pCur->row == nRow || nRow == - 1) && ((int)pCur ->col == nCol || nCol == - 1)) //Find the same position { //Find it and return the value if (pRet != 0) *pRet = pCur; return pCur->value; } return INFINITE_VALUE; }
这里我先要说明的是程序中的m_bRowFirst变量,它表示是先按row大小排列还是先按col大小排列。如果m_bRowFirst为逻辑真值,那么链表就如上面图二所示,如果为假,则如图三所示。
除了这个外,看到上面长长的条件表达式,你一定会吓坏了吧!更让人吓坏的是调用这段程序的代码:
对GetElement方法的调用
//来自NShortPath.cpp中ShortPath方法 eWeight = m_apCost->GetElement( -1, nCurNode, 0, &pEdgeList); //来自Segment.cpp中BiGraphGenerate方法 aWord.GetElement(pCur->col, -1, pCur, &pNextWords);//Get next words which begin with pCur->col