Python Object has no attribute error - python

class EmptyMap():
"""
EmptyMap has no slots
"""
__slots__ = ()
class NonEmptyMap():
"""
Has slots of left, key, value, and right.
"""
__slots__ = ('left', 'key', 'value', 'right')
def mkEmptyMap():
"""
Is a function that takes no arguments and returns an instance of EmptyMap
"""
return EmptyMap()
def mkNonEmptyMap(left, key, value, right):
"""
Is a function that takes a map, a key, a value, and another map,
and returns an instance of NonEmptyMap. This function merely initializes the slots;
it is possible to use this function to create trees that are not binary search trees.
"""
nonEmptyMap = NonEmptyMap()
nonEmptyMap.left = left
nonEmptyMap.key = key
nonEmptyMap.value = value
nonEmptyMap.right = right
return nonEmptyMap
def mapInsert(key, value, node):
"""
Is a function that takes a key, a value, and a map, and returns an instance
of NonEmptyMap. Further, the map that is returned is a binary search tree based
on the keys. The function inserts the key-value pair into the correct position in the
map. The map returned need not be balanced. Before coding, review the binary
search tree definition and the structurally recursive design pattern, and determine
what the function should look like for maps. If the key already exists, the new value
should replace the old value.
"""
if isinstance(node, EmptyMap):
return mkNonEmptyMap(mkEmptyMap(), key, value, mkEmptyMap())
else:
if key > node.key:
node.right = mapInsert(key, value, node.right)
return node.right
elif key < node.key:
node.left = mapInsert(key, value, node.left)
return node.left
elif key == node.key:
node.value = value
return mapInsert(key, value, node)
else:
raise TypeError("Bad Tree Map")
def mapToString(node):
"""
Is a function that takes a map, and returns a string that represents the
map. Before coding, review the structurally recursive design pattern, and determine
how to adapt it for maps. An EmptyMap is represented as ’ ’. For an instance of
NonEmptyMap, the left sub-tree appears on the left, and the right sub-tree appears
on the right.
"""
if isinstance(node, EmptyMap):
return '_'
elif isinstance(node, NonEmptyMap):
return '(' + mapToString(node.left) + ',' + str(node.key) + '->' + str(node.value) + ',' + mapToString(node.right)+ ')'
else:
raise TypeError("Not a Binary Tree")
def mapSearch(key, node):
"""
Is a function that takes a key and a map, and returns the value associated
with the key or None if the key is not there. Before coding, review the binary search
tree definition and the structurally recursive design pattern, and determine how it
should look for maps.
"""
if isinstance(node, EmptyMap):
return 'None'
elif isinstance(node, NonEmptyMap):
if key == node.key:
return str(node.value)
elif key < node.key:
return mapSearch(key, node.left)
elif key > node.key:
return mapSearch(key, node.right)
else:
raise TypeError("Not a Binary Tree")
def main():
smallMap = mapInsert(\
'one',\
1,\
mapInsert(\
'two',\
2,\
mapInsert(\
'three',\
3,\
mkEmptyMap())))
print(smallMap.key)
print(smallMap.left.key)
print(smallMap.right.key)
main()
When I run the program, I got a syntax which I have no idea what I am doing wrong. I am pretty sure the emptymap has an object which is in mkNonEmptyMap function. This is my homework problem.
A map is a data structure that associates values with keys. One can search for a particular key to find its associated value. For example, the value 3 could be associated with the key ’three’.
one
Traceback (most recent call last):
File "/Users/USER/Desktop/test.py", line 113, in <module>
main()
File "/Users/USER/Desktop/test.py", line 110, in main
print(smallMap.left.key)
AttributeError: 'EmptyMap' object has no attribute 'key'

If you look at what's in smallMap, its left and right are both EmptyMaps. So of course smallMap.left.key isn't going to work—EmptyMaps don't have keys.
So, why is it wrong? Well, let's break that monster expression down into steps and see where it goes wrong:
>>> empty = mkEmptyMap()
>>> mapToString(empty)
'_'
>>> three = mapInsert('three', 3, mkEmptyMap())
>>> mapToString(three)
'(_,three->3,_)'
>>> two = mapInsert('two', 2, three)
>>> mapToString(two)
(_,two->2,_)
There's a problem. The two object has no left or right. What about three?
>>> mapToString(three)
(_,three->3,(_,two->2,_))
OK, so we do have a valid balanced tree—but it's not in the two object returned by mapInsert, it's in the three object that you passed in to mapInsert (which your original program isn't even keeping a reference to).
So, why is that happening? Is that valid? It depends on your design. If you want to mutate your arguments like this, it's perfectly reasonable to do so (although I suspect it's not what your teacher actually wanted—anyone who's trying to force you to write ML in Python like this probably wants you to use non-mutating algorithms…). But then you need to always return the root node. Your function is clearly trying to return the newly-created node whether it's the root or not. So, just fix that:
if key > node.key:
node.right = mapInsert(key, value, node.right)
return node # not node.right
And likewise for the other two cases. (I'm not sure why you were trying to call yourself recursively in the == case in the first place.)
If you do that, the code no longer has an error.
It doesn't seem to be actually balancing the tree correctly, but that's the next problem for you to solve.

Related

Search in Binary Search Tree - why this code works?

I am looking into the LeetCode problem Search in a Binary Search Tree:
You are given the root of a binary search tree (BST) and an integer val.
Find the node in the BST that the node's value equals val and return the subtree rooted with that node. If such a node does not exist, return null.
[...]
Example 2:
Input: root = [4,2,7,1,3], val = 5
Output: []
I am not sure why this code below will work in Python:
def searchBST(self, root: Optional[TreeNode], val: int) -> Optional[TreeNode]:
#base case
if not root: return None
if root.val == val: return root
#recursion relation
if val > root.val:
return self.searchBST(root.right, val)
else:
return self.searchBST(root.left, val)
Specifically, in the question it says (1) if Tree is null, then we need to return [], (2) If value is not in the tree, we need to return null
Then for the line of code if not root: return None, does it cater to (1)? Aren’t we required to return []?
in the question it says (1) if Tree is null, then we need to return [], (2) If value is not in the tree, we need to return null
That is not entirely true. It says that we need to return null, but in the example it represents the output as [].
It is understandable that this leads to confusion, but it should be noted that LeetCode formats (serializes) the input and the output as lists (JSON notation), even though the actual input and output that your function deals with are not lists. The function gets a node instance as argument (or None), and the function is to return such a reference (or None).
The LeetCode framework takes care of conversion of the text-based input into a node reference before calling your function, and does the reverse when dealing with the value that your function returns. In particular, when your function returns None, that will serialise to [] -- which represents an empty tree.

Recursion puzzle with key in the box

I currently try to understand recursion on made up example. Imagine you have a briefcase, which can be opened by the key. The key is in the big box, which can contain other smaller boxes, which key might be in.
In my example boxes are lists. The recursion appears when we find the smaller box - we search it for the key. The problem is that my function can find the key if it is actually in the box and can't go back if there is nothing like 'key'.
Unfortunately, i could not understand how to go back if there is no key in the smaller box. Can you help me solve this puzzle? By the way, have a nice day! Here is the code (big box consists in the way when the key can be found and returned):
box = ['socks', 'papers', ['jewelry', 'flashlight', 'key'], 'dishes', 'souvernirs', 'posters']
def look_for_key(box):
for item in box:
if isinstance(item, list) == True:
look_for_key(item)
elif item == 'key':
print('found the key')
key = item
return key
print(look_for_key(box))
Iteration
The most closed to yours and yet readable solution I could find is:
def look_for_key(box):
for item in box:
if item == 'key':
return item
elif isinstance(item, list) and look_for_key(item) is not None:
return look_for_key(item)
else:
pass
box = [['sock','papers'],['jewelry','key']]
look_for_key(box)
# ==> 'key'
I don't like it because its deduction condition includes a recursive call which is hard to interpret. It does not help to improve interpretability if you assign look_for_key(item) to a variable and check for not None afterwards. It is just similarly difficult to interpret. An equivalent but more interpretable solution is:
def look_for_key(box):
def inner(item, remained):
if item == [] and remained == []:
return None
elif isinstance(item, list) and item != []:
return inner(item[0], [item[1:], remained])
elif item == [] or item != 'key':
return inner(remained[0], remained[1:])
elif item == 'key':
return item
return inner(box[0], box[1:])
box = [['sock','papers'],['jewelry','key']]
look_for_key(box)
# ==> 'key'
It explicitly splits the tree to branches (see below what this means) with return inner(item[0], [item[1:], remained]) and return inner(remained[0], remained[1:]) instead of intrinsically reusing the recursive call conditionally during deduction - if look_for_key(item) is not None: return look_for_key(item) - with this line of code it is hard to see a diagram and understand in which direction the recursion goes.
The 2nd solution also makes it easier to infer the complexity using a tree diagram since you see the branches explicitly, for example remained[0] vs. remained[1:].
As inner is simply an iteration written in a functional way and for loop is a syntactic sugar to form iteration, both solutions should have similar complexity in principle.
Since you do not just want a solution but also a better understanding of recursion, I would try the following approach.
Mapping over Trees (Map-Reduce)
This is a typical text book tree recursion question. What you want is to traverse a hieratical data structure called tree. A typical solution is mapping a function over the tree:
from functools import reduce
def look_for_key(tree):
def look_inner(sub_tree):
if isinstance(sub_tree, list):
return look_for_key(sub_tree)
elif sub_tree == 'key':
return [sub_tree]
else:
return []
return reduce(lambda left_branch, right_branch: look_inner(left_branch) + look_inner(right_branch), tree, [])
box = ['socks', 'papers', ['jewelry', 'flashlight', 'key'], 'dishes', 'souvernirs', 'posters']
look_for_key(box)
# ==> ['key']
To make it explicit I use tree, sub_tree, left_branch, right_branch as variable names instead of box, inner_box and so on as in your example. Notice how the function look_for_key is mapped over each left_branch and right_branch of the sub_trees in the tree. The result is then summarized using reduce (A classic map-reduce procedure).
To be more clear, you can omit the reduce part and keep only the map part:
def look_for_key(tree):
def look_inner(sub_tree):
if isinstance(sub_tree, list):
return look_for_key(sub_tree)
elif sub_tree == 'key':
return sub_tree
else:
return None
return list(map(look_inner, tree))
look_for_key(box)
# ==> [None, None, [None, None, 'key'], None, None, None]
This does not generate your intended format of the result. But it helps to understand how the recursion works. map just adds an abstract layer to recursively look for keys into sub trees which is equivalent to the syntactic sugar of for loop provided by python. That is not important. The essential thing is decomposing the tree properly (deduction) and set-up proper base condition to return the result.
Native Tree Recursion
If it is still not clear enough, you can get rid of all abstractions and syntactic sugars and just build a native recursion from scratch:
def look_for_key(box):
if box == []:
return []
elif not isinstance(box, list) and box == 'key':
print('found the key')
return [box]
elif not isinstance(box, list) and box != 'key':
return []
else:
return look_for_key(box[0]) + look_for_key(box[1:])
look_for_key(box)
# ==> found the key
# ==> ['key']
Here all three fundamental elements of recursion:
base cases
deduction
recursive calls
are explicitly displayed. You can also see from this example clearly that there is no miracle of going out of an inner box (or sub-tree). To look into every possible corner inside the box (or tree), you just repeatedly split it to two parts in every smaller box (or sub tree). Then you properly combine your results at each level (so called fold or reduce or accumulate), here using +, then recursive calls will take care of it and help to return to the top level.
Both the native recursion and map-reduce approaches are able to find out multiple keys, because they traverse over the whole tree and accumulate all matches:
box = ['a','key','c', ['e', ['f','key']]]
look_for_key(box)
# ==> found the key
# ==> found the key
# ==> ['key', 'key']
Recursion Visualization
Finally, to fully understand what is going on with the tree recursion, you could plot the recursive depth and visualize how the calls are moving to deeper levels and then returned.
import functools
import matplotlib.pyplot as plt
# ignore the error of unhashable data type
def ignore_unhashable(func):
uncached = func.__wrapped__
attributes = functools.WRAPPER_ASSIGNMENTS + ('cache_info', 'cache_clear')
#functools.wraps(func, assigned=attributes)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except TypeError as error:
if 'unhashable type' in str(error):
return uncached(*args, **kwargs)
raise
wrapper.__uncached__ = uncached
return wrapper
# rewrite the native recursion and cache the recursive calls
#ignore_unhashable
#functools.lru_cache(None)
def look_for_key(box):
global depth, depths
depth += 1
depths.append(depth)
result = ([] if box == [] else
[box] if not isinstance(box, list) and box == 'key' else
[] if not isinstance(box, list) and box != 'key' else
look_for_key(box[0]) + look_for_key(box[1:]))
depth -= 1
return result
# function to plot recursion depth
def plot_depths(f, *args, show=slice(None), **kwargs):
"""Plot the call depths for a cached recursive function"""
global depth, depths
depth, depths = 0, []
f.cache_clear()
f(*args, **kwargs)
plt.figure(figsize=(12, 6))
plt.xlabel('Recursive Call Number'); plt.ylabel('Recursion Depth')
X, Y = range(1, len(depths) + 1), depths
plt.plot(X[show], Y[show], '.-')
plt.grid(True); plt.gca().invert_yaxis()
box = ['socks', 'papers', ['jewelry', 'flashlight', 'key'], 'dishes', 'souvernirs']
plot_depths(look_for_key, box)
Whenever the function got called recursively, the curve goes to a deeper level - the downward slash. When the tree/sub-tree is splitted to left and right branches, two calls happen at the same level - the horizontal line that connected two dots (two calls look_for_key(box[0]) + look_for_key(box[1:])). When it traverses
over a complete sub-tree (or branch) and reaches to the last leave in that sub-tree (a base condition when a value or [] is returned), it starts to go back to upper levels - the valley in the curve. If you have multiple sub/nest lists there will be multiple valleys. Eventually it traverses over the whole tree and returns the results
You can play with boxes (or trees) of different nest structures to understand better how it works. Hopefully these provide you enough information and a more comprehensive understanding of tree-recursion.
Integrating the above comments:
box = ['socks', 'papers', ['jewelry', 'flashlight', 'key'], 'dishes', 'souvernirs', 'posters']
def look_for_key(box):
for item in box:
if isinstance(item, list) == True:
in_box = look_for_key(item)
if in_box is not None:
return in_box
elif item == 'key':
print('found the key')
return item
# not found
return None
print(look_for_key(box))
which prints:
found the key
key
If the key is deleted from the box, executing the code prints:
None

trying to understand node exists in binary tree (recursion func) in python

I am trying to understand below recursion function which says whether a particular node exists in a binary tree. I did my homework and got most of the recursion part but the last return statement (return root.value == value or inleft or inright) bothers me.
can someone please help me in understanding this method?
def existsInTree(self, root, value):
if root is None:
return False
else:
inleft = self.existsInTree(root.left, value)
inright = self.existsInTree(root.right, value)
print(inleft,inright)
return root.value == value or inleft or inright
example binary tree:
10
/ \
11 9
we will first compare data of root with data of node to be searched. If the match is found, set the flag to true. Else, search the node in left subtree and then in the right subtree.
There's another way of looking at that return statement, you can split the return statement at the or keyword
def ifRootExists(self,root, value):
if (root == None):
return False
if (root.value == value):
return True
""" then recur on left sutree """
res1 = ifrootExists(root.left, value)
# root found, no need to look further
if res1:
return True
""" root is not found in left,
so recur on right subtree """
res2 = ifrootExists(root.right, value)
return res2
We can get the result from the above function whether some node exists.
The algorithm is as follows.
root is None or Not. If None, get back the position which the parent function called with the value of "False".
Otherwise, the function continuously search based on the current node.
inleft is a value of function "existsInTree" in which the current node's left child is the root node.
inright is a value of function "existsInTree" in which the current node's right child is the root node.
Let's assume that we want to search value as called V.
Which V exists in the tree means the current value is V or in the left tree, or in the right tree.
To summarize, inleft and inright means whether V includes or not in the subtree.

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.

Build binary search tree using dictionary in Python

I'm trying to build a BST (binary search tree) with dict in python. I do not understand why my code is not adding nodes to the BST. I saw a similar post here:
How to implement a binary search tree in Python?
which looks the same as my code except declaring a node class, but I would like to know why my dict implementation fails (and hopefully improve my understanding of parameter passing with recursion in python).
keys = [10,9,2,5,3,7,101,18]
start = {'key': keys[-1], 'val': 1, 'left': None, 'right': None}
def binarySearch(root, node):
# compare keys and insert node into right place
if not root:
root = node
elif node['key'] < root['key']:
binarySearch(root['left'], node)
else:
binarySearch(root['right'], node)
# Now let's test our function and build a BST
while keys:
key = keys.pop()
node = {'key': key, 'val': 1, 'left': None, 'right': None}
binarySearch(start, node)
print(start) # unchanged, hence my confusion. Thx for your time!
===========================================
Edit: here is the code that would make it work!
def binarySearch(root, node):
# compare keys and insert node into right place
if not root:
root = node
elif node['key'] < root['key']:
if not root['left']: root['left'] = node
else: binarySearch(root['left'], node)
else:
if not root['right']: root['right'] = node
else: binarySearch(root['right'], node)
Here is what I think that is happening under the hood (why one version is able to add to BST but the other one is not):
In the original version, we will reach a recursion call where root still points to None inside the BST, but then root = node make root points to node which has absolutely no connection with start, i.e. the BST itself. Then local variables are deleted and no changes are made.
In the modified version, we will avoid this since when we add the node by e.g. root['left'] = node. Here root is still pointing to the original BST and thus we are modifying the key-val pair in the original BST instead of having root point to something totally outside the BST.
Let's run through your code as though we were the python interpreter.
Lets start at the first call: binarySearch(start, node)
Here start is the dict defined at the top of your script and node is another dict (which curiously has the same value).
Lets jump inside the call and we find ourselves at: if not root: where root refers to start above and so is truthy so fails this if.
Next we find ourselves at: elif node['key'] < root['key']: which in this case is not True.
Next we pass into the else: and we are at: binarySearch(root['right'], node).
Just before we jump into the first recursive call, lets review what the parameters to the call are: root['right'] from start has the value None and node is still the same dict which we want to insert somewhere. So, onto the recursive call.
Again we find ourselves at: if not root:
However this time root just refers to the first parameter of the first recursive call and we can see from the above review of the parameters that root refers to None.
Now None is considered falsy and so this time the if succeeds and we are on to the next line.
Now we are at root = node.
This is an assignment in python. What this means is that python will use the variable root to stop referring to None and to refer to whatever node currently refers to, which is the dict which was created in the while loop. So root (which is just a parameter, but you can think of as a local variable now) refers to a dict.
Now what happens is that we are at the end of the first recursive call and this function ends. Whenever a function ends, all the local variables are destroyed. That is root and node are destroyed. That is just these variables and not what they refer to.
Now we return to just after the first call site i.e. just after binarySearch(root['right'], node)
We can see here that the parameters: root['right'], node still refer to whatever they were referring to before. This is why your start is unchanged and why your program should deal with left and right now instead of recursing.
#Creted by The Misunderstood Genius
def add_root(e,key):
''''
e is node's name
key is the node's key search
'''
bst=dict()
bst[e]={'key':key,'P':None,'L':None,'R':None}
return bst
def root(tree):
for k,v in tree.items():
if v['P'] == None:
return k
def insert(tree, node, key):
tree[node]={'key':key,'P':None,'L':None,'R':None}
y =None
x = root(tree)
node_key = tree[node]['key']
while x is not None:
y=x
node_root=tree['R']['key']
if node_key < node_root:
x=tree[x]['L']
else:
x=tree[x]['R']
tree[node]['P']=y
if y is not None and node_key< tree[y]['key']:
tree[y]['L']=node
else:
tree[y]['R']=node
return tree
def print_all(tree):
for k,v in tree.items():
print(k,v)
print()
'''
Give a root node and key search target
Returns the name of the node with associated key
Else None
'''
def tree_search(tree,root, target):
if root ==None:
print(" key with node associate not found")
return root
if tree[root]['key'] == target:
return root
if target < tree[root]['key']:
return tree_search(tree,tree[root]['L'],target)
else:
return tree_search(tree,tree[root]['R'],target)
def tree_iterative_search(tree,root,target):
while root is not None and tree[root]['key']!=target:
if target < tree[root]['key']:
root=tree[root]['L']
else:
root=tree[root]['R']
return root
def minimum(tree,root):
while tree[root]['L'] is not None:
root=tree[root]['L']
return tree[root]['key']
bst=add_root('R',20)
bst=insert(bst,'M',10)
bst=insert(bst,'B',8)
bst=insert(bst,'C',24)
bst=insert(bst,'D',22)
bst=insert(bst,'E',25)
bst=insert(bst,'G',25)
print_all(bst)
print(tree_search(bst,'R',25))
x=tree_iterative_search(bst,'R',25)
print(x)
#print(minimum(bst,'R'))

Categories