【DIY系列:自己动手写分布式搜索引擎】第20篇:模糊搜索与拼写纠错实战 上一篇【第19篇】编辑距离有限状态机拼写纠错的底层武器下一篇【第21篇】迷你检索引擎完整跑通Hello Search摘要上篇把编辑距离和纠错 FSM 这把武器锻好了这一篇真刀真枪把它装到引擎上。你会看到纠错怎么无缝融进查询流程用户打错时引擎先精确查、查不到再走容错召回你是不是想搜无线耳机这条 did you mean 提示是怎么生成的同音字、形近字这些现实坑怎么填以及中英文纠错为什么完全是两码事。一篇把纠错从纸面原理变成你引擎里的一个实打实功能。一、把纠错接进查询流程纠错不该是独立功能而要融进现有查询链路。最自然的顺序是用户提交 query先正常分词、查倒排。如果命中结果足够比如命中数 ≥ 阈值直接返回不纠错避免画蛇添足。如果结果太少或为零对每个词跑一遍纠错召回候选用候选再去查并把你是不是想搜XXX提示出来。【带纠错的查询链路】 用户输入 无限耳机 │ 精确查询 ▼ 命中数 0 (词典里没有无限) │ 触发纠错 ▼ 对每个词跑容错: 无限→[无线], 耳机→[耳机] │ 用候选再查 ▼ 命中 无线耳机 的文档, 并提示 did you mean: 无线耳机这个先精确后纠错的顺序很关键绝大多数查询是对的别为了那百分之几的错词让所有人都付出纠错开销。只在确实查空时才兜底体验和性能都照顾到。二、did you mean提示怎么来你是不是想搜这条提示本质是选一个最像用户输入的候选词展示。选哪个用前面编辑距离当相似度再叠加一点业务权重比如词的文档频率越高越可能是本意。importjava.util.*;publicclassDidYouMean{privatefinalSetStringdictionary;privatefinalMapString,IntegerdocFreq;// 词 - 命中文档数publicDidYouMean(SetStringdictionary,MapString,IntegerdocFreq){this.dictionarydictionary;this.docFreqdocFreq;}/** 返回最可能的纠错建议, 没有则返回 null */publicStringsuggest(Stringwrong,intmaxEdit){if(dictionary.contains(wrong))returnnull;// 本来就对Stringbestnull;intbestScore-1;for(Stringword:dictionary){if(Math.abs(word.length()-wrong.length())maxEdit)continue;intdistEditDistance.distance(wrong,word);if(distmaxEdit)continue;// 分数: 距离越近越好, 文档频率越高越可能intscore(maxEdit-dist)*1000docFreq.getOrDefault(word,0);if(scorebestScore){bestScorescore;bestword;}}returnbest;}}注意分数里(maxEdit - dist) * 1000把距离放在主导位优先选改得最少的距离相同时又文档频率定胜负比如无限既能纠成无线也能纠成生僻词明显选高频的无线。这就是 did you mean 从不胡乱推荐的底气。三、常见纠错坑同音字与形近字真做纠错你会发现编辑距离 alone 不够用现实里有两类典型坑。同音字中文特有问题。“无线和无限拼音都是 wuxian用户用拼音输入时极易混。纯字形编辑距离认为它们差 1但用户本意其实是拼音错了。更好的做法是加一层拼音容错”把词转拼音对拼音做编辑距离容错能抓住 wuxian→wuxian 这类同音错误。形近字。“己”“已”巳三个字长得像手写的或者 OCR 识别的文档里常混。编辑距离把它们当不同字但用户眼里差不多。这类可以维护一张形近字映射表做预处理归一。【两类坑与对策】 同音: 无线(wuxian) vs 无限(wuxian) → 加拼音层容错 形近: 己 vs 已 vs 巳 → 形近字归一表 英文: recieve vs receive → 编辑距离即可抓重点编辑距离是地基但别迷信它。中文纠错真正难的是语义和拼音层单纯数改了几个字会漏掉同音错误。我们手写版先上编辑距离知道它的边界就够了真做中文搜索时再叠拼音层。四、中英文纠错为什么是两码事英文纠错相对舒服字母表小26 个、词有空格边界、拼写错误大多是键盘相邻键打错teh→the。编辑距离 词典频率就够覆盖大部分。中文完全另一个世界没有词边界先得分词分错了后面全错。纠错和分词耦合。同音字爆炸拼音输入法让无线/无限/无线同音靠字形距离分不清。简繁、多音字重有 zhong/chong增加复杂度。【中英文纠错差异速览】 英文: 有空格边界 字母表小 键盘错为主 → 编辑距离好使 中文: 无边界 同音爆炸 分词耦合 → 需拼音层 分词协同我们的玩具引擎目前按空格切词更贴近英文场景。真做中文得接专业分词 拼音容错那是后面 Lucene 中文篇的戏。这一篇你只要建立中文纠错比英文难一个量级的直觉即可。五、把所有件拼起来一个会纠错的 Searcher把纠错塞进上篇的MiniSearcher逻辑只是加一层查空兜底。publicclassCorrectingSearcher{privatefinalMiniSearchersearcher;privatefinalDidYouMeandym;privatefinalintmaxEdit1;publicCorrectingSearcher(MiniSearchersearcher,DidYouMeandym){this.searchersearcher;this.dymdym;}publicSearchResultsearch(Stringquery)throwsIOException{ListMiniSearcher.SearchHithitssearcher.search(query);if(!hits.isEmpty()){returnnewSearchResult(hits,null);// 查到了, 不纠错}// 查空, 逐词纠错生成建议 queryStringBuilderfixednewStringBuilder();for(Stringterm:newSimpleAnalyzer().analyze(query)){Stringsugdym.suggest(term,maxEdit);fixed.append(sug!null?sug:term).append( );}ListMiniSearcher.SearchHitfixedHitssearcher.search(fixed.toString().trim());StringhintfixedHits.isEmpty()?null:fixed.toString().trim();returnnewSearchResult(fixedHits,hint);// hint 即 did you mean}publicstaticclassSearchResult{publicfinalListMiniSearcher.SearchHithits;publicfinalStringdidYouMean;// 非空则提示publicSearchResult(ListMiniSearcher.SearchHith,Stringd){hitsh;didYouMeand;}}}跑一下search(无限耳机)精确查为空 → 触发纠错 → 建议无线耳机 → 用建议查到文档并带上didYouMean无线耳机。前端看到这个字段就能显示你是不是想搜无线耳机了。【一次纠错查询的输出】 输入: 无限耳机 输出: hits[doc1,...], didYouMean无线耳机 前端: (空结果区) 你是不是想搜无线耳机 [点一下用建议重查]六、纠错这一关你拿到了什么到这你的引擎有了容错这只眼睛用户打错字不再直接吃空结果而是被温柔地引回正确查询。底层是编辑距离 自动机思路表层是先精确后兜底的查询顺序和 did you mean 提示。你也看清了同音字、形近字、中英文差异这些真实坑。下一篇我们要把建索引、落盘、读取、查询、纠错这些零件全部串成一条线喂一份真实小语料跑通整个引擎验证性能和正确性给第二模块画上句号。七、拼音容错中文纠错的隐藏主线前面说同音字是中文纠错的大坑这里给个能落地的做法。维护一张词 → 拼音的映射可用开源拼音库批量生成纠错时同时计算字形编辑距离和拼音编辑距离取两者较小者当相似度。比如无限和无线拼音都是 wuxian拼音距离 0一眼就纠得出来而己和已拼音不同ji / yi靠字形距离兜住。【拼音层容错示意】 输入 wuxian耳机(拼音输入手误) 拼音距离: wuxian vs 无线(wuxian) 0 → 强烈候选 字形距离: 无限 vs 无线 1 → 也候选 综合: 无线 胜出这层不复杂但能把中文纠错从勉强能用拉到真能救场。代价是多存一份拼音索引空间换体验做中文搜索基本都值得。八、上线的考量纠错也有延迟预算纠错虽好不能无节制。线上搜索的延迟预算通常就几十毫秒纠错若每次都遍历词典算距离很容易超预算。工程上常见三招压成本只在精确查询命中太少时触发绝大多数正常查询零开销。词典不大时直接遍历算距离我们玩具版就是词典大时换自动机求交。把热门纠错对用户输入 → 正确词缓存起来重复错词直接命中缓存。这和我们前面先精确后兜底的顺序是一脉相承的把贵操作锁在最小触发面里。九、纠错之外联想词与 did you mean 的关系did you mean 是你打错了我猜你想搜这个联想词搜索框下拉是你还没打完我先猜你要搜啥。两者底层都依赖字符串相似 频率技术上同源。我们这篇做了 did you mean等后面讲搜索界面体验模块会把它和联想词一起系统做一遍。现在你至少知道它们都不是魔法而是编辑距离加频率表的组合拳。本篇小结纠错要融进查询链路先精确查结果太少才走容错召回避免无谓开销。did you mean 用编辑距离挑改得最少、文档频率最高的候选展示。现实坑是同音字加拼音层和形近字归一表中文纠错比英文难一个量级因为无词边界且同音爆炸。我们把纠错接进 Searcher跑通了无限耳机→无线耳机的兜底。下一篇把全部零件串起来端到端跑通。上一篇【第19篇】编辑距离有限状态机拼写纠错的底层武器下一篇【第21篇】迷你检索引擎完整跑通Hello Search