strings.Contains 无法实现模糊匹配,需用编辑距离等算法;推荐 agnivade/levenshtein 包计算 Levenshtein 距离,并采用归一化阈值(距离/最长长度≤0.3)提升适配性,中文场景建议先拼音化再计算。

用 strings.Contains 做不了模糊匹配,别硬套
很多人一上来就试 strings.Contains 或 strings.Index,发现只能找子串,完全不支持“类似”“接近”“拼错也命中”这类需求。模糊匹配不是字符串包含关系,而是基于编辑距离、n-gram、或相似度打分的算法问题。Go 标准库不提供开箱即用的模糊搜索,得靠第三方包或自己封装逻辑。
常见误区是把正则表达式当模糊匹配——regexp 能做模式匹配,但无法处理“用户输成 gole 仍想匹配 goel(实际应为 goal)”这类场景。编辑距离才是更贴合的模型。
推荐用 github.com/agnivade/levenshtein 计算编辑距离
这是目前最轻量、无依赖、文档清晰的 Levenshtein 距离实现。它返回两个字符串之间最少需要多少次单字符编辑(插入、删除、替换)才能相等。数值越小,越“像”。
- 安装:
go get github.com/agnivade/levenshtein - 基础用法:
levenshtein.ComputeDistance("kitten", "sitting")返回3 - 注意大小写敏感,如需忽略,提前统一转
strings.ToLower - 性能上,对长度 ≤ 100 的字符串非常快;超过 500 字符时建议加长度预筛(比如先用
len()快速排除长度差 > 阈值的候选)
构建可配置的模糊搜索函数,别写死阈值
硬编码“距离 ≤ 2 就算匹配”在实际项目中很快会出问题:短词(如 “db”)容错 1 次就占一半长度,长词(如 “configuration”)容错 3 次可能还不够。更合理的是用归一化阈值(如编辑距离 / 最长字符串长度 ≤ 0.3)。
示例函数结构:
func FuzzyMatch(query, target string, maxRatio float64) bool {
if query == "" || target == "" {
return false
}
dist := levenshtein.ComputeDistance(query, target)
maxLen := int(math.Max(float64(len(query)), float64(len(target))))
return float64(dist)/float64(maxLen) <= maxRatio
}
- 调用时传
0.2表示允许最多 20% 的字符差异 - 若需支持多字段搜索(如同时查 name 和 description),不要重复计算距离,先合并字段再比,或分别打分后加权
- 避免对每个候选字符串都调用
FuzzyMatch—— 如果候选集大(>1000 条),考虑预建 n-gram 索引(如用github.com/blevesearch/bleve)
中文模糊搜索要额外处理分词和 Unicode
直接对中文字符串跑 Levenshtein 效果很差:用户搜“微信支付”输成“微信之付”,编辑距离是 1,但语义偏差大;而“微 信 支 付”和“微信支付”因空格差异导致距离陡增。这不是算法问题,是输入表示问题。
- 简单方案:用
github.com/go-ego/gse先分词,再对关键词做集合匹配(Jaccard 相似度)或 term-level 编辑距离 - 更稳妥的做法是统一走拼音化:用
github.com/mozillazg/go-pinyin把“微信支付”转成weixinzhifu,再用前述 Levenshtein 方法——这对音近错别字(如“支负”→“zhifu”)更鲁棒 - 注意
runevsbyte:中文字符是多字节,len("你好") == 6(UTF-8 字节数),但utf8.RuneCountInString("你好") == 2。Levenshtein 包内部按rune计算,无需手动转换,但你自己做长度归一化时必须用utf8.RuneCountInString


















