这是M小白实验室第一篇娱乐性科普文章
笔者博客:mwhitelab.com
笔者公众号:技术杂学铺
又到了过年的时候,每到这个时候,你总是能见到自己几乎没印象但父母就是很熟的亲戚。
而且关系凌乱到你自己都说不清。
不信?那就来几道测试题。
2019年亲戚关系测试题正式开始。请完成如下填空。
- 爸爸的姐姐的女儿的女儿 = ________
- 妈妈的姐姐的弟弟的女儿 = ________
- 爷爷的弟弟的女儿的儿子 = ________
- 妈妈的妈妈的弟弟的儿子 = ________
参考答案如下:
- 表外甥女
- 舅表姐/舅表妹
- 堂姑表兄/堂姑表弟
- 舅表舅父
啥?
由于不知道该如何称呼,面对陌生的亲戚时,我们只能说一句“您好”,附带一个尴尬又不失礼貌的微笑。
BUT! 作为21世纪的资深网民(手机重度依赖患者),我们应该使用新时代的技术来解决这个尴尬的局面。那就是使用软件——亲戚关系计算器。
在微信小程序中搜索“亲戚计算器”会显示很多类似的程序。
操作界面和普通的计算器类似,不过数字和加减乘除键都变成了“父” “母” “兄” “姐” 这样的关系。
以后遇见类似有不确定该如何称呼的亲戚,直接这样用程序就可以了。
再BUT! 作为21世纪的新型人才(这个词是我现想的,我也不知道是啥意思),我们不能只能满足于使用,更应该探求其本质,了解亲戚关系计算器是如何实现的。
实现方法一:死记硬背法
关系 | 称谓 |
父亲的父亲 | 爷爷 |
父亲的母亲 | 奶奶 |
父亲的姐姐 | 姑妈 |
父亲的妹妹 | 姑妈 |
…… | …… |
该方法尝试记下所有亲戚关系的结果,当用户想要查询某一种关系时,直接查表。
假设亲戚之间的关系只有父、母、兄、弟、姐、妹、子、女八种。(先不考虑夫妻关系)
那么像父亲的父亲,哥哥的女儿这样简单的 “XX的XX” 的关系有 8*8 = 64种可能,而 “XX的XX的XX” 有8*8*8 = 512种可能,四个关系的有4096种可能!如此大量的关系,光是把这些可能一个一个地列举出来就会让程序员精神崩溃。
然而这样的死记硬背法是可行的,之前我们看到的微信小程序(其源代码已公布在github上)就是用这个方法来实现的:
我们先对关系进行简化,比如在 ‘我’ 是男性的情况下,那么‘我的儿子的父亲的XX’在正常情况下就可以化简成 ‘我的XX’。
再比如 母亲的丈夫 = 我的父亲,兄弟的父母=我的父母,姐姐的姐姐 = 姐姐等等。
接着,我们人为给出六七百条最常用的关系和其对应的称谓。任何一条用户输入的关系,其化简后如果在已知的六七百条关系中,我们则返回其对应的称谓,否则,返回不知道。
实现方法二:网状图
在亲戚关系中,所有的名词可以分为两类。
一类是称谓,也就是我们如何称呼其他人的,如父亲、母亲、爷爷、姑姑、舅舅等。
另一类是基础关系,是称谓的子集,由父亲、母亲、儿子、女儿、哥哥、弟弟、姐姐、妹妹、丈夫、妻子,这十个词组成。任何一个称谓都可以由基础关系来表达。
如 爷爷 = 父亲的父亲;姥爷 = 母亲的父亲;太爷爷 = 父亲的父亲的父亲。
圆角长方形框内为称谓,箭头为基础关系(先不考虑丈夫与妻子这两个关系)。我们可以用如下图来表示“我”的亲戚关系:
我们对图中的每一个圆角长方形(称谓)计算其八种关系(父母儿女兄弟姐妹)。
比如,对于上图,我们接着计算父亲和母亲的八种关系对应的人,获得如下图:
之后对于得到的这张图,接着计算每个圆角长方形(称谓)对应的关系, 不断地画下去,可以得到一张巨大的网状图。
这张网状图可以无限延伸(如父亲 的 父亲 的 父亲 的 父亲……),我们根据实际情况,将网状图扩展到足够用的地步就行了。
关系网
网状图的每一个称谓和关系都是我们人为确定了。不过其工作量远远小于“死记硬背法”。因为对于一个经过四个关系的问题,如:父亲的儿子的父亲的父亲=爷爷,母亲的母亲的女儿的父亲=姥爷,一个很小的网状图就能确定这个问题的结果。而“死记硬背法”则需要记录下所有 “XX的XX的XX的XX” 对应的结果才行。
网状图如何实现
不懂编程的朋友可跳过本节往下看。
C语言可以使用结构体来表示称谓,用指针来表示关系(这里没有考虑父亲的儿子可能是自己,哥哥,弟弟多种可能)。
struct node {struct node *father; struct node *mother; struct node *big_bro; struct node *small_bro; struct node *big_sister; struct node *small_sister; struct node *son; struct node *daughter;
};
python可以使用字典。这里主要用python写一个简单的示意程序,具体代码见github:
1.建立数据库(该工作量十分庞大,这里只展示几个例子):
me = {'f':'父亲','m':'母亲','bb':'哥哥','sb':'弟弟','bs':'姐姐','ss':'妹妹','son':'儿子','dau':'女儿'}
father = {'f':'爷爷','m':'奶奶','bb':'伯父','sb':'叔叔','bs':'姑妈','ss':'姑妈','son':['我','哥哥','弟弟'],'dau':['我','姐姐','妹妹']}
mother = {'f':'姥爷','m':'姥姥','bb':'大舅','sb':'小舅','bs':'大姨','ss':'小姨','son':['我','哥哥','弟弟'],'dau':['我','姐姐','妹妹']}
…………
2.建立中文名与变量的对应关系:
name2var = {'我':me,'父亲':father,'母亲':mother,'哥哥':big_bro,\'弟弟':small_bro,'姐姐':big_sister,'妹妹':small_sister,\'儿子':son,'女儿':daughter}
relation2char = dict(zip(me.values(), me.keys()))import numpy as np# 考虑返回值可能不止一个 如父亲的儿子可能为[‘我’,‘哥哥’,‘弟弟’]
def returnNext(names,relation):return_name = []for name in names:return_name.append(name2var[name][relation2char[relation]])return list(set(np.array(return_name).flatten()))
3.使用一个函数,封装所有操作:
def getName(relation_name):relationships = relation_name.split('的')name = [relationships[0]]for relation in relationships[1:]:name = returnNext(name,relation)return name
4. 使用:
输入 | 输出 |
getName(‘我的父亲的儿子’) | [‘哥哥’, ‘我’, ‘弟弟’] |
getName(‘我的母亲的女儿’) | [‘姐姐’, ‘我’, ‘妹妹’] |
getName(‘我的父亲的儿子的父亲’) | [‘父亲’] |
一些细节
首先,是性别:如果‘我’是女性,那么‘我的父亲的儿子’可以为[‘哥哥’,‘弟弟’],而不可以包含‘我’。(上述代码没实现)
另外,关于夫妻关系:在正常情况下,男性称谓只可以有‘妻子’,女性称谓只可以有‘丈夫’。(上述代码没实现)
第三,多种可能:‘我的父亲的儿子’ 可以是[‘我’,‘哥哥’,‘弟弟’],再若是再往后计算,如‘我的父亲的儿子的儿子’ ,需要同时考虑‘我的儿子’,‘哥哥的儿子’,‘弟弟的儿子’这三种可能。(上述代码已实现)
最后,给出一个线上的亲戚关系计算器。该计算器为开源代码,由mumuy开发。
参考资料
- “亲戚关系计算器”算法实现
- “亲戚关系”程序github源代码