Notice how node4’s tree is automatically preserved during the operation. Removing tree nodes works in a similar fashion. The node is simply removed from the linked list:
Each tree must always have a root. The bottom tree in the example below is not valid since it does not have a root. The API’s insertion and removal functions enforce this requirement. I.e. trying to insert a sibling next to a root triggers an assertion.
The API has no knowledge of the user’s node type so it must only use ecu_ntnode to remain portable. ECU_NTNODE_GET_ENTRY() and ECU_NTNODE_GET_CONST_ENTRY() convert an ecu_ntnode into the user’s node type, allowing data to be retrieved. Following up on the previous code example above:
Under the hood, these macros perform arithmetic on the supplied ecu_ntnode pointer to convert it back into the user’s node type. The details of this operation are fully explained in ECU_CONTAINER_OF() and ECU_CONST_CONTAINER_OF().
A custom destroy callback can be assigned to an ecu_ntnode when it is constructed. This callback executes when the node is destroyed. It performs additional cleanup specific to the the user’s data. For example cleaning up a file system or a GUI represented as a tree. The API handles uninitializing the ecu_ntnode member. For example:
Nodes can be parametrized by unique IDs to identify different types stored in the same tree. IDs are assigned when each node is constructed. Each type must have a unique ID. Example usage:
/* User-defined object IDs. */enumuser_object_ids{TYPE1=ECU_USER_OBJECT_ID_BEGIN,TYPE2,TYPE3};/* Data types of nodes stored in tree. */structtype1{inta;structecu_ntnodenode;};structtype2{structecu_ntnodenode;intb;};structtype3{intc;structecu_ntnodenode;intd;};structtype1n1;structtype2n2;structtype3n3;ecu_ntnode_ctor(&n1.node,ECU_NTNODE_DESTROY_UNUSED,TYPE1);ecu_ntnode_ctor(&n2.node,ECU_NTNODE_DESTROY_UNUSED,TYPE2);ecu_ntnode_ctor(&n3.node,ECU_NTNODE_DESTROY_UNUSED,TYPE3);/* Assume tree already constructed. Iterate over tree. Use IDs toidentify different data types in the same tree. */ECU_NTNODE_POSTORDER_FOR_EACH(i,....){switch(ecu_ntnode_id(i)){caseTYPE1:{structtype1*me=ECU_NTNODE_GET_ENTRY(i,structtype1,node);me->a=5;break;}caseTYPE2:{structtype2*me=ECU_NTNODE_GET_ENTRY(i,structtype2,node);me->b=10;break;}caseTYPE3:{structtype3*me=ECU_NTNODE_GET_ENTRY(i,structtype3,node);me->c=15;me->d=20;break;}default:{ECU_ASSERT((false));break;}}}
Constructor. Initializes the ecu_ntnode data structure for use. An optional destroy callback and object ID can be assigned to the node. See Node Destroy Callback and Node ID sections for more details.
Warning
Must be called once on startup before the node is used. User is responsible for allocating memory since ECU does not use dynamic memory allocation.
structecu_ntnodenode;/* User must allocate memory before constructor. */ecu_ntnode_remove(&node);/* ILLEGAL. Must construct before using. */ecu_ntnode_ctor(&node,ECU_NTNODE_DESTROY_UNUSED,ECU_OBJECT_ID_UNUSED);ecu_ntnode_remove(&node);/* Ok. */
Destructor. If supplied node is in a tree then it is removed. The ecu_ntnode data structure is then unitialized such that is has to be reconstructed in order to be used again. If the node has a destroy callback it is ran. See Node Destroy Callback Section for more details.
Warning
Memory is not freed when a node is destroyed since ECU library does not use dynamic memory allocation. Node can be freed within its destroy callback if it was allocated on the heap.
structecu_ntnodenode;/* User must allocate memory before constructor. */ecu_ntnode_ctor(&node,&destroy,ECU_OBJECT_ID_UNUSED);ecu_ntnode_destroy(&node);/* ecu_ntnode uninitialized and destroy(&node) ran. */
If the supplied node has descendants (children, grandchildren, etc), they are all destroyed. For example:
Node2 is destroyed. Nodes 4, 5, 6, 8, and 9 are also destroyed since they are node 2’s descendants. If node7 was destroyed, no other nodes would be destroyed since it is has no descendants.
Warning
Currently nodes are destroyed in postorder order. However this may be subject to change. The API only guarantees the proper nodes are destroyed. Application should not rely on destruction order.
Node2 is cleared. Nodes 4, 5, 6, 8, and 9 are also cleared since they are node 2’s descendants. If node7 was cleared, no other nodes would be effected since it is has no descendants.
Returns the number of direct children the supplied node has. Grandchildren, great-granchildren, etc are not counted. Returns 0 if the node has no children. Consider the following example tree:
Returns true if the node is in a tree. False otherwise. Note that a root with children is considered to be in a tree. Consider the following example tree:
The position node cannot be a root since every tree must have one root. The following operation is illegal since inserting a sibling will cause the tree to no longer have a root:
The position node cannot be a root since every tree must have one root. The following operation is illegal since inserting a sibling will cause the tree to no longer have a root:
Returns true if the node is a leaf, meaning it has no children. False otherwise. Note that this returns true for an empty node since that is technically a leaf. Consider the following example tree:
Returns true if the node is a root, meaning it has no parent. False otherwise. Note that this returns true for an empty node since that is technically a root. Consider the following example tree:
Returns the least common ancestor of the two supplied nodes. NULL is returned if the nodes are in separate trees and do not have an LCA. Consider the following example trees:
Returns the node’s next (right) sibling. NULL is returned if the node is the last (rightmost) sibling or has no siblings. Consider the following example tree:
Returns the node’s previous (left) sibling. NULL is returned if the node is the first (leftmost) sibling or has no siblings. Consider the following example tree:
Returns the total number of descendants (children, grandchildren, etc) the node has. Returns 0 if the node has no descendants. Consider the following example tree:
Iterates (for-loops) over all direct children. Grandchildren, great-grandchildren, etc are not included in the iteration. Consider the following example tree:
Iterating over node0 returns in this order: node1, node2, node3.
Iterating over node2 returns in this order node4, node5.
Iterating over node3 immediately terminates since node3 has no children.
structecu_ntnode_child_iteratoriter;ECU_NTNODE_CHILD_FOR_EACH(n,&iter,&node0){/* Iterating over &node0 returns in this order: &node1, &node2, &node3. */}ECU_NTNODE_CHILD_FOR_EACH(n,&iter,&node2){/* Iterating over &node2 returns in this order: &node4, &node5. */}ECU_NTNODE_CHILD_FOR_EACH(n,&iter,&node3){/* Iterating over &node3 immediately terminates since node3 has no children. */}
It is safe to remove or destroy the current node in the iteration. The following example iterates over node2 and removes all of its children:
Iterates (for-loops) over all next (right) siblings, including the supplied starting node. Terminates after the last (rightmost) sibling is reached. Consider the following example tree:
Iterating over node0 returns node0 then terminates since it has no siblings.
Iterating over node1 returns in this order: node1, node2, node3, node4.
Iterating over node2 returns in this order: node2, node3, node4.
Iterating over node4 returns node4 then terminates since it is the last sibling.
structecu_ntnode_next_sibling_iteratoriter;ECU_NTNODE_NEXT_SIBLING_AT_FOR_EACH(n,&iter,&node0){/* Iterating over &node0 returns &node0 then terminates since it has no siblings. */}ECU_NTNODE_NEXT_SIBLING_AT_FOR_EACH(n,&iter,&node1){/* Iterating over &node1 returns in this order: &node1, &node2, &node3, &node4. */}ECU_NTNODE_NEXT_SIBLING_AT_FOR_EACH(n,&iter,&node2){/* Iterating over &node2 returns in this order: &node2, &node3, &node4. */}ECU_NTNODE_NEXT_SIBLING_AT_FOR_EACH(n,&iter,&node4){/* Iterating over &node4 returns &node4 then terminates since it is the last sibling. */}
It is safe to remove or destroy the current node in the iteration. The following example iterates over node2 and removes all of its next siblings:
Iterating over node0 immediately terminates since node0 has no siblings.
Iterating over node1 returns in this order: node2, node3, node4.
Iterating over node2 returns in this order: node3, node4.
Iterating over node4 immediately terminates since node4 is the last sibling.
structecu_ntnode_next_sibling_iteratoriter;ECU_NTNODE_NEXT_SIBLING_FOR_EACH(n,&iter,&node0){/* Iterating over &node0 immediately terminates since &node0 has no siblings. */}ECU_NTNODE_NEXT_SIBLING_FOR_EACH(n,&iter,&node1){/* Iterating over &node1 returns in this order: &node2, &node3, &node4. */}ECU_NTNODE_NEXT_SIBLING_FOR_EACH(n,&iter,&node2){/* Iterating over &node2 returns in this order: &node3, &node4. */}ECU_NTNODE_NEXT_SIBLING_FOR_EACH(n,&iter,&node4){/* Iterating over &node4 immediately terminates since &node4 is the last sibling. */}
It is safe to remove or destroy the current node in the iteration. The following example iterates over node2 and removes all of its next siblings:
Iterating over node2 returns in this order: node2, node1, node0.
Iterating over node1 returns in this order: node1, node0.
Iterating over node0 returns node0 then terminates since node0 is a root.
structecu_ntnode_parent_iteratoriter;ECU_NTNODE_PARENT_AT_FOR_EACH(n,&iter,&node2){/* Iterating over &node2 returns in this order: &node2, &node1, &node0. */}ECU_NTNODE_PARENT_AT_FOR_EACH(n,&iter,&node1){/* Iterating over &node1 returns in this order: &node1, &node0. */}ECU_NTNODE_PARENT_AT_FOR_EACH(n,&iter,&node0){/* Iterating over &node0 returns &node0 then terminates since &node0 is a root. */}
It is safe to remove or destroy the current node in the iteration. The following example iterates over node2 and removes all parents:
Iterating over node2 returns in this order: node1, node0.
Iterating over node1 returns node0.
Iterating over node0 immediately terminates since node0 is a root.
structecu_ntnode_parent_iteratoriter;ECU_NTNODE_PARENT_FOR_EACH(n,&iter,&node2){/* Iterating over &node2 returns in this order: &node1, &node0. */}ECU_NTNODE_PARENT_FOR_EACH(n,&iter,&node1){/* Iterating over &node1 returns &node0. */}ECU_NTNODE_PARENT_FOR_EACH(n,&iter,&node0){/* Iterating over &node0 immediately terminates since &node0 is a root. */}
It is safe to remove or destroy the current node in the iteration. The following example iterates over node2 and removes all parents:
Iterating over node0 returns in this order: node1, node6, node4, node5, node2, node3, node0:
structecu_ntnode_postorder_iteratoriter;ECU_NTNODE_POSTORDER_FOR_EACH(n,&iter,&node0){/* Iterating over &node0 returns in this order: &node1, &node6, &node4, &node5, &node2, &node3, &node0. */}
Iterating over a child returns only its direct descendants. Using the same example tree as above, iterating over node2 returns in this order: node6, node4, node5, node2.
structecu_ntnode_postorder_iteratoriter;ECU_NTNODE_POSTORDER_FOR_EACH(n,&iter,&node2){/* Iterating over &node2 returns in this order: &node6, &node4, &node5, &node2. */}
It is safe to remove or destroy the current node in the iteration.
Iterating over node0 returns in this order: node0, node1, node2, node4, node6, node5, node3:
structecu_ntnode_preorder_iteratoriter;ECU_NTNODE_PREORDER_FOR_EACH(n,&iter,&node0){/* Iterating over &node0 returns in this order: &node0, &node1, &node2, &node4, &node6, &node5, &node3. */}
Iterating over a child returns only its direct descendants. Using the same example tree as above, iterating over node2 returns in this order: node2, node4, node6, node5:
structecu_ntnode_preorder_iteratoriter;ECU_NTNODE_PREORDER_FOR_EACH(n,&iter,&node2){/* Iterating over &node2 returns in this order: &node2, &node4, &node6, &node5. */}
Warning
Removing or destroying the current node in the iteration is NOT allowed since this is an unsafe operation during preorder traversal.
Iterates (for-loops) over all previous (left) siblings, including the supplied starting node. Terminates after the first (leftmost) sibling is reached. Consider the following example tree:
Iterating over node0 returns node0 then terminates since it has no siblings.
Iterating over node1 returns node1 then terminates since it is the first sibling.
Iterating over node2 returns in this order: node2, node1.
Iterating over node4 returns in this order: node4, node3, node2, node1.
structecu_ntnode_prev_sibling_iteratoriter;ECU_NTNODE_PREV_SIBLING_AT_FOR_EACH(n,&iter,&node0){/* Iterating over &node0 returns &node0 then terminates since it has no siblings. */}ECU_NTNODE_PREV_SIBLING_AT_FOR_EACH(n,&iter,&node1){/* Iterating over &node1 returns &node1 then terminates since it is the first sibling. */}ECU_NTNODE_PREV_SIBLING_AT_FOR_EACH(n,&iter,&node2){/* Iterating over &node2 returns in this order: &node2, &node1. */}ECU_NTNODE_PREV_SIBLING_AT_FOR_EACH(n,&iter,&node4){/* Iterating over &node4 returns in this order: &node4, &node3, &node2, &node1. */}
It is safe to remove or destroy the current node in the iteration. The following example iterates over node2 and removes all of its previous siblings:
Iterating over node0 immediately terminates since node0 has no siblings.
Iterating over node1 immediately terminates since node1 is the first sibling.
Iterating over node2 returns node1.
Iterating over node4 returns in this order: node3, node2, node1.
structecu_ntnode_prev_sibling_iteratoriter;ECU_NTNODE_PREV_SIBLING_FOR_EACH(n,&iter,&node0){/* Iterating over &node0 immediately terminates since &node0 has no siblings. */}ECU_NTNODE_PREV_SIBLING_FOR_EACH(n,&iter,&node1){/* Iterating over &node1 immediately terminates since &node1 is the first sibling. */}ECU_NTNODE_PREV_SIBLING_FOR_EACH(n,&iter,&node2){/* Iterating over &node2 returns &node1. */}ECU_NTNODE_PREV_SIBLING_FOR_EACH(n,&iter,&node4){/* Iterating over &node4 returns in this order: &node3, &node2, &node1. */}
It is safe to remove or destroy the current node in the iteration. The following example iterates over node2 and removes all of its previous siblings:
Iterates (for-loops) over all siblings, excluding the starting node. Performs wraparound if starting sibling is not the first sibling. Consider the following example tree:
Iterating over node0 immediately terminates since node0 has no siblings.
Iterating over node1 returns in this order: node2, node3, node4.
Iterating over node2 returns in this order: node3, node4, node1.
Iterating over node4 returns in this order: node1, node2, node3.
structecu_ntnode_sibling_iteratoriter;ECU_NTNODE_SIBLING_FOR_EACH(n,&iter,&node0){/* Iterating over &node0 immediately terminates since &node0 has no siblings. */}ECU_NTNODE_SIBLING_FOR_EACH(n,&iter,&node1){/* Iterating over &node1 returns in this order: &node2, &node3, &node4. */}ECU_NTNODE_SIBLING_FOR_EACH(n,&iter,&node2){/* Iterating over &node2 returns in this order: &node3, &node4, &node1. */}ECU_NTNODE_SIBLING_FOR_EACH(n,&iter,&node2){/* Iterating over &node4 returns in this order: &node1, &node2, &node3. */}
It is safe to remove or destroy the current node in the iteration. The following example iterates over node2 and removes all of its siblings: