How do we modify an object passed to a function in Python - python

I created a small function funct which should assign None to node if a value of -1 is passed else assign the value to the value attribute of the node object. I created a simple binary tree [root, left, right] and finally print the nodes.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def funct(node, val):
if val == -1:
### modify code here
node = None
else:
node.val = val
root = TreeNode()
root.val = 'root'
root.left = TreeNode()
root.right = TreeNode()
funct(root.left, 'left')
funct(root.left, -1)
print(root, root.val)
print(root.left, root.left.val)
print(root.right, root.right.val)
When I print the nodes I see the following output.
The right node is in memory and is not None.
How do I assign None to the orignal object by modifying the code inside the if in funct to get the following output instead.
esentially simulating the following code and getting the output :
root = TreeNode()
root.val = 'root'
root.left = TreeNode('left')
root.right = None
Note : I can change my algo. to create a new node only when val != -1. However I want to understand if there is a way to modify a passed object it in pyhton.
Edits : removed the word "delete". Added some clarification.

I created a small function funct which should assign None to node if a value of -1 is passed else assign the value to the value attribute of the node object.
funct cannot replace the node that was passed to it (i.e.: make one of the caller's variables refer to a new node instead of this one), because it receives the node, and not any variable name for that node.
It can modify the node that was passed to it. In particular, it can replace the child values, by reassigning to node.right and node.left. This means "the .right and .left of the TreeNode instance that was passed to me". node is a name that funct uses for that passed-in instance, that has nothing to do with the calling code in any way.
To "remove" a subtree, then, we need to pass the parent of the tree that will be removed, and also indicate which side will be removed. That might look like:
def remove_child(parent, which):
if which == 'left':
parent.left = None
elif which == 'right':
parent.right = None
else:
raise ValueError("invalid child name")
The root of the tree has no parent, so removing the entire tree must be treated as a special case.

Not sure if this is what you need, but I think its doing the behavior you want:
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def funct(self, child: str, val):
if val == -1:
### modify code here
setattr(self,child, None)
else:
setattr(self,child,TreeNode(val))
root = TreeNode()
root.val = 'root'
root.left = TreeNode()
root.right = TreeNode()
root.funct(child='left', val='left')
root.funct(child='right', val='right')
print(root, root.val)
print(root.left, root.left.val)
print(root.right, root.right.val)
print(None)
root.funct(child='left', val= -1)
print("\n")
print(root, root.val)
# print(root.left, root.left.val) - you cannot print root.left.val because None has no attribute
print(root.left)
print(root.right, root.right.val)

Related

Deletion in BST (python) | unexpected additional deletions?

def delete(node, key):
if not node: return None
# Wrong node, search correct child
if key < node.data:
delete(node.left, key)
elif key > node.data:
delete(node.right, key)
# Correct node found
else:
#1. node has no children
if not (node.left and node.right): return None
#2. node has only left child
if node.left and not node.right: return node.left
#3. node has only right child
if not node.left and node.right: return node.right
#4. node has both left & right children
## Need to replace current value with next biggest value
## So go right once then all left to end
## Once this value is found, assign to appropriate position
## Then remove this val from its previous position
temp = node.right
while temp.left: temp = temp.left
node.data = temp.data
node.right = delete(node.right, temp.data)
t = BinaryTree([100, 50, 200, 25, 75, 350])
delete(t.root, 100)
I think that this BST deletion code mostly works, but it's a little buggy. If I delete the root node, 100, then 350 will be missing, following, given the BST, t = BinaryTree([100, 50, 200, 25, 75, 350]).
What is going on here? I'm not sure why 350 has been deleted in the process. I'm wondering if it's related to how I replace the node value upon successful deletion.
Optional but possibly helpful context
class BinaryTreeNode:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class BinaryTree:
def __init__(self, *args):
if len(args) < 1:
self.root = None
elif isinstance(args[0], int):
self.root = BinaryTreeNode(args[0])
else:
self.root = None
for x in args[0]:
self.insert(x)
def insert(self, node_data):
new_node = BinaryTreeNode(node_data)
if not self.root:
self.root = new_node
else:
# root has no parent, so assign none for 1st iteration
parent = None
temp_pointer = self.root
while temp_pointer:
# update parent
parent = temp_pointer
#update temp_pointer to left or right child
if node_data <= temp_pointer.data:
temp_pointer = temp_pointer.left
else:
temp_pointer = temp_pointer.right
# eventually, temp_pointer will point to None, exiting while loop
# assign to left or right child as appropriate
if node_data <= parent.data:
parent.left = new_node
else:
parent.right = new_node
There are a few issues:
As delete is designed to return a node reference or None, you should make sure not to ignore that returned reference. You did it right near the end of your function (node.right = delete(node.right, temp.data)), but elsewhere delete is called without regards of the returned reference. So:
The initial call in the main program should look like this:
t.root = delete(t.root, 100)
This will ensure that the root attribute is set to None when the last node has been deleted from the tree.
The recursive call in the first if block should be:
node.left = delete(node.left, key)
And similarly in the second block:
node.right = delete(node.right, key)
The function delete should always return a node reference after a recursive call has been made, yet this is missing in many of your cases, so add at the very bottom of your function a kind of "catch all" and return the current reference you have:
return node
The condition for identifying a leaf node is wrong. The and should be a or:
if not (node.left or node.right): return None
The corrected code -- comments indicate changes:
def delete(node, key):
if not node: return None
if key < node.data:
node.left = delete(node.left, key) # assign back!
elif key > node.data:
node.right = delete(node.right, key) # assign back!
else:
if not (node.left or node.right): return None # condition corrected
if node.left and not node.right: return node.left
if not node.left and node.right: return node.right
temp = node.right
while temp.left: temp = temp.left
node.data = temp.data
node.right = delete(node.right, temp.data)
return node # always return a node when a recursive call was made
t = BinaryTree([100, 50, 200, 150, 175, 25, 75, 350])
t.root = delete(t.root, 350) # assign back!
Considerations
Not a problem in the algorithm, but it is a good habit to put the body of an if or while statement on the next line, indented
This function would better be a method on the BinaryTree class -- then the main program should not have to worry about getting/setting the root attribute -- and most of the function's (recursive) logic could be implemented as a method on the BinaryTreeNode class.

Why I am seeing None data after deleting the smallest or largest item from Binary Search Tree?

I have a binary search tree. I have written basic insert, delete, traversal of the full tree and find its maximum and minimum node of the tree but I have trouble with finding maximum and minimum node after deleting the minimum or the maximum node.
This function deletes a specific node:
def deleteNode(self,val):
if self.data:
if self.data > val:
self.left.deleteNode(val)
elif self.data < val:
self.right.deleteNode(val)
else:
if self.left is None:
self.data = self.right
return True
elif self.right is None:
self.data = self.left
return True
else:
dltNode = self
dltNode.data = self.data
largest = self.left.findMax()
dltNode.data = largest
dltNode.left.deleteNode(largest)
return True
This function finds the minimum node:
def findMin(self):
if self.data:
if self.left is None:
return self.data
else:
return self.left.findMin()
And this is for the maximum node:
def findMax(self):
if self.data:
if self.right is None:
return self.data
else:
return self.right.findMax()
findMin and findMax functions work fine without deleting any node.
If I ever call then after deleting the minimum and maximum node they will return None, whether they were supposed to return only integer node data. Here is the screenshot of my output:
It should print 34 instead of None.
Here is my full code
There are a few issues in deleteNode
if self.data should not be there: this would mean you cannot delete a node with value 0 from the tree. A similar problem exists in other methods (findMin, findMax, insert, ...).
self.left.deleteNode(val) is executed without checking first that self.left is not None. The same is true for self.right.deleteNode(val)
self.data = self.right assigns a Node reference to a data attribute (which is supposed to be a number).
The function sometimes returns nothing (None) or True. Because of the recursive nature, the original caller of the method will get None. This is not consistent.
The function cannot deal with deleting the root Node, as the caller will keep using the root node reference (t or t2 in your example code).
To solve these issues, you should either create a separate Tree class, or agree that deleteNode returns the root node of the tree, which could be a different root than the root on which the call was made (when the root node was deleted), or even None when that was the last node of the tree.
Here is how that would look:
def deleteNode(self,val):
if self.data > val:
if self.left:
self.left = self.left.deleteNode(val)
elif self.data < val:
if self.right:
self.right = self.right.deleteNode(val)
else:
if self.left is None:
return self.right
elif self.right is None:
return self.left
else:
largest = self.left.findMax()
self.data = largest
self.left = self.left.deleteNode(largest)
return self
To do it right, you would need to use the return value from this method, for example:
t1 = t1.deleteNode(34)
NB: In some methods you check whether self.data is None. I understand this is some special condition for indicating that the root node is not really a node, and should be considered an empty tree, but this is not a nice pattern. Instead, an empty tree should be just None, or you should define another Tree class which has a root attribute which can be None.

adding a node in Binary Search Tree

I am currently working on implementing a Binary search tree but I have a problem with creating the add_node() function. I tried to make it in a recursive way, but the result was weird.
For example- If I run add_node(1), add_node(5), and add_node(0), my tree has only 1, no 5 and 0.
I would be happy if you tell me what the problem is.
def add_node(self, value: int) -> None:
if self.root == None:
self.root = Node(value)
return
else:
return self.add_recursion(self.root, value)
def add_recursion(self, node: Node, value: int) -> None:
if node == None:
node = Node(value)
return
elif value < node.value:
return self.add_recursion(node.left, value)
else:
return self.add_recursion(node.right, value)
When a None value is passed into a function, it is passed by value, not by reference, since... there is no reference.
elif value < node.value:
return self.add_recursion(node.left, value)
else:
return self.add_recursion(node.right, value)
When node.left or node.right is None, a Node ends up being created but not attached to node.
So what you could do is handle the cases where they are None separately.
def add_recursion(self, node: Node, value: int) -> None:
if node == None:
node = Node(value)
elif value < node.value:
if node.left == None:
node.left = Node(value)
else:
self.add_recursion(node.left, value)
else:
if node.right == None:
node.right = Node(value)
else:
self.add_recursion(node.right, value)
While this is workable, it becomes quite ugly. Look at the answer by Locke to see a better way to structure your code.
Also, an additional tip for readability, avoid using return where they aren't necessary such as at the end of a flow.
The issue is how you handle if node.left or node.right does not exist. Since the arguments are copied for each call, setting the value of node in add_recursion has no effect on the tree.
def foo(val):
print("foo start:", val)
val = 5
print("foo end:", val)
bar = 3
foo(bar) # Value of bar is copied for call
print("after foo:", bar) # Prints bar is still 3
I think you might also be getting confused due to how the root node is handled. Here is some starter code on how can handle the initial call to add_recursive.
class Node:
def __init__(self, value: int):
self.value = value
self.left = None
self.right = None
def add_recursive(self, value: int):
# TODO: recursively add to left or right
# For example, here is how you could recursively make a linked list
if self.right is None:
self.right = Node(value)
else:
self.right.add_recursive(value)
class BinarySearchTree:
def __init__(self):
self.root = None
def add_node(self, value: int):
if self.root is None:
self.root = Node(value)
else:
# Call add_recursive on root instead of with root.
self.root.add_recursive(value)
tree = BinarySearchTree()
tree.add_node(1)
tree.add_node(5)
tree.add_node(0)

Can't understand this Binary Search Tree (BST) algorithm for Insertion of value in Python

I am learning algorithms. For the BST Construction to insert a value in the BST, I came across this code. I am not that good at OOPS concept and can't figure out how currentnode.left = BST(value) and currentnode = currentnode.left works. If someone can help me understand this, it would be of great help.
class BST:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def insert(self, value):
currentnode = self
while True:
if value < currentnode.value:
if currentnode.left is None:
currentnode.left = BST(value)
break
else:
currentnode = currentnode.left
else:
if currentnode.right is None:
currentnode.right = BST(value)
break
else:
currentnode = currentnode.right
return self
In the insert function, the currentnode has been assigned to self. Starting with the while loop, the parameter value is checked with currentnode's value. If it is small, the first condition is executed else second.
Now comes your doubt.
Let's say the first condition is being executed. If the currentnode's left value is none, then the code calls BST(value) i.e. the constructor is called which initiates a new node, which in turn becomes the currentnode's left child.
Else, if there is already a left child, that child becomes the currentnode and the while loop is iterated again and again until a suitable, position is found.
Also, If this code seems complicated. You should refer this, just in case if it helps.
class Node:
def __init__(self,key):
self.left = None
self.right = None
self.val = key
def insert(root,node):
if root is None:
root = node
else:
if root.val < node.val:
if root.right is None:
root.right = node
else:
insert(root.right, node)
else:
if root.left is None:
root.left = node
else:
insert(root.left, node)
# 5
# / \
# 3 7
# / \ / \
# 2 4 6 8
r = Node(5)
insert(r,Node(3))
insert(r,Node(2))
insert(r,Node(4))
insert(r,Node(7))
insert(r,Node(6))
insert(r,Node(8))

Python Binary Search Tree Delete Function

class Node:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
class BST:
def __init__(self, root=None):
self.root = root
def remove(self, val):
if self.root == None:
return root
else:
self._remove(val, self.root)
def _remove(self, val, node):
if node == None:
return node # Item not found
if val < node.val:
self._remove(val, node.left)
elif val > node.val:
self._remove(val, node.right)
else:
# FOUND NODE TO REMOVE
if node.left != None and node.right != None: # IF TWO CHILDREN
node.val = self._find_min(node.right)
node.right = self._remove(node.val, node.right)
else: # ZERO OR ONE CHILD
if node.left == None: # COVERS ZERO CHILD CASE
node = node.right
elif node.right == None:
node = node.left
return node
Cannot figure out why this function will not delete some values. I debugged with print statements and can see that if I try to remove a value, the function will enter the else block and successfully remove a node with two children. However, when attempting to remove a node with one or zero children, the codes executes with no errors, but when I print the tree to view its contents the node is still there.
The node to be removed will have at least one None child, and it seems straightforward to set the node equal to its right (or left) child, which I assume sets the node to None.
I have some experience with Java, but fairly new to Python, and I sometimes run into trouble with the "self" protocol, but I don't think that is the case here.

Categories