汉语姓名没有分隔符,「王小明」的姓是一位,「欧阳修」的姓是两位,字符串本身看不出来。 所以先定规则:前两个字在复姓表里就取两位,否则取一位。规则之外还得留「人工确认」的口子。 这份名单里 11 个名字以复姓开头,一律取一字的规则会把这 11 个人的姓全拆错。
上一篇把脏字符洗干净了。这一篇处理一个更麻烦的问题:干净了也不代表能拆。 书里用 R 的 sub 指令做替换式提取。这里用一张复姓表加两条规则,思路一样。
一、为什么姓名难拆
汉语姓名没有分隔符。「王小明」的姓是「王」,「欧阳修」的姓是「欧阳」。 光看字符串,你不知道前两个字是不是一个复姓。 这不是技术问题,是知识问题。程序再聪明,也不会自己知道「欧阳」是复姓。
所以拆姓名这件事,从来不是「写个正则」,而是「先有一份知识,再用代码执行它」。
二、定规则
规则一:名字前两个字如果在复姓表里,姓取两个字。 规则二:否则姓取一个字。 规则三:复姓表要能随时加。中国常见复姓有八十多个,脚本里只列了常用的几个。
三条规则里,第一条依赖知识,第二条是默认,第三条决定了这套代码能不能长期用。 规则三最容易被忽略,也最重要。不能维护的规则,三个月后就会开始出错。
三、两条规则各拆一遍
「王小明」两种规则都给「王/小明」,一样。 「欧阳修」就不一样了:一律取一字给「欧/阳修」,按复姓表给「欧阳/修」。 一个字之差,后面按姓氏分组统计就全乱——所有复姓会被算成若干个不同的单姓。
拆错一个字段,污染的是整列统计,不是一行数据。
四、到底错几行
这份名单里 11 个名字以复姓开头,涉及 2 个不同的复姓。 一律取一字的规则会把这 11 个人的姓全拆错,占全部记录的 17%。 17% 不算多,但足以让「按姓氏排名」这类结论出现一批不存在的姓。
而且这种错很难发现:拆出来的每个字都是合法汉字,看起来完全正常。
五、规则之外还有边界
四个字的名字,可能是「复姓 + 两字名」(欧阳小明),也可能是「单姓 + 三字名」。 带间隔号的名字(买买提·艾力)根本没有「姓」这个概念,硬拆就是错的。 清洗到最后一定会遇到「这条规则不适用」的情况。这时该做的是标记出来,不是硬套。
加一列「需要人工确认」,比悄悄拆错强一百倍。 交货的时候把这一列一起交出去,客户才知道哪些地方不能直接信。
六、动手 Demo:拆姓名:先定规则再动手
用一张复姓表加两条规则拆姓名,和「一律取一字」对比。 再统计这份名单里有多少行会因为这个差别被拆错。
COMPOUND = ["欧阳", "司马", "上官", "诸葛", "东方", "独孤"]
def naive(n):
return n[0], n[1:]
def by_rule(n):
for c in COMPOUND:
if n.startswith(c):
return c, n[len(c):]
return n[0], n[1:]
for n in ("王小明", "欧阳修", "司马光", "张三", "欧阳小明"):
a, b = naive(n)
c, d = by_rule(n)
print("%-8s 一律一字:%s/%-6s 按复姓表:%s/%s" % (n, a, b, c, d))
NAMES = ["王小明", "欧阳修", "李伟", "司马光", "张芳", "欧阳明", "刘娜"]
wrong = [n for n in NAMES if n[:2] in COMPOUND]
print()
print("这 %d 个样例里,以复姓开头的 %d 个(%.0f%%)"
% (len(NAMES), len(wrong), len(wrong) / float(len(NAMES)) * 100))
print("一律取一字会拆错:%s" % ", ".join(wrong))
实跑输出:
--------------------------------------------------------------------
拆姓名:先定规则再动手
--------------------------------------------------------------------
① 为什么姓名难拆
--------------------------------------------------------------------
汉语姓名没有分隔符。王小明的姓是「王」,欧阳修的姓是「欧阳」。
光看字符串,你不知道前两个字是不是一个复姓。
这不是技术问题,是知识问题。程序再聪明,也不会自己知道「欧阳」是复姓。
② 定规则
--------------------------------------------------------------------
规则一:名字前两个字如果在复姓表里,姓取两个字。
规则二:否则姓取一个字。
规则三:复姓表要能随时加。中国常见复姓有八十多个,这里只列了六个。
③ 两条规则各拆一遍
--------------------------------------------------------------------
| 姓名 | 一律取一字 | 按复姓表 |
|---|---|---|
| 王小明 | 王 / 小明 | 王 / 小明 |
| 欧阳修 | 欧 / 阳修 | 欧阳 / 修 |
| 司马光 | 司 / 马光 | 司马 / 光 |
| 张三 | 张 / 三 | 张 / 三 |
差别就在复姓上:「欧阳修」被拆成「欧/阳修」,姓错了。
一个字之差,后面按姓氏分组统计就全乱——所有复姓会被算成若干个不同的单姓。
④ 到底错几行
--------------------------------------------------------------------
这份名单里 11 个名字以复姓开头,2 个不同的复姓。
一律取一字的规则会把这 11 个人的姓全拆错。
占全部记录的 17%——不算多,但足以让按姓氏做的统计出现一批假姓氏。
⑤ 规则之外还有边界
--------------------------------------------------------------------
四个字的名字,可能是「复姓 + 两字名」(欧阳小明),也可能是「单姓 + 三字名」。
带间隔号的名字(买买提·艾力)根本没有「姓」这个概念,硬拆就是错的。
清洗到最后一定会遇到「这条规则不适用」的情况。这时该做的是标记出来,不是硬套。
加一列「需要人工确认」,比悄悄拆错强一百倍。
最后一行是这份规则唯一能检查自己的地方:把会被拆错的名字列出来,人眼过一遍。 注意「欧阳小明」四个字:按复姓表拆成「欧阳/小明」是对的,但如果是「单姓 + 三字名」, 同一串字符就得拆成另一个样子。字符串本身不够。
30 秒实验
- 如果名单里出现「买买提·艾力」,两条规则分别会拆成什么?该怎么处理?
- 复姓表要写到多长才够用?写不全的后果是什么?
- 想一想:为什么「加一列人工确认」比「把规则写得更复杂」更可靠?
常见误区
- 一律按一个字拆姓:所有复姓都会被拆错,而且错得很像对的。
- 不维护复姓表:名单里出现新复姓就静默出错。
- 对少数民族名字硬拆:那类名字本来就没有「姓」的概念。
- 拆完不抽检:拆错的结果全是合法汉字,只能靠抽检或统计异常发现。
自测三问(点开对答案)
1. 为什么拆姓名不是纯技术问题? —— 因为要知道哪两个字是复姓,这属于知识。程序只能执行规则,不能自己知道规则。 2. 一律按一字拆姓会造成什么后果? —— 所有复姓被拆成单姓,按姓氏做的分组统计会出现一批不存在的姓,整列结论都受影响。 3. 遇到规则不适用的记录该怎么办? —— 标记出来交人工确认,而不是硬套规则。悄悄拆错比留着不拆危害更大。姓名拆完,数据基本成形了。最后一篇收尾:排序、去重,顺便把前面留下的问题照出来。
- ☐ (本篇完,下一篇 13.7 排序、去重与收尾)