IKAnalyzer源码分析—歧义词
根据《IKAnalyzer源码分析—1》所示,IKSegmenter的next函数每次处理完一批数据后,会通过IKArbitrator的process函数进行歧义处理。
IKArbitrator::process
void process(AnalyzeContext context , boolean useSmart){
QuickSortSet orgLexemes = context.getOrgLexemes();
Lexeme orgLexeme = orgLexemes.pollFirst();
LexemePath crossPath = new LexemePath();
while(orgLexeme != null){
if(!crossPath.addCrossLexeme(orgLexeme)){
if(crossPath.size() == 1 || !useSmart){
context.addLexemePath(crossPath);
}else{
QuickSortSet.Cell headCell = crossPath.getHead();
LexemePath judgeResult = this.judge(headCell, crossPath.getPathLength());
context.addLexemePath(judgeResult);
}
crossPath = new LexemePath();
crossPath.addCrossLexeme(orgLexeme);
}
orgLexeme = orgLexemes.pollFirst();
}
if(crossPath.size() == 1 || !useSmart){
context.addLexemePath(crossPath);
}else{
QuickSortSet.Cell headCell = crossPath.getHead();
LexemePath judgeResult = this.judge(headCell, crossPath.getPathLength());
context.addLexemePath(judgeResult);
}
}
从AnalyzeContext中获取前面通过Segmenter分析出的词集合orgLexemes,然后遍历orgLexemes,将相交的词依次调用addCrossLexeme添加进crossPath中,如果遇到一个不相交的词,比方说AB、BCD、CD、DE、FG(这里假设的每个英文字符为中文字符),当遇到FG词时,addCrossLexeme返回false,此时,如果该crossPath中只有一个词Lexeme,或者没开启智能分词,则直接将当前LexemePath添加到AnalyzeContext中,否则通过judge函数处理当前LexemePath,并将结果添加到AnalyzeContext。while后的代码用来处理最后一个LexemePath,原理和前面一样。
IKArbitrator::process->judge
private LexemePath judge(QuickSortSet.Cell lexemeCell , int fullTextLength){
TreeSet<LexemePath> pathOptions = new TreeSet<LexemePath>();
LexemePath option = new LexemePath();
Stack<QuickSortSet.Cell> lexemeStack = this.forwardPath(lexemeCell , option);
pathOptions.add(option.copy());
QuickSortSet.Cell c = null;
while(!lexemeStack.isEmpty()){
c = lexemeStack.pop();
this.backPath(c.getLexeme() , option);
this.forwardPath(c , option);
pathOptions.add(option.copy());
}
return pathOptions.first();
}
judge首先通过forwardPath遍历LexemePath中的所有Lexeme,将不冲突的词存入option,将有冲突的词存入lexemeStack,然后遍历冲突的词的集合lexemeStack,首先调用backPath从option中移除与lexemeStack中最后一个Lexeme冲突的所有词,再通过forwardPath向前在option中添加与c中的Lexeme不冲突的词,然后将处理完的option添加到待分析的集合pathOptions中。
judge函数最后通过pathOptions的first函数返回最优的option。pathOptions是TreeSet,其内部会调用LexemePath的compareTo函数返回最优的LexemePath,后面来看该函数。
IKArbitrator::process->judge->forwardPath
private Stack<QuickSortSet.Cell> forwardPath(QuickSortSet.Cell lexemeCell , LexemePath option){
Stack<QuickSortSet.Cell> conflictStack = new Stack<QuickSortSet.Cell>();
QuickSortSet.Cell c = lexemeCell;
while(c != null && c.getLexeme() != null){
if(!option.addNotCrossLexeme(c.getLexeme())){
conflictStack.push(c);
}
c = c.getNext();
}
return conflictStack;
}
forwardPath遍历一个LexemePath下的所有LexemePath,将不相交的部分通过addNotCrossLexeme函数添加到option中,将相交的部分添加到conflictStack中并返回。例如AB、BC、DE,则option中最终保存AB、DE,conflictStack最终保存BC。
IKArbitrator::process->judge->backPath
private void backPath(Lexeme l , LexemePath option){
while(option.checkCross(l)){
option.removeTail();
}
}
backPath从后往前将与参数Lexeme冲突的词从option中移除。
下面回头重点看下LexemePath的compareTo函数,该函数表示IKAnalyzer歧义处理的准则。
LexemePath::compareTo
public int compareTo(LexemePath o) {
if(this.payloadLength > o.payloadLength){
return -1;
}else if(this.payloadLength < o.payloadLength){
return 1;
}else{
if(this.size() < o.size()){
return -1;
}else if (this.size() > o.size()){
return 1;
}else{
if(this.getPathLength() > o.getPathLength()){
return -1;
}else if(this.getPathLength() < o.getPathLength()){
return 1;
}else {
if(this.pathEnd > o.pathEnd){
return -1;
}else if(pathEnd < o.pathEnd){
return 1;
}else{
if(this.getXWeight() > o.getXWeight()){
return -1;
}else if(this.getXWeight() < o.getXWeight()){
return 1;
}else {
if(this.getPWeight() > o.getPWeight()){
return -1;
}else if(this.getPWeight() < o.getPWeight()){
return 1;
}
}
}
}
}
}
return 0;
}
payloadLength表示LexemePath中的所有Lexeme覆盖的长度,越大越好。
例如第一个LexemePath包含“中华人民”一个Lexeme,第二个LexemePath包含“华人”,则第一个LexemePath的payloadLength为4,第二个LexemePath的payloadLength为2,此时认为第一个LexemePath优于第二个LexemePath。
size函数表示LexemePath中的词元个数,越小越好。
例如第一个LexemePath包含“中华人民”一个Lexeme,第二个LexemePath包含“中华”、“人民”两个Lexeme,则第一个LexemePath的size为1,第二个LexemePath的size为2,此时认为第一个LexemePath优于第二个LexemePath。
pathEnd表示最后的结束位置,假设payloadLength和size都相等,pathEnd位置越靠后越好。
例如第一个LexemePath包含“中华人民”一个Lexeme,第二个LexemePath包含“华人民族”,则第一个LexemePath的pathEnd为4,第二个LexemePath的pathEnd为5,此时认为第一个LexemePath优于第二个LexemePath。
getXWeight返回所有词长的乘积,如下所示,
int getXWeight(){
int product = 1;
Cell c = this.getHead();
while( c != null && c.getLexeme() != null){
product *= c.getLexeme().getLength();
c = c.getNext();
}
return product;
}
词长的乘积可以理解为面积,说明词长越平均,面积越大,因此越好。
例如假设payloadLength、size和pathEnd都相等,第一个LexemePath包含“中华”“人民”“族”,第二个LexemePath包含“中”“华”“人民族”,则第一个LexemePath的getXWeight为2*2*1=4,第二个LexemePath的getXWeight为1*1*3=3,此时认为第一个LexemePath优于第二个LexemePath。
getPWeight返回词元位置权重,如下所示,
int getPWeight(){
int pWeight = 0;
int p = 0;
Cell c = this.getHead();
while( c != null && c.getLexeme() != null){
p++;
pWeight += p * c.getLexeme().getLength() ;
c = c.getNext();
}
return pWeight;
}
getPWeight表示词元位置和词元长度的乘积,越大越好,通俗理解,就是越往后的词越长就越好。
例如假设payloadLength、size、pathEnd和getXWeight都相等,第一个LexemePath包含“中华”“人民”“族”,第二个LexemePath包含“中”“华人”“民族”,则第一个LexemePath的getPWeight为1*2+2*2+3*1=9,第二个LexemePath的getPWeight为1*1+2*2+3*2=10,此时认为第二个LexemePath优于第一个LexemePath。