家族树软件中的循环


1594

我是一些家谱软件(用C ++和Qt编写)的开发人员。在我的一位客户向我发送错误报告之前,我没有遇到任何问题。问题是该客户有两个孩子,并且有自己的女儿,结果,由于错误,他无法使用我的软件。

这些错误是由于我对要处理的族图的各种断言和不变性导致的(例如,经过一个循环后,程序指出X不能同时是Y的父亲和祖父)。

如何在不删除所有数据断言的情况下解决这些错误?



30
如果您向后追溯家谱的次数足够多,那么您遇到此问题的频率就会比您想的要高得多。放弃树表示可能会很痛苦,但最终会更正确。
托马斯

55
您不应该为不可能的事情添加断言,仅对不可能的事情添加断言。循环是家谱图中不可能出现的显而易见的事情……没有人可以通过任何方法成为自己的祖先。这些其他断言只是虚假的,应该删除。
pgod 2011年

44
在宠物繁殖领域,这根本不是一个愚蠢的问题。在那里,女儿对父亲,母亲对儿子,姐妹对兄弟,孙子对祖父母是标准技术,宠物饲养者也需要家谱软件。“纯种”我的¤%#&。
kaleissin11年

31
与表亲结婚在维多利亚时代的英格兰非常普遍,尤其是在上层阶级中(这是在家庭中存钱的绝妙方式)。例如,查尔斯·达尔文(Charles Darwin)嫁给了他的第一任堂兄艾玛·韦奇伍德(Emma Wedgwood)。任何家族树软件都需要支持这种情况。
rtperson 2011年

Answers:


727

您(和/或您的公司)似乎对家谱应该是什么有基本的误解。

让我澄清一下,我也为一家在其投资组合中拥有家族树(作为其产品之一)的公司工作,我们一直在努力解决类似的问题。

就我们而言,我也认为您的情况也来自于GEDCOM格式,该格式对于一家人应该如何对待非常有见地。但是,这种格式包含一些关于家谱到底是什么样子的严重误解。

GEDCOM存在许多问题,例如与同性关系不兼容,乱伦等。在现实生活中,这种情况发生的频率比您想象的要多(尤其是回到1700-1800年时)。

我们已经根据现实世界中发生的事情建模了家谱:事件(例如,出生,婚礼,订婚,工会,死亡,收养等)。除了逻辑上不可能的限制外,我们对这些没有任何限制(例如,一个人不能成为自己的父母,关系需要两个人,等等。)

缺乏验证使我们有了一个“现实世界”,更简单,更灵活的解决方案。

对于这种特定情况,我建议删除断言,因为它们并不通用。

为了显示问题(将会出现),我建议根据需要绘制相同的节点多次,并通过在选择其中一个副本时点亮所有副本来暗示重复。


32
这看起来像正确的方法,并且很容易扩展以检测更复杂的问题。您可以计算出事件之间的一组“ A发生在B之前”的关系。例如,某人在涉及任何其他事件之前出生。这是有向图。然后,您可以检查该图是否不包含循环。请参见StackOverflow上的此问题。 在发明时间旅行之前,这应该没问题。
Paul Harrison

41
@ paul-harrison如果只有那么简单。在较早的记录(甚至是新记录)中,日期不一致。出生前的洗礼,多重出生记录等。因此,在一定程度上,官方记录中有时间旅行。我们允许这种不一致的数据。我们允许用户指出在重复的情况下应用程序应将“出生记录”视为什么。如果发现,我们将指示时间安排不正确。
Bert Goethals

38
@ ben-voigt GEDCOM是由耶稣基督后期圣徒教会创建的格式。该规范明确指出,婚姻(MARR)是男女之间的婚姻。对于同性婚姻或乱伦,应使用ASSO标签(ASSOCIATES),也应用于表示友谊或邻居。显然,同性婚姻是该规范内的第二类关系。更加中立的规范不会要求男女关系。
Bert Goethals,

1
@Bert Goethals:您正在将GEDCOM与某些不支持同性婚姻的程序(PAF,旧版)混淆。GEDCOM不排除诸如“ 0 @ F1 @ FAM / 1 HUSB @ I1 @ / 1 HUSB @ I2 @”之类的构造,因此如果您的软件选择支持同性婚姻。
皮埃尔

1
@Pierre您确实可以欺骗系统。这直接来自5.5.1文档:“ MAR {MARRIAGE}:=建立男人和女人作为丈夫和妻子的家庭单位的法律,习惯法或惯例事件。” (homepages.rootsweb.ancestry.com/~pmcbride/gedcom/55gcappa.htm)如您所见,这里没有同性婚姻。
Bert Goethals 2014年

563

放宽您的主张。

无需更改规则,这对于99.9%的客户在输入数据时发现错误非常有帮助。

而是将其从错误“无法添加关系”更改为带有“仍然添加”的警告。


143
当遇到一种极不可能的情况时,即用户通常只会错误地执行此操作时,最好向用户显示警告。很好的反馈。但是,如果用户确实确定要这样做,则让用户继续前进。因此,我认为这是一个很好的答案,即使它没有涉及到如何做到这一点。
thomasrutter 2011年

15
好答案!我只是想知道,这种软件将如何处理“我是我自己的爷爷”(youtube.com/watch?v=eYlJH81dSiw)情况?
Zaur Nasibov 2011年

4
这不是一个真正的答案,因为我认为问题出在实际上遍历树上吗?但是,这是一个很好的建议。
bdwakefield

3
@bdwakefield:问题是“如何在不删除所有数据断言的情况下解决这些错误?” 我相信我已经回答了。
Ben Voigt

2
@Ben取决于声明的用途。如果它们阻止了无限循环或致命错误的发生,那么您实际上建议删除这些断言。如果他们只是在警告用户潜在的错误,那么您的答案是一个很好的答案。
rm999

224

这是家谱的问题:它们不是树。它们是有向无环图或DAG。如果我正确理解人类生殖生物学的原理,就不会有任何循环。

据我所知,甚至基督徒也接受表亲之间的婚姻(也就是孩子),这会将家族树变成家族DAG。

这个故事的寓意是:选择正确的数据结构。


7
对于在体外和有性生殖中指向该节点的最大节点数为1或2的每个节点,都将需要进一步的限制。尽管更真实地描述了现实生活,您可能会允许多条虚线表示父亲一方的后代不确定性(一直很清楚母亲是谁,但只有通过DNA检测才能确保父亲是谁,即使在今天,这种情况很少见),甚至两者都考虑到采用。
manixrock 2011年

7
@manixrock-因为这个问题是关于极少数情况的,所以我想断言并不总是清楚母亲是谁。收养,被遗弃的婴儿,代孕的母亲等都会使事情复杂化。
Peter Recore 2011年

9
它不一定非周期性,是吗?嫁给祖母。
Ed Ropple 2011年

13
与祖母结婚的人不会使自己成为自己的祖父,也不会增加周期。如果他们有孩子,它将是非循环的正则图边。
exDM69 2011年

11
实际上是两个ADG。有亲子关系图和法律关系图。通常是相同的,但分歧可能超过预期。
JSacksteder 2011年

115

我想您具有一些可以唯一识别您作为支票基础的人的价值。

这是一个棘手的问题。假设您想将该结构保留为一棵树,我建议这样做:

假设:A与自己的女儿有孩子。

A将自己添加为AB。一旦扮演父亲的角色,就称其为男朋友。

添加一个is_same_for_out()函数,该函数告诉程序的输出生成部分,B内部所有链接都应在A数据显示时进行。

这将为用户带来一些额外的工作,但是我想IT相对容易实现和维护。

在此基础上,您可以进行代码同步AB避免不一致。

这种解决方案肯定不是完美的,但是是第一种方法。


9
可能这样的“代理”节点确实是合适的解决方案。但是我不知道如何在不冒犯用户的情况下将它们放在用户界面中。我可以告诉你,与真正的人(尤其是客户)打交道的编写软件并不容易。
PartickHöse2011年

6
它永远不会结束-B的新儿子将是他自己的叔叔。我会考虑将该计划全额退款!
Bo Persson

3
@Will A:然后意识到自己也是自己的母亲,并把自己的年轻自我招募到时间代理公司吗?
空集

2
在一个系统内复制(和同步)数据是一种不好的做法。这表明该解决方案是次优的,应该重新考虑。如果需要创建额外的(重复的)节点,请将其指示为代理,并将数据读写操作委托给原始节点。
Bert Goethals

84

您应该专注于真正为您的软件带来价值的东西。花费一个许可证的价格让一个消费者使用它所花费的时间是否值得?可能不会。

我建议您向该客户表示歉意,告诉他该客户的情况超出了您的软件范围,请向他退款。


3
非常真实 但是,还要权衡其他潜在问题以及其他带来的类似麻烦。
Falken教授的合同

2
当然。原因是:如果在非关键应用程序上很少出现这种情况,则不需要修复或实现任何东西。如果它确实在伤害您的用户,那么在它上进行工作就很有价值。
christopheml,

10
大概每个人的祖先都有乱伦的案例。因此,如果您(对)家庭历史进行过深的挖掘,就会遇到麻烦。
datenwolf 2011年

1
制作某些奇怪情况(近亲版税,Fritzl等)的家谱树是软件的有效使用。
Bulwersator

1
不允许第二个表亲结婚的家谱软件是没有用的。几乎所有家庭都至少有这种情况。这就是为什么我认为原始示例可以弥补影响的原因。
Fuzzy76

79

您应该已经建立了Atreides家族(现代的DuneOedipus Rex的古老)作为测试用例。通过使用经过清理的数据作为测试用例,您不会发现错误。


2
可悲的是,太多的人首先想到了“好的”数据,而不是破坏系统的边缘情况。
sjas 2012年

59

这就是“ Go”之类的语言没有断言的原因之一。它们经常用于处理您可能没有想到的案例。您只能断言不可能,而不仅仅是不可能。这样做会使断言声名狼藉。每次输入时间assert(,走开十分钟,真是想想。

在您特别令人不安的情况下,可以想象并且令人震惊的是,这样的主张在极少数但可能的情况下是虚假的。因此,如果只是说“此软件并非旨在处理您提出的情况”,则在您的应用程序中进行处理。

断言你的曾曾曾祖父不可能成为你的父亲是一件合理的事情。

如果我在一家受雇测试您的软件的测试公司工作,那么我当然会提出这种情况。为什么?每个少年而又聪明的“用户”都将做完全相同的事情,并喜欢结果“错误报告”。


5
同意“何时使用断言”论点;看不到它与“某些语言有断言,而Go却没有”的关系。
phooji 2011年

2
@Red Hue-有时编译器使不可能变为可能。某些版本的gcc认为abs()实现中的-10 == 10。
Tim Post

2
@Red Hue:断言的全部目的是记录和测试应始终为true(或false)的条件。它可以防止您(和其他人)以无法解决问题的方式“修复”问题,因为这样可能会明确(而不是巧妙地)破坏应用程序。如果有合理的理由要出现“不可能”的案例,那么您就断言了太多。
cHao 2011年

1
@cHao @Tim Post我只是想了解为什么不使用断言是一件好事,因为你们中的大多数人都认为断言很重要。
阿伦

5
具有断言(或类似断言的代码)是无关紧要的。像Go这样的语言的代码可以并且将对数据的结构做出假设;它只是不能用断言记录和执行那些假设。底线:该应用程序存在错误。
Tommy McGuire

41

我讨厌评论这种情况,但是不重新抖动所有不变量的最简单方法是在图形中创建一个幻像顶点,作为回乱父亲的代理。


37

因此,我已经完成了有关家族树软件的一些工作。我认为您要解决的问题是,您必须能够在不陷入无限循环的情况下走树,换句话说,树需要是非循环的。

但是,您似乎在断言一个人与其祖先之一之间只有一条道路。这样可以保证没有周期,但是太严格了。从生物学上讲,后代是有向无环图(DAG)。您遇到的情况肯定是退化的情况,但是这种事总是在大树上发生。

例如,如果您查看第n代有2 ^ n个祖先,如果没有重叠,那么公元1000年的祖先会比活着的人多。因此,必须有重叠。

但是,您也确实会得到无效的循环,只是错误的数据。如果要遍历树,则必须处理循环。您可以在每个单独的算法中或在加载时执行此操作。我是在加载时完成的。

在树中找到真正的循环可以通过几种方式完成。错误的方法是标记给定个体中的每个祖先,并且在遍历时,如果已经标记了您要移至下一个的人,则剪切该链接。这将切断潜在的准确关系。正确的方法是从每个人开始,并用通往该人的道路标记每个祖先。如果新路径包含当前路径作为子路径,则这是一个循环,应将其断开。您可以将路径存储为vector <bool>(MFMF,MFFFMF等),这使得比较和存储都非常快。

还有其他几种检测周期的方法,例如发出两个迭代器并查看它们是否与子集测试相撞,但是最后我还是使用了本地存储方法。

还要注意,您实际上不需要切断该链接,只需将其从普通链接更改为“弱”链接即可,某些算法不会遵循该链接。选择哪个链接标记为弱链接时,您还需要注意。有时您可以通过查看出生日期信息来找出应该中断周期的位置,但是由于缺少太多数据,您常常无法找出任何东西。


小心那些假设;当人们适应时,一男一女的父母是不存在的,或者认为自己是父母的新婚夫妇,在不久的将来,他们甚至可以真正成为父母,而不是女孩。就此而言,如果我们将多莉应用于人类,那么即使“一个人有两个不同的父母”这一假设也没有了。
阿格拉贾格2012年

1
@Agrajag,是的,这就是为什么我为循环检测指定“生物学意义上的”。即使在生物学上,也存在很多可能的问题,例如代孕妈妈和人工授精。如果您还允许采用收养和其他非生物学方法来定义父母,那么就有可能在树上形成一个有效的真实周期-例如,某人长大后不再领养自己的外祖父母。对人们的家庭生活做出假设总是很复杂。但是,编写软件时,你需要作出一些假设..
tfinniga

36

一个愚蠢的问题的另一个模拟严肃答案:

真正的答案是,使用适当的数据结构。使用没有循环的纯树无法完全表达人类谱系。您应该使用某种图形。另外,在进行进一步研究之前,请先与人类学家交谈,因为在其他地方,即使是在最简单的“西方父权一夫一妻制婚姻”案例中,也可能会出现类似的错误,试图对家谱进行建模。

即使我们要忽略此处讨论的局部禁忌关系,也有许多完全合法且完全出乎意料的方法可以将循环引入家谱。

例如:http : //en.wikipedia.org/wiki/Cousin_marriage

基本上,表亲结婚不仅普遍而且是预期的,这也是人类从成千上万的小型家庭变成全球60亿人口的原因。它无法以任何其他方式工作。

关于家谱,家族和血统,确实很少有通用性。关于规范的几乎任何严格的假设,都暗示着姑姑是谁,或者谁可以嫁给谁,或者为了继承目的如何使儿童合法化,这可能会因世界或历史上某个地方的例外而感到不安。


9
您的评论使我想到了一夫多妻制。仅对性生殖进行建模的家谱软件可能需要在精子和卵子上附加一个名称,但不需要更广泛的家庭结构定义。
Steve Kalemkiewicz 2011年

族谱软件通常允许模型中有不止一位配偶。在视图中显示模型的方式各不相同,即使在一个程序中,也取决于提供的“模式”。
托德·霍普金森

20

除了潜在的法律含义外,您似乎当然需要将家谱上的“节点”视为前任人员,而不是假定该节点可以是一个人。

让树节点既包含一个人又包含一个继任者-然后您可以在树的更深处拥有另一个节点,该节点包含具有不同继任者的同一个人。


13

一些答案显示了保留断言/不变式的方法,但这似乎是对断言/不变式的滥用。断言是为了确保应为真的东西是正确的,而不变性则是要确保不应改变的东西不会改变。

您在这里断言不存在乱伦关系。显然它们确实存在,因此您的断言无效。您可以解决此断言,但真正的错误在于断言本身。断言应删除。



5

家谱数据是循环的,不适合非循环图,因此,如果您对循环有断言,则应将其删除。

在视图中处理而不创建自定义视图的方法是将循环父级视为“重影”父级。换句话说,当一个人既是同一个人的父亲又是祖父时,则正常显示该祖父节点,但是该父节点被渲染为具有简单标签(例如,请参见“见祖父” )并指向祖父。

为了进行计算,您可能需要改进处理循环图的逻辑,以便在有循环的情况下,节点被访问的次数不会超过一次。


4

最重要的是to avoid creating a problem,所以我相信您应该使用直接关系来避免出现循环。

正如@markmywords所说,#include“ fritzl.h”。

最后我不得不说recheck your data structure。可能那边出了问题(双向链接列表可以解决您的问题)。


4

断言无法在现实中生存

通常,断言在与现实世界数据的联系中无法幸免。这是决定要处理哪些数据以及哪些数据超出范围的软件工程过程的一部分。

循环族图

关于家庭“树”(实际上,它是完整的图表,包括循环),有一个很好的轶事:

我娶了一个育有一个女儿的寡妇。经常拜访我们的父亲爱上了我的继女,并嫁给了她。结果,父亲成为我的儿子,女儿成为我的母亲。一段时间后,我给了我妻子一个儿子,他是我父亲的兄弟,还有我叔叔。我父亲的妻子(也是我的女儿和母亲)生了一个儿子。结果,我在同一个人中生了一个兄弟和一个孙子。我的妻子现在是我的祖母,因为她是我母亲的母亲。所以我是我妻子的丈夫,同时也是我妻子的继孙。换句话说,我是我自己的祖父。

当您考虑代孕或“模糊的父亲身份” 时,事情变得更加奇怪。

如何处理

将周期定义为范围外

您可以决定您的软件不应处理这种罕见情况。如果发生这种情况,则用户应使用其他产品。这使得处理更常见的情况变得更加健壮,因为您可以保留更多的断言和更简单的数据模型。

在这种情况下,请向您的软件中添加一些良好的导入和导出功能,以便用户在必要时可以轻松地迁移到其他产品。

允许手动关系

您可以允许用户添加手动关系。这些关系不是“一等公民”,即软件按原样对待它们,不检查它们,也不在主数据模型中处理它们。

然后,用户可以手动处理罕见情况。您的数据模型仍将保持非常简单,并且您的断言将继续存在。

注意手动关系。有一种诱惑使它们完全可配置,从而创建一个完全可配置的数据模型。这将行不通:您的软件无法扩展,您会遇到奇怪的错误,最终用户界面将变得无法使用。此反模式称为“软编码”,而“每日WTF”中包含许多示例。

使您的数据模型更加灵活,跳过断言,测试不变式

最后的选择是使您的数据模型更加灵活。您将不得不跳过几乎所有的断言,并将数据模型基于完整的图表。如以上示例所示,很容易成为自己的祖父,因此您甚至可以拥有周期。

在这种情况下,您应该广泛测试软件。您几乎必须跳过所有断言,因此很有可能会出现其他错误。

使用测试数据生成器检查异常的测试案例。有针对HaskellErlangC的快速检查库。对于Java / Scala,有ScalaCheckNyaya。一个测试想法是模拟一个随机的种群,让它随机杂交,然后让您的软件首先导入,然后导出结果。期望输出中的所有连接也都在输入中,反之亦然。

属性保持不变的情况称为不变式。在这种情况下,不变是模拟人口中个体之间的“浪漫关系”集合。尝试找到尽可能多的不变式,并使用随机生成的数据对其进行测试。不变量可以是函数,例如:

  • 即使您添加了更多的“浪漫关系”,叔叔仍然是叔叔
  • 每个孩子都有父母
  • 有两代人的人口至少有一个祖父母

或者它们可以是技术性的:

  • 您的软件在最多100亿成员的图表上不会崩溃(无论有多少互连)
  • 您的软件可扩展为O(节点数)和O(边数^ 2)
  • 您的软件可以保存和重新加载每个家族图,最多可容纳100亿个成员

通过运行模拟测试,您会发现很多奇怪的极端情况。修复它们将花费很多时间。同样,您将失去很多优化,您的软件运行速度会慢得多。您必须确定是否值得,以及是否在软件范围内。


3

除了删除所有断言之外,您还应该检查诸如某人作为其父母或其他不可能的情况之类的情况并提出错误。如果不太可能发出警告,则用户仍可以检测到常见的输入错误,但是如果一切正确,它将起作用。

我将数据存储在每个人都有一个永久整数的向量中,并将父母和子女存储在人对象中,其中所说的int是向量的索引。这将是世代相传的很快(但是对于名称搜索之类的东西来说却很慢)。对象将按照创建时间的顺序排列。


-3

复制父亲(或使用符号链接/引用)。

例如,如果您使用分层数据库:

$ #each person node has two nodes representing its parents.
$ mkdir Family
$ mkdir Family/Son
$ mkdir Family/Son/Daughter
$ mkdir Family/Son/Father
$ mkdir Family/Son/Daughter/Father
$ ln -s Family/Son/Daughter/Father Family/Son/Father
$ mkdir Family/Son/Daughter/Wife
$ tree Family
Family
└── Son
    ├── Daughter
       ├── Father
       └── Wife
    └── Father -> Family/Son/Daughter/Father

4 directories, 1 file

3
ln -s命令不能这样工作;链接的分辨率将从链接所在的位置(而不是您发出命令的位置)下Family/Son/Father查找。Family/Son/Daughter/FatherFamily/Son.ln -s
musiphil 2012年

48
日内瓦公约禁止克隆
MikeIsrael 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.