While writing an iterator for a tree class in python I stumbled upon the issue, that I apparently cannot access fields and methods of the mother class without instanciating an iterator instance with a reference to an already existing Tree instance, so I always need to call the iterator like "it.iterate(tree)" which is pretty ugly. I was wondering whether there was some way to design this stuff, so an instance reference for the iterator and its methods is not required. SO, can I somehow access the fields of an instance of Tree from an instance of the Iterator without passing a reference of the Tree instance to the iterator?
########################################################################
# basic tree class, can be traversed bottom up from right hand corner
########################################################################
import sys
########################################################################
########################################################################
class Node:
"""!
#brief node class
"""
def __init__(self, pred=-1, ID=0, label=None):
self.sucs = list()
self.pred = pred
self.ID = ID
self.suc_count = 0
self.label = label
########################################################################
def make_suc(self, ID, label=None):
"""!
#brief generates new successor node, assigning it a unique ID
"""
self.suc_count += 1
sucID = ID+1
suc = Node(self, sucID, label)
self.sucs.append(suc)
return suc
########################################################################
########################################################################
class Tree:
"""!
#brief tree class
"""
def __init__(self):
self.root = Node()
self.allNodes = dict() # mapping from IDs (strings) to nodes
self.init()
self.leaves = list()
########################################################################
# initializes node dict
def init(self):
self.allNodes[0] = self.root
########################################################################
def find_node(self, ID):
"""!
#brief looks up a node's ID and returns the node itself
"""
return self.allNodes[ID]
########################################################################
def add(self, parent, label=None):
"""!
#brief adds a new node under parent with label label
"""
if parent != Node:
parent = self.find_node(parent)
suc = parent.make_suc(len(self.allNodes)-1, label)
self.allNodes[suc.ID] = suc
########################################################################
def traverse(self, node):
"""!
#brief traverses tree
"""
for suc in node.sucs:
self.traverse(suc)
print suc.label
########################################################################
def get_leaves(self, node):
"""!
#brief when called resets leveas field and build it up anew by
traversing tree and adding all leaves to it
"""
self.leaves = list()
self._find_leaves(node)
return self.leaves
########################################################################
def get_dominated(self, node, dom_nodes=[]):
"""!
#brief finds all dominated nodes
"""
for suc in node.sucs:
self.get_dominated(suc, dom_nodes)
dom_nodes.append(suc)
########################################################################
def _find_leaves(self, node):
"""!
#brief traverses tree in in order and adds all leaves to leaves field
last leaf in list will be right hand corner of tree, due to in
order travsersal
"""
if node.suc_count == 0:
self.leaves.append(node)
for suc in node.sucs:
self._find_leaves(suc)
########################################################################
class TreeRHCIterator:
"""!
#brief Right hand corner initialised iterator, traverses tree bottom
up, right to left
"""
def __init__(self, tree):
self.current = tree.get_leaves(tree.root)[-1] # last leaf is right corner
self.csi = len(self.current.sucs)-1 # right most index of sucs
self.visited = list() # visisted nodes
########################################################################
def begin(self, tree):
return tree.get_leaves(tree.root)[-1]
########################################################################
def end(self, tree):
return tree.root
########################################################################
def find_unvisited(self, node, tree):
"""!
#brief finds rightmost unvisited node transitively dominated by node
"""
leaves = tree.get_leaves(tree.root)
# loop through leaves from right to left, as leaves are listed
# in order, thus rightmost list elememt is rightmost leaf
for i in range(len(leaves)-1, -1, -1):
# return the first leaf, that has not been visited yet
if leaves[i] not in self.visited:
return leaves[i]
# return None if all leaves have been visited
return None
########################################################################
def go_up(self, node, tree):
"""!
#brief sets self.current to pred of self.current,
appends current node to visited nodes, reassignes csi
"""
self.visited.append(self.current)
self.current = self.current.pred
if self.current.sucs[0] not in self.visited:
self.current = self.find_unvisited(self.current, tree)
self.csi = len(self.current.sucs)-1
self.visited.append(self.current)
########################################################################
def iterate(self, tree):
"""!
#brief advances iterator
"""
# if current node is a leaf, go to its predecessor
if self.current.suc_count == 0 or self.current in self.visited:
self.go_up(self.current, tree)
# if current node is not a leaf, find the next unvisited
else:
self.current = self.find_unvisited(self.current, tree)
########################################################################
########################################################################
called like this:
tree = Tree()
it = tree.TreeRHCIterator(tree)
end = it.end(tree)
while (it.current != end):
print it.current.label
it.iterate(tree)
Edit
After implementing the standard iterator protocol I am a little confused about its workings. Somehow the start node is being skipped when looping over the tree. SO I made a test class to study the behavior, and there no element is being skipped, even though the iteration method basically works the same way. Could somebody shed some light on this for me?
Redesigned iterator:
########################################################################
# RIGHT-HAND-CORNER-BOTTOM-UP-POST-ORDER-TRAVERSAL-ITERATOR
########################################################################
class RBPIter:
"""!
#brief Right hand corner initialised iterator, traverses tree bottom
up, right to left
"""
def __init__(self, tree):
self.current = tree.get_leaves(tree.root)[-1] # last leaf is right corner
self.csi = len(self.current.sucs)-1 # right most index of sucs
self.visited = list() # visisted nodes
self.tree = tree
self.label = self.current.label
########################################################################
def __iter__(self):
print "iter: ", self.label
return self
########################################################################
def begin(self):
return self.tree.get_leaves(self.tree.root)[-1]
########################################################################
def end(self):
return self.tree.root
########################################################################
def find_unvisited(self, node):
"""!
#brief finds rightmost unvisited node transitively dominated by node
"""
leaves = self.tree.get_leaves(self.tree.root)
# loop through leaves from right to left, as leaves are listed
# in order, thus rightmost list elememt is rightmost leaf
for i in range(len(leaves)-1, -1, -1):
# return the first leaf, that has not been visited yet
if leaves[i] not in self.visited:
self.label = leaves[i].label
return leaves[i]
# return None if all leaves have been visited
return None
########################################################################
def go_up(self, node):
"""!
#brief sets self.current to pred of self.current,
appends current node to visited nodes, reassignes csi
"""
self.visited.append(self.current)
self.current = self.current.pred
if self.current.sucs[0] not in self.visited:
self.current = self.find_unvisited(self.current)
self.label = self.current.label
self.csi = len(self.current.sucs)-1
self.visited.append(self.current)
########################################################################
def next(self):
"""!
#brief advances iterator
"""
print "next: ", self.label
# if current node is a leaf, go to its predecessor
if self.current.suc_count == 0 or self.current in self.visited:
self.go_up(self.current)
# if current node is not a leaf, find the next unvisited
else:
self.current = self.find_unvisited(self.current)
if self.current == self.end():
raise StopIteration
return self
########################################################################
########################################################################
For the following test file I get the the following output:
tree1 = Tree()
tree1.add(0, "t")
tree1.add(1, "e")
tree1.add(2, "s")
tree1.add(3, "t")
tree1.add(2, "t")
tree1.add(5, "r")
tree1.add(6, "i")
tree1.add(7, "s")
tree1.add(6, "a")
for node in tree1.RBPIter(tree1):
print node.label
output:
iter: a
next: a
s
next: s
i
next: i
r
next: r
t
next: t
t
next: t
s
next: s
e
next: e
t
next: t
The tree to this looks like this:
! 1 [a trie]
So, as you see, the "a" - meaning the right hand corner node is missing, and I don't understand why, because the iterator method returns the first element correctly, as you can see in the debug output.
You can make an iterator look nice with something like this:
class TreeIter:
def __init__(self, parametersIfAny):
code godes here
def __iter__(self):
return self
def __next__(self):
code that makes the iteration
class Tree:
def __iter__(self):
return TreeIter(parametersIfAny)
Then you can invoke it like this:
tree = Tree()
for node in tree:
print node.label
If you need to have many different iterators, i.e. inorder, postorder, etc. I had to do something like this last year (albeit with graphs). What i did then was something like:
class PostOrderIter:
def __init__(self, tree):
self.tree = tree #and some more stuff
def __iter__(self):
return self
class PostOrder:
def __init__(self, tree):
self.tree = tree #and some more stuff
def __iter__(self):
return PostOrderIter(self.tree)
to invoke it with:
for node in PostOrder(tree):
print node.label
Related
For example if I have 2 classes Tree and Node, in the add_node method in Tree, I can add self referring to the Tree into the Node object as shown below. This is done for convenience when accessing other parts of the Tree with just the keys once in the Node object (i.e. can just use keys). However, I was wondering if this was considered best practice since the IDE (PyCharm) also screams Unresolved attribute reference 'tree' for class 'Node' so might be not a great sign as well.
class Tree:
def __init__(self):
self.nodes = {}
def add_node(self, count):
new_node = Node()
new_node.tree = self
self.nodes[count] = new_node
class Node:
def __init__(self):
# Some data e.g. parent, children index
pass
Pass the tree in the initializer for the Node
class Node:
def __init__(self, tree):
self.tree = tree
And when creating the node:
...
new_node = Node(self)
self.nodes[count] = new_node
...
I was doing this leetcode question:(https://leetcode.com/problems/binary-tree-inorder-traversal/) in which I came up with this solution:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
if root is None:
return None
result = []
if root.left is None and root.right is None:
result.append(root.val)
return result
return self.traverse(root,result)
def traverse(self,node,result):
if node is None:
return result
result = self.traverse(node.left,result)
result.append(node.val)
result = self.traverse(node.right,result)
return result
However I found out I actually don't need to store the results of recursion call in the variable and I can simply do this:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
if root is None:
return None
result = []
if root.left is None and root.right is None:
result.append(root.val)
return result
return self.traverse(root,result)
def traverse(self,node,result):
if node is None:
return result
self.traverse(node.left,result)
result.append(node.val)
self.traverse(node.right,result)
return result
My understanding was that in each recursion call, we are passing a reference to the result variable, not copying the result variable, so what is happening is that when the recursion call gets to the left most node, it appends the value and returns to its parent node, and since we had pass by reference, the result variable in parent node already has the leftmost node added to it, so it just adds the parent node to it and keep continuing on the recursion.
Is my understanding correct or is there something else going on?
Thanks
Yes, your understanding is right.
Note: you are sharing the same code in both boxes.
I have a class of the node which contain his parent and want to create iterator on it. Here is my try:
class Node:
def __init__(self, parent=None):
self._parent = parent
def __iter__(self):
self = self.parent
def __next__(self):
if self.parent is None:
raise StopIteration
else:
self = self.parent
return self
But when I try to loop over the instance, it's never stops and returns the same value, what I did wrong?
The reason your code doesn't work is that you're trying to keep track of the current node in the iterator by assigning to self, which is just a local variable, so nothing is actually updated.
The correct way would be to extract an iterator class and keep track of the current node there:
class Node:
def __init__(self, parent=None):
self.parent = parent
def __iter__(self):
return NodeIterator(self)
class NodeIterator:
def __init__(self, node):
self.next_node = node
def __iter__(self):
return self
def __next__(self):
if self.next_node is None:
raise StopIteration
else:
current_node = self.next_node
self.next_node = self.next_node.parent
return current_node
This can be used like so:
root = Node()
inner_1 = Node(root)
leaf_1 = Node(inner_1)
inner_2 = Node(root)
inner_2_1 = Node(inner_2)
leaf_2 = Node(inner_2_1)
for node in leaf_2:
# will loop through:
# leaf_2,
# inner_2_1;
# inner_2,
# root
I have a a binary tree, with only self._root and self._nodes. Is there anyway I could figure out how to find a parent tree of the current tree through the constructor?
class BinaryTree:
"""
- If _root is None, then _nodes is empty and _parent_tree is None
- nodes is allowed to contain empty nodes
- if _parent_tree is not empty, then self is in _parent_tree._nodes
"""
def __init__(self, root, nodes):
"""Initialize a new BinaryTree.
#type self: BinaryTree
#type root: object
#type nodes: list[BinaryTree]
#rtype: None
"""
self._root = root
self._nodes = nodes
self._parent_tree = None
"""Implement _parent_tree"""
if self._root is not None:
"""implement"""
else:
pass
To be honest, I have no idea of how to even start this. How would I know any other BinaryTree objects without constructing this one first. What would I be recursively looking through? I can't possibly look through this BinaryTree
Following should do good for you :
class BinaryTree:
def __init__(self, root, nodes):
self._root = root
self._nodes = nodes
self._parent_tree = None
if self._root is not None:
for node in self._nodes:
if node is not None:
node._parent_tree = self
else:
pass
tree = Node ("one",
Node ("two", Leaf ("three"), Leaf ("four")),
Node ("five", Leaf ("six"), Leaf ("seven")))
Trying to declare a Tree class with 2 subclasses Node and Leaf to handle the tree object
class Tree:
def __init__(self, root):
self.root = root
# some functions
#def inorder(self, visitor):
# Node.inorder(self.left, visitor)
# visitor(self.data)
# Node.inorder(self.right, visitor)
#def fns(tree):
# return
class Node (Tree):
def __init__(self, value, left, right):
self.left = left
self.right = right
self.value = value
class Leaf (Tree):
def __init__(self, value):
self.value = value
Would this be the correct implementation?
This would be a correct implementation of binary tree. But keep in mind one thing, if you ever change root of the tree you will need to update every single node and leaf in that tree.
self.root is a bad idea in case you would create subtrees which you would later add to another tree. But if you are not planning to do something like this this would be a good idea.