void process(AnalyzeContext context , boolean useSmart){ QuickSortSet orgLexemes = context.getOrgLexemes(); Lexeme orgLexeme = orgLexemes.pollFirst(); LexemePath crossPath = new LexemePath(); while(orgLexeme != null){ //jw出现不相交的分词,把之前的所有词进行歧义处理 if(!crossPath.addCrossLexeme(orgLexeme)){ //找到与crossPath不相交的下一个crossPath //jw非智能歧义处理,即使相交,也直接输出分词结果 if(crossPath.size() == 1 || !useSmart){ //crossPath没有歧义 或者 不做歧义处理 //直接输出当前crossPath context.addLexemePath(crossPath); }else{ //jw出现一个不相交的分词,将之前相交的词开始歧义处理 //对当前的crossPath进行歧义处理 QuickSortSet.Cell headCell = crossPath.getHead(); LexemePath judgeResult = this.judge(headCell, crossPath.getPathLength()); //输出歧义处理结果judgeResult context.addLexemePath(judgeResult); } //jw只要出现不相交的词,即进行歧义处理,选出当前最优结果,然后继续处理后面的词 //把orgLexeme加入新的crossPath中 crossPath = new LexemePath(); crossPath.addCrossLexeme(orgLexeme); } orgLexeme = orgLexemes.pollFirst(); } //处理最后的path if(crossPath.size() == 1 || !useSmart){ //crossPath没有歧义 或者 不做歧义处理 //直接输出当前crossPath context.addLexemePath(crossPath); }else{ //对当前的crossPath进行歧义处理 QuickSortSet.Cell headCell = crossPath.getHead(); LexemePath judgeResult = this.judge(headCell, crossPath.getPathLength()); //输出歧义处理结果judgeResult context.addLexemePath(judgeResult); }
下面来看最重要的judge()方法
/** * 歧义识别 * @param lexemeCell 歧义路径链表头 * @param fullTextLength 歧义路径文本长度 * @param option 候选结果路径 * @return */ private LexemePath judge(QuickSortSet.Cell lexemeCell , int fullTextLength){ //候选路径集合 TreeSet<LexemePath> pathOptions = new TreeSet<LexemePath>(); //候选结果路径 LexemePath option = new LexemePath(); //对crossPath进行一次遍历,同时返回本次遍历中有冲突的Lexeme栈 Stack<QuickSortSet.Cell> lexemeStack = this.forwardPath(lexemeCell , option); //当前词元链并非最理想的,加入候选路径集合 pathOptions.add(option.copy()); //jw这种处理方式应该不是最优的,只是采用贪心的方法获取近似最优方案,并没有遍历所有的可能集合 //jw每次从一个歧义位置开始,贪心的获取一种分词方案 //存在歧义词,处理 QuickSortSet.Cell c = null; while(!lexemeStack.isEmpty()){ System.out.println("jwdebug one option begin"); c = lexemeStack.pop(); //回滚词元链 this.backPath(c.getLexeme() , option); //从歧义词位置开始,递归,生成可选方案 this.forwardPath(c , option); pathOptions.add(option.copy()); } //jw跳转到LexemePath.java中的compareTo()接口查看最优方案选择策略 //返回集合中的最优方案 return pathOptions.first(); }
这个TreeSet用来保存候选分词结果集,并按照排序策略对分词结果集进行排序,排序策略后面说
treeSet<LexemePath> pathOptions = new TreeSet<LexemePath>();
接下来是forwardPath
/** * 向前遍历,添加词元,构造一个无歧义词元组合 * @param LexemePath path * @return */ private Stack<QuickSortSet.Cell> forwardPath(QuickSortSet.Cell lexemeCell , LexemePath option){ //发生冲突的Lexeme栈 Stack<QuickSortSet.Cell> conflictStack = new Stack<QuickSortSet.Cell>(); QuickSortSet.Cell c = lexemeCell; //迭代遍历Lexeme链表 while(c != null && c.getLexeme() != null){ if(!option.addNotCrossLexeme(c.getLexeme())){ //词元交叉,添加失败则加入lexemeStack栈 conflictStack.push(c); } c = c.getNext(); } return conflictStack; }
然后就是分词结果组合遍历方法:
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; }
到此,IK分词中两个最核心的模块:3个子分词器+歧义处理已经介绍完了。到这里,对IK分词的思路应该了解大部分了。