Рекурсивная печать генеалогического дерева с объектами

Мне нужна помощь с рекурсивным представлением генеалогического дерева. Вот данные:

children_and_parents = {
    "Mary": ["Patricia", "Lisa"], 
    "Patricia": ["Barbara", "Helen", "Maria"], 
    "Maria": ["Keren", "Carol"], 
    "Barbara": ["Betty"]
}

Я должен упомянуть, что значения являются объектами, поэтому мне нужно вызвать их children_and_parents["Maria"].child, чтобы получить ['Patricia', 'Lisa'].

Рекурсивная программа, которую я сейчас имею:

def draw_family_tree(person, level=0):
    if person in children_and_parents:
        for i in range (len(children_and_parents[person].child)):
            print (" "*level, person)
            return draw_family_tree(children_and_parents[person].child[i], level+3) 

Что он делает в настоящее время:

Mary
   Patricia
      Barbara

но результат должен быть примерно таким:

Mary
   Patricia
       Barbara
           Betty
       Helen
       Maria
           Keren
           Carol
   Lisa

Я застрял в начале программы. Если кто-то захочет помочь, я был бы очень признателен.

Примерный код: https://repl.it/repls/BlondCavernousExponents


person D. K.    schedule 27.11.2018    source источник


Ответы (1)


Поиск корня дерева — хороший кандидат на отдельную операцию. В вашем примере мы знаем, что это "Mary", поэтому мы можем выполнить соответствующую итерацию. Если это неизвестно, вы можете написать функцию для возврата первого родительского узла, который не является дочерним элементом любого другого узла:

def find_root(tree):
    all_children = {x for y in tree.values() for x in y}
    
    for node in tree:
        if node not in all_children:
            return node

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

def draw_family_tree(tree, root, gap=3, level=0):
    if root:
        print(" " * level + root)

        if root in tree:
            for child in tree[root]:
                draw_family_tree(tree, child, gap, level + gap)

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

def find_root(tree):
    all_children = {x for y in tree.values() for x in y}
    
    for node in tree:
        if node not in all_children:
            return node

def draw_family_tree(tree, root, gap=3, level=0):
    if root:
        yield " " * level + root

        if root in tree:
            for child in tree[root]:
                yield from draw_family_tree(tree, child, gap, level + gap)

if __name__ == "__main__":
    children_and_parents = {
        "Mary": ["Patricia", "Lisa"], 
        "Patricia": ["Barbara", "Helen", "Maria"], 
        "Maria": ["Keren", "Carol"], 
        "Barbara": ["Betty"]
    }
    root = find_root(children_and_parents)

    for node in draw_family_tree(children_and_parents, root):
        print(node)

Выход:

Mary
   Patricia
      Barbara
         Betty
      Helen
      Maria
         Keren
         Carol
   Lisa

Как упоминалось ранее, я не рекомендую использовать класс для простой пары <string, list>; он добавляет много многословия без функциональности и на самом деле немного вводит в заблуждение, потому что parent предполагает, что у человека есть родитель (на самом деле это относится к имени человека, представленного объектом). Если вы решите пойти по этому пути, вам нужно добавить .child ко всем обращениям к скобкам и написать функцию __repr__(self) для вашего класса (или print(root.parent).

person ggorlen    schedule 27.11.2018
comment
Он получает мое сообщение об ошибке, которое может только конкатенировать str (не dict) с str , как я уже упоминал значения, дочерние элементы являются объектами, потому что я работаю с ними в другой части программы, где они должны быть, поэтому это может быть проблемой - person D. K.; 27.11.2018
comment
Вы не опубликовали этот код, поэтому вам нужно опубликовать его или написать метод str() для своего класса. В противном случае мне остается только гадать, что такое определение класса. Однако логика печати такая же, поэтому, не публикуя ее, я предполагаю, что вы можете экстраполировать ее на свои нужды. - person ggorlen; 27.11.2018
comment
только секунду я выложу это для вас - person D. K.; 27.11.2018
comment
там должен быть приложенный файл, я проверил - person D. K.; 27.11.2018
comment
Давайте продолжим обсуждение в чате. - person D. K.; 27.11.2018