Search function for a n-ary tree using python - python

I am trying to build a search function for the n-ary tree. Here is how node class is :
class node(object):
"""docstring for node"""
def __init__(self, val=''):
self.val = val #value of the node
self.subtrees = [] #list of subtree node objects
following is the code how I call search function:
temp_search(root, "1")
And there is one node whose value is "1". And I expect node object to be returned on successful search.
following is my first search function I implemented:
#temp_search v0.1
def temp_search(node, key):
if node.val == key:
print 'Found', node
return node
for subtree in node.subtrees:
return temp_search(subtree, key)
The above thing returns 'None' and it never printed 'Found'
Now I modified it a bit:
#temp_search v0.2
def temp_search(node, key):
if node.val == key:
print 'found', node
return node
for subtree in node.subtrees:
temp_search(subtree, key)
Though it returns 'None' it still printed 'Found'. Okay thats some improvement.
So I realized that loop is running on each subtree object even after it returned the node. Does that make any sense? Because I think once it returned something, it should come out of it right? So modified it again:
#temp_search v0.3
def temp_search(node, key):
if node.val == key:
print 'Found', node
return node
for subtree in node.subtrees:
temp = temp_search(subtree, key)
if temp:
return temp
Similarly I implemented multi search like this [i.e. it should return all nodes whose value matches to key]
#temp_multi_search v0.1
def temp_multi_search(some_node, key, result=[]):
if some_node.val == key:
print 'found', some_node
return result.append(some_node)
for subtree in some_node.subtrees:
temp = temp_multi_search(subtree, key, result)
if temp:
result.append(temp)
return result
I would call above function like this:
temp_multi_search(root, "1")
But I got result as :
[<__main__.node object at 0x101eb4610>, [...], [...], [...], [...], [...], [...], [...], <__main__.node object at 0x101eb47d0>, [...], [...], [...], [...]]
So it was appending empty lists(?). This is how I fixed it:
#temp_multi_search v0.2
def temp_multi_search(some_node, key, result=[]):
#result = []
if some_node.val == key:
print 'found', some_node
return result.append(some_node)
for subtree in some_node.subtrees:
temp = temp_multi_search(subtree, key, result)
if isinstance(temp, node):
result.append(temp)
return result
Now I would get correct, expected result :
[<__main__.node object at 0x10b697610>, <__main__.node object at 0x10b6977d0>]
Here are my questions :
Why temp_search v0.1 failing? When going to through each subtree, when the search result is found, the value is returned. Why the loop is not getting terminated?
In temp_search v0.2, I don't have return statement in for loop. So what difference here it is making? Since the object is being found, but how do I return it successfully?
Have I implemented temp_search v0.3 in a right way? Any improvements?
Why temp_multi_search v0.1 is failing?
Have I implemented temp_multi_search v0.2 in a right way? Any improvements?
Here is what I think:
I think the for loop is running is only once. It searches in the first subtree only (recursively), which returns if item not present it returns none. I confirmed this via sending a value which was present in first subtree
Here for loop is successfully running on each subtree, thats why it is able to find the node. But how do I make it return the node other method than temp_search v0.3? I find temp_search v0.3 less pythonic
-
I think it is failing because the return temp would be some list, so it is just appending it to result.
-

Your answer is correct. Since the function call on first subtree always returns (regardless the value is found or not), it won't check for next subtrees.
You don't have return statement, so at the end of the function Python will implicitly insert return None
This is already optimal. There is no single Python statement that can do that.
The lists (not empty list!) are added because you do return result, and so the parent function call will receive a list (of results) as the result. Then it will append to its local copy of result, and return.
You can improve this by not giving result as a parameter:
#temp_multi_search v0.25
def temp_multi_search(some_node, key):
result = [] # Line 1
if some_node.val == key:
print 'found', some_node
result.append(some_node) # Line 2
for subtree in some_node.subtrees:
result.extend(temp_multi_search(subtree, key)) # Line 3
return result
Explanation:
It will first check the value on the root node, if it doesn't match, we don't append that node to the search result, otherwise, we add that to our result so far (which would only contain itself). Next we check every subtree.
Now, we already know that the function temp_multi_search(subtree, key) will return all occurrences on that tree. So after we call temp_multi_search(subtree, key) on each child, we extend the result found in that subtree to our result so far (which might have included the results from previous children).
At the end of iterating all children, we return our result.
Example:
Suppose we are looking for the number 1 in the following tree, and expects the function to return all occurrences.
0
_______|________
| | |
1 2 3
_|_ _|_ ___|___
| | | | | | |
1 4 1 5 6 1 7
So first we call temp_multi_search(root,1). It's not 1, so result is still empty now.
Next we check each subtree:
Child 1: The node value matches, so we add it into the result (at Line 2). Now say we have result = [node1]. Then check each subtree:
GrandChild 1: The node value matches, so we add it into the result (at Line 2). No more subtrees. Return [node2]. The Child 1 call will extend the result [node2] into his result [node1] (Line 3). So the Child 1 now has [node1, node2].
GrandChild 2: Node value doesn't match. No more subtrees. Return []. Child 1 extend empty list, so no change.
Child 2: Doesn't match. Check each subtree:
GrandChild 3: Match. Add to result (Line 2). No more subtrees. Return [node5]. Child 2 becomes [node5] (Line 3).
GrandChild 4: Doesn't match. No more subtrees. Return []. Child 2 still [node5].
Child 3: Doesn't match. Check each subtree:
GrandChild 5: Doesn't match. No more subtrees. Return []
GrandChild 6: Match Add to result (Line 2). Return [node9]. Child 3 will extend it to be [node9] (Line 3)
GrandChild 7: Doesn't match. No more subtrees. Return [].
At the end of each mentioned step, the returned result will be extended to Root result by Line 3. So after Step 1, Root result is [node1, node2]. After Step 2, Root result is [node1, node2, node5]. After Step 3, Root result is [node1, node2, node5, node9].
Then after checking all children, return the result.

Related

Python - Recursive count through BST

so I am doing a Find Kth Largest Value in BST leetcode problem and I want to
update the k_count every time I iterate through the tree. As you can see I print the tree value and the k_count everytime I go through. It works in that I am returning the value of the tree from largest value to smallest but I want the assocaited K_count with each tree node to update respectively. That is I want the largest value to have a k_count of 1 the second largest to have a k_count of 2 the third largest a k_count of 3 etc. I know I am close but getting thrown off with the recursive stack.
Any help with this would be greatly appreciated!
# This is an input class. Do not edit.
class BST:
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
def findKthLargestValueInBst(tree, k):
k_count = 0
result = 0
return findhelper(tree, k, k_count, result)
def findhelper(tree, k, k_count, result):
if tree is not None:
findhelper(tree.right, k, k_count, result)
print(tree.value)
print(k_count)
if k_count == k:
result = tree.value
findhelper(tree.left, k, k_count+1, result)
return result
The main issue in your attempt is that k_count is a local variable that
never is assigned a different value, and
its value is never communicated back to the caller
More concretely, whatever the first recursive call does (to walk through the right subtree), that print(k_count) is always going to print the value of k_count it had before that recursive call was made, simply because that recursive call is not going to affect its value.
Again, each recursive execution of the function will get its own k_count variable, that is not related to any other k_count variable that exists in the call stack.
What you really want, is that when the first recursive call returns to the caller (and has visited the right subtree) that k_count will represent the number of nodes that were visited during that recursive call. But for that you would have to pass a reference to a single k_count attribute somewhere, or have the updated value returned by the recursive function.
But I would suggest to do this with a generator. It makes it so much easier to think about: perform a reversed inorder traversal using yield. Once you have that, just pull the first k values from that iterator and you're done.
Code:
def reversedInOrder(tree):
if tree:
yield from reversedInOrder(tree.right)
yield tree.value
yield from reversedInOrder(tree.left)
def findKthLargestValueInBst(tree, k):
for value in reversedInOrder(tree):
k -= 1
if k == 0:
return value

seek the lower ancestor in a binary tree

I wanna find the lower ancestor in a binary tree, and what i do is first, list the fathers of each node, then compare the list and the last item in common is the lower ancestry.
I have this code:
def ancesters(self, node, list= []):
if self.seekNode(node) != False:
if node < self.id:
list.append(self.id)
return self.left.ancesters(node)
elif node > self.id:
list.append(self.id)
return self.right.ancesters(node)
elif self.id == node:
list.append(self.id)
return list
else:
return False
the function seekNode works, and this method too, but when i use the method twice, shows the list of ancestor of the last call, example:
i have this tree:
2
|
---
5
---
3 6
and when i call the method ancesters(6), the list will be (2,5,6), and when i call again to search the fathers of 3, shows (2,5,6,2,5,3).
so, when i set the parameter (list=[]) why the list does not initialize and save the list value?.
I call the method with the same object, in this case will be the node (root) of the tree. the nodes are instance of the node (root) of the tree.
In short, do not use mutable object (like list) as a default value.
This is Anti-Pattern, because in Python the default value is shared between all function calls. so it must be immutable. popular option is None and condition if not None: ...
You can also read here

Root to leaf algo bug

Wrote an unnecessarily complex solution to the following question:
Given a binary tree and a sum, determine if the tree has a
root-to-leaf path such that adding up all the values along the path
equals the given sum.
Anyway, I'm trying to debug what went wrong here. I used a named tuple so that I can track both whether the number has been found and the running sum, but it looks like running sum is never incremented beyond zero. At any given leaf node, though, the running sum will be the leaf node's value, and in the next iteration the running sum should be incremented by the current running sum. Anyone know what's wrong with my "recursive leap of faith" here?
def root_to_leaf(target_sum, tree):
NodeData = collections.namedtuple('NodeData', ['running_sum', 'num_found'])
def root_to_leaf_helper(node, node_data):
if not node:
return NodeData(0, False)
if node_data.num_found:
return NodeData(target_sum, True)
left_check = root_to_leaf_helper(node.left, node_data)
if left_check.num_found:
return NodeData(target_sum, True)
right_check = root_to_leaf_helper(node.right, node_data)
if right_check.num_found:
return NodeData(target_sum, True)
new_running_sum = node.val + node_data.running_sum
return NodeData(new_running_sum, new_running_sum == target_sum)
return root_to_leaf_helper(tree, NodeData(0, False)).num_found
EDIT: I realize this is actually just checking if any path (not ending at leaf) has the correct value, but my question still stands on understanding why running sum isn't properly incremented.
I think you need to think clearly about whether information is flowing down the tree (from root to leaf) or up the tree (from leaf to root). It looks like the node_data argument to root_to_leaf_helper is initialized at the top of the tree, the root, and then passed down through each node via recursive calls. That's fine, but as far as I can tell, it's never changed on the way down the tree. It's just passed along untouched. Therefore the first check, for node_data.num_found, will always be false.
Even worse, since node_data is always the same ((0, False)) on the way down the tree, the following line that tries to add the current node's value to a running sum:
new_running_sum = node.val + node_data.running_sum
will always be adding node.val to 0, since node_data.running_sum is always 0.
Hopefully this is clear, I realize that it's a little difficult to explain.
But trying to think very clearly about information flowing down the tree (in the arguments to recursive calls) vs information flowing up the tree (in the return value from the recursive calls) will make this a little bit more clear.
You can keep a running list of the path as part of the signature of the recursive function/method, and call the function/method on both the right and left nodes using a generator. The generator will enable you to find the paths extending from the starting nodes. For simplicity, I have implemented the solution as a class method:
class Tree:
def __init__(self, *args):
self.__dict__ = dict(zip(['value', 'left', 'right'], args))
def get_sums(self, current = []):
if self.left is None:
yield current + [self.value]
else:
yield from self.left.get_sums(current+[self.value])
if self.right is None:
yield current+[self.value]
else:
yield from self.right.get_sums(current+[self.value])
tree = Tree(4, Tree(10, Tree(4, Tree(7, None, None), Tree(6, None, None)), Tree(12, None, None)), Tree(5, Tree(6, None, Tree(11, None, None)), None))
paths = list(tree.get_sums())
new_paths = [a for i, a in enumerate(paths) if a not in paths[:i]]
final_path = [i for i in paths if sum(i) == 15]
Output:
[[4, 5, 6]]

Breaking out of the recursion in multi nested tree python

I have a multi nested tree structure and i am trying to find the current level of the provided string/node.I an using recursion to traverse the nodes and return the current level.
def dicq(self,value,current_level):
d = {}
for child in self.children:
if child.name == value:
print current_level
else:
(child.dicq(value,current_level+1))
return current_level
root.dicq('7.3',1)
root is the nested tree structure and i am giving it 7.3(The node which i am finding the level of) and 1(the default level meaning first children). If i simply print the current_level in the if statement it is correct but if i put return in the if statement it doesn't return.I want to return the current level as soon as i find the node.Any suggestions?
Right now your code is returning current_level irrespective of wheather node you are looking for is found or not.
What you need to do is to return current_level only if matching node is found. If not, return something which indicates matching node is not found. Also, you need to ensure your results propagates properly across levels of recursion and you stop looking further when a match is found.
Here is code which might help. Note: I have not tested it.
def dicq(self,value,current_level):
d = {}
retval = -1
for child in self.children:
if child.name == value:
retval = current_level
else:
retval = child.dicq(value,current_level+1)
if retval != -1:
break
return retval
If value is found, that level will be returned. If not, -1 will be returned indicating that its not found anywhere in that part of the tree.

Recursively Navigating Through Tree In Python

I am to implement a length method for a custom Phylogenetic Tree class so we can call len(TreeObject) on it. The length of a tree is defined by how many leafs it has. A leaf means that node has no children. 'self.children' is equal to a list of tuples (node, weight) of that nodes children. I am very close I believe:
def __len__(self):
# everytime it reaches the base case I should add 1
if self.isLeaf():
print('base case - reached leaf!')
return 1
for t,w in self.children:
print('not leaf so sent through loop')
numLeaves = len(t)
return numLeaves
The code is reaching the if statement the correct number of times, e.g. if the length is 3 it outputs 'base case - reached leaf!' 3 separate times. I just need a way of adding those together and storing it in a variable.
Very close indeed. You are just overwriting numLeaves instead of summing them:
numLeaves = 0
for t,w in self.children:
print('not leaf so sent through loop')
numLeaves += len(t)
It can also be implemented differently:
sum(len(t) for (t,w) in self.children)

Categories

Resources