Рассчитать семейные отношения по генеалогическим данным

Я хотел бы иметь возможность рассчитать семейные отношения между двумя людьми в генеалогическом дереве, учитывая следующую схему данных (упрощенную из моей фактической схемы данных, отображающую только столбцы, которые непосредственно относятся к этой проблеме):

individual
----------
id
gender

child
----------
child_id
father_id
mother_id

Как с помощью этой структуры можно рассчитать отношения между двумя отдельными идентификаторами (например, двоюродным братом, двоюродным дедушкой и т. д.).

Кроме того, поскольку на самом деле существует два отношения (т. е. AB может быть племянником, а BA — дядей), как одно может создать дополнение к другому (учитывая дядю и предполагая, что мы знаем пол, как мы можем создать племянника?). Это более тривиальный вопрос, первый меня действительно интересует.

Спасибо всем!


person defines    schedule 30.06.2009    source источник
comment
Это не алгоритмическое решение или что-то в этом роде, но я подумал, что вас может заинтересовать, насколько хорошо Wolfram Alpha может анализировать генеалогические отношения из естественного языка: www48.wolframalpha.com/input/   -  person Maciek    schedule 30.06.2009
comment
ОБНОВЛЕНИЕ Я завершил свою реализацию PHP для расчета отношений на основе приведенной выше схемы данных. Мой алгоритм LCA далеко не оптимален, но эффективен. Я скоро опубликую свою реализацию в качестве ответа и опубликую отдельные вопросы для более оптимизированного алгоритма LCA и для определения более сложных отношений (например, двойных кузенов, инцеста и т. д.).   -  person defines    schedule 06.07.2009
comment
@Maciek: Очень интересно. www48.wolframalpha .com/ввод/   -  person Chris Doggett    schedule 07.07.2009


Ответы (6)


Сначала вам нужно вычислить Наименьший общий предок обоих < em>A и B. Назовите этот младший общий предок C.

Затем рассчитайте расстояние в шагах от C до A (CA) и от C до B (CB). Эти значения должны быть проиндексированы в другую таблицу, которая определяет отношение на основе этих двух значений. Например:

CA      CB      Relation
1       2       uncle
2       1       nephew
2       2       cousin
0       1       father
0       2       grandfather

Вы можете сохранить основные отношения в этой таблице и добавить «пра-» для дополнительных расстояний в определенных отношениях, таких как дедушка, например: (0, 3) = прадедушка.

Надеюсь, это укажет вам правильное направление. Удачи!

ОБНОВЛЕНИЕ: (я не могу комментировать ваш код, так как у меня пока нет репутации.)

Я думаю, ваша функция aggrandize_relationships немного неверна. Вы можете упростить его, добавив префикс «большой», если смещение равно 1 или больше, а затем префикс «великий-» (смещение - 1) раз. Ваша версия может включать префикс «пра-пра-пра-пра» для очень дальних родственников. (Не уверен, что у меня есть правильный параметр в этом объяснении, но, надеюсь, вы поняли суть. далеко назад, но суть остается в силе.)

ТАКЖЕ ОБНОВЛЕНИЕ: Извините, приведенное выше неверно. Я неправильно понял случай по умолчанию и подумал, что он снова рекурсивно вызывает функцию. В свою защиту скажу, что я не был знаком с обозначением «2-й прадедушка» и сам всегда использовал «прапрадед». Код вперед!!

person taserian    schedule 02.07.2009
comment
Это привело меня к тому, что я считаю решением проблемы. На самом деле это немного сложнее в отношении канонического и гражданского поколений, и в результате получаются 1-й/2-й/и т.д. двоюродные братья, равные 1/2/и т.д. раз удалены. Ваша ссылка привела меня к дальнейшему чтению, и я считаю, что у меня есть вся информация, необходимая для создания алгоритма и его реализации. - person defines; 02.07.2009
comment
Возможно, вам придется не останавливаться на первом найденном самом низком общем предке. Например, хотите ли вы различать полных и неполнородных братьев и сестер? Или между, скажем, нормальными двоюродными братьями и сестрами и двоюродными братьями и сестрами (где два брата женятся на двух сестрах, и у обеих пар есть дети, у которых все дедушки и бабушки общие). Подумайте о том, чтобы сделать вашу реализацию пуленепробиваемой и против инцеста, что, к сожалению, происходит, например, если отец и дед для человека одинаковы, вы не хотите перезаписывать это в таблице поиска. - person Anon; 02.07.2009
comment
@Anon Определенно проблема, которая пришла мне в голову, но я думаю, что это станет вторым вопросом, чтобы пересмотреть / улучшить мою реализацию, как только я ее закончу. Спасибо! - person defines; 06.07.2009
comment
Спасибо за новости :) Мне самому нравится суффикс порядкового номера, и хотя у меня есть слабость к избыточности, я ненавижу считать/говорить «отлично, здорово, здорово...». В настоящее время самая длинная доказанная прямая линия в моем генеалогическом древе насчитывает 16 поколений. Мне не нужно 13 великих, чтобы сосчитать :-p - person defines; 07.07.2009
comment
@defines Вы когда-нибудь шли дальше с менее прямыми отношениями? Я изо всех сил пытаюсь понять, как оптимально ходить по дереву, чтобы соединить, скажем, мужа тети, что и удается сделать Ancestry. - person Philip Colmer; 06.02.2016
comment
Я не пытался решить это сам, но это то, что меня интересовало. Я мог бы откопать это и попробовать завтра. Я думаю, что это можно решить с помощью другой ветки, используя тот же подход (наименее общий предок), но мне нужно немного подумать об этом, прежде чем сказать наверняка:) Если вы возитесь с этим и найдете решение, я бы интересно посмотреть! - person defines; 06.02.2016

Ниже приведена моя PHP-реализация моего алгоритма для расчета отношений. Это основано на схеме данных, которую я изложил в исходном вопросе. Это находит только «самые близкие», то есть отношения кратчайшего пути между двумя людьми, но не разрешает сложные отношения, такие как сводные братья и сестры или двойные двоюродные братья.

Обратите внимание, что функции доступа к данным, такие как get_father и get_gender, написаны в стиле слоя абстракции базы данных, который я всегда использую. Должно быть довольно просто понять, что происходит, в основном все специфичные для СУБД функции, такие как mysql_query, заменяются обобщенными функциями, такими как db_query; это совсем не сложно, особенно в примерах в этом коде, но не стесняйтесь задавать вопросы в комментариях, если что-то непонятно.

<?php
/* Calculate relationship "a is the ___ of b" */

define("GENDER_MALE", 1);
define("GENDER_FEMALE", 2);

function calculate_relationship($a_id, $b_id)
{
  if ($a_id == $b_id) {
    return 'self';
  }

  $lca = lowest_common_ancestor($a_id, $b_id);
  if (!$lca) {
    return false;
  }
  $a_dist = $lca[1];
  $b_dist = $lca[2];

  $a_gen = get_gender($a_id);

  // DIRECT DESCENDANT - PARENT
  if ($a_dist == 0) {
    $rel = $a_gen == GENDER_MALE ? 'father' : 'mother';
    return aggrandize_relationship($rel, $b_dist);
  }
  // DIRECT DESCENDANT - CHILD
  if ($b_dist == 0) {
    $rel = $a_gen == GENDER_MALE ? 'son' : 'daughter';
    return aggrandize_relationship($rel, $a_dist);
  }

  // EQUAL DISTANCE - SIBLINGS / PERFECT COUSINS
  if ($a_dist == $b_dist) {
    switch ($a_dist) {
      case 1:
        return $a_gen == GENDER_MALE ? 'brother' : 'sister';
        break;
      case 2:
        return 'cousin';
        break;
      default:
        return ordinal_suffix($a_dist - 2).' cousin';
    }
  }

  // AUNT / UNCLE
  if ($a_dist == 1) {
    $rel = $a_gen == GENDER_MALE ? 'uncle' : 'aunt';
    return aggrandize_relationship($rel, $b_dist, 1);
  }
  // NEPHEW / NIECE
  if ($b_dist == 1) {
    $rel = $a_gen == GENDER_MALE ? 'nephew' : 'niece';
    return aggrandize_relationship($rel, $a_dist, 1);
  }

  // COUSINS, GENERATIONALLY REMOVED
  $cous_ord = min($a_dist, $b_dist) - 1;
  $cous_gen = abs($a_dist - $b_dist);
  return ordinal_suffix($cous_ord).' cousin '.format_plural($cous_gen, 'time', 'times').' removed';
} //END function calculate_relationship

function aggrandize_relationship($rel, $dist, $offset = 0) {
  $dist -= $offset;
  switch ($dist) {
    case 1:
      return $rel;
      break;
    case 2:
      return 'grand'.$rel;
      break;
    case 3:
      return 'great grand'.$rel;
      break;
    default:
      return ordinal_suffix($dist - 2).' great grand'.$rel;
  }
} //END function aggrandize_relationship

function lowest_common_ancestor($a_id, $b_id)
{
  $common_ancestors = common_ancestors($a_id, $b_id);

  $least_distance = -1;
  $ld_index = -1;

  foreach ($common_ancestors as $i => $c_anc) {
    $distance = $c_anc[1] + $c_anc[2];
    if ($least_distance < 0 || $least_distance > $distance) {
      $least_distance = $distance;
      $ld_index = $i;
    }
  }

  return $ld_index >= 0 ? $common_ancestors[$ld_index] : false;
} //END function lowest_common_ancestor

function common_ancestors($a_id, $b_id) {
  $common_ancestors = array();

  $a_ancestors = get_ancestors($a_id);
  $b_ancestors = get_ancestors($b_id);

  foreach ($a_ancestors as $a_anc) {
    foreach ($b_ancestors as $b_anc) {
      if ($a_anc[0] == $b_anc[0]) {
        $common_ancestors[] = array($a_anc[0], $a_anc[1], $b_anc[1]);
        break 1;
      }
    }
  }

  return $common_ancestors;
} //END function common_ancestors

function get_ancestors($id, $dist = 0)
{
  $ancestors = array();

  // SELF
  $ancestors[] = array($id, $dist);

  // PARENTS
  $parents = get_parents($id);
  foreach ($parents as $par) {
    if ($par != 0) {
      $par_ancestors = get_ancestors($par, $dist + 1);
      foreach ($par_ancestors as $par_anc) {
        $ancestors[] = $par_anc;
      }
    }
  }

  return $ancestors;
} //END function get_ancestors

function get_parents($id)
{
  return array(get_father($id), get_mother($id));
} //END function get_parents

function get_father($id)
{
  $res = db_result(db_query("SELECT father_id FROM child WHERE child_id = %s", $id));
  return $res ? $res : 0;
} //END function get_father

function get_mother($id)
{
  $res = db_result(db_query("SELECT mother_id FROM child WHERE child_id = %s", $id));
  return $res ? $res : 0;
} //END function get_mother

function get_gender($id)
{
  return intval(db_result(db_query("SELECT gender FROM individual WHERE id = %s", $id)));
}

function ordinal_suffix($number, $super = false)
{
  if ($number % 100 > 10 && $number %100 < 14) {
    $os = 'th';
  } else if ($number == 0) {
    $os = '';
  } else {
    $last = substr($number, -1, 1);

    switch($last) {
      case "1":
        $os = 'st';
        break;
      case "2":
        $os = 'nd';
        break;
      case "3":
        $os = 'rd';
        break;
      default:
        $os = 'th';
    }
  }

  $os = $super ? '<sup>'.$os.'</sup>' : $os;

  return $number.$os;
} //END function ordinal_suffix

function format_plural($count, $singular, $plural)
{
  return $count.' '.($count == 1 || $count == -1 ? $singular : $plural);
} //END function plural_format

?>

Как я упоминал ранее, алгоритм определения LCA далеко не оптимален. Я планирую опубликовать отдельный вопрос, чтобы оптимизировать это, и еще один, чтобы решить проблему расчета сложных отношений, таких как двойные двоюродные братья.

Большое спасибо всем, кто помог мне двигаться в правильном направлении! С вашими советами это оказалось намного проще, чем я думал изначально.

person defines    schedule 06.07.2009
comment
Я оставлю это открытым, не принимая ответа в течение как минимум 2 дней, чтобы можно было продолжить обсуждение, указав на любые допущенные мной глупые ошибки, предложения по улучшению и т. д. - person defines; 06.07.2009

Это может помочь. Калькулятор взаимосвязей дерева — это объект, который принимает XML-представление дерева и вычисляет взаимосвязь любых двух членов внутри него. В этой статье описывается, как рассчитываются отношения, и что означают такие термины, как троюродный брат или двоюродный брат после удаления. Этот код включает в себя объект для расчета отношений, написанный на JavaScript, а также веб-интерфейс для рендеринга и взаимодействия с деревом. Пример проекта настроен как классическая страница ASP.

http://www.codeproject.com/Articles/30315/Tree-Relationship-Calculator

person Ramy Shosha    schedule 28.04.2015

Я решил эту проблему, используя концепцию списка смежности в java. Можно иметь узел для каждого человека и иметь его дочерние отношения, связанные с ним на самом его узле. Ниже приведен код для поиска только братьев, сестер и кузенов. Тем не менее, вы можете улучшить его в соответствии с вашими требованиями. Я написал этот код только для демонстрации.

public class Person {
    String name;
    String gender;
    int age;
    int salary;
    String fatherName;
    String motherName;

    public Person(String name, String gender, int age, int salary, String fatherName,
            String motherName) {
        super();
        this.name = name;
        this.gender = gender;
        this.age = age;
        this.salary = salary;
        this.fatherName = fatherName;
        this.motherName = motherName;
    }

}

Ниже приведен основной код для добавления родных людей и установления связи между собой.

import java.util.LinkedList;

public class PeopleAndRelationAdjacencyList {
    private static String MALE = "male";
    private static String FEMALE = "female";

public static void main(String[] args) {
    int size = 25;
    LinkedList<Person> adjListArray[] = new LinkedList[size];
    for (int i = 0; i < size; i++) {
        adjListArray[i] = new LinkedList<>();
    }

    addPerson( adjListArray, "GGM1", MALE, null, null );
    addPerson( adjListArray, "GGF1", FEMALE, null, null );

    addPerson( adjListArray, "GM1", MALE, "GGM1", "GGF1" );
    addPerson( adjListArray, "GM2", MALE, "GGM1", "GGF1" );

    addPerson( adjListArray, "GM1W", FEMALE, null, null );
    addPerson( adjListArray, "GM2W", FEMALE, null, null );

    addPerson( adjListArray, "PM1", MALE, "GM1", "GM1W" );
    addPerson( adjListArray, "PM2", MALE, "GM1", "GM1W" );
    addPerson( adjListArray, "PM3", MALE, "GM2", "GM2W" );

    addPerson( adjListArray, "PM1W", FEMALE, null, null );
    addPerson( adjListArray, "PM2W", FEMALE, null, null );
    addPerson( adjListArray, "PM3W", FEMALE, null, null );

    addPerson( adjListArray, "S1", MALE, "PM1", "PM1W" );
    addPerson( adjListArray, "S2", MALE, "PM2", "PM2W" );
    addPerson( adjListArray, "S3", MALE, "PM3", "PM3W" );
    addPerson( adjListArray, "S4", MALE, "PM3", "PM3W" );

    printGraph(adjListArray);
    System.out.println("Done !");


    getRelationBetweenPeopleForGivenNames(adjListArray, "S3", "S4");
    getRelationBetweenPeopleForGivenNames(adjListArray, "S1", "S2");

}


private static void getRelationBetweenPeopleForGivenNames(LinkedList<Person>[] adjListArray, String name1, String name2) {

    if ( adjListArray[getIndexOfGivenNameInHeadPositionOfList(adjListArray, name1)].peekFirst().fatherName
            .equalsIgnoreCase(
                    adjListArray[getIndexOfGivenNameInHeadPositionOfList(adjListArray, name2)].peekFirst().fatherName) ) {
        System.out.println("SIBLIGS");
        return;
    }

    String name1FatherName = adjListArray[getIndexOfGivenNameInHeadPositionOfList(adjListArray, name1)].peekFirst().fatherName;
    String name2FatherName = adjListArray[getIndexOfGivenNameInHeadPositionOfList(adjListArray, name2)].peekFirst().fatherName;

    if ( adjListArray[getIndexOfGivenNameInHeadPositionOfList(adjListArray, name1FatherName)].peekFirst().fatherName
            .equalsIgnoreCase(
                    adjListArray[getIndexOfGivenNameInHeadPositionOfList(adjListArray, name2FatherName)].peekFirst().fatherName) ) {
        System.out.println("COUSINS");
    }
}



private static void addPerson(LinkedList<Person>[] adjListArray, String name, String gender, String fatherName, String motherName) {
    Person person = new Person(name, gender, 0, 0, fatherName, motherName);
    int indexToPutperson = getEmptyIndexInAdjListToInserterson(adjListArray);
    adjListArray[indexToPutperson].addLast(person);
    if( fatherName!=null ){
        int indexOffatherName = getIndexOfGivenNameInHeadPositionOfList( adjListArray, fatherName);
        adjListArray[indexOffatherName].addLast(person);
    }
    if( motherName!=null ){
        int indexOfMotherName = getIndexOfGivenNameInHeadPositionOfList( adjListArray, motherName);
        adjListArray[indexOfMotherName].addLast(person);
    }
}

private static int getIndexOfGivenNameInHeadPositionOfList( LinkedList<Person>[] adjListArray, String nameToBeSearched ) {
    for (int i = 0; i < adjListArray.length; i++) {
        if( adjListArray[i] != null ){
            if(adjListArray[i].peekFirst() != null){
                if(adjListArray[i].peekFirst().name.equalsIgnoreCase(nameToBeSearched)){
                    return i;
                }
            }
        }
    }
    // handle if father name is not found
    return 0;
}


private static void printGraph(LinkedList<Person>[] adjListArray) {
    for (int v = 0; v < 15; v++) {
        System.out.print("head");

        LinkedList<Person> innerLinkedList = adjListArray[v];
        for (int i = 0; i < innerLinkedList.size(); i++) {
            Person person = innerLinkedList.get(i);
            System.out.print(" -> " + person.name);
        }

        System.out.println("\n");
    }
}

private static int getEmptyIndexInAdjListToInserterson( LinkedList<Person>[] adjListArray) {
    for (int i = 0; i < adjListArray.length; i++) {
        if(adjListArray[i].isEmpty()){
            return i;
        }
    }
    throw new IndexOutOfBoundsException("List of relation is full.");
}

}

person AdZzZ    schedule 04.10.2017

Это может вам помочь, это много теории и реализации SQL-запросов для создания и запроса древовидных структур.

http://www.artfulsoftware.com/mysqlbook/sampler/mysqled1ch20.html

В частности, обратите внимание на модель списка смежности, в которой используется генеалогическое древо. как пример.

person Charles Ma    schedule 30.06.2009
comment
Спасибо за ссылку, но я уже реализовал большую часть того, что продемонстрировано на этой странице. Мне нужно рассчитать семейные отношения, что значительно сложнее, чем те примеры. - person defines; 30.06.2009

Как бы странно это ни звучало, PROLOG кажется именно тем, что вам нужно. Учитывая следующую специальную программу (http://www.pastey.net/117134 лучшая окраска)

female(alice).
female(eve).
female(kate).

male(bob).
male(carlos).
male(dave).

% mother(_mother, _child).
mother(alice, bob).
mother(kate, alice).

% father(_father, _child)
father(carlos, bob).

child(C, P) :- father(P, C).
child(C, P) :- mother(P, C).

parent(X, Y) :- mother(X, Y).
parent(X, Y) :- father(X, Y).

sister(alice, eve).
sister(eve, alice).
sister(alice, dave).

brother(dave, alice).

% brother(sibling, sibling)
sibling(X, Y) :- brother(X, Y).
sibling(X, Y) :- sister(X, Y).


uncle(U, C) :- sibling(U, PARENT),
    child(C, PARENT),
    male(U).


relationship(U, C, uncle) :- uncle(U, C).
relationship(P, C, parent) :- parent(P, C).
relationship(B, S, brother) :- brother(B, S).
relationship(G, C, grandparent) :- parent(P, C), parent(G, P).

Вы можете спросить у интерпретатора Пролога что-то вроде этого:

relationship(P1, P2, R).

с ответами:


P1 = dave, P2 = bob, R = uncle ;
P1 = alice, P2 = bob, R = parent ;
P1 = kate, P2 = alice, R = parent ;
P1 = carlos, P2 = bob, R = parent ;
P1 = dave, P2 = alice, R = brother ;
P1 = kate, P2 = bob, R = grandparent ;
false.

Это мощный инструмент, если вы знаете, как и когда его использовать. Это похоже на место для Пролога. Я знаю, что он не очень популярен или легко встраивается, но впечатляющая функция wolfram alpha, показанная в одном из комментариев, может быть закодирована с использованием только конструкций, использованных выше, и это Prolog 101.

person Wojciech Bederski    schedule 06.07.2009
comment
На самом деле я просмотрел это решение несколько месяцев назад, но на самом деле это очень плохой алгоритм, способный вычислять только самые простые отношения (брат, родитель, ребенок, дядя). Его метод решения отношений также довольно неуклюж, вместо того, чтобы вычислять отношения, он выполняет жесткую проверку для всех возможных отношений. Мне нужно гораздо более надежное решение, чем это. - person defines; 06.07.2009
comment
Я не думаю, что обвинение людей в краже — это хорошая стратегия для получения помощи. Я закодировал базовый пример пролога, используемый почти в каждой когда-либо созданной книге/учебнике, это все равно, что обвинить кого-то в краже пузырьковой сортировки. Просто чтобы вы знали, Пролог прекрасно способен выражать безумно сложные отношения, и есть способы вычислять базы данных отношений более эффективно, чем представленное наивное решение. - person Wojciech Bederski; 06.07.2009
comment
@Wuub мои извинения, если это так - я плохо разбираюсь в Прологе, но нашел этот точный пример только в одном месте (и я искал другие примеры, но безуспешно). Мое решение, по общему признанию, наивно, но оно гораздо более оптимально, чем представленный вами пример, как по оптимальному времени выполнения, так и по надежности алгоритма. Пожалуйста, не нужно относиться к этим вещам серьезно. Это программирование, мы все здесь учимся и (надеюсь) так будет всегда. - person defines; 06.07.2009
comment
-серьезно +лично я это имел в виду - person defines; 06.07.2009
comment
Кроме того, просто чтобы прояснить возможную путаницу, я обращался к представленному алгоритму, а не к самому ПРОЛОГУ, который на самом деле кажется очень хорошо подходящим для рассматриваемой проблемы, поскольку он разработан специально для обработки сложных взаимосвязей. - person defines; 06.07.2009
comment
Еще раз, мои искренние извинения, если вы написали это - я определенно не хочу обвинять вас в плагиате, если это ваша собственная работа, и я очень ценю ваше время и вклад. Я понимаю, что будучи таким коротким фрагментом кода и являясь очевидным кандидатом в качестве примера кода, он может быть таким же распространенным, как вы говорите, и снова признаю, что я почти не знаком с ПРОЛОГОМ, что способствовало тому, что я сразу сделал такой вывод. Пожалуйста, поймите, я очень серьезно отношусь к плагиату; из-за этого я чувствую, что ложно обвинить кого-то в этом чуть ли не хуже, чем совершить это. Мои извинения. - person defines; 06.07.2009
comment
Без проблем. Я сам написал каждый байт приведенного выше кода, но никогда не говорил, что это была моя первоначальная идея. Я согласен, что есть гораздо лучшие алгоритмы для вычисления запрошенного вывода (даже на Прологе). Я согласен с тем, что Prolog не является приемлемым решением для веб-разработки. Но я думаю, что это все же стоит упомянуть. - person Wojciech Bederski; 06.07.2009
comment
@WojciechBederski Эй, если вы все еще здесь, не могли бы вы дать некоторое представление здесь stackoverflow.com/questions/32699313/? Я пытаюсь воспроизвести эту функцию wolframalpha, но для другого языка. - person Pranav; 23.09.2015