
本文介绍在 go 语言中处理英文名词单复数归一化的实用方法,重点解决犯罪报告等场景中因“armed robbery”与“armed robberies”等词形差异导致的统计重复问题,涵盖规则推导、第三方库选型及轻量级实现建议。
本文介绍在 go 语言中处理英文名词单复数归一化的实用方法,重点解决犯罪报告等场景中因“armed robbery”与“armed robberies”等词形差异导致的统计重复问题,涵盖规则推导、第三方库选型及轻量级实现建议。
在构建大学犯罪统计系统时,你可能会遇到类似这样的原始数据:
- "Armed Robbery"(出现 2 次)
- "Armed Robberies"(出现 1 次)
若直接以原始字符串为键存入 map[string]int,将导致同一类犯罪被拆分为两个独立条目,破坏统计准确性。根本需求并非“判断 A 是否为 B 的复数”,而是将语义等价的不同屈折形式映射到同一规范词干(canonical form)——这属于自然语言处理中的词形归一化(lemmatization / inflection normalization)范畴。
✅ 推荐方案:使用成熟 Inflection 库
Go 生态中较成熟、轻量且维护良好的英文屈折处理库是 jinzhu/inflection(非官方但广泛采用)。它支持单复数双向转换,API 简洁:
go get github.com/jinzhu/inflection
package main
import (
"fmt"
"strings"
"github.com/jinzhu/inflection"
)
func normalizeCrimeTitle(title string) string {
// 提取核心犯罪名词(示例:从 "Armed Robberies" 中提取 "Robberies")
// 实际中建议结合分词或正则更精准提取,此处简化演示
words := strings.Fields(title)
if len(words) == 0 {
return title
}
lastWord := words[len(words)-1]
// 尝试转为单数(主流场景:复数 → 单数归一)
singular := inflection.Singular(lastWord)
// 替换原词为单数形式,重建标题
newTitle := strings.Join(words[:len(words)-1], " ") + " " + singular
return strings.TrimSpace(newTitle)
}
func main() {
fmt.Println(normalizeCrimeTitle("Armed Robberies")) // "Armed Robbery"
fmt.Println(normalizeCrimeTitle("Armed Robbery")) // "Armed Robbery"(不变)
fmt.Println(normalizeCrimeTitle("Thefts")) // "Theft"
fmt.Println(normalizeCrimeTitle("Theft")) // "Theft"
}⚠️ 注意:inflection.Singular() 基于启发式规则(如 -ies → -y, -es/-s → trim),对规则名词准确率高,但对不规则名词(如 children, mice, data)或专业术语可能失效。生产环境建议配合人工词表校验。
? 替代方案:轻量规则 + 白名单兜底
若项目要求零依赖或仅需覆盖有限词汇(如常见犯罪类型),可构建简易归一化函数:
var pluralToSingular = map[string]string{
"robberies": "robbery",
"thefts": "theft",
"assaults": "assault",
"burglaries": "burglary",
"vandalisms": "vandalism", // 不规则,需显式定义
"data": "datum", // 特殊复数,按需添加
}
func simpleNormalize(word string) string {
lower := strings.ToLower(word)
if singular, ok := pluralToSingular[lower]; ok {
return singular
}
// 基础规则回退(仅处理常见后缀)
switch {
case strings.HasSuffix(lower, "ies") && len(lower) > 3:
return lower[:len(lower)-3] + "y"
case strings.HasSuffix(lower, "es") && len(lower) > 2:
return lower[:len(lower)-2]
case strings.HasSuffix(lower, "s") && len(lower) > 1:
return lower[:len(lower)-1]
}
return word // 无法归一,保留原词
}? 关键实践建议
- 不要依赖字符串包含或前缀匹配:"robbery" 是 "robberies" 的子串,但 "bus" 也是 "buses" 的子串——这种启发式极易误判。
- 优先归一化再统计:在 crimes[feed.Items[i].Title[11:]]++ 前,先调用 normalizeCrimeTitle(...) 处理标题,确保键的一致性。
- 警惕大小写与标点:预处理时统一转小写,并移除末尾括号、数字、括号内说明(如 " (with a count of 1)"),避免干扰词干提取。
- 验证与日志:对首次出现的未归一化词记录日志,持续完善白名单或调整规则。
通过合理选用 inflection 库或构建可控的归一化逻辑,即可将分散的复数犯罪记录聚合为统一语义条目,显著提升统计报告的专业性与可信度。


















