How to implement 'range' with a python BST - python

My code at the moment allows me to search for a specific node, I would like to edit it so that I can search within a range of numbers. For example, I have a price list of apples, and I would like to add all apples to a list/dictionary that cost between $2-$4 or something like that.
Here is my current code
def valueOf(self, key):
node = self._bstSearch(self._root, key)
assert node is not None, "Invalid map key."
return node.value
def _bstSearch(self, subtree, target):
if subtree is None:
return None
elif target < subtree.key:
return self._bstSearch( subtree.left, target)
elif target > subtree.key:
return self._bstSearch(subtree.right, target)
else:
return subtree
I think I should be editing target to change it from a single item search to a ranged item search but I'm not 100% sure how to

Using recursive in-order traversal:
def range(self, a, b):
return self._traverse_range(self._root, a, b)
def _traverse_range(self, subtree, a, b, cumresult=None):
if subtree is None:
return
# Cumulative variable.
if cumresult is None:
cumresult = []
# Traverse LEFT subtree if it is possible to find values in required range there.
if subtree.key > a:
self._traverse_range(subtree.left, a, b, cumresult)
# Push VALUE if it is in our range.
if a <= subtree.key <= b: # Change to strict "< b" to act like python's range
cumresult.append(subtree.key)
# Traverse RIGHT subtree if it is possible to find values in required range there.
if subtree.key < b:
self._traverse_range(subtree.right, a, b, cumresult)
return cumresult

Related

Finding Shortest Path to Node with Given Value - Binary Tree

Given a Binary Tree, I wanted to find the depth at which a specific value appears.
def find_depth_of_val(self, root, val):
if not root:
return 10000000
if root.val == val:
return 0
return 1 + min(self.find_node(root.right, val), self.find_node(root.left, val))
This is the idea I had, you'll probably see a wild "return 10000000", which I used to make it so that if there is nothing else on a specific path, i.e. a leaf's next has been reached without finding the node we want, that the function knows the answer isn't there and doesn't return that possibility.
What I'm wondering is whether there's a better way to do this, one without using a random "return 10000000".
Edit: Someone gave me a solution in which I kind of change it to:
def find_depth_of_val(self, root, val):
if not root:
return None
if root.val == val:
return 0
right_side = self.find_node(root.right, val)
left_side = self.find_node(root.left, val)
if right_side is None:
return left_side
elif left_side is None:
return right_side
else:
return 1 + min(self.find_node(root.right, val), self.find_node(root.left, val))
In such a case like this, how should I type hint it considering we could be returning either None or an integer?
That said, I'm still open to seeing if anyone else has any different solution designs!!
This looks weird that find_depth_of_val has both self (a tree) and root (another tree) as parameters. Besides when you state your problem you talk of only one tree and self is actually not used in your method.
So assuming your values in the tree are unique, here is a solution. The method returns None if no path is found or otherwise the depth. Optional[int] means either an int or either None:
def find_depth_of_val(self, val: int) -> Optional[int]:
if self.val == val:
return 0
for node in (self.left, self.right):
if node is not None:
depth = node.find_depth_of_val(val)
if depth is not None:
return depth + 1
return None

Return smaller values in a BST inorder

I am trying to implement this method, "smaller" for a BST, that returns the values in the tree which are smaller than a given item, in order.
class BinarySearchTree:
def __init__(self, root: Optional[Any]) -> None:
if root is None:
self._root = None
self._left = None
self._right = None
else:
self._root = root
self._left = BinarySearchTree(None)
self._right = BinarySearchTree(None)
def is_empty(self) -> bool:
return self._root is None
def smaller(self, item: Any) -> List:
if self.is_empty():
return []
else:
return self._left.items() + [self._root] + self._right.items()
So far, the "smaller" method will return all of the values in the tree in order, but I'm not sure how to check if those values are smaller and than a given item, and to only return those in a list.
Let's write pseudocode for in-order-tree-walk method which prints the keys of BST in sorted (in-order) order.
in-order-tree-walk(T, x)
if (T != NULL)
in-order-tree-walk(T.left, x)
print T's key
in-order-tree-walk(T.right, x)
smaller method has exactly the same structure as in-order-tree-walk except that it's additional condition which makes it to print keys that are smaller. smaller method's pseudocode will look like
smaller(T, x)
if (T != NULL)
smaller(T.left, x)
if (T's key is less than x)
print T's key
smaller(T.right, x)
We're done. smaller method is now completed. Now let's look at your actual implementation.
Your code prints all keys of BST in sorted order because of the way you implemented it. You have the problem the following part:
def smaller(self, item: Any) -> List:
if self.is_empty():
return []
else:
return self._left.items() + [self._root] + self._right.items()
In return self._left.items() + [self._root] + self._right.items(), you don't check whether the [self.root] is less than item's value or not. You have to check that because you put restraint on printing the key of tree, but in implementation you didn't check it. Since I'm not qualified in Python, I can't complete this part, but I think you've get what the problem is with your code based on above explanations.

Sum of nodes in a subtree (not binary)

I'm currently trying to find the sum of all nodes in a specified subtree. For example if I have a tree
A(5)
/ \
B(5) C(6)
/ / \
D(3) E(3) F(7)
|
G(1)
and I want to know the the sum(C), which should return 17.
This is the code I came up with using recursion, but I can't seem to reach a subtree which has more than 2 levels. E.g. my algorithm doesn't seem to reach G. I'm trying to get better at recursion, but I can't seem to fix this.
def navigate_tree(node,key): #node of the root of subtree, along with its key
children = node.get_children()
if (len(children) ==0):
return node.key
else:
for child in children: #not a binary tree so trying to loop through siblings
key += navigate_tree(child,key) #summing up key recursively
return key
You would be better with an improved interface and being able to lean on the features of collections:
def navigate_tree(node):
children = node.get_children()
key = node.key
for child in children:
key += navigate_tree(child)
return key
# class Node and data A..G elided
print(navigate_tree(C))
Output:
17
The reason why your code appeared not to work, was that you were passing the previous key down to the next level of recursion. However, your code seemed to recurse OK. If you had added some print(node.key) you would have seen that you were visiting all the correct nodes.
You can use recursion with sum:
class Node:
def __init__(self, n, v, c=[]):
self.name, self.val, self.children = n, v, c
def get_sum(node):
return node.val+sum(map(get_sum, node.children))
tree = Node('A', 5, [Node('B', 5, [Node('D', 3)]), Node('C', 6, [Node('E', 3), Node('F', 7, [Node('G', 1)])])])
print(get_sum(tree.children[-1]))
Output:
17
However, if you do not have access to the exact node C, you can apply a simple search as part of the recursive function:
def get_sum(t, node):
def inner_sum(d, s=False):
return d.val*(s or d.name == t)+sum(inner_sum(i, s or d.name == t) for i in d.children)
return inner_sum(node)
print(get_sum('C', tree))
Output:
17

Algorithm to exchange the roles of two randomly chosen nodes from a tree moving pointers

I have created an algorithm whose purpose should be of, given two nodes A and B in a BST, it switches the roles (or positions in the tree) of the two by simply moving pointers. In my representation of a BST, I am using a double linked connection (i.e. A.parent == B and (B.left == A) or (B.right == A)). I am not sure if it's completely correct or not. I have divided the algorithm in two situations.
A and B are directly connected (either A is the parent of B or B the parent of A)
All the other cases
For each of the previous cases I have created a nested function. I would like to have your opinion on the first the correctness of the algorithms and if I can somehow then improve it. Here's the code:
def switch(self, x: BSTNode, y: BSTNode, search_first=False):
if not x:
raise ValueError("x cannot be None.")
if not y:
raise ValueError("y cannot be None.")
if x == y:
raise ValueError("x cannot be equal to y")
if search_first:
if not self.search(x.key) or not self.search(y.key):
raise LookupError("x or y not found.")
def switch_1(p, s):
"""Switches the roles of p and s,
where p (parent) is the direct parent of s (son)."""
assert s.parent == p
if s.is_left_child():
p.left = s.left
if s.left:
s.left.parent = p
s.left = p
s.right, p.right = p.right, s.right
if s.right:
s.right.parent = s
if p.right:
p.right.parent = p
else:
p.right = s.right
if s.right:
s.right.parent = p
s.right = p
s.left, p.left = p.left, s.left
if s.left:
s.left.parent = s
if p.left:
p.left.parent = p
if p.parent:
if p.is_left_child():
p.parent.left = s
else:
p.parent.right = s
else: # p is the root
self.root = s
s.parent = p.parent
p.parent = s
def switch_2(u, v):
"""u and v are nodes in the tree
that are not related by a parent-son
or a grandparent-son relantionships."""
if not u.parent:
self.root = v
if v.is_left_child():
v.parent.left = u
else:
v.parent.right = u
elif not v.parent:
self.root = u
if u.is_left_child():
u.parent.left = v
else:
u.parent.right = v
else: # neither u nor v is the root
if u.is_left_child():
if v.is_left_child():
v.parent.left, u.parent.left = u, v
else:
v.parent.right, u.parent.left = u, v
else:
if v.is_left_child():
v.parent.left, u.parent.right = u, v
else:
v.parent.right, u.parent.right = u, v
v.parent, u.parent = u.parent, v.parent
u.left, v.left = v.left, u.left
u.right, v.right = v.right, u.right
if u.left:
u.left.parent = u
if u.right:
u.right.parent = u
if v.left:
v.left.parent = v
if v.right:
v.right.parent = v
if x.parent == y:
switch_1(y, x)
elif y.parent == x:
switch_1(x, y)
else:
switch_2(x, y)
I really need that switch works in all cases no matter which nodes x or y we choose. I have already done some tests, and it seems to work, but I am still not sure.
EDIT
Eventually, if it's helpful somehow, here you have the complete implementation of my BST (with the tests I am doing):
https://github.com/dossan/ands/blob/master/ands/ds/BST.py
EDIT 2 (just a curiosity)
#Rishav commented:
I do not understand the intention behind this function.. if it is to swap two nodes in the BST, is it not sufficient to swap their data instead of manipulating pointers?
I answered:
Ok, maybe I should have added a little bit more about the reason behind all this "monster" function. I can insert BSTNode objects or any comparable objects in my BST. When the user decides to insert any comparable object, the responsibility of creating the BSTNode is mine, therefore the user has no access to a initial BSTNode reference, unless they search for the key. But a BSTNode would only be returned after the insertion of the key, or there's already another BSTNode object in the tree with the same key (or value), but this latter case is irrelevant.
The user can also insert a BSTNode object in the tree which has an initial (and should remain constant) key (or value). Nevertheless, if I just exchanged the values or keys of the nodes, the user would have a reference to a node with a different key then the key of the node he inserted. Of course, I want to avoid this.
you need proper unit testing. I recommend python-nose - very easy to use.
As for the test vectors I'd recommend using every potential combination of two nodes a and b:
In the case of BST trees you have 3 types of nodes:
leaf node,
1-child node,
2-children node.
in combination with the following additional cases:
a is root, or
a is the parent of b,
a is not the parent of b.
and their combinations as well (also in the symmetric situation).
then after swapping you'll need to check all the nodes involved i.e.:
a,b, children of a and b, parents of a and b if everything went as planned.
I'd do that using a small tree that contains all the types of nodes.
Then go through all possible combinations of the nodes and swap the nodes and check against the expected outcome, and then swap again to bring the tree back to its original state.
[ EDIT ]
If your question was how to avoid all the tedious work. You may consider looking for some well established BST implementation and compare results with your function. Vectors can be created automatically by using a prepared tree and generating all possible pairs of nodes of this tree.
[/EDIT]
As for the unwanted input to the function. You'll need to use your imagination although in my opinion you have most of the cases covered. Except the one that Austin Hastings mentions where at least on of the input nodes does not belong to the tree.
I found an old version of the same function written for one of my private projects, maybe you can find it useful:
def swap( a, b ):
if a == b: return
if a is None or b is None: return
#if a not in self or b not in self: return
if b.parent == a:
a, b = b, a
#swap connections naively
a.parent, b.parent = b.parent, a.parent
a.left, b.left = b.left, a.left
a.right, b.right = b.right, a.right
if b.parent == b: #b was the p of a
b.parent = a
if a.parent is not None:
if a.parent.left == b: a.parent.left = a
else: a.parent.right = a
else:
self.root = a
if b.parent is not None:
if b.parent.left == a: b.parent.left = b
else: b.parent.right = b
else:
self.root = b
if a.right is not None: a.right.parent = a
if a.left is not None: a.left.parent = a
if b.right is not None: b.right.parent = b
if b.left is not None: b.left.parent = b
and performance optimised version:
def swap_opt( a, b ):
if a == b: return
if a is None or b is None: return
#if a not in self or b not in self: return
if b.p == a:
a, b = b, a
#swap connections naively
a.p, b.p = b.p, a.p
a.l, b.l = b.l, a.l
a.r, b.r = b.r, a.r
if b.p == b: #b was the p of a
b.p = a
if a.l == a:
a.l = b
if a.r is not None: a.r.p = a
else:
a.r = b
if a.l is not None: a.l.p = a
if b.r is not None: b.r.p = b
if b.l is not None: b.l.p = b
if a.p is not None:
if a.p.l == b: a.p.l = a
else: a.p.r = a
else:
#set new root to a
pass
else:
if a.r is not None: a.r.p = a
if a.l is not None: a.l.p = a
if b.r is not None: b.r.p = b
if b.l is not None: b.l.p = b
if a.p is not None:
if a.p.l == b: a.p.l = a
else: a.p.r = a
else:
#set new root to a
pass
if b.p is not None:
if b.p.l == a: b.p.l = b
else: b.p.r = b
else:
#set new root to b
pass
I haven't done proper unit tests for this code - it worked as I expected it to. I was more interested in performance differences between the implementations.
swap_opt handles neighbouring nodes a bit faster giving it around 5% of speed increase over the compact implementation of swap. [EDIT2] But that depends on the tree used for testing and hardware [/EDIT2]
Your BST.py defines class BST. Members of that class have an element, self.root that can point to a node. Your code, as shown, does not account for this.
I believe you need to handle these cases:
Swap the root node with one of its children.
Swap the root node with a non-child.
Swap a non-root node with one of its children.
Swap a non-root node with a non-child non-root node.
Edit: After re-examining switch_1, I think you do handle all the cases.
Also, there is the possibility that a caller could request you swap a node that is not a member of the tree for a node that is a member. Or swap two nodes that are both not members of the current tree. It would cost some code to detect these cases, but you could probably get by with a dict or set to trace tree membership. I don't know if you want to consider "swap-ins" as a valid operation or not.
In several places you compare nodes using ==. That is an operation that can be overridden. You should use is and is not for identity comparisons and comparisons against None.
Finally, please consider Pythonifying your BST class. It is a mutable iterable container, so it should support the standard operations as much as possible.

Deleting Parts of Binary Search Tree

I am trying to solve the following problem:
Return the root of a binary search tree t modified to contain only values <= k. (Using the normal BST class where we have an item,left and right)
def prune(t,k):
if not t:
return None
if k < t.item
while t.item > k:
t = t.left
return t
I think I am doing it completely wrong.. Maybe there is some easy recursive way to do it?
I think you want something like:
def prune(t, k):
if t is None or t.item > k:
return None
t.right = prune(t.right, k)
return t
This is recursive, and will "prune" when it reaches any None node or node larger than k. As this is a BST, t.item <= k means all nodes in t.left will be too, so we can ignore them.

Categories