Find all parent nodes of a node in a tree - python

I am creating a tree in python, and I had a method to find a path from the parent to the root node of the tree. That is as follows:
def get_sub_net(self, index):
node = self.nodes[index]
sub_net = []
sub_net.append(node)
while node.parent is not None:
node = node.parent
sub_net.append(node)
return sub_net[::-1]
Now I am trying to allow for each node to have multiple parents, and I am running into trouble.
def sub_net(self, index):
node = self.nodes[index]
sub_net = []
if node.parents == None:
return sub_net
else:
sub_net += node.parents
for i in node.parents:
while i is not None:
sub_net += i.parents
node = i
break
return sub_net[::-1]
#Noreddine-Kessa pointed out that this would be a graph, and not a tree, which is correct. However, I also solved my own problem, the solution I used i
def recursive_sub_net(self, node):
sub_net = []
sub_net.append(node)
if node.parents is not None:
for i in node.parents:
sub_net += self.recursive_sub_net(i)
return sub_net

By definition, every node in a tree has a single parent (and can have many ancestors), if you need to use a network of nodes (not tree) then you should use a graph.

Related

Retrieving Index from Linked List

I am wanting to iterate through my linked list and only retrieve the value at a specific index. I have written a very slow way of accomplishing this:
`
def get_index(self, data):
if self.head is None:
raise Exception("List is empty")
reg_list = []
for i in self:
reg_list.append(i)
print(reg_list[data])
`
This code works but I realize this is not the fastest nor the best method for completing this operation. How can I write this better and without creating a standard list to accomplish the desired outcome?
For clarity as some are asking, here is the entirety of relevant code to make this run as I have written it and to show it is indeed a linked list:
class Node:
def __init__(self, data):
self.data = data
self.next = None
def __repr__(self):
return self.data
class Linked_List:
def __init__(self, nodes=None):
self.head = None
if nodes is not None:
node = Node(data=nodes.pop(0))
self.head = node
for elem in nodes:
node.next = Node(data=elem)
node = node.next
def __repr__(self):
node = self.head
nodes = []
while node is not None:
nodes.append(node.data)
node = node.next
nodes.append("None")
return " -> ".join(nodes)
def __iter__(self):
node = self.head
while node is not None:
yield node
node = node.next
def get_index(self, data):
if self.head is None:
raise Exception("List is empty")
reg_list = []
for i in self:
reg_list.append(i)
print(reg_list[data])
```
It is confusing that your code names the argument data while it is supposed to be an index, and then names the loop variable i, which is supposed to be a node. Why not choose names that actually say what it is? Like index and node?
You can change the given code to the following:
def get_index(self, index):
return next((node for i, node in enumerate(self) if i == index), None)
Note that now:
The function returns the node. This is appropriate for such a method. Only printing the result would make the method less useful.
No error is raised when the list is empty. There is no reason to treat an empty list differently. The function will just return None when the given index is out of range, whether the list is empty or not.

How to extract all ancestors of a nodes and leaves in a decision tree?

I don't know how difficult is this question. Suppose we train a decision tree and then I want to records all nodes and leaves in the tree, what are their ancestors (set of p for parents), and which of their ancestors are divided based on x<a (set A_l) and x>a (set A_r).
Actually, nodes can be classified as follows and I want to extract all of this information:
t = 1,...,T set of all nodes
p(t): parent of node t
A(t): set of all ancestors of node t
A_L(t): the set of ancestors of t whose left branch has been followed on the path from the root node to t
A_R(t): the set of ancestors of t whose left branch has been followed on the path from the root node to t
T_b: the set of branch nodes
T_l: the set of leaf nodes
I wrote a code and I could store all left and right children of a node, but I don't know how to determine the aforementioned sets.
class Nodes():
no_nodes = 0
def __init__(self,tree,no_total_nodes,node):
self.no_nodes = no_total_nodes
self.current_node= node
self.left_child = all_left_children(tree,self.current_node)
self.right_child = all_right_children(tree,self.current_node)
where
def func_right_node(tree,start_node):
node = tree.children_right[start_node]
return node
def func_left_node(tree,start_node):
node = tree.children_left[start_node]
return node
def all_right_children(tree,start_node):
List=[]
while True:
r_node = func_right_node(tree,start_node)
if r_node!= -1:
List.append(r_node)
start_node = r_node
elif r_node== -1:
break
return List
def all_left_children(tree,start_node):
List=[]
while True:
r_node = func_left_node(tree,start_node)
if r_node!= -1:
List.append(r_node)
start_node = r_node
elif r_node== -1:
break
return List
and all nodes and leaves are stores as follows:
TREE = decision_tree.tree_
total_number_nodes = decision_tree.tree_.node_count
All_Nodes = []
All_Leaves = []
for i in range(total_number_nodes):
All_Nodes.append(Nodes(TREE,total_number_nodes,i))
if not All_Nodes[i].left_child:
All_Leaves.append(All_Nodes[i])

Python - Removing a node from a linked-list at beginning, in between and at end

I learned about linked-lists today. I learned how to insert and remove nodes from them. In the following code, it teaches me how to insert nodes with three distinct functions: at beginning (the head node), in between and at the end. However, they teach me how to remove the node in and single function. I don't find the code in the remove function to be very clear. Can anyone help make an easier to understand code under the remove function? Is there a way to make it into three functions just like the inserting ones? I'm open to any suggestion or explanation. Thanks in advance.
Here's my code:
class Node:
def __init__(self, data=None):
self.data = data
self.nextnode = None
class LinkedList:
def __init__(self):
self.headnode = None
def printlist(self):
node = self.headnode
while node is not None:
print (node.data)
node = node.nextnode
def atbegining(self,new_node):
new_node.nextnode = self.headnode
self.headnode = new_node
# Function to add newnode
def AtEnd(self, newnode):
if self.headnode is None:
self.headnode = newnode
return
node = self.headnode
while(node.nextnode):
node = node.nextnode
node.nextnode=newnode
# Function to add node
def Inbetween(self,preNode,newNode):
if preNode is None:
print("The mentioned node is absent")
return
newNode.nextnode = preNode.nextnode
preNode.nextnode = newNode
# Function to remove node
def RemoveNode(self, RemoveVal):
node = self.headnode
if (node is not None):
if (node.data == RemoveVal):
self.headnode = node.nextnode
node = None
return
while (node is not None):
if node.data == RemoveVal:
break
prevnode = node
node = node.nextnode
if (node == None):
return
prevnode.nextnode = node.nextnode
node = None
list1 = LinkedList()
list1.headnode = Node("Mon")
n2 = Node("Tue")
n3 = Node("Wed")
# Link first Node to second node
list1.headnode.nextnode = n2
# Link second Node to third node
n2.nextnode = n3
n4 = Node("Sun")
n5 = Node("Tur")
n6 = Node("Newdate")
list1.atbegining(n4)
list1.AtEnd(n5)
list1.Inbetween(list1.headnode,n6)
list1.RemoveNode("Newdate")
list1.printlist()
RemoveNode is complicated by the fact that there are two structurally distinct kinds of LinkedLists: one whose head is None, and one whose head is not None. You can fix this by making sure every LinkedList contains at least one node. This is typically referred to as a dummy node, and you can use this node to store metadata (such as the length of the list).
The Node class itself does not change.
class Node:
def __init__(self, data=None):
self.data = data
self.nextnode = None
The LinkedList, however, simplifies by creating a dummy node. This provides
the guarantee that every node that stores real data is point to by another node.
class LinkedList:
def __init__(self):
self.headnode = Node(0)
def insert(self, preNode, newNode):
newNode.nextnode = preNode.nextnode
preNode.nextnode = newNode
self.headnode.data += 1
def append(self, newNode):
curr = self.headnode
while curr.nextNode is not None:
curr = curr.nextNode
self.insert(curr, newNode)
def prepend(self, newNode):
self.insert(self.headnode, newNode)
def _find_before(self, val):
pre = self.headnode
while pre.nextnode is not None:
curr = pre.nextnode
if curr.data == val:
return pre
pre = curr
def remove(self, RemoveVal):
pre = self._find_before(RemoveVal)
if pre is None:
return
pre.nextnode = pre.nextnode.nextnode
self.headnode.data -= 1
This simplifies all three insertions. The general case can always apply, since there is always a node that comes before the node you insert. append and prepend are simple wrappers that find the appropriate node to pass to insert.
Likewise, remove simply finds the node before the given value, and if the search succeeds, handles updating the prior node's nextnode attribute.
insert and remove also update the size of the list stored in the dummy node.
A find method becomes a simple wrapper around _find_before; if you find a node before the value you are looking for, just return the node that follows it.
I think that an alternative design will make the code much clearer. Consider for example the following:
class Node:
def __init__(self, data=None):
self.data = data
self.nextnode = None
def printlist(self):
print(self.data)
if self.nextnode is not None:
self.nextnode.printlist()
def push(self, node):
node.nextnode = self
return node
def insertafter(self, node):
node.nextnode = self.nextnode
self.nextnode = node
return self
def append(self, node):
lastnode = self
while lastnode.nextnode is not None:
lastnode = lastnode.nextnode
lastnode.nextnode = node
return self
def remove(self, value):
prev = None
walk = self
while walk is not None:
if walk.data == value:
if prev is None:
return walk.nextnode
else:
prev.nextnode = walk.nextnode
return self
else:
prev = walk
walk = walk.nextnode
return self
list1 = Node("Mon")
n2 = Node("Tue")
n3 = Node("Wed")
# Link first Node to second node
list1 = list1.insertafter(n2)
# Link second Node to third node
n2 = n2.insertafter(n3)
n4 = Node("Sun")
n5 = Node("Tur")
n6 = Node("Newdate")
list1 = list1.push(n4)
list1 = list1.append(n5)
list1 = list1.insertafter(n6)
list1 = list1.remove("Newdate")
list1.printlist()
The main idea is that a Node is the linked list. As long as you have the head of the list kept in a variable, you can have access to the entire list, without the need for a separate data structure.

Python: breadth first search

Node contains:
State(an integer unique ID of the node of the graph), Parent (a node in the search tree that generated this node), Path cost(a cost associated with current node)
"""A node in a search tree. Contains a pointer to the parent (the node
that this is a successor of) and to the state for this node
representing actual graph node (graph node ID)."""
#given function
def __init__(self, state, parent=None, path_cost=0):
"""Create a search tree Node"""
self.state = state
self.parent = parent
self.path_cost = path_cost
self.depth = 0
if parent:
self.depth = parent.depth + 1
#given function
def child_node(self, graph, state):
"""Create a new search node and mark it as a child.
The child node will have current node as a parent."""
next_node = Node(state,
parent=self,
path_cost=self.path_cost + 1)
return next_node
# expand function was given to us
def expand(self, graph):
"""List the nodes reachable in one step from this node i.e
neighbours of the current node."""
return [self.child_node(graph, state)
for state in graph.neighbors(self.state)]
Here we have implement the bfs function as our assignment.
def bfs(graph, start, dest):
to_check = []
visited = set()
to_check.insert(0, start)
#while to_check has nodes
while to_check:
next_node = queue.pop()
visited.add(next_node)
for sibling in next_node.expand(graph):
if sibling not in visited and sibling not in to_check:
if sibling == dest:
return sibling
else:
to_check.insert(0, next_node)
The code I ran using the above functions:
start_graph_state = 3470524959
destination_graph_state = 5674155097
start = Node(state=start_graph_state)
dest = Node(state=destination_graph_state)
solution_node = bfs(graph, start, dest)
I'm supposed to plot an appropriate graph, but I've commented out and instead I was running function to check whether the nodes were being iterated correctly. But I keep getting stuck on the starting node because it is never placed in set nor is it removed from the to_check queue even after I have popped it out. (Even when the destination node is directly one edge away, I get an infinite loop) I'm very new to Python and I feel that I'm just stuck on simple syntax problems, but I'm confused. Any help would be appreciated!
You should make sure that you use a FIFO queue (such as the one available in collections).
The general structure of the traversal would be as follows:
from collections import deque
q = deque()
q.append(firstNode)
visited = set()
while q:
node = q.popleft()
if node in visited: continue
visited.add(node)
for nextNode in node.neighbours:
q.append(nextNode)
# perform required processing on node

Counting nodes in General tree in python

I have created a Tree structure that is not a binary tree and having difficulties in getting the correct node count.
class TreeNode(object):
def __init__(self, name='root', children=None,Parent=[]):
self.Name = name
self.Parents=Parent
self.Children = []
if children is not None:
for child in children:
self.add_child(child.Name)
def __repr__(self):
return self.Name
def add_child(self, node):
self.Children.append(node)
and this is the latest in what I have tried to do in order to count the number of nodes in the tree.
def countNodes(Tree):
for Child in Tree.Children:
return countNodes(Child)+1
return 1
Could someone explain why this doesn't work?
EDIT: I should clarify, When I say doesn't work it gives me a completely wrong count for he number of nodes in my graph.
You countNodes function is not well. A parent node can have two childs, if you put a return statement within the for loop, it will return on the first child count and the second child count will be missing. You need to do something like this:
def countNodes(Tree):
count = 1
for Child in Tree.Children:
count += countNodes(Child)
return count
Just to add #levi has missed an edge case where root is None
so the modified code will be :
def numNodes(root):
if root == None:
return 0
node = 1
for child in root.children:
node = node + numNodes(child)
return node

Categories