Tree

The pyTooling.Tree package provides fast and simple tree data structure based on a single Node class.

Hint

This tree data structure outperforms anytree by far and even itertree by factor of 2.

Further alternatives:

treelib

treelib

Example Tree:

        %%{init: { "flowchart": { "nodeSpacing": 15, "rankSpacing": 30, "curve": "linear", "useMaxWidth": false } } }%%
graph TD
  R(Root)
  A(...)
  BL(Node); B(GrandParent); BR(Node)
  CL(Uncle); C(Parent); CR(Aunt)
  DL(Sibling); D(Node);  DR(Sibling)
  ELN1(Niece); ELN2(Nephew)
  EL(Child);   E(Child); ER(Child);
  ERN1(Niece);ERN2(Nephew)
  F1(GrandChild); F2(GrandChild)

  R:::mark1 --> A
  A --> BL & B & BR
  B --> CL & C & CR
  C --> DL & D & DR
  DL --> ELN1 & ELN2
  D:::cur --> EL & E & ER
  DR --> ERN1 & ERN2
  E --> F1 & F2

  classDef node fill:#eee,stroke:#777,font-size:smaller;
  classDef cur fill:#9e9,stroke:#6e6;
  classDef mark1 fill:#69f,stroke:#37f,color:#eee;
    

Root of the current node are marked in blue.

Comprehensive Example:

The following example code demonstrates a few features in a compact form:

# Create a new tree by creating a root node (no parent reference)
root = Node(value="OSVVM Regression Tests")

# Construct the tree top-down
lib = Node(value="Utility Library", parent=root)

# Another standalone node with unique ID (actually an independent tree)
common = Node(nodeID=5, value="Common")

# Construct bottom-up
axi = Node(value="AXI")
axiCommon = Node(value="AXI4 Common")
axi.AddChild(axiCommon)

# Group nodes and handover children at node creation time
vcList = [common, axi]
vcs = Node(value="Verification Components", parent=root, children=vcList)

# Add multiple nodes at once
axiProtocols = (
  Node(value="AXI4-Stream"),
  Node(value="AXI4-Lite"),
  Node(value="AXI4")
)
axi.AddChildren(axiProtocols)

# Create another standalone node and attach it later to a tree.
uart = Node(value="UART")
uart.Parent = vcs

The presented code will generate this tree:

  • OSVVM Regression Tests
    • Utility Library
    • Verification Components
      • Common
      • AXI
        • AXI4 Common
        • AXI4-Stream
        • AXI4-Lite
        • AXI4
      • UART

Features

  • Fast and simple tree data structure based on a single Node class.

  • A tree can be constructed top-down and bottom-up.

  • A node can have a unique ID.

  • A node knows its level (distance from root).

  • A node can have a value.

  • A node can store key-value-pairs via dictionary syntax.

  • A node has a reference to its parent node.

  • A node can add and remove children; a removed child takes its subtree along.

  • A tree no longer needed can be deleted, so its memory is freed without waiting for the garbage collector.

  • A node has a reference to the root node in a tree (representative node).

  • Rendering to simple ASCII art for debugging purposes.

Missing Features

  • Insert a node (currently, only add/append is supported).

  • Move a node in same hierarchy level.

  • Move node to a different level/node in the same tree in a single operation.

  • Allow node deletion.

Planned Features

  • Allow filters (predicates) in generators to allow node filtering.

  • Tree export to formats like GraphML, …

  • Export the tree data structure to file the YAML format.

  • Allow nodes to have tags and group nodes by tags.

  • Allow nodes to link to other nodes (implement proxy behavior?)

Out of Scope

  • Preserve or recover the tree data structure before an erroneous operation caused an exception and aborted a tree modification, which might leave the tree in a corrupted state.

  • Export the tree data structure to various file formats like JSON, TOML, …

  • Import a tree data structure from various file formats like JSON, YAML, TOML, …

  • Tree visualization or rendering to complex formats like GraphViz, Mermaid, …

By Feature

Danger

Accessing internal fields of a node is strongly not recommended for users, as it might lead to a corrupted tree data structure. If a power-user wants to access these fields, feel free to use them for achieving a higher performance, but you got warned 😉.

Unique ID

A node can be created with a unique ID when the object is created. Afterwards, the ID is a readonly property. Any hashable object can be used as an ID. The ID must be unique per tree. If trees are merged or nodes are added to an existing tree, the newly added node’s ID(s) are checked and might cause an exception.

# Create node with unique ID 5
node = Node(nodeID=5)

# Read a node's ID
nodeID = node.ID

Level

Each node has a level describing the distance from root node. It can be accessed via the read-only property Level.

The root node has a level of 0, children of root have a level of 1, and so on.

# Create node
root = Node(nodeID=0)
node2 = Node(nodeID=1, parent=root)

# Read a node's level
nodeLevel = node2.Level

Value

A node’s value can be given at node creating time or it can be set ant any later time via property Value. Any data type is accepted. The internally stored value can be retrieved by the same property. If a node’s string representation is requested via __str__() and a node’s value isn’t None, then the value’s string representation is returned.

# Create node with value 5
node = Node(value=5)

# Set or change a node's value
node.Value = 10

# Access a node's Value
value = node.Value

Key-Value-Pairs

Besides a unique ID and a value, each node can hold an arbitrary set of key-value-pairs.

# Create node
node = Node()

# Create or update a key-value-pair
node["key"] = value

# Access a value by key
value = node["key"]

# Delete a key-value-pair
del node["key"]

# Check for a key, count the pairs, iterate the keys
if "key" in node: ...
count = len(node)
for key in node: ...

in, len(node) and iterating a node address the key-value-pairs, not the children.

Parent Reference

Each node has a reference to its parent node. In case, the node is the root node, the parent reference is None. The parent-child relation can be set at node creation time, or a parent can be assigned to a node at any later time via property Parent. The same property can be used to retrieve the current parent reference.

# Create node without parent relation ship (root node)
root = Node(nodeID=0)

# Create a node add directly attach it to an existing tree
node = Node(nodeID=1, parent=root)

# Access a node's parent
parent = node.Parent

Merging Trees

In case, two trees were created (a single node is already a minimal tree), trees get merged if one tree’s root node is assigned a parent relationship.

# Create a tree with a single node
root = Node(nodeID=0)

# Create a second minimalistic tree
otherTree = Node(nodeID=100)

# Set parent relationship and merge trees
otherTree.Parent = root

See also

See Merging Trees for more details.

Splitting Trees

In case, a node within a tree’s hierarchy is updated with respect to it’s parent relationship to None, then the tree gets split into 2 trees.

# Create a tree of 4 nodes
root1 = Node(nodeID=0)
node1 = Node(nodeID=1, parent=root1)

root2 = Node(nodeID=2, parent=node1)
node3 = Node(nodeID=3, parent=root2)

# Split the tree between node1 and root2
root2.Parent = None

A parent node can remove its children, too: RemoveChild() removes one child and RemoveChildren() removes several, the counterparts of AddChild() and AddChildren(). Each removed child takes its subtree along and becomes the root node of its own tree.

# Split the tree between node1 and root2
node1.RemoveChild(root2)

# Remove all children of root1
root1.RemoveChildren(root1.GetChildren())

See also

See Splitting Trees for more details.

Moving a branch in same tree

Todo

TREE::Parent::move-branch in same tree - needs also testcases

Moving a branch to another tree

Todo

TREE::Parent::move-branch into another tree - needs also testcases

Root Reference

Each node has a reference to the tree’s root node. The root node can also be considered the representative node of a tree and can be accessed via read-only property Root.

When a node is assigned a new parent relation and this parent is a node in another tree, the root reference will change. (A.k.a. moving a branch to another tree.)

The root node of a tree contains tree-wide data structures like the list of unique IDs (_nodesWithID, _nodesWithoutID). By utilizing the root reference, each node can access these data structures by just one additional reference hop.

# Create a simple tree
root = Node()
nodeA = Node(parent=root)
nodeB = Node(parent=root)

# Check if nodeA and nodeB are in same tree
isSameTree = nodeA is nodeB

Path

The property Path returns a tuple describing the path top-down from root node to the current node.

# Create a simple tree representing directories
root = Node(value="C:")
dir = Node(value="temp", parent=root)
file = Node(value="test.log", parent=dir)

# Convert a path to string
path = "\".join(file.Path)

While the tuple returned by Path can be used in an iteration (e.g. a for-loop), also a generator is provided by method GetPath() for iterations.

# Create a simple tree representing directories
root = Node(value="C:")
dir = Node(value="temp", parent=root)
file = Node(value="test.log", parent=dir)

# Render path from root to node with indentations to ASCII art
for level, node in enumerate(file.GetPath()):
  print(f"{'  '*level}'\-'{node}")

# \-C:
#   \-temp
#     \-test.log

Ancestors

The method GetAncestors() returns a generator to traverse bottom-up from current node to the root node. If the top-down direction is needed, see Path for more details.

Python Code

Diagram

Tree Construction:

# Create an example tree
root =        Node(nodeID=0)
dots =        Node(nodeID=1, parent=root)
node1 =       Node(nodeID=2, parent=dots)
grandParent = Node(nodeID=3, parent=dots)
node2 =       Node(nodeID=4, parent=dots)
uncle =       Node(nodeID=5, parent=grandParent)
parent =      Node(nodeID=6, parent=grandParent)
aunt =        Node(nodeID=7, parent=grandParent)
sibling1 =    Node(nodeID=8, parent=parent)
me =          Node(nodeID=9, parent=parent)
sibling2 =    Node(nodeID=10, parent=parent)
niece1 =      Node(nodeID=11, parent=sibling1)
nephew1 =     Node(nodeID=12, parent=sibling1)
child1 =      Node(nodeID=13, parent=me)
child2 =      Node(nodeID=14, parent=me)
child3 =      Node(nodeID=15, parent=me)
niece2 =      Node(nodeID=16, parent=sibling2)
nephew2 =     Node(nodeID=17, parent=sibling2)
grandChild1 = Node(nodeID=18, parent=child2)
grandChild2 = Node(nodeID=19, parent=child2)

Usage

# Walk bottom-up all the way to root
for node in me.GetAncestors():
  print(node.ID)

Result

6   # parent
3   # grandparent
1   # ...
0   # root
        %%{init: { "flowchart": { "nodeSpacing": 15, "rankSpacing": 30, "curve": "linear", "useMaxWidth": false } } }%%
graph TD
  R(Root)
  A(...)
  BL(Node); B(GrandParent); BR(Node)
  CL(Uncle); C(Parent); CR(Aunt)
  DL(Sibling); D(Node);  DR(Sibling)
  ELN1(Niece); ELN2(Nephew)
  EL(Child);   E(Child); ER(Child);
  ERN1(Niece);ERN2(Nephew)
  F1(GrandChild); F2(GrandChild)

  R:::mark1 --> A
  A:::mark2 --> BL & B & BR
  B:::mark2 --> CL & C & CR
  C:::mark2 --> DL & D & DR
  DL --> ELN1 & ELN2
  D:::cur --> EL & E & ER
  DR --> ERN1 & ERN2
  E --> F1 & F2

  classDef node fill:#eee,stroke:#777,font-size:smaller;
  classDef cur fill:#9e9,stroke:#6e6;
  classDef mark1 fill:#69f,stroke:#37f,color:#eee;
  classDef mark2 fill:#69f,stroke:#37f;
    

Common Ancestors

If needed, method GetCommonAncestors() provides a generator to iterate the common ancestors of two nodes in a tree. It iterates from root node top-down until the common branch in the tree splits of.

Python Code

Diagram

Tree Construction:

# Create an example tree
root =        Node(nodeID=0)
dots =        Node(nodeID=1, parent=root)
node1 =       Node(nodeID=2, parent=dots)
grandParent = Node(nodeID=3, parent=dots)
node2 =       Node(nodeID=4, parent=dots)
uncle =       Node(nodeID=5, parent=grandParent)
parent =      Node(nodeID=6, parent=grandParent)
aunt =        Node(nodeID=7, parent=grandParent)
sibling1 =    Node(nodeID=8, parent=parent)
me =          Node(nodeID=9, parent=parent)
sibling2 =    Node(nodeID=10, parent=parent)
niece1 =      Node(nodeID=11, parent=sibling1)
nephew1 =     Node(nodeID=12, parent=sibling1)
child1 =      Node(nodeID=13, parent=me)
child2 =      Node(nodeID=14, parent=me)
child3 =      Node(nodeID=15, parent=me)
niece2 =      Node(nodeID=16, parent=sibling2)
nephew2 =     Node(nodeID=17, parent=sibling2)
grandChild1 = Node(nodeID=18, parent=child2)
grandChild2 = Node(nodeID=19, parent=child2)

Usage

# Walk bottom-up all the way to root
for node in nephew1.GetCommonAncestors(grandChild2):
  print(node.ID)

Result

0   # root
1   # ...
3   # grandparent
6   # parent
        %%{init: { "flowchart": { "nodeSpacing": 15, "rankSpacing": 30, "curve": "linear", "useMaxWidth": false } } }%%
graph TD
  R(Root)
  A(...)
  BL(Node); B(GrandParent); BR(Node)
  CL(Uncle); C(Parent); CR(Aunt)
  DL(Sibling); D(Node);  DR(Sibling)
  ELN1(Niece); ELN2(Nephew)
  EL(Child);   E(Child); ER(Child);
  ERN1(Niece);ERN2(Nephew)
  F1(GrandChild); F2(GrandChild)

  R:::mark1 --> A
  A:::mark2 --> BL & B & BR
  B:::mark2 --> CL & C & CR
  C:::mark2 --> DL & D & DR
  DL --> ELN1 & ELN2
  D --> EL & E & ER
  DR --> ERN1 & ERN2
  E --> F1 & F2
  ELN2:::cur; F2:::cur
  classDef node fill:#eee,stroke:#777,font-size:smaller;
  classDef cur fill:#9e9,stroke:#6e6;
  classDef mark1 fill:#69f,stroke:#37f,color:#eee;
  classDef mark2 fill:#69f,stroke:#37f;
    

Children

Children are all direct successors of a node.

A node object supports returning children either as a tuple via a property or as a generator via a method call. ChildCount returns their number.

Return a Tuple

Return a Generator

Children

Children

GetChildren()

Children and children thereof

— — — —

GetDescendants()

Python Code

Diagram

Tree Construction:

# Create an example tree
root =        Node(nodeID=0)
dots =        Node(nodeID=1, parent=root)
node1 =       Node(nodeID=2, parent=dots)
grandParent = Node(nodeID=3, parent=dots)
node2 =       Node(nodeID=4, parent=dots)
uncle =       Node(nodeID=5, parent=grandParent)
parent =      Node(nodeID=6, parent=grandParent)
aunt =        Node(nodeID=7, parent=grandParent)
sibling1 =    Node(nodeID=8, parent=parent)
me =          Node(nodeID=9, parent=parent)
sibling2 =    Node(nodeID=10, parent=parent)
niece1 =      Node(nodeID=11, parent=sibling1)
nephew1 =     Node(nodeID=12, parent=sibling1)
child1 =      Node(nodeID=13, parent=me)
child2 =      Node(nodeID=14, parent=me)
child3 =      Node(nodeID=15, parent=me)
niece2 =      Node(nodeID=16, parent=sibling2)
nephew2 =     Node(nodeID=17, parent=sibling2)
grandChild1 = Node(nodeID=18, parent=child2)
grandChild2 = Node(nodeID=19, parent=child2)

Usage

# Walk bottom-up all the way to root
for node in me.GetChildren():
  print(node.ID)

Result

13  # child1
14  # child2
15  # child3
        %%{init: { "flowchart": { "nodeSpacing": 15, "rankSpacing": 30, "curve": "linear", "useMaxWidth": false } } }%%
graph TD
  R(Root)
  A(...)
  BL(Node); B(GrandParent); BR(Node)
  CL(Uncle); C(Parent); CR(Aunt)
  DL(Sibling); D(Node);  DR(Sibling)
  ELN1(Niece); ELN2(Nephew)
  EL(Child);   E(Child); ER(Child);
  ERN1(Niece);ERN2(Nephew)
  F1(GrandChild); F2(GrandChild)

  R --> A
  A --> BL & B & BR
  B --> CL & C & CR
  C --> DL & D & DR
  DL --> ELN1 & ELN2
  D:::cur --> EL & E & ER
  DR --> ERN1 & ERN2
  E --> F1 & F2
  EL:::mark2; E:::mark2; ER:::mark2
  classDef node fill:#eee,stroke:#777,font-size:smaller;
  classDef cur fill:#9e9,stroke:#6e6;
  classDef mark1 fill:#69f,stroke:#37f,color:#eee;
  classDef mark2 fill:#69f,stroke:#37f;
    

Descendants

Descendants are all direct and indirect successors of a node (child nodes and child nodes thereof a.k.a. grandchild, grand-grandchildren, …).

A node object supports returning descendants as a generator via a method call to GetDescendants(), due to the recursive behavior.

See also

See Iterating a Tree for various other forms for iterating nodes in a tree.

Python Code

Diagram

Tree Construction:

# Create an example tree
root =        Node(nodeID=0)
dots =        Node(nodeID=1, parent=root)
node1 =       Node(nodeID=2, parent=dots)
grandParent = Node(nodeID=3, parent=dots)
node2 =       Node(nodeID=4, parent=dots)
uncle =       Node(nodeID=5, parent=grandParent)
parent =      Node(nodeID=6, parent=grandParent)
aunt =        Node(nodeID=7, parent=grandParent)
sibling1 =    Node(nodeID=8, parent=parent)
me =          Node(nodeID=9, parent=parent)
sibling2 =    Node(nodeID=10, parent=parent)
niece1 =      Node(nodeID=11, parent=sibling1)
nephew1 =     Node(nodeID=12, parent=sibling1)
child1 =      Node(nodeID=13, parent=me)
child2 =      Node(nodeID=14, parent=me)
child3 =      Node(nodeID=15, parent=me)
niece2 =      Node(nodeID=16, parent=sibling2)
nephew2 =     Node(nodeID=17, parent=sibling2)
grandChild1 = Node(nodeID=18, parent=child2)
grandChild2 = Node(nodeID=19, parent=child2)

Usage

# Walk bottom-up all the way to root
for node in me.GetDescendants():
  print(node.ID)

Result

13  # child1
14  # child2
18  # grandChild1
19  # grandChild2
15  # child3
        %%{init: { "flowchart": { "nodeSpacing": 15, "rankSpacing": 30, "curve": "linear", "useMaxWidth": false } } }%%
graph TD
  R(Root)
  A(...)
  BL(Node); B(GrandParent); BR(Node)
  CL(Uncle); C(Parent); CR(Aunt)
  DL(Sibling); D(Node);  DR(Sibling)
  ELN1(Niece); ELN2(Nephew)
  EL(Child);   E(Child); ER(Child);
  ERN1(Niece);ERN2(Nephew)
  F1(GrandChild); F2(GrandChild)

  R --> A
  A --> BL & B & BR
  B --> CL & C & CR
  C --> DL & D & DR
  DL --> ELN1 & ELN2
  D:::cur --> EL & E & ER
  DR --> ERN1 & ERN2
  E --> F1 & F2
  EL:::mark2; E:::mark2; ER:::mark2; F1:::mark2; F2:::mark2
  classDef node fill:#eee,stroke:#777,font-size:smaller;
  classDef cur fill:#9e9,stroke:#6e6;
  classDef mark1 fill:#69f,stroke:#37f,color:#eee;
  classDef mark2 fill:#69f,stroke:#37f;
    

Siblings

Siblings are all direct child nodes of a node’s parent node except itself.

A node object supports returning siblings either as tuples via a property or as a generator via a method call. Either all siblings are returned or just siblings left from the current node (left siblings) or right from the current node (right siblings). Left and right is based on the order of child references in the current node’s parent.

Sibling Selection

Return a Tuple

Return a Generator

Left Siblings

LeftSiblings

GetLeftSiblings()

All Siblings

Siblings

GetSiblings()

Right Siblings

RightSiblings

GetRightSiblings()

Attention

In case a node has no parent, an exception is raised, because siblings cannot exist.

Python Code

Diagram

Tree Construction:

# Create an example tree
root =        Node(nodeID=0)
dots =        Node(nodeID=1, parent=root)
node1 =       Node(nodeID=2, parent=dots)
grandParent = Node(nodeID=3, parent=dots)
node2 =       Node(nodeID=4, parent=dots)
uncle =       Node(nodeID=5, parent=grandParent)
parent =      Node(nodeID=6, parent=grandParent)
aunt =        Node(nodeID=7, parent=grandParent)
sibling1 =    Node(nodeID=8, parent=parent)
me =          Node(nodeID=9, parent=parent)
sibling2 =    Node(nodeID=10, parent=parent)
niece1 =      Node(nodeID=11, parent=sibling1)
nephew1 =     Node(nodeID=12, parent=sibling1)
child1 =      Node(nodeID=13, parent=me)
child2 =      Node(nodeID=14, parent=me)
child3 =      Node(nodeID=15, parent=me)
niece2 =      Node(nodeID=16, parent=sibling2)
nephew2 =     Node(nodeID=17, parent=sibling2)
grandChild1 = Node(nodeID=18, parent=child2)
grandChild2 = Node(nodeID=19, parent=child2)

Usage

# Walk bottom-up all the way to root
for node in me.GetLeftSiblings():
  print(node.ID)
for node in me.GetRightSiblings():
  print(node.ID)

Result

8   # sibling1
10  # sibling2
        %%{init: { "flowchart": { "nodeSpacing": 15, "rankSpacing": 30, "curve": "linear", "useMaxWidth": false } } }%%
graph TD
  R(Root)
  A(...)
  BL(Node); B(GrandParent); BR(Node)
  CL(Uncle); C(Parent); CR(Aunt)
  DL(Sibling); D(Node);  DR(Sibling)
  ELN1(Niece); ELN2(Nephew)
  EL(Child);   E(Child); ER(Child);
  ERN1(Niece);ERN2(Nephew)
  F1(GrandChild); F2(GrandChild)

  R --> A
  A --> BL & B & BR
  B --> CL & C & CR
  C --> DL & D & DR
  DL --> ELN1 & ELN2
  D:::cur --> EL & E & ER
  DR --> ERN1 & ERN2
  E --> F1 & F2
  DL:::mark2; DR:::mark2
  classDef node fill:#eee,stroke:#777,font-size:smaller;
  classDef cur fill:#9e9,stroke:#6e6;
  classDef mark1 fill:#69f,stroke:#37f,color:#eee;
  classDef mark2 fill:#69f,stroke:#37f;
    

Relatives

Relatives are siblings and their descendants.

A node object supports returning relatives as a generator via a method call, due to the recursive behavior. Either all relatives are returned or just relatives left from the current node (left relatives) or right from the current node (right relatives). Left and right is based on the order of child references in the current node’s parent.

Relative Selection

Return a Generator

Left Siblings

GetLeftRelatives()

All Siblings

GetRelatives()

Right Siblings

GetRightRelatives()

Attention

In case a node has no parent, an exception is raised, because siblings and therefore relatives cannot exist.

Python Code

Diagram

Tree Construction:

# Create an example tree
root =        Node(nodeID=0)
dots =        Node(nodeID=1, parent=root)
node1 =       Node(nodeID=2, parent=dots)
grandParent = Node(nodeID=3, parent=dots)
node2 =       Node(nodeID=4, parent=dots)
uncle =       Node(nodeID=5, parent=grandParent)
parent =      Node(nodeID=6, parent=grandParent)
aunt =        Node(nodeID=7, parent=grandParent)
sibling1 =    Node(nodeID=8, parent=parent)
me =          Node(nodeID=9, parent=parent)
sibling2 =    Node(nodeID=10, parent=parent)
niece1 =      Node(nodeID=11, parent=sibling1)
nephew1 =     Node(nodeID=12, parent=sibling1)
child1 =      Node(nodeID=13, parent=me)
child2 =      Node(nodeID=14, parent=me)
child3 =      Node(nodeID=15, parent=me)
niece2 =      Node(nodeID=16, parent=sibling2)
nephew2 =     Node(nodeID=17, parent=sibling2)
grandChild1 = Node(nodeID=18, parent=child2)
grandChild2 = Node(nodeID=19, parent=child2)

Usage

# Walk bottom-up all the way to root
for node in me.GetLeftRelatives():
  print(node.ID)
for node in me.GetRightRelatives():
  print(node.ID)

Result

8   # sibling1
11  # niece1
12  # nephew1

10  # sibling2
16  # niece2
17  # nephew2
        %%{init: { "flowchart": { "nodeSpacing": 15, "rankSpacing": 30, "curve": "linear", "useMaxWidth": false } } }%%
graph TD
  R(Root)
  A(...)
  BL(Node); B(GrandParent); BR(Node)
  CL(Uncle); C(Parent); CR(Aunt)
  DL(Sibling); D(Node);  DR(Sibling)
  ELN1(Niece); ELN2(Nephew)
  EL(Child);   E(Child); ER(Child);
  ERN1(Niece);ERN2(Nephew)
  F1(GrandChild); F2(GrandChild)

  R --> A
  A --> BL & B & BR
  B --> CL & C & CR
  C --> DL & D & DR
  DL --> ELN1 & ELN2
  D:::cur --> EL & E & ER
  DR --> ERN1 & ERN2
  E --> F1 & F2
  DL:::mark2; ELN1:::mark2; ELN2:::mark2; DR:::mark2; ERN1:::mark2; ERN2:::mark2
  classDef node fill:#eee,stroke:#777,font-size:smaller;
  classDef cur fill:#9e9,stroke:#6e6;
  classDef mark1 fill:#69f,stroke:#37f,color:#eee;
  classDef mark2 fill:#69f,stroke:#37f;
    

Iterating a Tree

A tree (starting at the root node) or a subtree (starting at any node in the tree) can be iterated in various orders:

Merging Trees

A tree B is merged into an existing tree A, when a tree B’s parent relation is set to a non-None value. Therefore use the B.Parent property and set it to A: B.Parent = A.

The following operations are executed on the tree B:

  1. register all nodes of B with and without ID in A, then

  2. delete the list and dictionary objects for nodes with and without IDs from B.

The following operations are executed on all nodes in tree B:

  • set root reference to A.

  • recompute the level within A.

Attention

In case a node’s ID already exists in A, an exception is raised, because IDs are unique.

Splitting Trees

A subtree B is split from its tree A, when B’s parent relation is set to None. Therefore use the B.Parent property and set it to None: B.Parent = None, or remove B from its parent node P: P.RemoveChild(B).

The following operations are executed on the subtree B:

  1. create the list and dictionary objects for nodes with and without IDs in B, then

  2. move all nodes of B with and without ID from A to B, then

  3. remove B from the children of P.

The following operations are executed on all nodes in subtree B:

  • set root reference to B.

  • recompute the level within B.

Afterwards, Size and GetNodeByID() of A no longer see the nodes of B, and ChildCount of P no longer counts B.

Deleting a Tree

The nodes of a tree reference each other: a node its parent and its children, every node the root, and the root all nodes of the tree and itself. These are reference cycles, so when the last reference to a tree is dropped, Python’s reference counting can’t free it. The memory is freed only by the cyclic garbage collector (gc), which runs from time to time, or when a program calls gc.collect(). A full collection visits every object of the program, not only the tree.

Delete() deletes a node and its subtree: a node with a parent is removed from it first, like RemoveChild() does, then every node of the subtree loses its root, its parent, its children and its key-value-pairs. Afterwards, nothing but the program’s own references keeps the nodes alive, so reference counting frees them as soon as these are dropped. A deleted node belongs to no tree and can’t be used any more; its value is kept.

root = Node(nodeID=0)
node = Node(nodeID=1, parent=root)
Node(nodeID=2, parent=node)

...                    # use the tree

root.Delete()
del root, node         # freed now, not at the next garbage collection

Deleting pays off where a program replaces a large tree while it keeps running. A value referencing its node is the program’s own cycle: the program clears that reference itself.

Tree Rendering

The tree data structure can be rendered as text art. The Render() method renders the tree into a multi line string: the node it is called on first, then every node below it, one line per node, with markers drawing the branches. A node is written by its format function (parameter format of Node), or else by str(node), which is the node’s ID.

root =  Node(nodeID=0)
node1 = Node(nodeID=1, parent=root)
Node(nodeID=2, parent=root)
node3 = Node(nodeID=3, parent=root)
Node(nodeID=4, parent=node1)
Node(nodeID=5, parent=node1)
Node(nodeID=6, parent=node3)
Node(nodeID=7, parent=node3)

print(root.Render())
0
├─1
│ ├─4
│ └─5
├─2
└─3
  ├─6
  └─7

The parameters choose the markers and what surrounds each line:

Parameter

Default

Printed

prefix

""

in front of every line, e.g. for indentation

lineend

"\n"

at the end of every line

nodeMarker

"├─"

in front of a node that has a following sibling

lastNodeMarker

"└─"

in front of a node that is the last child of its parent

bypassMarker

"│ "

for a level whose node has a following sibling; below a last child, as many spaces

E.g. plain ASCII markers, indented by two spaces:

print(root.Render(prefix="  ", nodeMarker="o-- ", lastNodeMarker="o-- ", bypassMarker="|   "))
0
o-- 1
|   o-- 4
|   o-- 5
o-- 2
o-- 3
    o-- 6
    o-- 7

Competing Solutions

This tree data structure outperforms anytree by far and even itertree by factor of 2.

anytree

Source: anytree

Todo

TREE::anytree write comparison here.

Disadvantages

  • …

Standoff

  • …

Advantages

  • …

# add code here

itertree

Source: itertree

Todo

TREE::itertree write comparison here.

Disadvantages

  • …

Standoff

  • …

Advantages

  • …

# add code here

treelib

Source: treelib

Todo

TREE::treelib write comparison here.

Disadvantages

  • …

Standoff

  • …

Advantages

  • …

# add code here