Tree Search – given two nodes in a tree, check if they are connected – python

Question:

Given a search tree, e.g.

"1"
 └ "2"
    ├ "2.1"
    ┊   └ "3"
    ┊
    └ "2.2"
        └ "2.2.1"
             └ "3"

As well as two nodes, a and b, that belong on that tree, e.g. “2.1” and “3”. How can we check whether a and b are parent-child (or child-parent) related / connected?

For the first example, True should be yielded. Here are some more:

a="3"      b="1"    -> False
a="3"      b="2"    -> False
a="2.2.1"  b="2.2"  -> True
a="2.2.1"  b="3"    -> True

I’m currently using the anytree library, with which I am struggling to implement this solution. The above graph is a structural simplification. What I have currently tried implementing is outlined here: https://pastebin.com/Mjk7gyqH

If the answer could be given with either pure python or anytree, that would be fantastic, but any answer is better than none.

Asked By: Sergey Ronin

||

Answers:

You can use simple recursion:

tree = {'name': '1', 'children': [{'name': '2', 'children': [{'name': '2.1', 'children': [{'name': '3'}]}, {'name': '2.2', 'children': [{'name': '2.2.1', 'children': [{'name': '3'}]}]}]}]}
def in_tree(d, node):
   return d['name'] == node or any(in_tree(i, node) for i in d.get('children', []))

def lookup(tree, a, b, flag=False):
   if tree['name'] == b and flag:
     return True
   return any(lookup(j, a, b, tree['name'] == a) for j in tree.get('children', []))

test = [['3', '1'], ['3', '2'], ['2.2.1', '2.2'], ['2.2.1', '3'], ['60', '70']]
for a, b in test:
   if not in_tree(tree, a) or not in_tree(tree, b):
      raise AttributeError('Node(s) missing in tree')
   print(any([lookup(tree, a, b), lookup(tree, b, a)]))

Output:

False
False
True
True
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
AttributeError: Node(s) missing in tree
Answered By: Ajax1234

If I understand well you ‘just’ ask for a direct parent child relationship without any intermediate nodes.
If this is not what you’re looking for, then please provide another example showing where below code fails and I can fix it.

The code uses anytree as this is the library, that you suggested

from anytree import Node, RenderTree

nodes = {} # a dict as a lookup to find nodes by name

def add_node(val, parentval=None):
    if parentval is not None:
        node = nodes[val] = Node(val, parent=nodes[parentval])
    else:
        node = nodes[val] = Node(val)
    return node

def mk_tree():
    top = add_node("1")
    add_node("2", "1")
    add_node("2.1", "2")
    add_node("3", "2.1")
    add_node("2.2", "2")
    add_node("2.2.1", "2.2")
    add_node("3", "2.2.1")
    return top

def is_child_or_parent(n1, n2):
    return n1.parent == n2 or n2.parent == n1

testpatterns = [
    ("3", "1", False),
    ("3", "2", False),
    ("2.2.1", "2.2", True),
    ("2.2.1", "3", True),
    ]

def run_test():
    for name1, name2, expected in testpatterns:
        node1 = nodes[name1]
        node2 = nodes[name2]
        rslt = is_child_or_parent(node1, node2)
        print(node1, node2, expected, rslt)
        assert rslt == expected

tree = mk_tree()
print(RenderTree(tree))
run_test()
Answered By: gelonida

bigtree is a Python tree and graph implementation that integrates with Python lists, dictionaries, and pandas DataFrame.

For this scenario, there is a built-in find_names and find_children method which does this for you. The idea is that we can use find_name to find the parent node (or multiple parent nodes), and use find_children to check if the child is related to the parent node.

import numpy as np
from bigtree import list_to_tree, print_tree, find_names, find_children

# Construct tree
path_list = ["1/2/2.1/3", "1/2/2.2/2.2.1/3"]
root = list_to_tree(path_list)

# Validate tree structure
print_tree(root)
1
└── 2
    ├── 2.1
    │   └── 3
    └── 2.2
        └── 2.2.1
            └── 3

# Function to compare
def is_related(a, b):
    # Check if a is child of b
    if np.any([find_children(node, a) for node in find_names(root, b)]):
        return True
    # Check if b is child of a
    if np.any([find_children(node, b) for node in find_names(root, a)]):
        return True
    return False

is_related("3", "1")  # False
is_related("3", "2")  # False
is_related("2.2.1", "2.2")  # True
is_related("2.2.1", "3")  # True

Source/Disclaimer: I’m the creator of bigtree 😉

Answered By: Kay Jan