我是一些家谱软件(用C++和Qt编写)的开发人员。直到我的一位客户给我邮寄了一份错误报告,我才发现问题。问题是,客户有两个孩子和自己的女儿,因此,由于错误,他无法使用我的软件。

这些错误是我对正在处理的族图的各种断言和不变量的结果(例如,在走完一个循环后,程序声明X不能既是Y的父亲又是Y的祖父)。

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


当前回答

这就是为什么像“Go”这样的语言没有断言的原因之一。它们被用来处理那些你可能没有想到的案例,所有这些都太频繁了。你只应该断言不可能,而不仅仅是不可能。做后者会给断言带来坏名声。每次你输入assert(,走开十分钟,认真思考一下。

在你特别令人不安的案例中,在罕见但可能的情况下,这样的断言是假的,这是可以想象的,也是令人震惊的。因此,在你的应用程序中处理它,如果只是说“这个软件不是为处理你所呈现的场景而设计的”。

断言你的曾、曾、曾祖父是你的父亲是不可能的,这是合理的做法。

如果我是为一家被雇佣来测试你的软件的测试公司工作的话,我当然会提出这种情况。为什么?每一个年轻而聪明的“用户”都会做同样的事情,并对由此产生的“错误报告”津津乐道。

其他回答

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

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

$ #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

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

据我所知,即使是基督徒也接受表兄弟之间的婚姻(以及孩子),这将把家谱变成家庭DAG。

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

所以,我在家谱软件上做了一些工作。我认为你要解决的问题是你需要能够在树上行走而不陷入无限循环——换句话说,树需要是非循环的。

然而,你似乎在断言一个人和他们的祖先之间只有一条路。这将保证没有周期,但过于严格。从生物学上讲,后代是一个有向无环图(DAG)。你的情况当然是一个退化的情况,但这种情况在更大的树上总是发生。

例如,如果你看看你在第n代的祖先,如果没有重叠,那么你在公元1000年的祖先会比活着的人多。所以,必须有重叠。

然而,您也会得到无效的循环,只是坏数据。如果您正在遍历树,那么必须处理循环。您可以在每个单独的算法中或在加载时执行此操作。我是负重做的。

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

还有一些其他方法可以检测循环,例如发送两个迭代器,看看它们是否与子集测试冲突,但我最终使用了本地存储方法。

还需要注意的是,您不需要实际切断链接,只需将其从正常链接更改为“弱”链接,而某些算法不会遵循该链接。在选择将哪个链接标记为弱链接时,您也需要小心;有时,你可以通过查看出生日期信息来找出应该打破这个周期的地方,但通常你什么都搞不清楚,因为缺少了太多数据。

你应该关注真正为你的软件创造价值的东西。为一个消费者提供服务所花费的时间是否值得许可证的价格?可能不会。

我建议您向这位客户道歉,告诉他他的情况超出了您的软件范围,并向他退款。

家谱数据是循环的,不适合于非循环图,所以如果你有针对循环的断言,你应该删除它们。

在不创建自定义视图的情况下在视图中处理此问题的方法是将循环父对象视为“幽灵”父对象。换句话说,当一个人同时是同一个人的父亲和祖父时,祖父节点正常显示,但父亲节点被渲染为“幽灵”节点,该节点具有简单的标签(如“看见祖父”)并指向祖父。

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