Generate valid binary search tree with Python hypothesis by paramertizing recursive calls - python

How do you parametrize recursive strategies in the Python hypothesis library?
I'd like to test that the is_valid_bst function works by generating valid BSTs with a recursive strategy.
import hypothesis as hp
from hypothesis import strategies as hps
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
def __repr__(self):
if not self.left and not self.right:
return f'TreeNode({self.val})'
return f'TreeNode({self.val}, left={self.left}, right={self.right}'
def is_valid_bst(node):
if not node:
return True
is_valid = True
if node.left:
is_valid = is_valid and node.val > node.left.val
if node.right:
is_valid = is_valid and node.val < node.right.val
if not is_valid:
return False
return is_valid_bst(node.left) and is_valid_bst(node.right)
#hps.composite
def valid_bst_trees(draw, strategy=None, min_value=None, max_value=None):
val = draw(hps.integers(min_value=min_value, max_value=max_value))
node = TreeNode(val)
node.left = draw(strategy)
node.right = draw(strategy)
return node
def gen_bst(tree_strategy, min_value=None, max_value=None):
return hps.integers(min_value=min_value, max_value=max_value).flatmap(
lambda val: valid_bst_trees(
strategy=tree_strategy, min_value=min_value, max_value=max_value))
#hp.given(hps.recursive(hps.just(None), gen_bst))
def test_is_valid_bst_works(node):
assert is_valid_bst(node)

I figured it out. My main misunderstanding was:
The tree_strategy created by the hypothesis.recursive strategy is safe to draw from multiple times and will generate appropriate recursion.
A few other gotchas:
The base case needs both None and a singleton tree. With only None, you'll only generate None.
For the singleton tree, you must generate a new tree every time. Otherwise, you'll end up with cycles in the tree since each node is the same tree. Easiest way to accomplish this is hps.just(-111).map(TreeNode).
You'll need to overwrite the base case if it's a singleton tree to respect min_value and max_value.
Full working solution:
import hypothesis as hp
from hypothesis import strategies as hps
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
def __repr__(self):
if not self.left and not self.right:
return f'TreeNode({self.val})'
return f'TreeNode({self.val}, left={self.left}, right={self.right}'
def is_valid_bst(node):
if not node:
return True
is_valid = True
if node.left:
is_valid = is_valid and node.val > node.left.val
if node.right:
is_valid = is_valid and node.val < node.right.val
if not is_valid:
return False
return is_valid_bst(node.left) and is_valid_bst(node.right)
#hps.composite
def valid_bst_trees(
draw, tree_strategy, min_value=None, max_value=None):
"""Returns a valid BST.
Idea is to pick an integer VAL in [min_value, max_value) for this tree and
and use it as a constraint for the children by parameterizing
`tree_strategy` so that:
1. The left child value is in [min_value, VAL).
2. The right child value is in (VAL, min_value].
"""
# We're drawing either a None or a singleton tree.
node = draw(tree_strategy)
if not node:
return None
# Can't use implicit boolean because the values might be falsey, e.g. 0.
if min_value is not None and max_value is not None and min_value >= max_value:
return None
# Overwrite singleton tree.val with one that respects min and max value.
val = draw(hps.integers(min_value=min_value, max_value=max_value))
node.val = val
node.left = draw(valid_bst_trees(
tree_strategy=tree_strategy,
min_value=min_value,
max_value=node.val - 1))
node.right = draw(valid_bst_trees(
tree_strategy=tree_strategy,
min_value=node.val + 1,
max_value=max_value))
return node
def gen_bst(tree_strategy, min_value=None, max_value=None):
return valid_bst_trees(
tree_strategy=tree_strategy,
min_value=min_value,
max_value=max_value)
# Return a new, distinct tree node every time to avoid self referential trees.
singleton_tree = hps.just(-111).map(TreeNode)
#hp.given(hps.recursive(hps.just(None) | singleton_tree, gen_bst))
def test_is_valid_bst_works(node):
assert is_valid_bst(node)
# Simple tests to demonstrate how the TreeNode works
def test_is_valid_bst():
assert is_valid_bst(None)
assert is_valid_bst(TreeNode(1))
node1 = TreeNode(1)
node1.left = TreeNode(0)
assert is_valid_bst(node1)
node2 = TreeNode(1)
node2.left = TreeNode(1)
assert not is_valid_bst(node2)
node3 = TreeNode(1)
node3.left = TreeNode(0)
node3.right = TreeNode(1)
assert not is_valid_bst(node3)

Related

Delete a branch in Binary tree( Linked List implementation)

I am trying to create a binary tree using a linked list. I want to delete a node in a binary tree, the way I implemented is to set the address(pointer) to the branch that I want to delete as None, but when I run the traversal methods, the branch still shows up.
here is my code
class tree:
def __init__(self,val):
self.val=val
self.left=None
self.right=None
def preorder(root):
if root is None:
return
print(root.val)
tree.preorder(root.left)
tree.preorder(root.right)
def inorder(root):
if root is None:
return
tree.inorder(root.left)
print(root.val)
tree.inorder(root.right)
def postorder(root):
if root is None:
return
tree.postorder(root.left)
tree.postorder(root.right)
print(root.val)
def levelorder(root):
if root is None:
return
Q=[]
Q.append(root)
while Q!=[]:
l=len(Q)
for i in range(l):
print(Q[i].val)
temp=[]
for i in Q:
if i.left is not None:
temp.append(i.left)
if i.right is not None:
temp.append(i.right)
Q.clear()
Q=temp.copy()
def search(root,value):
o=[]
o.append(tree.levelorder(root))
if value in o:
return "Found"
else:
return "Not Found"
def insert(root,value,where):
if root is None:
return
Q=[]
Q.append(root)
value=tree(value)
while Q!=[]:
for i in Q:
if i.val==where:
if i.left is not None:
i.right=value
else:
i.left=value
return
temp=[]
for i in Q:
if i.left is not None:
temp.append(i.left)
if i.right is not None:
temp.append(i.right)
Q.clear()
Q=temp.copy()
def delete(root,value):
if root is None:
return
Q=[]
Q.append(root)
value=tree(value)
while Q!=[]:
for i in Q:
if i.left is not None and i.left.val==value:
i.left.val=None
i.left=None
if i.right is not None and i.right.val==value:
i.right.val=None
i.right=None
return
temp=[]
for i in Q:
if i.left is not None:
temp.append(i.left)
if i.right is not None:
temp.append(i.right)
Q.clear()
Q=temp.copy()
and here is how I created the tree
base=tree("drinks")
L=tree("hot")
R=tree("cold")
LL=tree("coffe")
LR=tree("tea")
LRL=tree("w milk")
LRR=tree("wo milk")
base.left=L
base.right=R
L.left=LL
L.right=LR
LR.left=LRL
LR.right=LRR
Now when I run the delete method, the object that is supposed to be deleted still shows up.
tree.delete(base,"w milk")
tree.levelorder(base)
drinks
hot
cold
coffe
tea
ice cream
w milk
wo milk <=== the node i am trying to delete
The main issues:
delete method's while loop will exit immediately, because of the return statement
The value to compare with should not be turned into a node with value=tree(value)
Some remarks on your code:
You'll not be able to ever delete the root node of the tree, since you only check the values of child nodes. In order to allow the root to be deleted, and to represent an empty tree, you should really consider creating a second class. The current class could then be renamed to Node and the new class could become Tree.
It is a common habit to use a capital first letter for class names, while for variable names you would always start with a lower case letter. So not Q, but q.
It is not such good practice to print inside methods of such classes: these methods should just provide tools to iterate over nodes, but they should not print them. Use yield instead.
For instance, search should return the node when it is found and None otherwise. Don't print the result.
The insert method may actually delete node(s) when both the left and right child of the where node are already occupied. It would be better to reject such an insert.
In the OOP pattern, it is widely accepted to call the first argument of instance methods self. In OOP, you would call these methods with the dot notation: so instead of tree.postorder(root.left) you would do root.left.postorder().
You have quite some code repetition: mainly for traversals, and where you use the queue algorithm twice. Try to avoid that.
Here is how you could modify your code to align to these suggestions:
class Node:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
def traverse(self, kind=0, parent=None):
if kind == 0: # pre-order
yield (self, parent) # don't print in methods
if self.left:
yield from self.left.traverse(kind, self)
if kind == 1: # in-order
yield (self, parent)
if self.right:
yield from self.right.traverse(kind, self)
if kind == 2: # post-order
yield (self, parent)
class Tree:
def __init__(self):
self.root = None # Representing an empty tree
def traversevalues(self, kind=0):
if self.root:
for node, parent in self.root.traverse(kind):
yield node.val
def preorder(self):
yield from self.traversevalues(0)
def inorder(self):
yield from self.traversevalues(1)
def postorder(self):
yield from self.traversevalues(2)
def levelorder(self):
if self.root:
q = [self.root]
while q:
temp = []
for node in q:
yield node.val
if node.left:
temp.append(node.left)
if node.right:
temp.append(node.right)
q = temp
def search(self, value):
if self.root:
return next((edge for edge in self.root.traverse() if edge[0].val == value), None)
def insert(self, value, where=None):
if not where:
if self.root:
raise ValueError("Must call insert with second argument")
else:
self.root = Node(value)
else:
edge = self.search(where)
if edge:
parent = edge[0]
if not parent.left:
parent.left = Node(value)
elif not parent.right:
parent.right = Node(value)
else:
raise ValueError(f"Node {where} already has 2 children")
else:
raise ValueError(f"Node {where} not found")
def deletesubtree(self, value):
edge = self.search(value)
if edge:
node, parent = edge
if parent.left == node:
parent.left = None
else:
parent.right = None
else:
raise ValueError(f"Node {value} not found")
tree = Tree()
tree.insert("drinks")
tree.insert("hot", "drinks")
tree.insert("cold", "drinks")
tree.insert("coffee", "hot")
tree.insert("tea", "hot")
tree.insert("with milk", "tea")
tree.insert("without milk", "tea")
print(*tree.levelorder())
tree.deletesubtree("without milk")
print(*tree.levelorder())

Binary Search Tree Find minimum not clear

The logic I tried:
def min_tree_value(self):
while self.left:
self.left = self.left.left
return self.data
Actual Python program Logic:
def min_tree_value(self):
if self.left is None:
return self.data
return self.left.min_tree_value()
The actual Python program logic is in recursion form. I tried the same logic in While loop()
I'm not sure whether my logic is correct. Do help me to figure out the incorrect logic and point where I'm Wrong.
Your logic is almost there, but not quite:
def min_tree_value(self):
node = self
while node.left:
# don't change the structure by rebinding node.left,
# but iterate the tree by moving along nodes!
node = node.left
return node.data
Note that in the original code, you never reassign self before returning its value, so you always returned the root value.
First of all, the question asks about finding the minimum element in a binary tree.
The algorithm you used, will find the minimum element in the Binary Search Tree (as the leftmost element is the minimum).
For finding minimum element in a simple Binary Tree, use the following algorithm:
# Returns the min value in a binary tree
def find_min_in_BT(root):
if root is None:
return float('inf')
res = root.data
lres = find_min_in_BT(root.leftChild)
rres = find_min_in_BT(root.rightChild)
if lres < res:
res = lres
if rres < res:
res = rres
return res
Additions to the answer after OP changed the question:
The logic for the algorithm you tried is correct, with a small correction in the implementation: self = self.data. Both of them find the leftmost element.
I have also tested both the functions which return the same output:
class Node:
def __init__(self, data):
self.left = None
self.right = None
self.data = data
def insert(self, data):
if self.data:
if data < self.data:
if self.left is None:
self.left = Node(data)
else:
self.left.insert(data)
elif data > self.data:
if self.right is None:
self.right = Node(data)
else:
self.right.insert(data)
else:
self.data = data
def findval(self, lkpval):
if lkpval < self.data:
if self.left is None:
return str(lkpval)+" Not Found"
return self.left.findval(lkpval)
elif lkpval > self.data:
if self.right is None:
return str(lkpval)+" Not Found"
return self.right.findval(lkpval)
else:
print(str(self.data) + ' is found')
def PrintTree(self):
if self.left:
self.left.PrintTree()
print( self.data),
if self.right:
self.right.PrintTree()
def min_tree_value_original(self):
if self.left is None:
return self.data
return self.left.min_tree_value_original()
def min_tree_value_custom(self):
while self.left:
self = self.left
return self.data
root = Node(12)
root.insert(6)
root.insert(14)
root.insert(3)
root.insert(3)
root.insert(1)
root.insert(0)
root.insert(-1)
root.insert(-2)
print(root.min_tree_value_original())
print(root.min_tree_value_custom())
Output:
-2
-2
Here -2 is the smallest and the leftmost element in the BST.

How can I find the depth of a specific node inside a binary tree?

I'm trying to figure out a recursive solution to this problem. The main thing is to return the level in the binary tree where the node is.
def find_depth(tree, node):
if node == None:
return 0
else:
return max(find_depth(tree.left))
#recursive solution here
Using this class for the values:
class Tree:
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
Example: Calling find_depth(tree, 7) should return the level where 7 is in the tree. (level 2)
3
/ \
7 1 <------ return that 7 is at level 2
/ \
9 3
maybe this is what you are looking for
def find_depth(tree, node):
if node is None or tree is None:
return 0
if tree == node:
return 1
left = find_depth(tree.left, node)
if left != 0:
return 1 + left
right = find_depth(tree.right, node)
if right != 0:
return 1 + right
return 0
You need to provide information about depth in find_depth call. It might look like this (assuming 0 is a sentinel informing that node is not found):
def find_depth(tree, node, depth=1):
if node == None:
return 0
if tree.value == node:
return depth
left_depth = find_depth(tree.left, node, depth+1)
right_depth = find_depth(tree.right, node, depth+1)
return max(left_depth, right_depth)
Then you call it with two parameters: x = find_depth(tree, 7).
Recursion is a functional heritage and so using it with functional style yields the best results -
base case: if the input tree is empty, we cannot search, return None result
inductive, the input tree is not empty. if tree.data matches the search value, return the current depth, d
inductive, the input tree is not empty and tree.data does not match. return the recursive result of tree.left or the recursive result of find.right
def find (t = None, value = None, d = 1):
if not t:
return None # 1
elif t.value == value:
return d # 2
else:
return find(t.left, value, d + 1) or find(t.right, value, d + 1) # 3
class tree:
def __init__(self, value, left = None, right = None):
self.value = value
self.left = left
self.right = right
t = tree \
( 3
, tree(7, tree(9), tree(3))
, tree(1)
)
print(find(t, 7)) # 2
print(find(t, 99)) # None
You can implement the find method in your tree class too
def find (t = None, value = None, d = 1):
# ...
class tree
def __init__ #...
def find(self, value)
return find(self, value)
print(t.find(7)) # 2
print(t.find(99)) # None

recursive insert only inserts first element python bst

I'm trying to implement insert function of bst but my recursive approach only inserts the first value. My approach was to traverse until the empty child and set that node to new value.
class BSTNode(object):
def __init__(self, val):
self.val = val
self.left = None
self.right = None
def insert(self, val):
if self.root is None:
self.root = BSTNode(val)
else:
self.insertNode(self.root,val)
def insertNode(self,node, val):
if node is None:
node = BSTNode(val)
return
elif val <= node.val:
node.left = self.insertNode(node.left, val)
elif val > node.val:
node.right = self.insertNode(node.right, val)
bst = new BST()
bst.insert(5)
bst.insert(10)
You're not returning BSTNode in insertNode (so you create a node but return None). You should do this instead:
if node is None:
return BSTNode(val)
So the full method becomes:
def insertNode(self, node, val):
if node is None:
return BSTNode(val)
elif val <= node.val:
node.left = self.insertNode(node.left, val)
elif val > node.val:
node.right = self.insertNode(node.right, val)
return node
It might be helpful to first create a node class... it's easier to think of a node alone than it is to think about a Tree.
class Node:
def __init__(self,value):
self.value = value
self.left = None
self.right = None
def insert_value(self,value):
# there are 3 cases
if not self.value: # in one case this node has no value
self.value = value
elif value < self.value: # if value is less than this nodes value
# then insert it to the left
if not self.left:
self.left = Node(value)
else:
self.left.insert_value(value)
else: # otherwise if value is greater than or equal to this nodes value
# then insert it to the right
if not self.right:
self.right = Node(value)
else:
self.right.insert_value(value)
Once you have this Node class the tree implementation becomes trivial.
class BST:
def __init__(self,root_value=None):
self.root = Node(root_value)
def insert_value(self,value):
self.root.insert_value(value)
bst = BST()
bst.insert_value(5)
bst.insert_value(10)
print(bst.root)
Now you certainly do not have to solve this in this fashion ... but it makes it easier to reason about (at least for me).

Binary seach tree, remove node, need help here (reposting)

My code is looking like this:
class Treenode:
def __init__(self,data,left=None,right=None):
self.data=data
self.left=left
self.right=right
def __str__(self):
return str(self.data)
def delete(self):
child=self.left
grandchild=child.right
print(grandchild)
if self.left == self.right==None:
return None
if self.left==None:
return self.right
if self.right==None:
return self.left
if grandchild:
while grandchild.right:
child = grandchild
grandchild = child.right
self.data = grandchild.data
child.right = grandchild.left
else:
self.left = child.left
self.data = child.data
return self
class Bintree:
def __init__(self):
self.root = None
def put(self,data):
if self.root == None:
self.root = Treenode(data)
return
p = self.root
while True:
if data < p.data:
if p.left == None:
p.left = Treenode(data)
return
else:
p = p.left
elif data > p.data:
if p.right == None:
p.right = Treenode(data)
return
else:
p = p.right
elif data == p.data:
return False
else:
return
def exists(self, data):
return finns(self.root, data)
def isempty(self):
return self.root == None
def height(self):
def hp(tree):
if tree is None:
return 0
else:
return 1 + max(hp(tree.left), hp(tree.right))
return hp(self.root)
def printTree(self):
skriv(self.root)
def remove(self, data):
if self.root and self.root.data == data: #self.root kanske inte behövs, undersök
self.root = self.root.delete()
return
else:
parent = self.root
while parent:
if data < parent.data:
child = parent.left
if child and child.data== data:
parent.left = child.delete()
return
parent = child
else:
child = parent.right
if child and child.data == data:
parent.right = child.delete()
return
parent = child
def skriv(tree):
if tree == None:
return
skriv(tree.left)
print(tree.data)
skriv(tree.right)
def finns(roten, key):
if roten == None:
return False
if key == roten.data:
return True
elif key < roten.data:
return finns(roten.left, key)
else:
return finns(roten.right, key)
Everything about my code is working, and I've simply added (see copied) the delete method and the remove method. Im desperately trying to understand the delete-method but I cannot understand it. I use this code to run the thing and see how the tree is implemented:
from labb8test import Bintree
from labb8test import Treenode
tree = Bintree()
tree.put(8)
tree.put(3)
tree.put(1)
tree.put(6)
tree.put(4)
tree.put(7)
tree.put(10)
tree.put(14)
tree.put(13)
tree.remove(6)
tree.printTree()
I'm trying to draw it on a paper and see, especially how the while-loop is working. According to my above code, I would think it is like this:
child = self.left (child=3) grandchild= child.right=self,left.right=6. If grandchild (yes, 6) while grandchild.right (yes, 7) child = grandchild, 3-->6 grandchild = child.right (is this even needed, 6--->6?) self.data=grandchild.data (8--->6) child.right = grandchild.left (6---->4) ??
But it cannot be like this, because then the while-loop would never end. Is there anyone who can help me understanding where I lose myself?
I recommend you this material from algorithm Princeton:
http://algs4.cs.princeton.edu/32bst/
The delete method is using this approach to delete a node from a bst.
Delete. We can proceed in a similar manner to delete any node that has
one child (or no children), but what can we do to delete a node that
has two children? We are left with two links, but have a place in the
parent node for only one of them. An answer to this dilemma, first
proposed by T. Hibbard in 1962, is to delete a node x by replacing it
with its successor. Because x has a right child, its successor is the
node with the smallest key in its right subtree. The replacement
preserves order in the tree because there are no keys between x.key
and the successor's key. We accomplish the task of replacing x by its
successor
in four (!) easy steps:
Save a link to the node to be deleted in t
Set x to point to its successor min(t.right)
Set the right link of x (which is supposed to point to the BST containing all the keys larger than x.key) to deleteMin(t.right), the
link to the BST containing all the keys that are larger than x.key
after the deletion.
Set the left link of x (which was null) to t.left (all the keys that are less than both the deleted key and its successor).

Categories

Resources