"家谱"数据结构

7

我正在寻找一种在PHP中表示家谱的方法。这意味着孩子需要从两个(或更多)父母那里继承。

以下是要求:

  • 1、2或更多父母
  • 如果我可以附加元数据,例如姓氏或关系状态,则得到额外积分

这是我未能成功实现的尝试(不幸的是,没有将数组作为键):

$tree = array(
    'uncle' => false, // no children
    array('mom', 'dad') => array(
        'me' => false,
        array('brother', 'sister-in-law') => array(
            'niece' => false
        )
    )
);

这个问题是如何在满足这些要求的前提下呈现一个家谱树?

2
这只是一个有向图,对吗?http://zh.wikipedia.org/wiki/有向图 - Mike Daniels
是的,但问题是:在PHP中表示有向图的好方法是什么?(希望它是DAG,但可能存在“我是自己的爷爷”类型的情况) - Daniel Martin
你找到答案了吗?我也很感兴趣,谢谢。 - ngen
2个回答

21
你不能像这样在单个array()中完成所有操作。你可以像那样设置树形结构,但要设置更复杂的具有多个父级和其他关系的图形需要多行代码。
如果你加入一些面向对象编程(OO),它会对你有很大帮助。让我们创建一个Person类来帮助管理关系。基本上,我们有人和他们与其他人之间的关系,所以我们从那里开始。

Person类

我想象每个人都有一个关系数组。该数组首先按关系类型进行索引,例如“父母”或“孩子”。然后,每个条目将是一个Person数组。
class Person {
    var $name, $relations;

    function __construct($name) {
        $this->name      = $name;
        $this->relations = array();
    }

    function addRelation($type, $person) {
        if (!isset($this->relations[$type])) {
            $this->relations[$type] = array();
        }

        $this->relations[$type][] = $person;
    }

    // Looks up multiple relations, for example "parents".
    function getRelations($type) {
        if (!isset($this->relations[$type])) {
            return array();
        }

        return $this->relations[$type];
    }

    // Looks up a single relation, for example "spouse".
    function getRelation($type) {
        $relations = $this->getRelations($type);
        return empty($relations) ? null : $relations[0];
    }

    function __toString() {
        return $this->name;
    }

友好的添加和获取方法

有了上述基础,我们可以添加一些更友好命名的方法。为了说明,我们将处理父母/子女关系和配偶关系。

    function addParents($mom, $dad) {
        $mom->addChild($this);
        $dad->addChild($this);
    }

    function addChild($child) {
        $this ->addRelation('children', $child);
        $child->addRelation('parents',  $this);
    }

    function addSpouse($spouse) {
        $this  ->addRelation('spouse', $spouse);
        $spouse->addRelation('spouse', $this);
    }

    function getParents () { return $this->getRelations('parents');  }
    function getChildren() { return $this->getRelations('children'); }
    function getSpouse  () { return $this->getRelation ('spouse');   }
}

创建人

现在我们可以创建几个人并建立他们之间的关系。让我们试着创建比利和他的父母约翰和简。

$john  = new Person('John');
$jane  = new Person('Jane');
$billy = new Person('Billy');

$john ->addSpouse ($jane);
$billy->addParents($jane, $john);

我们可以这样查看它们之间的关系:

echo "John is married to " . $john->getSpouse() . ".\n";
echo "Billy's parents are " . implode(" and ", $billy->getParents()) . ".\n";

输出:

John和Jane结婚了。
Billy的父母是Jane和John。

显示家谱

如果家谱树逐渐变得更加复杂,我们可以使用递归来遍历它。这是一个示例的树遍历函数,用于显示简单的家谱树。我已经加入了Sara、她的丈夫Mike和他们的儿子Bobby。

$john  = new Person('John');
$jane  = new Person('Jane');
$sara  = new Person('Sara');
$mike  = new Person('Mike');
$bobby = new Person('Bobby');
$billy = new Person('Billy');

$john ->addSpouse ($jane);
$sara ->addParents($jane, $john);
$sara ->addSpouse ($mike);
$bobby->addParents($sara, $mike);
$billy->addParents($jane, $john);

function displayFamilyTree($root, $prefix = "") {
    $parents = array($root);

    if ($root->getSpouse() != null) {
        $parents[] = $root->getSpouse();
    }

    echo $prefix . implode(" & ", $parents) . "\n";

    foreach ($root->getChildren() as $child) {
        displayFamilyTree($child, "....$prefix");
    }
}

displayFamilyTree($john);

输出:

约翰和简
....莎拉和迈克
........鲍比
....比利


编辑: 下面是@Wrikken的评论,为了易读性而重复:

确实如此。 我认为每个关系都应该添加一个从-到日期(可能为NULL表示没有结束)。 离婚会发生,收养等也会发生。 此外:我会在addRelation()函数中添加反向类型和“回嗔回来”:

function addRelation($type, $person, $reverseType, $pingback = false) {
    if (!isset($this->relations[$type])) {
        $this->relations[$type] = array();
    }

    if (!in_array($person, $this->relations[$type], true)) {
        $this->relations[$type][] = $person;
    }

    if (!$pingback) {
        $person->addRelation($reverseType, $this, $type, true);
    }
}

确实如此。在每个关系中加入一个起始和结束日期(对于没有结束日期的情况可能为空)。离婚、收养等情况都会发生。另外:我会在addRelation()函数中添加reverse_types和“ping-back”:function addRelation($type, $person,$reversetype,$pingback=false){if (!isset($this->rel[$type])){ $this->rel[$type] = array();} if(!in_array($person,$this->relations[$type],true)){$this->relations[$type][] = $person;}if(!$pingback){$person->addRelation($reversetype, $this,$reversetype,true)}} - Wrikken

4

GEDCOM是一个开放的规范,用于在不同的家谱软件之间交换家谱数据。GEDCOM文件是纯文本(通常为ANSEL或ASCII),包含有关个人的家谱信息以及将这些记录链接在一起的元数据。大多数家谱软件都支持导入和/或导出GEDCOM格式。

使用GEDCOM的主要优点是,您可以使用桌面程序,如Aldfaer(仅限荷兰语)GrampsLegacy Family Tree以及在线工具,如Geneanet来构建或修改您的家谱,并将其与其他人的家谱进行比较。

使用GEDCOM格式的另一个主要优势是,有多种编程语言的库可供您保存和加载数据。PHP库的示例包括GEDCOM导入/导出过滤器, 家谱GEDCOMPHP GEDCOM
使用PHP GEDCOM,读取和解析GEDCOM文件就像这样简单:
$parser = new \PhpGedcom\Parser();
$gedcom = $parser->parse('gedcom.ged');

使用GEDCOM的一个主要缺点是,GEDCOM的数据格式是建立在核心家庭结构之上的,这意味着该标准在支持同性伴侣、混合家庭或同居等非传统家庭结构方面的能力受到限制。虽然可以扩展GEDCOM以支持此类关系,但不同软件之间的互操作性对于这些扩展来说是有限的。
使用GEDCOM的另一个主要缺点是,GEDCOM文件是单块式的。如果您有数千个人的数据集或者您想经常更改数据结构,那么您可能会遇到性能问题。特别是在这些情况下,最好将数据存储在数据库中。不过,这并不意味着GEDCOM是无用的。在这种情况下,您可能希望考虑使用基于GEDCOM格式的数据库架构,允许您在数据库和GEDCOM格式之间进行导入/导出。为此,也存在库。Oxy-Gen将是一个例子。
替代GEDCOM的选择包括GenTech的数据模型Gramps数据模型。虽然它们不像GEDCOM标准那样常用,但可能更适合您的需求。
如果您想使用Gramps数据模型,可以使用例如Gramps PHP导出器将数据导出到SQLite数据库中。另请参阅此来源,了解如何设计适用于Gramps数据模型的数据库。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接