Вычислить родство по генеалогическим данным


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

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

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

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

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

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

6 16

6 ответов:

Сначала вам нужно вычислитьнаименьшего общего предка обоих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 немного не в себе, Я думаю. Вы можете упростить его, добавив префикс "grand", если смещение равно 1 или больше, а затем префикс" great - " (смещение - 1) раз. Ваша версия может быть включите приставку "пра-пра - пра-пра" для очень дальних родственников.(Не уверен, что у меня есть правильный параметр в этом объяснении, но надеюсь, вы поймете суть. Кроме того, не знаю, идет ли ваше семейное древо так далеко назад, но точка зрения остается в силе.)

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

Ниже приведена моя 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 гораздо менее оптимален. Я планирую опубликовать отдельный вопрос, чтобы оптимизировать это, и еще один, чтобы решить проблему вычисление сложных отношений, таких как двойные кузены. Большое спасибо всем, кто помог мне двигаться в правильном направлении! С твоими советами это оказалось намного проще, чем я первоначально думал.

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

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

Я решил эту проблему, используя концепцию списка смежности в 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.");
}

}

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

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

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

Как бы странно это ни звучало, пролог - это то, что вы ищете. Дана следующая специальная программа (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.
Это мощный инструмент, если вы знаете, как и когда им пользоваться. Это похоже на место для Пролога. Я знаю, что это не очень популярно, или легко встроить, но впечатляющая особенность wolphram Альфа, показанная в одном из комментариев, может быть закодирована только с помощью конструкций, использованных выше, и это пролог 101.