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 Downtrees is an object that has links to its direct children. A Tree is has links to both its children and its parent. A binary tree has exactly two children (left and right). A mutable tree can change its structure once created.

ABC

Inherits from

Abstract Methods

Mixin Methods

AbstractTree

nid, eqv()

DownTree

AbstractTree

children

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

Tree

DownTree

siblings

MutableDownTree

DownTree

add_child(), remove_child()

add_children()

MutableTree

Tree, MutableDownTree

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:

import abstracttree
from abstracttree import print_tree

class MyTree(abstracttree.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

Adapter

In practice, not all existing tree data structures implement one of the abstract classes specified in Abstract classes. As a bridge, you can use Tree.convert to convert these trees to a Tree instance. However, whenever possible, it’s recommended to inherit from Tree directly for minimal overhead.

Tree.convert already does the right thing on many objects of the standard library:

# Inheritance hierarchy
Tree.convert(int)

# Abstract syntax tree
Tree.convert(ast.parse("1 + 1 == 2"))

# Filesystem
Tree.convert(pathlib.Path("abstracttree"))

# Zipfile
Tree.convert(zipfile.ZipFile("eclipse.jar"))

# Nested list
Tree.convert([[1, 2, 3], [4, 5, 6]])

It can also construct a tree by ducktyping on parent and children attributes:

# Works on objects by anytree, bigtree and littletree
Tree.convert(anytree.Node('node'))

Alternatively, you can use astree and explicitly specify how to find children and parent:

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

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

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

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

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

Traversal

There are 3 common ways to traverse a tree:

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 tree.nodes.preorder():
    ...

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

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

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 (the nodes without the root), similar methods are defined:

for descendant, item in tree.descendants.preorder():
    ...

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

for node in tree.nodes:
    ...

for descendant in tree.descendants:
    ...

Export

Pretty printing:

print_tree(Path())
# .
# ├─ abstracttree
# │  ├─ abstracttree\conversions.py
# │  ├─ abstracttree\export.py
# │  ├─ abstracttree\predicates.py
# │  ├─ abstracttree\treeclasses.py
# │  └─ abstracttree\__init__.py
# ├─ LICENSE
# ├─ Makefile
# ├─ manual.txt
# ├─ pyproject.toml
# ├─ README.md
# └─ tests
#    ├─ tests\test_downtree.py
#    ├─ tests\test_export.py
#    ├─ tests\test_mutabletree.py
#    ├─ tests\test_tree.py
#    ├─ tests\test_uptree.py
#    └─ tests\tree_instances.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 = astree(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