Usage

Abstract tree classes

        graph TD;
Tree[Tree];
MutableTree[MutableTree];
DownTree[DownTree];
Tree[Tree];
MutableTree[MutableTree];
MutableDownTree[MutableDownTree];
MutableTree[MutableTree];
BinaryDownTree[BinaryDownTree];
BinaryTree[BinaryTree];
Tree-->MutableTree;
DownTree-->Tree;
DownTree-->MutableDownTree;
MutableDownTree-->MutableTree;
DownTree-->BinaryDownTree;
BinaryDownTree-->BinaryTree;
Tree-->BinaryTree;
    

A downtree is an object that has links to its direct children. A tree is similar to a downtree, but also has a link to its parent. A binary tree may have at most two children. A mutable tree can change its structure once created.

ABC

Inherits from

Abstract Methods

Mixin Methods

AbstractTree

nid

DownTree

AbstractTree

children

nodes, descendants, leaves, levels, is_leaf, transform()

Tree

DownTree

parent

ancestors, siblings, path

MutableDownTree

DownTree

add_child(), remove_child()

add_children()

MutableTree

MutableDownTree, Tree

detach()

BinaryDownTree

DownTree

left_child, right_child

children

BinaryTree

BinaryDownTree, Tree

In your own code, you can inherit from these trees. For example, if your tree only has links to children:

from abstracttree import DownTree, print_tree

class MyTree(DownTree):
    def __init__(self, value, children=()):
        self.value = value
        self._children = children

    def __str__(self):
        return "MyTree " + str(self.value)

    @property
    def children(self):
        return self._children

You can now use this class in the following way to generate output:

tree = MyTree(1, children=[MyTree(2), MyTree(3)])
print_tree(tree)

# MyTree 1
# ├─ MyTree 2
# └─ MyTree 3

Generics

Unfortunately, not all trees inherit from the mixins above. Yet, some objects still have treelike behaviour. Therefore, AbstractTree provides support for a slightly weaker protocol.

The following objects are TreeLike:

  • All objects that support obj.children and obj.parent.

  • Builtins classes pathlib.Path and zipfile.Path.

  • Third party tree classes from anytree, bigtree, itertree and littletree.

The following objects are DownTreeLike:

  • All objects that support obj.children.

  • Recursive collections like lists, tuples, sets, dicts. This can be useful when dealing with json-data.

This can be tested using isinstance:

isinstance(Path(r"C:\\Windows\System"), TreeLike)  # True
isinstance(range(100), DownTreeLike)  # True
isinstance(range(100), TreeLike)  # False
isinstance(5, DownTreeLike)  # False
isinstance("some text", DownTreeLike)  # False (even though it might be considered a collection by python).

Basic functions

On downtreelikes:

children(node)  # Children of node
label(node)  # String representation of node (similar to str, but output excludes parent and children)
nid(node)  # Address of node (similar to id, but supports delegates).
eqv(node1, node2)  # Check if 2 nodes have the same identity (similar to is, but supports delegates)

Additionally, on treelikes:

parent(node)  # Parent of node or None if node is root of its own tree.
root(node)  # Find root of this tree.

Examples:

>>> from abstracttree import *
>>> children([1, 2, 3])
[1, 2, 3]
>>> children({"name": "Philip", "children": ["Pete", "Mariam"]})
[MappingItem(key="name", value="Philip"), MappingItem(key="children", value=["Pete", "Miriam"])]
>>> parent(Path(r"C:\\Windows\System"))
Path(r"C:\\Windows")
>>> label(Path(r"C:\\Windows\System"))
"System"
>>> eqv(Path(r"C:\\Windows\System"), Path(r"C:\\Windows\System"))
True
>>> eqv([1, 2, 3], [1, 2, 3])
False

Iterators

The following methods can iterate through nodes:

nodes(tree)  # Iterate through all nodes in tree (in no particular order).
descendants(node)  # Children and grand-(grand-*)-children of node.
leaves(root)  # Leaves reachable from root
ancestors(node)  # Ancestors of node.
path(node)  # Path from root to this node including this node.
siblings(node)  # Siblings of node

Traversal

The following methods also iterate, but in a very specific order.

Pre-order

The parent is iterated over before its children.

Post-order

The children are iterated over before their parent.

Level-order

Nodes closer to root are iterated over before nodes further from the root.

All these are possible by writing one of:

for node, item in preorder(tree):
    ...

for node, item in postorder(tree):
    ...

for node, item in levelorder(tree):
    ...

# If Downtree is implemented, tree.nodes.preorder() also works.

These methods return an item in addition to a node. This item is a tuple of the following fields:

depth

This indicates how deep the node is relative to the root of the (sub)tree iterated over. The root of the (sub)tree always has depth 0. To find the absolute depth of a node, use node.ancestors.count().

index

The index of this node among its siblings in relation to its direct parent. The first child of a parent gets index 0, the second gets index 1. The root of the (sub)tree always gets an index of 0 even if it has prior siblings.

To iterate over the descendants without the root, use the following:

for descendant, item in preorder(tree, include_root=False):
    ...
# If Downtree is implemented, tree.descendants.preorder() also works.

If the order of iteration doesn’t matter an alternative way to iterate is as follows:

for node in nodes(tree):
    ...

for descendant in descendants(tree):
    ...

Adapters

If you want a Tree-object, you can use as_tree to convert these treelikes to a full Tree. Alternatively, you can explicitly specify how to find children and parent:

# Tree from json-data
data = {"name": "a",
        "children": [
            {"name": "b", "children": []},
            {"name": "c", "children": []}
]}
as_tree(data, children=operator.itemgetter["children"])

# pyqt.QtWidget
as_tree(widget, children=lambda w: w.children(), parent = lambda w: w.parent())

# Tree from treelib
as_tree(tree.root, children=lambda nid: tree.children(nid), parent=lambda nid: tree.parent(nid))

# itertree
as_tree(tree, children=iter, parent=lambda t: t.parent)

# Infinite binary tree
inf_binary = as_tree(0, children=lambda n: (2*n + 1, 2*n + 2))

Export

Pretty printing:

print_tree(Path())

# ├─ adapters
# │  ├─ adapters.py
# │  ├─ heaptree.py
# │  └─ __init__.py
# ├─ export.py
# ├─ generics.py
# ├─ iterators.py
# ├─ mixins
# │  ├─ trees.py
# │  ├─ views.py
# │  └─ __init__.py
# ├─ predicates.py
# ├─ route.py
# ├─ utils.py
# └─ __init__.py

Plotting with matplotlib:

import matplotlib.pyplot as plt

plot_tree(ast.parse("y = x*x + 1"))
plt.show()
_images/tree_calc_plot.png

Export to graphviz:

tree = as_tree(seq, children=lambda x: [x[:-2], x[1:]] if x else [])
to_graphviz(tree)
_images/tree_dot.png

Export to mermaid:

to_mermaid(str)
_images/str_mermaid.png

Export to latex:

data = [["james", "steve"],
        ["patrick", "mike", "bod", "piet"]]
to_latex(data)
_images/latex_img.png