Recursively printing a family tree with objects

2.5k Views Asked by At

I need help with recursive representation of a family tree. Here is the data:

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

I need to mention that the values are objects, so I need to call them children_and_parents["Maria"].child to get ['Patricia', 'Lisa'].

The recursive program I currently have:

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) 

What it's currently doing is:

Mary
   Patricia
      Barbara

but the result should be something like:

Mary
   Patricia
       Barbara
           Betty
       Helen
       Maria
           Keren
           Carol
   Lisa

I'm stuck at the beginning of the program. If someone would be willing to help I would really appreciate it.

Rough code: https://repl.it/repls/BlondCavernousExponents

1

There are 1 best solutions below

5
On

Finding the root of the tree is a good candidate for a separate operation. In your example, we know it's "Mary", so we can iterate accordingly. If it's not known, you can write a function to return the first parent node that isn't a child of any other node:

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

As for the actual printing procedure, try printing the parent node before iterating over children. I also recommend passing the tree as a parameter to the function to maintain encapsulation and keep it reusable (i.e. not dependent on some variable called children_and_parents existing in the calling scope).

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)

We can also avoid the side effect in draw_family_tree and have it return a generator, letting the caller decide what to do with the result:

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)

Output:

Mary
   Patricia
      Barbara
         Betty
      Helen
      Maria
         Keren
         Carol
   Lisa

As mentioned in an earlier discussion, I don't recommend using a class for a simple <string, list> pair; it adds a lot of verbosity without functionality and is actually a bit misleading, because parent suggests the person has a parent (it's actually referring to the name of the person represented by the object). If you do choose to go this route, you'll need to append .child to all of the bracket accesses and write a __repr__(self) function for your class (or print(root.parent).