3030/**
3131 * IK分词歧义裁决器
3232 */
33- class IKArbitrator {
34-
35- IKArbitrator (){
36-
37- }
33+ class IKArbitrator {
34+ private static final int LONG_CN_WORD_LENGTH_THRESHOLD = 10 ;
35+ private static final int LONG_CN_WORD_COUNT_THRESHOLD = 5 ;
36+ private static final int TOTAL_LEXEME_COUNT_THRESHOLD = 50 ;
37+ private static final int DENSE_CROSS_PATH_SIZE_THRESHOLD = 20 ;
38+ private static final int DENSE_OVERLAP_RATIO_THRESHOLD = 4 ;
39+
40+ IKArbitrator (){
41+
42+ }
3843
3944 /**
4045 * 分词歧义处理
@@ -53,13 +58,12 @@ void process(AnalyzeContext context , boolean useSmart){
5358 //crossPath没有歧义 或者 不做歧义处理
5459 //直接输出当前crossPath
5560 context .addLexemePath (crossPath );
56- }else {
57- //对当前的crossPath进行歧义处理
58- QuickSortSet .Cell headCell = crossPath .getHead ();
59- LexemePath judgeResult = this .judge (headCell , crossPath .getPathLength ());
60- //输出歧义处理结果judgeResult
61- context .addLexemePath (judgeResult );
62- }
61+ }else {
62+ //对当前的crossPath进行歧义处理
63+ LexemePath judgeResult = this .judge (crossPath );
64+ //输出歧义处理结果judgeResult
65+ context .addLexemePath (judgeResult );
66+ }
6367
6468 //把orgLexeme加入新的crossPath中
6569 crossPath = new LexemePath ();
@@ -74,157 +78,167 @@ void process(AnalyzeContext context , boolean useSmart){
7478 //crossPath没有歧义 或者 不做歧义处理
7579 //直接输出当前crossPath
7680 context .addLexemePath (crossPath );
77- }else {
78- //对当前的crossPath进行歧义处理
79- QuickSortSet .Cell headCell = crossPath .getHead ();
80- LexemePath judgeResult = this .judge (headCell , crossPath .getPathLength ());
81- //输出歧义处理结果judgeResult
82- context .addLexemePath (judgeResult );
83- }
84- }
85-
86- /**
87- * 检测是否为叠词模式
88- * @param lexemeCell 词元链表头
89- * @return 如果检测到叠词模式返回简化的路径,否则返回null
90- */
91- private LexemePath detectRepeatedWords (QuickSortSet .Cell lexemeCell ) {
92- if (lexemeCell == null || lexemeCell .getLexeme () == null ) {
93- return null ;
94- }
95-
96- // 检查是否有连续的长词元(长度>10)或大量重复词元
97- QuickSortSet .Cell current = lexemeCell ;
98- int longLexemeCount = 0 ;
99- int totalCount = 0 ;
100- Lexeme firstLexeme = null ;
101- Lexeme lastLexeme = null ;
102-
103- while (current != null && current .getLexeme () != null ) {
104- Lexeme lexeme = current .getLexeme ();
105- if (firstLexeme == null ) {
106- firstLexeme = lexeme ;
107- }
108- lastLexeme = lexeme ;
109-
110- if (lexeme .getLength () > 10 ) {
111- longLexemeCount ++;
112- }
113- totalCount ++;
114-
115- // 如果发现多个长词元或词元总数过多,认为是叠词
116- if (longLexemeCount > 5 || totalCount > 50 ) {
117- // 构造简化路径:第一个词元 + 剩余部分合并为一个词元
118- LexemePath simplifiedPath = new LexemePath ();
119-
120- // 添加第一个词元
121- simplifiedPath .addNotCrossLexeme (firstLexeme );
122-
123- // 如果有剩余部分,创建一个合并的词元
124- if (totalCount > 1 && lastLexeme != null ) {
125- // 计算剩余部分的起始位置和长度
126- int remainStart = firstLexeme .getBegin () + firstLexeme .getLength ();
127- int remainEnd = lastLexeme .getBegin () + lastLexeme .getLength ();
128- int remainLength = remainEnd - remainStart ;
129-
130- if (remainLength > 0 ) {
131- // 创建一个表示剩余部分的词元
132- // offset 应该是第一个词元的 offset + 第一个词元的长度
133- int remainOffset = firstLexeme .getOffset () + firstLexeme .getLength ();
134- Lexeme remainLexeme = new Lexeme (remainOffset , remainStart , remainLength , Lexeme .TYPE_CNCHAR );
135- simplifiedPath .addNotCrossLexeme (remainLexeme );
136- }
137- }
138-
139- return simplifiedPath ;
140- }
141-
142- current = current .getNext ();
143- }
144-
145- return null ; // 没有检测到叠词模式
146- }
147-
148- /**
149- * 歧义识别
150- * @param lexemeCell 歧义路径链表头
151- * @param fullTextLength 歧义路径文本长度
152- * @return
153- */
154- private LexemePath judge (QuickSortSet .Cell lexemeCell , int fullTextLength ){
155- // 首先检测是否为叠词模式,如果是则直接返回简化路径
156- LexemePath simplifiedPath = this .detectRepeatedWords (lexemeCell );
157- if (simplifiedPath != null ) {
158- //System.out.println("Detected repeated words pattern, using simplified path");
159- return simplifiedPath ;
160- }
161-
162- //候选路径集合
163- TreeSet <LexemePath > pathOptions = new TreeSet <LexemePath >();
164- //候选结果路径
165- LexemePath option = new LexemePath ();
166-
167- //对crossPath进行一次遍历,同时返回本次遍历中有冲突的Lexeme栈
168- Stack <QuickSortSet .Cell > lexemeStack = this .forwardPath (lexemeCell , option );
169-
170- //当前词元链并非最理想的,加入候选路径集合
171- pathOptions .add (option .copy ());
172-
173- //存在歧义词,处理
174- QuickSortSet .Cell c = null ;
175- while (!lexemeStack .isEmpty ()){
176- c = lexemeStack .pop ();
177- //回滚词元链
178- this .backPath (c .getLexeme () , option );
179- //从歧义词位置开始,递归,生成可选方案
180- this .forwardPath (c , option );
181- pathOptions .add (option .copy ());
182- }
183-
184- //返回集合中的最优方案
185- return pathOptions .first ();
186-
187- }
188-
189- /**
190- * 向前遍历,添加词元,构造一个无歧义词元组合
191- // * @param LexemePath path
192- * @return
193- */
194- private Stack <QuickSortSet .Cell > forwardPath (QuickSortSet .Cell lexemeCell , LexemePath option ){
195- //发生冲突的Lexeme栈
196- Stack <QuickSortSet .Cell > conflictStack = new Stack <QuickSortSet .Cell >();
197- QuickSortSet .Cell c = lexemeCell ;
198- //迭代遍历Lexeme链表
199- while (c != null && c .getLexeme () != null ){
200- //限制大长度叠词,避免性能问题和整数溢出
201- //只对中文词元应用长度限制,因为只有中文才可能有叠词问题
202- //对于数字、字母、字母数字混合等其他类型的词元,不限制长度
203- if (c .getLexeme ().getLexemeType () == Lexeme .TYPE_CNWORD && c .getLexeme ().getLength () > 10 ){
204- //跳过过长的中文叠词
205- c = c .getNext ();
206- continue ;
207- }
208-
209- if (!option .addNotCrossLexeme (c .getLexeme ())){
210- //词元交叉,添加失败则加入lexemeStack栈
211- conflictStack .push (c );
212- }
213- c = c .getNext ();
214- }
215- return conflictStack ;
216- }
217-
218- /**
219- * 回滚词元链,直到它能够接受指定的词元
220- // * @param lexeme
221- * @param l
222- */
223- private void backPath (Lexeme l , LexemePath option ){
224- while (option .checkCross (l )){
225- option .removeTail ();
226- }
227-
228- }
229-
230- }
81+ }else {
82+ //对当前的crossPath进行歧义处理
83+ LexemePath judgeResult = this .judge (crossPath );
84+ //输出歧义处理结果judgeResult
85+ context .addLexemePath (judgeResult );
86+ }
87+ }
88+
89+ /**
90+ * 为过于复杂的交叉路径构造低成本兜底路径。
91+ * 该逻辑是ik_smart歧义裁决的性能护栏,避免在高密度crossPath上进行昂贵的回溯裁决。
92+ * @param crossPath 当前待裁决的交叉词元路径
93+ * @return 如果路径复杂度超过阈值则返回兜底路径,否则返回null
94+ */
95+ private LexemePath tryBuildFallbackPathForComplexCrossPath (LexemePath crossPath ) {
96+ if (crossPath == null || crossPath .isEmpty ()) {
97+ return null ;
98+ }
99+
100+ if (!this .shouldFallbackForComplexCrossPath (crossPath )) {
101+ return null ;
102+ }
103+
104+ return this .buildFallbackPath (crossPath );
105+ }
106+
107+ /**
108+ * 判断当前交叉路径是否已经复杂到需要跳过正常回溯裁决。
109+ */
110+ private boolean shouldFallbackForComplexCrossPath (LexemePath crossPath ) {
111+ if (crossPath .size () > TOTAL_LEXEME_COUNT_THRESHOLD ) {
112+ return true ;
113+ }
114+
115+ int longCnWordCount = 0 ;
116+ long totalLexemeLength = 0 ;
117+ QuickSortSet .Cell current = crossPath .getHead ();
118+ while (current != null && current .getLexeme () != null ) {
119+ Lexeme lexeme = current .getLexeme ();
120+
121+ totalLexemeLength += lexeme .getLength ();
122+
123+ if (lexeme .getLexemeType () == Lexeme .TYPE_CNWORD
124+ && lexeme .getLength () > LONG_CN_WORD_LENGTH_THRESHOLD ) {
125+ longCnWordCount ++;
126+ }
127+ current = current .getNext ();
128+ }
129+
130+ int pathLength = crossPath .getPathEnd () - crossPath .getPathBegin ();
131+ if (pathLength <= 0 ) {
132+ return false ;
133+ }
134+
135+ return longCnWordCount > LONG_CN_WORD_COUNT_THRESHOLD
136+ && crossPath .size () >= DENSE_CROSS_PATH_SIZE_THRESHOLD
137+ && totalLexemeLength >= (long ) pathLength * DENSE_OVERLAP_RATIO_THRESHOLD ;
138+ }
139+
140+ /**
141+ * 构造低成本兜底路径。保留首词,并将其后的覆盖区间合成为一个词元。
142+ */
143+ private LexemePath buildFallbackPath (LexemePath crossPath ) {
144+ Lexeme firstLexeme = crossPath .peekFirst ();
145+ if (firstLexeme == null ) {
146+ return null ;
147+ }
148+
149+ LexemePath fallbackPath = new LexemePath ();
150+ if (!fallbackPath .addNotCrossLexeme (firstLexeme )) {
151+ return null ;
152+ }
153+
154+ int remainStart = firstLexeme .getBegin () + firstLexeme .getLength ();
155+ int remainLength = crossPath .getPathEnd () - remainStart ;
156+
157+ if (remainLength > 0 ) {
158+ Lexeme remainLexeme = new Lexeme (
159+ firstLexeme .getOffset (),
160+ remainStart ,
161+ remainLength ,
162+ Lexeme .TYPE_CNWORD );
163+ if (!fallbackPath .addNotCrossLexeme (remainLexeme )) {
164+ return null ;
165+ }
166+ }
167+
168+ return fallbackPath ;
169+ }
170+
171+ /**
172+ * 歧义识别
173+ * @param crossPath 歧义路径
174+ * @return
175+ */
176+ private LexemePath judge (LexemePath crossPath ){
177+ // 首先判断当前crossPath是否过于复杂,如果是则直接返回低成本兜底路径
178+ LexemePath fallbackPath = this .tryBuildFallbackPathForComplexCrossPath (crossPath );
179+ if (fallbackPath != null ) {
180+ return fallbackPath ;
181+ }
182+
183+ QuickSortSet .Cell lexemeCell = crossPath .getHead ();
184+
185+ //候选路径集合
186+ TreeSet <LexemePath > pathOptions = new TreeSet <LexemePath >();
187+ //候选结果路径
188+ LexemePath option = new LexemePath ();
189+
190+ //对crossPath进行一次遍历,同时返回本次遍历中有冲突的Lexeme栈
191+ Stack <QuickSortSet .Cell > lexemeStack = this .forwardPath (lexemeCell , option );
192+
193+ //当前词元链并非最理想的,加入候选路径集合
194+ pathOptions .add (option .copy ());
195+
196+ //存在歧义词,处理
197+ QuickSortSet .Cell c = null ;
198+ while (!lexemeStack .isEmpty ()){
199+ c = lexemeStack .pop ();
200+ //回滚词元链
201+ this .backPath (c .getLexeme () , option );
202+ //从歧义词位置开始,递归,生成可选方案
203+ this .forwardPath (c , option );
204+ pathOptions .add (option .copy ());
205+ }
206+
207+ //返回集合中的最优方案
208+ return pathOptions .first ();
209+
210+ }
211+
212+ /**
213+ * 向前遍历,添加词元,构造一个无歧义词元组合
214+ // * @param LexemePath path
215+ * @return
216+ */
217+ private Stack <QuickSortSet .Cell > forwardPath (QuickSortSet .Cell lexemeCell , LexemePath option ){
218+ //发生冲突的Lexeme栈
219+ Stack <QuickSortSet .Cell > conflictStack = new Stack <QuickSortSet .Cell >();
220+ QuickSortSet .Cell c = lexemeCell ;
221+ //迭代遍历Lexeme链表
222+ while (c != null && c .getLexeme () != null ){
223+ if (!option .addNotCrossLexeme (c .getLexeme ())){
224+ //词元交叉,添加失败则加入lexemeStack栈
225+ conflictStack .push (c );
226+ }
227+ c = c .getNext ();
228+ }
229+ return conflictStack ;
230+ }
231+
232+ /**
233+ * 回滚词元链,直到它能够接受指定的词元
234+ // * @param lexeme
235+ * @param l
236+ */
237+ private void backPath (Lexeme l , LexemePath option ){
238+ while (option .checkCross (l )){
239+ option .removeTail ();
240+ }
241+
242+ }
243+
244+ }
0 commit comments