Find the longest path in a Tree - python-3.x

class Tree:
def __init__(self, root, subtrees) -> None:
"""Initialize a new Tree with the given
root value and subtrees.
If <root> is None, the tree is empty.
Precondition:
- if <root> is None,
then <subtrees> is empty.
"""
self._root = root
self._subtrees = subtrees
def long(self):
"""Return a list of items on the longest possible path
between the root of this tree and one of its leaves.
If there is more than one path with the maximum length,
return the one that ends at the leaf that is furthest to the left.
If this tree is empty, return an empty list.
#type self: Tree
#rtype: list
"""
if len([s
for s in self._subtrees
if s is not None]) == 0:
return []
else:
return [self._root] + [s.long() for s in self._subtrees]
I'm trying to find the longest path from the root to leaf in a Tree. My code doesn't recurse until the end of the tree and returns a bunch of empty lists. Please help.

Related

How to find the sum of nodes in a tree without recursion

I've tried to solve this problem a few different ways.
The Node for my code is defined as:
A Node is an object
- value : Number
- children : List of Nodes
class Node:
def __init__(self, key, childnodes):
self.key = key
self.childnodes = childnodes
def __repr__(self):
return f'Node({self.key!r}, {self.childnodes!r})'
testTree = Node(1, [Node(2, []), Node(3, [Node(4, [Node(5, []), Node(6, [Node(7, [])])])])])
I've gotten close to completing the problem with the code:
def sum_of_nodes(root):
sum = 0
while root:
sum += root.key
print(sum)
root = root.childnodes
print(root)
root = root.pop()
print(root)
print(sum)
However, it skips some portions of the code and I'm not sure how to go about fixing it. The result of the above code is:
1
[Node(2, []), Node(3, [Node(4, [Node(5, []), Node(6, [Node(7, [])])])])]
Node(3, [Node(4, [Node(5, []), Node(6, [Node(7, [])])])])
4
[Node(4, [Node(5, []), Node(6, [Node(7, [])])])]
Node(4, [Node(5, []), Node(6, [Node(7, [])])])
8
[Node(5, []), Node(6, [Node(7, [])])]
Node(6, [Node(7, [])])
14
[Node(7, [])]
Node(7, [])
21
[]
Along with an error:
Traceback (most recent call last):
File "D:/Documents/project1.py", line 191, in <module>
print(f'itersum_of_nodes(testTree) => {itersum_of_nodes(testTree)}') # 28
File "D:/Documents/project1.py", line 108, in sum_of_nodes
root = root.pop()
IndexError: pop from empty list
I've also tried the method taught at geeksforgeeks.org/inorder-tree-traversal-without-recursion/ however, my children nodes are defined as a list rather than .right or .left and I'm not sure how to get the info I need out of it because of that. The code is:
stack = []
sum = 0
current = root
while True:
if current.childnodes[0] is not None:
stack.append(current)
current = current.childnodes[0]
elif stack:
current = stack.pop()
sum += current.value
current = current.childnodes[1]
else:
break
Here is a working version of while loop that traverses your tree without recursion. The main problem with this is that your nodes don't store the children individually, but rather as a list. This means that you need to be vigilant in checking how many children the node actually has.
def sum_of_nodes(root):
sum = 0
stack = []
# While the current node is not none
while root:
# Add the value of the current node to the sum
sum += root.key
# Check if the current node has children
if root.childnodes:
# If it has two children, append the second child to the stack
if len(root.childnodes) == 2:
stack.append(root.childnodes[1])
# Set the first child as the current node
root = root.childnodes[0]
# If the current node doesn't have any children, take one from the stack
elif stack:
root = stack.pop()
# If we run out of children in the stack, end the while loop
else:
break
return sum

Disjoint set implementation in Python

I am relatively new to Python. I am studying Disjoint sets, and implemented it as follows:
class DisjointSet:
def __init__(self, vertices, parent):
self.vertices = vertices
self.parent = parent
def find(self, item):
if self.parent[item] == item:
return item
else:
return self.find(self.parent[item])
def union(self, set1, set2):
self.parent[set1] = set2
Now in the driver code:
def main():
vertices = ['a', 'b', 'c', 'd', 'e', 'h', 'i']
parent = {}
for v in vertices:
parent[v] = v
ds = DisjointSet(vertices, parent)
print("Print all vertices in genesis: ")
ds.union('b', 'd')
ds.union('h', 'b')
print(ds.find('h')) # prints d (OK)
ds.union('h', 'i')
print(ds.find('i')) # prints i (expecting d)
main()
So, at first I initialized all nodes as individual disjoint sets. Then unioned bd and hb which makes the set: hbd then hi is unioned, which should (as I assumed) give us the set: ihbd. I understand that due to setting the parent in this line of union(set1, set2):
self.parent[set1] = set2
I am setting the parent of h as i and thus removing it from the set of bd. How can I achieve a set of ihbd where the order of the params in union() won't yield different results?
Your program is not working correctly because you have misunderstood the algorithm for disjoint set implementation. Union is implemented by modifying the parent of the root node rather than the node provided as input. As you have already noticed, blindly modifying parents of any node you receive in input will just destroy previous unions.
Here's a correct implementation:
def union(self, set1, set2):
root1 = self.find(set1)
root2 = self.find(set2)
self.parent[root1] = root2
I would also suggest reading Disjoint-set data structure for more info as well as possible optimizations.
To make your implementation faster, you may want to update the parent as you find()
def find(self, item):
if self.parent[item] == item:
return item
else:
res = self.find(self.parent[item])
self.parent[item] = res
return res

How to say if a tree is included in another one?

I would like to create an alogritmo that allows me to say if a tree is included in another. Thanks to this site I managed an algorithm that allows me to know for binary trees, but I would like to generalize it.
def isSubtree(T,S):
'''
function to say if a tree S is a subtree of another one, T
'''
# Base Case
if S is None:
return True
if T is None:
return True
# Check the tree with root as current node
if (areIdentical(T, S)):
return True
# IF the tree with root as current node doesn't match
# then try left and right subtreee one by one
return isSubtree(T.children, S) or isSubtree(T.children, S) # won't work because we have several children
def areIdentical(root1, root2):
'''
function to say if two roots are identical
'''
# Base Case
if root1 is None and root2 is None:
return True
if root1 is None or root2 is None:
return False
# Check if the data of both roots their and children are the same
return (root1.data == root2.data and
areIdentical(root1.children, root2.children)) # Here problem because it won't work for children
Expectd output
For instance :
>>># first tree creation
>>>text = start becoming popular
>>>textSpacy = spacy_nlp(text1)
>>>treeText = nltk_spacy_tree(textSpacy)
>>>t = WordTree(treeText[0])
>>># second tree creation
>>>question = When did Beyonce start becoming popular?
>>>questionSpacy = spacy_nlp(question)
>>>treeQuestion = nltk_spacy_tree(questionSpacy)
>>>q = WordTree(treeQuestion[0])
>>># tree comparison
>>>isSubtree(t,q)
True
In case this may be useful, here is the WordTree class that I used:
class WordTree:
'''Tree for spaCy dependency parsing array'''
def __init__(self, tree, is_subtree=False):
"""
Construct a new 'WordTree' object.
:param array: The array contening the dependency
:param parent: The parent of the array if exists
:return: returns nothing
"""
self.parent = []
self.children = []
self.data = tree.label().split('_')[0] # the first element of the tree # We are going to add the synonyms as well.
for subtree in tree:
if type(subtree) == Tree:
# Iterate through the depth of the subtree.
t = WordTree(subtree, True)
t.parent=tree.label().split('_')[0]
elif type(subtree) == str:
surface_form = subtree.split('_')[0]
self.children.append(surface_form)
It works very well with trees made with Spacy phrases.
question = "When did Beyonce start becoming popular?"
questionSpacy = spacy_nlp(question)
treeQuestion = nltk_spacy_tree(questionSpacy)
t = WordTree(treeQuestion[0])
You can just iterate over all the children of T, and if S is a subtree of any of the children of T, then S is a subtree of T. Also, you should return False when T is None because it means that you are already at a leaf of T and S is still not found to be a subtree:
def isSubtree(T,S):
if S is None:
return True
if T is None:
return False
if areIdentical(T, S):
return True
return any(isSubtree(c, S) for c in T.children)

Printing family tree until certain level | Python 3

I am struggling with a recursive function that prints a family tree until a certain "depth/level".
I have defined class "Person" and each person has some descendant(s), so lets say:
>>> george.children
[<__main__.Person object at 0x000002C85FB45A58>]
>>> george.name
'George'
And I want to print the family tree in a way that each generation is separated by 4 whitespaces, for example:
>>> family_tree(george, level = 2)
George
Michael
Juliet
Peter
Mary
George is the level 0, then his two sons are level 1, etc.
Do you please have any ideas how to write this using recursion? I would greatly appreciate it.
You could use recursion. At each deeper level of recursion you should produce 4 more spaces. So for that purpose you could pass an argument depth that is increased at every recursive call.
Here is how you could do it:
# You'll have a class like this:
class Person:
def __init__(self, name):
self.name = name
self.children = []
def addChild(self, child):
self.children.append(child)
return self # to allow chaining
# The function of interest:
def family_tree(person, level = 2):
def recurse(person, depth):
if depth > level: return
print (" " * (4 * depth) + person.name)
for child in person.children:
recurse(child, depth+1)
recurse(person, 0)
# Sample data
george = Person("George").addChild(
Person("Michael").addChild(
Person("Juliet").addChild(
Person("don't show me")
)
)
).addChild(
Person("Peter").addChild(
Person("Mary")
)
)
# Call the function
family_tree(george)

Error in implementing BFS to find the shortest transformation from one word to another(Word Ladder Challenge)

I am trying to implement the word ladder problem where I have to convert one word to another in shortest path possible.Obviously we can use the breadth first search (BFS) to solve it but before that we have to first draw the graph.I have implemented the concept of buckets where certain words fall under a bucket if they match the bucket type.But my graph is not implementing correctly.
The given word list is ["CAT", "BAT", "COT", "COG", "COW", "RAT", "BUT", "CUT", "DOG", "WED"]
So for each word I can create a bucket.For example for the word 'CAT', I can have three buckets _AT, C_T, CA_. Similarly I can create buckets for the rest of the words and which ever words match the bucket type will fall under those buckets.
My code for expressing the problem in graph works fine and I get a graph like this (theoritical)
Now I need to find the shortest no of operations to transform 'CAT' to 'DOG'.So I use a modified method of BFS to achieve it.It works fine when I make a sample graph of my own.For example
graph = {'COG': ['DOG', 'COW', 'COT'], 'CAT': ['COT', 'BAT', 'CAT', 'RAT'], 'BUT': ['CUT', 'BAT'], 'DOG': ['COG']}
The code works fine and I get the correct result.But if I have a huge list of words say 1500, it's not feasible to type and create a dictionary that long.So I made a function which takes those words from the list, implements the technique I dicussed above and creates the graph for me which works just fine until here.But when I try to get the shortest distance between two words, I get the following error
for neighbour in neighbours:
TypeError: 'Vertex' object is not iterable
Here is my code below
class Vertex:
def __init__(self,key):
self.id = key
self.connectedTo = {}
# add neighbouring vertices to the current vertex along with the edge weight
def addNeighbour(self,nbr,weight=0):
self.connectedTo[nbr] = weight
#string representation of the object
def __str__(self):
return str(self.id) + " is connected to " + str([x.id for x in self.connectedTo])
def getConnections(self):
return self.connectedTo.keys()
def getId(self):
return self.id
def getWeight(self,nbr):
return self.connectedTo[nbr]
class Graph:
def __init__(self):
self.vertList = {}
self.numVertices = 0
def addVertex(self,key):
self.numVertices += 1
newVertex = Vertex(key)
self.vertList[key] = newVertex
return newVertex
def getVertex(self,n):
if n in self.vertList:
return self.vertList[n]
else:
return None
def addEdge(self,f,t,cost=0):
if f not in self.vertList:
nv = self.addVertex(f)
if t not in self.vertList:
nv = self.addVertex(t)
self.vertList[f].addNeighbour(self.vertList[t],cost)
def getVertices(self):
return self.vertList.keys()
def __iter__(self):
return iter(self.vertList.values())
# I have only included few words in the list to focus on the implementation
wordList = ["CAT", "BAT", "COT", "COG", "COW", "RAT", "BUT", "CUT", "DOG", "WED"]
def buildGraph(wordList):
d = {} #in this dictionary the buckets will be the keys and the words will be their values
g = Graph()
for i in wordList:
for j in range(len(i)):
bucket = i[:j] + "_" + i[j+1:]
if bucket in d:
#we are storing the words that fall under the same bucket in a list
d[bucket].append(i)
else:
d[bucket] = [i]
# create vertices for the words under the buckets and join them
#print("Dictionary",d)
for bucket in d.keys():
for word1 in d[bucket]:
for word2 in d[bucket]:
#we ensure same words are not treated as two different vertices
if word1 != word2:
g.addEdge(word1,word2)
return g
def bfs_shortest_path(graph, start, goal):
explored = []
queue = [[start]]
if start == goal:
return "The starting node and the destination node is same"
while queue:
path = queue.pop(0)
node = path[-1]
if node not in explored:
neighbours = graph[node] # it shows the error here
for neighbour in neighbours:
new_path = list(path)
new_path.append(neighbour)
queue.append(new_path)
if neighbour == goal:
return new_path
explored.append(node)
return "No connecting path between the two nodes"
# get the graph object
gobj = buildGraph(wordList)
# just to check if I am able to fetch the data properly as mentioned above where I get the error (neighbours = graph[node])
print(gobj["CAT"]) # ['COT', 'BAT', 'CUT', 'RAT']
print(bfs_shortest_path(gobj, "CAT", "DOG"))
To check neighbouring vertices of each vertex, we can do
for v in gobj:
print(v)
The output is obtained as below which correctly depicts the graph above.
CAT is connected to ['COT', 'BAT', 'CUT', 'RAT']
RAT is connected to ['BAT', 'CAT']
COT is connected to ['CUT', 'CAT', 'COG', 'COW']
CUT is connected to ['COT', 'BUT', 'CAT']
COG is connected to ['COT', 'DOG', 'COW']
DOG is connected to ['COG']
BUT is connected to ['BAT', 'CUT']
BAT is connected to ['BUT', 'CAT', 'RAT']
COW is connected to ['COT', 'COG']
CAT is connected to ['COT', 'BAT', 'CUT', 'RAT']
What could be going wrong then?
Ok so I figured out the issue.The problem was in this line of code
neighbours = graph[node]
Basically it is trying the fetch the neigbours for the particular node.So it needs to access the vertList dictionary declared as an attribute for the Graph class.So for an object to access the dictonary value, one has to implement a __getitem__ special method.So I declare this under the Graph class as follows
# returns the value for the key which will be an object
def __getitem__(self, key):
return self.vertList[key]
Now graph[node] will be able to fetch the object representation of the node since the value of vertList dictionary is a vertex object (vertList stores vertex name as key and vertex object as value) .So I have to explicitly tell it to fetch the object's neighbours and not the object itself.So I can call the getConnections() method under the Vertex class which further calls the connectedTo attribute to get the neighbour objects for the particular vertex object. (connectedTo dictionary has the vertex object as the key and edge weight as the value.)
So now those neighbour objects will have their own ids which I can access and use it for the BFS operation.The below line is the modified code (under the bfs_shortest_path method) which does the above work.
if node not in explored:
neighbours = [x.id for x in graph[node].getConnections()]
Now I get the list of the neigbours for the particular node and use it.The rest of the code stays the same.

Resources