码上生活

如何开发亲戚计算器

这是M小白实验室第一篇娱乐性科普文章

又到了过年的时候,每到这个时候,你总是能见到自己几乎没印象但父母就是很熟的亲戚。

而且关系凌乱到你自己都说不清。

不信?那就来几道测试题。

2019年亲戚关系测试题正式开始。请完成如下填空。

  1. 爸爸的姐姐的女儿的女儿 = ________
  2. 妈妈的姐姐的弟弟的女儿 = ________
  3. 爷爷的弟弟的女儿的儿子 = ________
  4. 妈妈的妈妈的弟弟的儿子 = ________

参考答案如下:

  1. 表外甥女
  2. 舅表姐/舅表妹
  3. 堂姑表兄/堂姑表弟
  4. 舅表舅父
啥?

由于不知道该如何称呼,面对陌生的亲戚时,我们只能说一句“您好”,附带一个尴尬又不失礼貌的微笑。

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开发。

参考资料

发表回复