我是一些家谱软件(用C ++和Qt编写)的开发人员。在我的一位客户向我发送错误报告之前,我没有遇到任何问题。问题是该客户有两个孩子,并且有自己的女儿,结果,由于错误,他无法使用我的软件。
这些错误是由于我对要处理的族图的各种断言和不变性导致的(例如,经过一个循环后,程序指出X不能同时是Y的父亲和祖父)。
如何在不删除所有数据断言的情况下解决这些错误?
我是一些家谱软件(用C ++和Qt编写)的开发人员。在我的一位客户向我发送错误报告之前,我没有遇到任何问题。问题是该客户有两个孩子,并且有自己的女儿,结果,由于错误,他无法使用我的软件。
这些错误是由于我对要处理的族图的各种断言和不变性导致的(例如,经过一个循环后,程序指出X不能同时是Y的父亲和祖父)。
如何在不删除所有数据断言的情况下解决这些错误?
Answers:
您(和/或您的公司)似乎对家谱应该是什么有基本的误解。
让我澄清一下,我也为一家在其投资组合中拥有家族树(作为其产品之一)的公司工作,我们一直在努力解决类似的问题。
就我们而言,我也认为您的情况也来自于GEDCOM格式,该格式对于一家人应该如何对待非常有见地。但是,这种格式包含一些关于家谱到底是什么样子的严重误解。
GEDCOM存在许多问题,例如与同性关系不兼容,乱伦等。在现实生活中,这种情况发生的频率比您想象的要多(尤其是回到1700-1800年时)。
我们已经根据现实世界中发生的事情建模了家谱:事件(例如,出生,婚礼,订婚,工会,死亡,收养等)。除了逻辑上不可能的限制外,我们对这些没有任何限制(例如,一个人不能成为自己的父母,关系需要两个人,等等。)
缺乏验证使我们有了一个“现实世界”,更简单,更灵活的解决方案。
对于这种特定情况,我建议删除断言,因为它们并不通用。
为了显示问题(将会出现),我建议根据需要绘制相同的节点多次,并通过在选择其中一个副本时点亮所有副本来暗示重复。
放宽您的主张。
无需更改规则,这对于99.9%的客户在输入数据时发现错误非常有帮助。
而是将其从错误“无法添加关系”更改为带有“仍然添加”的警告。
这是家谱的问题:它们不是树。它们是有向无环图或DAG。如果我正确理解人类生殖生物学的原理,就不会有任何循环。
据我所知,甚至基督徒也接受表亲之间的婚姻(也就是孩子),这会将家族树变成家族DAG。
这个故事的寓意是:选择正确的数据结构。
我想您具有一些可以唯一识别您作为支票基础的人的价值。
这是一个棘手的问题。假设您想将该结构保留为一棵树,我建议这样做:
假设:A
与自己的女儿有孩子。
A
将自己添加为A
和B
。一旦扮演父亲的角色,就称其为男朋友。
添加一个is_same_for_out()
函数,该函数告诉程序的输出生成部分,B
内部所有链接都应在A
数据显示时进行。
这将为用户带来一些额外的工作,但是我想IT相对容易实现和维护。
在此基础上,您可以进行代码同步A
并B
避免不一致。
这种解决方案肯定不是完美的,但是是第一种方法。
您应该专注于真正为您的软件带来价值的东西。花费一个许可证的价格让一个消费者使用它所花费的时间是否值得?可能不会。
我建议您向该客户表示歉意,告诉他该客户的情况超出了您的软件范围,请向他退款。
这就是“ Go”之类的语言没有断言的原因之一。它们经常用于处理您可能没有想到的案例。您只能断言不可能,而不仅仅是不可能。这样做会使断言声名狼藉。每次输入时间assert(
,走开十分钟,真是想想。
在您特别令人不安的情况下,可以想象并且令人震惊的是,这样的主张在极少数但可能的情况下是虚假的。因此,如果只是说“此软件并非旨在处理您提出的情况”,则在您的应用程序中进行处理。
断言你的曾曾曾祖父不可能成为你的父亲是一件合理的事情。
如果我在一家受雇测试您的软件的测试公司工作,那么我当然会提出这种情况。为什么?每个少年而又聪明的“用户”都将做完全相同的事情,并喜欢结果“错误报告”。
因此,我已经完成了有关家族树软件的一些工作。我认为您要解决的问题是,您必须能够在不陷入无限循环的情况下走树,换句话说,树需要是非循环的。
但是,您似乎在断言一个人与其祖先之一之间只有一条道路。这样可以保证没有周期,但是太严格了。从生物学上讲,后代是有向无环图(DAG)。您遇到的情况肯定是退化的情况,但是这种事总是在大树上发生。
例如,如果您查看第n代有2 ^ n个祖先,如果没有重叠,那么公元1000年的祖先会比活着的人多。因此,必须有重叠。
但是,您也确实会得到无效的循环,只是错误的数据。如果要遍历树,则必须处理循环。您可以在每个单独的算法中或在加载时执行此操作。我是在加载时完成的。
在树中找到真正的循环可以通过几种方式完成。错误的方法是标记给定个体中的每个祖先,并且在遍历时,如果已经标记了您要移至下一个的人,则剪切该链接。这将切断潜在的准确关系。正确的方法是从每个人开始,并用通往该人的道路标记每个祖先。如果新路径包含当前路径作为子路径,则这是一个循环,应将其断开。您可以将路径存储为vector <bool>(MFMF,MFFFMF等),这使得比较和存储都非常快。
还有其他几种检测周期的方法,例如发出两个迭代器并查看它们是否与子集测试相撞,但是最后我还是使用了本地存储方法。
还要注意,您实际上不需要切断该链接,只需将其从普通链接更改为“弱”链接即可,某些算法不会遵循该链接。选择哪个链接标记为弱链接时,您还需要注意。有时您可以通过查看出生日期信息来找出应该中断周期的位置,但是由于缺少太多数据,您常常无法找出任何东西。
一个愚蠢的问题的另一个模拟严肃答案:
真正的答案是,使用适当的数据结构。使用没有循环的纯树无法完全表达人类谱系。您应该使用某种图形。另外,在进行进一步研究之前,请先与人类学家交谈,因为在其他地方,即使是在最简单的“西方父权一夫一妻制婚姻”案例中,也可能会出现类似的错误,试图对家谱进行建模。
即使我们要忽略此处讨论的局部禁忌关系,也有许多完全合法且完全出乎意料的方法可以将循环引入家谱。
例如:http : //en.wikipedia.org/wiki/Cousin_marriage
基本上,表亲结婚不仅普遍而且是预期的,这也是人类从成千上万的小型家庭变成全球60亿人口的原因。它无法以任何其他方式工作。
关于家谱,家族和血统,确实很少有通用性。关于规范的几乎任何严格的假设,都暗示着姑姑是谁,或者谁可以嫁给谁,或者为了继承目的如何使儿童合法化,这可能会因世界或历史上某个地方的例外而感到不安。
一些答案显示了保留断言/不变式的方法,但这似乎是对断言/不变式的滥用。断言是为了确保应为真的东西是正确的,而不变性则是要确保不应改变的东西不会改变。
您在这里断言不存在乱伦关系。显然它们确实存在,因此您的断言无效。您可以解决此断言,但真正的错误在于断言本身。断言应删除。
家谱数据是循环的,不适合非循环图,因此,如果您对循环有断言,则应将其删除。
在视图中处理而不创建自定义视图的方法是将循环父级视为“重影”父级。换句话说,当一个人既是同一个人的父亲又是祖父时,则正常显示该祖父节点,但是该父节点被渲染为具有简单标签(例如,请参见“见祖父” )并指向祖父。
为了进行计算,您可能需要改进处理循环图的逻辑,以便在有循环的情况下,节点被访问的次数不会超过一次。
最重要的是to avoid creating a problem
,所以我相信您应该使用直接关系来避免出现循环。
正如@markmywords所说,#include“ fritzl.h”。
最后我不得不说recheck your data structure
。可能那边出了问题(双向链接列表可以解决您的问题)。
通常,断言在与现实世界数据的联系中无法幸免。这是决定要处理哪些数据以及哪些数据超出范围的软件工程过程的一部分。
关于家庭“树”(实际上,它是完整的图表,包括循环),有一个很好的轶事:
我娶了一个育有一个女儿的寡妇。经常拜访我们的父亲爱上了我的继女,并嫁给了她。结果,父亲成为我的儿子,女儿成为我的母亲。一段时间后,我给了我妻子一个儿子,他是我父亲的兄弟,还有我叔叔。我父亲的妻子(也是我的女儿和母亲)生了一个儿子。结果,我在同一个人中生了一个兄弟和一个孙子。我的妻子现在是我的祖母,因为她是我母亲的母亲。所以我是我妻子的丈夫,同时也是我妻子的继孙。换句话说,我是我自己的祖父。
当您考虑代孕或“模糊的父亲身份” 时,事情变得更加奇怪。
您可以决定您的软件不应处理这种罕见情况。如果发生这种情况,则用户应使用其他产品。这使得处理更常见的情况变得更加健壮,因为您可以保留更多的断言和更简单的数据模型。
在这种情况下,请向您的软件中添加一些良好的导入和导出功能,以便用户在必要时可以轻松地迁移到其他产品。
您可以允许用户添加手动关系。这些关系不是“一等公民”,即软件按原样对待它们,不检查它们,也不在主数据模型中处理它们。
然后,用户可以手动处理罕见情况。您的数据模型仍将保持非常简单,并且您的断言将继续存在。
注意手动关系。有一种诱惑使它们完全可配置,从而创建一个完全可配置的数据模型。这将行不通:您的软件无法扩展,您会遇到奇怪的错误,最终用户界面将变得无法使用。此反模式称为“软编码”,而“每日WTF”中包含许多示例。
最后的选择是使您的数据模型更加灵活。您将不得不跳过几乎所有的断言,并将数据模型基于完整的图表。如以上示例所示,很容易成为自己的祖父,因此您甚至可以拥有周期。
在这种情况下,您应该广泛测试软件。您几乎必须跳过所有断言,因此很有可能会出现其他错误。
使用测试数据生成器检查异常的测试案例。有针对Haskell,Erlang或C的快速检查库。对于Java / Scala,有ScalaCheck和Nyaya。一个测试想法是模拟一个随机的种群,让它随机杂交,然后让您的软件首先导入,然后导出结果。期望输出中的所有连接也都在输入中,反之亦然。
属性保持不变的情况称为不变式。在这种情况下,不变是模拟人口中个体之间的“浪漫关系”集合。尝试找到尽可能多的不变式,并使用随机生成的数据对其进行测试。不变量可以是函数,例如:
或者它们可以是技术性的:
通过运行模拟测试,您会发现很多奇怪的极端情况。修复它们将花费很多时间。同样,您将失去很多优化,您的软件运行速度会慢得多。您必须确定是否值得,以及是否在软件范围内。
复制父亲(或使用符号链接/引用)。
例如,如果您使用分层数据库:
$ #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
ln -s
命令不能这样工作;链接的分辨率将从链接所在的位置(而不是您发出命令的位置)下Family/Son/Father
查找。Family/Son/Daughter/Father
Family/Son
.
ln -s