@@ -44,6 +44,10 @@ func formatLayerTrace(trace []layerHit) string {
4444
4545// CompositeDict 聚合词库
4646// 按优先级组合多个词库层,实现分层叠加查询
47+ //
48+ // 排序模式不由 CompositeDict 持有 —— 它属于当前活跃方案,
49+ // 由调用方(引擎)通过 SearchOptions.SortMode 每次显式传入。
50+ // 这样设计避免多方案共享同一 dm 时 sortMode 跨方案污染。
4751type CompositeDict struct {
4852 mu sync.RWMutex
4953 layers []DictLayer // 按优先级排序(LayerType 小的在前)
@@ -53,9 +57,14 @@ type CompositeDict struct {
5357
5458 // 词频评分器(可选)
5559 freqScorer FreqScorer
60+ }
5661
57- // 排序模式
58- sortMode candidate.CandidateSortMode
62+ // SearchOptions 查询选项。
63+ // 引入 struct 是为后续扩展(如 IncludeLayers、FilterMode 等查询级配置)留口子,
64+ // 当前仅有 Limit / SortMode 两个字段。零值(SortMode == "")等价于按词频排序(Better)。
65+ type SearchOptions struct {
66+ Limit int // 最大返回数量,0 表示不限制
67+ SortMode candidate.CandidateSortMode // 排序模式,空值默认为词频排序
5968}
6069
6170// seenIdxPool 复用 searchInternal 中的去重 map。每次按键都会触发一轮 search,
@@ -133,44 +142,70 @@ func (c *CompositeDict) SetFreqScorer(scorer FreqScorer) {
133142 c .freqScorer = scorer
134143}
135144
136- // SetSortMode 设置候选排序模式
137- func (c * CompositeDict ) SetSortMode (mode candidate.CandidateSortMode ) {
138- c .mu .Lock ()
139- defer c .mu .Unlock ()
140- c .sortMode = mode
141- }
142-
143- // GetSortMode 获取当前排序模式
144- func (c * CompositeDict ) GetSortMode () candidate.CandidateSortMode {
145+ // Search 聚合查询
146+ // 按优先级遍历所有层,合并结果。
147+ // opt.SortMode 由调用方传入;空值视为词频排序。
148+ func (c * CompositeDict ) Search (code string , opt SearchOptions ) []candidate.Candidate {
145149 c .mu .RLock ()
146150 defer c .mu .RUnlock ()
147- if c .sortMode == "" {
148- return candidate .SortByFrequency
149- }
150- return c .sortMode
151+
152+ return c .searchInternal (code , opt , false )
151153}
152154
153- // Search 聚合查询
154- // 按优先级遍历所有层,合并结果,应用 Shadow 规则
155- func (c * CompositeDict ) Search (code string , limit int ) []candidate.Candidate {
155+ // SearchPrefix 聚合前缀查询
156+ func (c * CompositeDict ) SearchPrefix (prefix string , opt SearchOptions ) []candidate.Candidate {
156157 c .mu .RLock ()
157158 defer c .mu .RUnlock ()
158159
159- return c .searchInternal (code , limit , false )
160+ return c .searchInternal (prefix , opt , true )
160161}
161162
162- // SearchPrefix 聚合前缀查询
163- func (c * CompositeDict ) SearchPrefix (prefix string , limit int ) []candidate.Candidate {
163+ // SearchSystemOnly 仅查询系统码表 / 细胞词库层(LayerTypeCell + LayerTypeSystem),
164+ // 跳过 user/temp/shadow/phrase 等层。
165+ // 用于 ProtectTopN: "锁定码表原始顺序"语义只看系统层,避免被用户词/临时词污染。
166+ // 不应用 freqScorer——保护的是码表静态权重序,而不是当前调频后的实时顺序。
167+ func (c * CompositeDict ) SearchSystemOnly (code string , opt SearchOptions ) []candidate.Candidate {
164168 c .mu .RLock ()
165169 defer c .mu .RUnlock ()
166170
167- return c .searchInternal (prefix , limit , true )
171+ results := make ([]candidate.Candidate , 0 , 32 )
172+ seen := make (map [string ]int , 16 )
173+ for _ , layer := range c .layers {
174+ t := layer .Type ()
175+ if t != LayerTypeSystem && t != LayerTypeCell {
176+ continue
177+ }
178+ for _ , cand := range layer .Search (code , 0 ) {
179+ if idx , exists := seen [cand .Text ]; exists {
180+ if cand .Weight > results [idx ].Weight {
181+ results [idx ].Weight = cand .Weight
182+ }
183+ continue
184+ }
185+ seen [cand .Text ] = len (results )
186+ results = append (results , cand )
187+ }
188+ }
189+
190+ comparator := candidate .Better
191+ if opt .SortMode == candidate .SortByNatural {
192+ comparator = candidate .BetterNatural
193+ }
194+ sort .SliceStable (results , func (i , j int ) bool {
195+ return comparator (results [i ], results [j ])
196+ })
197+
198+ if opt .Limit > 0 && len (results ) > opt .Limit {
199+ results = results [:opt .Limit ]
200+ }
201+ return results
168202}
169203
170204// searchInternal 内部查询逻辑
171205// Shadow 的 pin/delete 不在此处处理——统一由引擎层 Phase 6(ApplyShadowPins)在最终排序后应用。
172206// CompositeDict 只负责层级合并、去重和基础排序。
173- func (c * CompositeDict ) searchInternal (code string , limit int , isPrefix bool ) []candidate.Candidate {
207+ func (c * CompositeDict ) searchInternal (code string , opt SearchOptions , isPrefix bool ) []candidate.Candidate {
208+ limit := opt .Limit
174209 // 1. 遍历所有层收集候选词
175210 // 去重策略:保留高优先级层(先出现)的词条信息,但继承后续层中同 Text 词条的更高权重。
176211 // 这确保用户词不会因为低权重而丢失码表词的自然排序位置。
@@ -243,7 +278,7 @@ func (c *CompositeDict) searchInternal(code string, limit int, isPrefix bool) []
243278
244279 // 3. 排序
245280 comparator := candidate .Better
246- if c . sortMode == candidate .SortByNatural {
281+ if opt . SortMode == candidate .SortByNatural {
247282 comparator = candidate .BetterNatural
248283 }
249284 sort .SliceStable (results , func (i , j int ) bool {
@@ -459,11 +494,9 @@ func (c *CompositeDict) GetLayersByType(layerType LayerType) []DictLayer {
459494// 查询便捷方法
460495// ============================================================
461496
462- // Lookup 按编码查询候选词
497+ // Lookup 按编码查询候选词(便捷方法,使用默认排序模式)
463498func (c * CompositeDict ) Lookup (pinyin string ) []candidate.Candidate {
464- results := c .Search (pinyin , 0 )
465- // log.Printf("[CompositeDict] Lookup: pinyin=%q results=%d", pinyin, len(results))
466- return results
499+ return c .Search (pinyin , SearchOptions {})
467500}
468501
469502// LookupPhrase 将音节列表拼接后查询
@@ -478,12 +511,12 @@ func (c *CompositeDict) LookupPhrase(syllables []string) []candidate.Candidate {
478511 code += s
479512 }
480513
481- return c .Search (code , 0 )
514+ return c .Search (code , SearchOptions {} )
482515}
483516
484517// LookupPrefix 实现 dict.PrefixSearchable 接口
485518func (c * CompositeDict ) LookupPrefix (prefix string , limit int ) []candidate.Candidate {
486- return c .SearchPrefix (prefix , limit )
519+ return c .SearchPrefix (prefix , SearchOptions { Limit : limit } )
487520}
488521
489522// LookupCommand 实现 dict.CommandSearchable 接口
0 commit comments