I wrote a dictionary class of an avl search tree with values in leaves, but it is necessary to add immutability to it, that is, when deleting and adding anything (whether it is changing an element with an existing key or adding a new element), a new tree should be created based on an existing one. I can't figure out how to write the delete function. It should return a new tree based on the old one, but without the deleted element. I will be very grateful for any help(question of life and death)
class Node:
def __init__(self,key,value=None,left = None, right = None, inv = False):
self.key = key
self.value = value
if inv:
left,right = right,left
if left and right:
self.height = max(left.height,right.height)+1
if left:
self.height = left.height+1
if right:
self.height = right.height+1
else:
self.height = 1
self.left = left
self.right = right
class Dict:
def __init__(self):
self.root = None
self.len = 0
def __len__(self):
return self.len
def __getitem__(self,key):
if self.root:
return self.get(self.root,key)
raise KeyError()
def __contains__(self,key):
try:
self.__getitem__(key)
return True
except:
return False
def __setitem__(self,key,value):
if not self.root:
self.root = Node(key,value)
else:
self.root = self.put(self.root,key,value)
self.len+=1
def __delitem__(self,key):
if key not in self:
raise KeyError()
else:
self.root = self.delete(self.root,key)
self.len-=1
#def delete(self,tree,key):
#???
def height(self,tree): return tree.height if tree else 0
def children(self,tree, inv): return (tree.right,tree.left) if inv else (tree.left,tree.right)
def put(self,tree,key,value):
if not tree.left:
if key == tree.key:
self.len-=1
return Node(key,value,tree.left,tree.right)
elif key < tree.key:
return Node(tree.key,tree,Node(key,value),tree)
else:
return Node(tree.key, tree, tree, Node(key, value))
if tree.key == key:
self.put(tree.value,key,value)
return tree
inv = key < tree.key
left, right = self.children(tree, inv)
return self.avl(Node(tree.key, tree.value, left, self.put(right, key, value), inv), inv)
def get(self,tree,key):
if not tree.left:
if key != tree.key:
raise KeyError()
return tree.value
if key == tree.key:
return self.get(tree.value,key)
return self.get(self.children(tree, key < tree.key)[1], key)
def avl(self,tree,inv):
l,r = self.children(tree,inv)
if self.height(r) - self.height(l) < 2:
return tree
rl, rr = self.children(r, inv)
if self.height(rl) - self.height(rr) == 1:
r = self.reassoc(r, not inv)
return self.reassoc(Node(tree.key, tree.value, l, r, inv), inv)
def reassoc(self, tree, inv):
l, r = self.children(tree, inv)
rl, rr = self.children(r, inv)
return Node(r.key, r.value, Node(tree.key, tree.value, l, rl, inv), rr, inv)
def print_tree(tree, indent = ""):
if tree.right:
print_tree(tree.right, indent + " ")
if tree.left == tree.right:
print(f"{indent}{tree.key} -> {tree.value}")
else:
print(f"{indent}{tree.key}")
if tree.value is None:
raise ValueError
if tree.left:
print_tree(tree.left, indent + " ")
t = Dict()
for v, k in enumerate([5,7,2,1,3,6,2,7]):
t.__setitem__(k, v)
print_tree(t.root)
print()
The
put
method needs correction:When a leaf node with the same key is replaced, this new node does not get referenced by a new parent node. If you would get it referenced by a parent node, you would also need to adjust the
value
references to that leaf.This means that
value
reference in internal nodes loses a bit of its usefulness. I would therefore go for another approach where thevalue
attribute of internal nodes will always beNone
. When the targeted key is found in an internal node, then let the drill down continue in both left and right directions. One of these will successfully find the leaf node with that key, and the other not.To make sure that nodes are not replaced when they occur in the subtree that does not change -- as this is an unnecessary burden for memory management -- you could write a
getNode
method that would take the existing node, and the parameters for which you are about to create a new node. It would only create a node when the parameters (key, value, ...etc) are not exactly those of the node that already exists. Otherwise it will return the node as-is.The delete operation is not too difficult. Since your tree has all the values in its leaves, apply this procedure:
None
to the parent of this node, to indicate its deletionSo, this makes a leaf from a previous parent of (two) leaves.
Another challenge is what to do with a key that is deleted, but still occurs in internal nodes. In principle this is not a problem, but for the
put
algorithm it is easier if the presence of a key in an internal node is a guarantee that it exists in a leaf too. Surely it could be solved, but I went for an approach where internal keys are replaced when the corresponding key was deleted from the tree. This replacement key could bubble up from recursion too, as it will be the key of the sibling leaf, next to the deleted leaf.Apply the
avl
logic as you did withput
.Here is your adapted code. Comments indicate where the most important changes were applied: