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
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:
Features
Fast and simple tree data structure based on a single
Nodeclass.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 and children thereof |
— — — — |
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 |
||
All Siblings |
||
Right Siblings |
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 |
|
All Siblings |
|
Right Siblings |
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:
IterateLeafs()- iterates only over leafs from left to rightIterateLevelOrder()- iterates all sub nodes level by levelIteratePreOrder()- iterates left to right and returns itself before its descendantsIteratePostOrder()- iterates left to right and returns its descendants before itself
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:
register all nodes of B with and without ID in A, then
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:
create the list and dictionary objects for nodes with and without IDs in B, then
move all nodes of B with and without ID from A to B, then
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 |
|---|---|---|
|
|
in front of every line, e.g. for indentation |
|
|
at the end of every line |
|
|
in front of a node that has a following sibling |
|
|
in front of a node that is the last child of its parent |
|
|
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