Recursion and return statements [duplicate] - python

This question already has answers here:
Why does my recursive function return None?
(4 answers)
Closed 8 months ago.
I'm fairly new to Python and recursive functions as a whole, so pardon my ignorance.
I am trying to implement a binary search tree in Python and have the following insert method (taken out of a class):
def insert(self, key, root=None):
'''Inserts a node in the tree'''
if root == None:
root = self.root
if root.key == None:
self._update(root, key)
return 0
else:
tmp = root
if key > tmp.key: # we work with the right subtree
self.insert(key, root=tmp.right)
elif key < tmp.key: # we work with the left subtree
self.insert(key, root=tmp.left)
else: # key already exists
return 0
I'm not sure if this is legible, but it traverses the tree until it gets to a None value and updates the node with the key to insert.
Now, the method works nicely and correctly creates a BST from scratch. But there's a problem with the return statements, as it only returns 0 if there is no recursion performed.
>>> bst.insert(10)
0
>>> bst.insert(15)
>>> bst.root.right.key
15
>>>
"Inserting" the root key again returns 0 (from line 15) the way it should.
>>> bst.insert(10)
0
I can't figure out why this happens. If I put a print statement in line 6, it executes correctly, yet it just won't return anything past the first insertion. Why is this? (I'm pretty sure I'm missing some basic information regarding Python and recursion)
Thanks for your help,
Ivan
P.S.: I've read that recursion is not the best way to implement a BST, so I'll look into other solutions, but I'd like to know the answer to this before moving on.

On your recursive lines, you do not return anything. If you want it to return 0, you should replace them with lines like:
return self.insert(key, root=tmp.left)
instead of just
self.insert(key, root=tmp.left)

You are inside a function and want to return a value, what do you do? You write
def function():
return value
In your case you want to return the value returned by a function call, so you have to do.
def function():
return another_function()
However you do
def function():
another_function()
Why do you think that should work? Of course you use recursion but in such a case you should remember the Zen of Python which simply says:
Special cases aren't special enough to break the rules.

You need a return statement in your recursive case. Try this adjustment.
def insert(self, key, root=None):
'''Inserts a node in the tree'''
if root == None:
root = self.root
if root.key == None:
self._update(root, key)
return 0
else:
tmp = root
if key > tmp.key: # we work with the right subtree
return self.insert(key, root=tmp.right)
elif key < tmp.key: # we work with the left subtree
return self.insert(key, root=tmp.left)
else: # key already exists
return 0

Related

Inserting a node in Binary search tree in python

I know the basic working code for inserting a node in BST. But I expected this function to add a node at the right end of BST (because its value is the maximum in that tree), but it doesn't work and I wanna know the reason. I tried debugging it but I'm still not clear why it's behaving this way.
def putValueInBST(root, val): # assuming the val = 7 which is max of all the existing node values in BST
if root is None:
root = Node(val)
elif val > root.data:
putValueInBST(root.right, val)
else:
putValueInBST(root.left, val)
This code below behaves as expected.
def put_val_manually(r, val):
r.right.right = Node(val)
Aren't both of the above functions kinda similar, since they are adding the node at the end of the BST?
(of course in the put_val_manually() function, I am doing it directly.)
The full code is here: https://i.ibb.co/yf2YTYy/code.png
Try to return the node
def putValueInBST(root, val): # assuming the val = 7 which is max of all the existing node values in BST
if root is None:
return Node(val)
elif val > root.data:
root.right=putValueInBST(root.right, val)
else:
root.left=putValueInBST(root.left, val)
root=putValueInBST(root,val)
This code works

How to understand recursion in Binary Tree

I have started to learn Recursion , I seem to understand the concept , but at the same time I feel completely lost.
For eg, I was trying to solve find the ancestors of a given node in Binary Tree
My Tree:
1
/ \
7 9
/ \ / \
6 5 2 3
My code :
def findAns(node,target,ans):
if node is None:
return ans
ans.append(node.data)
if node == target:
return ans[:-1] #return the list except the last item(whch will be the target node)
ans = findAns(node.left,target,ans)
ans = findAns(node.right,target,ans)
del ans[-1] #Delete last node while backtracking
return ans
ans=[]
findAns(root,target,ans) #target node is 5
print(ans)
print(ans[:-1])
OUTPUT :
[1,7,5]
[1, 7]
I am unable to understand the below questions,
When backtracking is completed for the entire tree, list 'ans' will be empty # root position, how come I am able to get the value [1,7,5] in my list ?
when the if condition is satisfied , I return ans[:-1] , because I should not include the target node, but how come when I print 'ans' I am getting the target node as well ?
In the same 'if condition' , if I return ans instead of ans[:-1], y code doesn't work, I get back an empty 'ans' list, why is that ?
But to avoid the confusion I changed my code as mentioned below, I used a global variable . However I don't think it is efficient , any resource or material or explanations for my above question would be of great help to me. Thanks !
Code with Global variable:
_ans=[]
def findAns(node,target,ans):
global _ans
if node is None:
return ans
ans.append(node.data)
if node == target:
_ans.append(list(ans[:-1]))
#return ans[:-1]
ans = findAns(node.left,target,ans)
ans = findAns(node.right,target,ans)
del ans[-1]
return ans
ans=[]
findAns(root,target,ans)
_ans[0]
You expected the root after backtracking returns empty list, well it does. The problem is the function returns the empty list but you don't catch it:
res=findAns(root, target, ans)
print(res)
Output:
[]
From question 1 you may still think printing ans will be the same as catching the return value, but when you return ans[:-1] you have already lost the original list you passed in. This is heavily related to list in python being mutable and list[:-1] will return a completely new list and modifying the new list won't affect the ans you first passed in. So after it returns a new list, the backtracking ans = findAns(root.right, target, ans) takes a new list, after that changing the 'new' ans won't change the 'old' ans(the one used to print)
If you change to return ans then it will print out empty list. The problem is every time you iterate through a step in the recursion you append a new node but at the same time you delete one last node, which results in add one and delete one being zero.

Python - Iterative searchtree implementation

I am trying to implement an iterative function, which searches a given search-tree for an integer, and reports back whether that integer exist within the search-tree.
So far it works for returning True if the value exists, but runs into a "list index out of range" error when searching for values not existant within the search-tree.
return [l,v,r]
def left(l) :
return l[0]
def right(l) :
return l[2]
def value(l) :
return l[1]
def empty() :
return []
def iterative_check_for_elem(val,tree):
while not tree == False:
if val == value(tree):
return True
break
elif val < value(tree):
tree = left(tree)
elif val > value(tree):
tree = right(tree)
return False
test_tree = node(node(node(empty(),30,empty()),40,node(empty(),45,empty())),50,node(node(empty(),55,empty()),60,node(empty(),70,empty())))
print(iterative_check_for_elem(45,test_tree))
45 works in the call to print, 47 runs into the error.
I can not figure out what is going wrong honestly.
Your tree has a bunch of empty nodes in it (created by empty()), and your search code doesn't deal with them properly. While an empty list is falsey, it's not equal to False, which your code seems to expect. Try rewriting the loop condition as:
while tree:
...
Or you could be more explicit and do a check like:
while len(tree) > 0:
...

Python-Recursion-New To Programming

I need to Check that every number in numberList is positive and implement the below
function using recursion. I'm stuck. Just learning recursion and I'm completely lost as I am very new to programming. Help!
def isEveryNumberPositiveIn(numberList):
foundCounterexampleYet = False
for number in numberList:
if(number <= 0):
foundCounterexampleYet = True
return not(foundCounterexampleYet)
Your function is not recursive because it never calls itself; a recursive version would look like
def all_positive(lst):
if lst:
return lst[0] > 0 and all_positive(lst[1:])
# ^
# this is the recursive bit -
# the function calls itself
else:
return True
# this keeps the function from looping forever -
# when it runs out of list items, it stops calling itself
This is a bad example to choose for a recursive function because (a) there is a simple non-recursive solution and (b) passing it a large list (ie over 1000 items) will overflow the call stack and crash your program. Instead, try:
def all_positive(lst):
return all(i > 0 for i in lst)
Your indentation is incorrect, but your thinking is correct, though the algorithm is not recursive. You could make it a bit more efficient though, by jumping out of the loop when a negative number is detected:
def isEveryNumberPositiveIn(numberList):
foundCounterexampleYet = False
for number in numberList:
if number <= 0:
foundCounterexampleYet = True
break
return not foundCounterexampleYet
then for example:
a = [1,-2,3,4,45]
print(isEveryNumberPositiveIn(a))
returns False
By the way, those parentheses forif and not are unnecessary.
With this sort of recursive problem, here is how you should think about it:
There should be a "basis case", which answers the question trivially.
There should be a part that does something that brings you closer to a solution.
In this case, the "basis case" will be an empty list. If the list is empty, then return True.
The part that brings you closer to a solution: shorten the list. Once the list get shortened all the way to a zero-length (empty) list, you have reached the basis case.
In pseudocode:
define function all_positive(lst)
# basis case
if lst is zero-length:
return True
if the first item in the list is not positive:
return False
# the actual recursive call
return all_positive(lst[with_first_value_removed]
Try to convert the above pseudocode into Python code and get it working. When you are ready to peek at my answer, it's below.
def all_positive(lst):
"""
Recursive function to find out if all members of lst are positive.
Because it is recursive, it must only be used with short lists.
"""
# basis case
if len(lst) == 0:
return True
if lst[0] <= 0:
return False
# recursive call
return all_positive(lst[1:])
There's several ways you can write this. One way would be to use lst.pop() to remove one element from the list. You could combine that with the if statement and it would be kind of elegant. Then the list would already be shortened and you could just do the recursive call with the list.
if lst.pop() <= 0:
return False
return all_positive(lst)
There is one problem though: this destroys the list! Unless the caller knows that it destroys the list, and the caller makes a copy of the list, this is destructive. It's just plain dangerous. It's safer to do it the way I wrote it above, where you use "list slicing" to make a copy of the list that leaves off the first item.
Usually in a language like Python, we want the safer program, so we make copies of things rather than destructively changing them ("mutating" them, as we say).
Here's one more version of all_positive() that makes a single copy of the list and then destroys that copy as it works. It relies on a helper function; the helper is destructive. We don't expect the user to call the helper function directly so it has a name that starts with an underscore.
def _all_positive_helper(lst):
"""
Recursive function that returns True if all values in a list are positive.
Don't call this directly as it destroys its argument; call all_positive() instead.
"""
if len(lst) == 0:
return True
if lst.pop() <= 0:
return False
return _all_positive_helper(lst)
def all_positive(lst):
"""
Return True if all members of lst are positive; False otherwise.
"""
# use "list slicing" to make a copy of the list
lst_copy = lst[:]
# the copy will be destroyed by the helper but we don't care!
return _all_positive_helper(lst_copy)
It's actually possible in Python to use a default argument to implement the above all in one function.
def all_positive(lst, _lst_copy=None):
"""
Return True if all members of lst are positive; False otherwise.
"""
if _lst_copy is None:
return all_positive(lst, lst[:])
if len(_lst_copy) == 0:
return True
if _lst_copy.pop() <= 0:
return False
return all_positive(lst, _lst_copy)
Recursion doesn't really help you with this. A better use for recursion would be, for example, visiting every node in a binary tree.

Python: what's the pythonic way to perform this loop?

What is the pythonic way to perform this loop. I'm trying to pick a random key that will return a subtree and not the root. Hence: 'parent == None' cannot be true. Nor can 'isRoot==True' be true.
thekey = random.choice(tree.thedict.keys())
while (tree.thedict[thekey].parent == None)or(tree.thedict[thekey].isRoot == True):
thekey = random.choice(tree.thedict.keys())
.......
edit: it works now
key = random.choice([key for key, subtree in tree.thedict.items()
if subtree.parent and not subtree.isRoot])
(Corrected after comments and question edition)
get a random subtree that is not the
root
not_root_nodes = [key, node for key,node in tree.thedict.iteritems() if not ( node.parent is None or node.isRoot)]
item = random.choice( not_root_nodes )
I think that's a bit better:
theDict = tree.thedict
def getKey():
return random.choice(theDict.keys())
theKey = getKey()
while theDict[thekey].parent in (None, True):
thekey = getKey()
What do you think?
thekey = random.choice(tree.thedict.keys())
parent = thedict[thekey].parent
while parent is None or parent.isRoot:
thekey = random.choice(tree.thedict.keys())
parent = thedict[thekey].parent
thekey = random.choice(tree.thedict.keys())
parent = tree.thedict[thekey].parent
while parent is None or tree.thedict[thekey].isRoot:
thekey = random.choice(tree.thedict.keys())
parent = thedict[thekey].parent
I think your while condition is flawed:
I think you expect this: tree.thedict[thekey].parent == None
should be equal to this: tree.thedict[thekey].parent.isRoot == True
When in fact, for both to mean "this node is not the root", you should change the second statement to: tree.thedict[thekey].isRoot == True
As written, your conditional test says "while this node is the root OR this node's parent is the root". If your tree structure is a single root node with many leaf nodes, you should expect an infinite loop in this case.
Here's a rewrite:
thekey = random.choice(k for k in tree.thedict.keys() if not k.isRoot)
def is_root(v):
assert (v.parent != None) == (v.isRoot)
return v.isRoot
#note how dumb this function looks when you guarantee that assertion
def get_random_nonroot_key():
while True:
thekey = random.choice(tree.thedict.keys())
value = tree.thedict[thekey]
if not is_root(value): return key
or a refactoring of Roberto Bonvallet's answer
def get_random_nonroot_key():
eligible_keys = [k for k, v in tree.thedict.items() if not is_root(v)]
return random.choice(eligible_keys)
Personally, I don't like the repetition of initializing thekey before the while loop and then again inside the loop. It's a possible source of bugs; what happens if someone edits one of the two initializations and forgets to edit the other? Even if that never happens, anyone reading the code needs to check carefully to make sure both initializations match perfectly.
I would write it like so:
while True:
thekey = random.choice(tree.thedict.keys())
subtree = tree.thedict[thekey]
if subtree.parent is not None and not subtree.isRoot:
break
P.S. If you really just want the subtree, and don't care about the key needed to lookup the subtree, you could even do this:
while True:
subtree = random.choice(tree.thedict.values())
if subtree.parent is not None and not subtree.isRoot:
break
Some people may not like the use of "while True:" but that is the standard Python idiom for "loop forever until something runs break". IMHO this is simple, clear, idiomatic Python.
P.P.S. This code should really be wrapped in an if statement that checks that the tree has more than one node. If the tree only has a root node, this code would loop forever.

Categories

Resources