Python: passing parameters over functions - python

Python Experts,
I have been trying to implement BST using Python and here is my code for the insert function:
Draft 1:
def insert(self, val):
newNode = self._Node(val)
if (self._root is None):
self._root = newNode
else:
self._insert(self._root,val)
def _insert(self, node, val):
if node is None:
node = self._Node(val)
elif val >= node._val:
self._insert(node._right, val)
else:
self._insert(node._left, val)
But, I'm unable to construct the tree except the root. I'm guessing I messed up somewhere with the parameters passing over the two functions because when I modify the code as below, I get it alright:
Draft 2:
def insert(self, val):
newNode = self._Node(val)
if (self._root is None):
self._root = newNode
else:
self._insert(self._root,val)
def _insert(self, node, val):
if val >= node._val:
if node._right is None:
node._right = self._Node(val)
else:
self._insert(node._right, val)
else:
if node._left is None:
node._left = self._Node(val)
else:
self._insert(node._left, val)
I'm trying hard to understand why the draft 2 works but draft 1 doesn't. Any help here? Thanks in advance!

The fundamental misunderstanding you have is how variable assignment works and interacts with Python's evaluation strategy: call-by-sharing.
Essentially, in your first draft, when you do the following:
def _insert(self, node, val):
if node is None:
node = self._Node(val)
...
You are simply assigning to the name (variable) node the value of self._Node(val) but then when you leave the scope, the new object is destroyed! Even though node used to refer to the value that was passed in by the method call, simple assignment doesn't mutate the object that is referenced by the name, rather, it reassigns the name.
In your second draft, however:
def _insert(self, node, val):
if val >= node._val:
if node._right is None:
node._right = self._Node(val)
else:
self._insert(node._right, val)
You are mutating an object i.e.: `node._right = self._Node(val)
Here is a simple example that is hopefully illuminating:
>>> def only_assign(object):
... x = 3
... object = x
...
>>> def mutate(object):
... object.attribute = 3
...
>>> class A:
... pass
...
>>> a = A()
>>> a
<__main__.A object at 0x7f54c3e256a0>
>>> only_assign(a)
>>> a
<__main__.A object at 0x7f54c3e256a0>
>>> mutate(a)
>>> a.attribute
3

I believe this is due to the fact that by doing :
node = self._Node(val)
in the _insert function you are not changing the value of the left/right node but binding the name node to a new _Node object, thus letting the left/right node as None.
On your second draft you are effectively affecting a new object to left / right node.
Here's a simple example to illustrate what happens on your code.
Guess what the print(test) will display?
test = [5, 5, 5]
def function(list):
list[0] = 10
list = range(1, 3)
function(test)
print test
If you think it will display [1,2] you're wrong .. it will actually display [10, 5, 5] because when doing list = range(1, 3) we are binding the name list to another object and not changing the first object it was bound to (the one test is actually bound to)

Related

Python BST not working

I'm new to Python thus the question,this is the implementation of my my BST
class BST(object):
def __init__(self):
self.root = None
self.size = 0
def add(self, item):
return self.addHelper(item, self.root)
def addHelper(self, item, root):
if root is None:
root = Node(item)
return root
if item < root.data:
root.left = self.addHelper(item, root.left)
else:
root.right = self.addHelper(item, root.right)
This is the Node object
class Node(object):
def __init__(self, data):
self.data = data
self.left = None
self.right = None
This is my implmentation of str
def __str__(self):
self.levelByLevel(self.root)
return "Complete"
def levelByLevel(self, root):
delim = Node(sys.maxsize)
queue = deque()
queue.append(root)
queue.append(delim)
while queue:
temp = queue.popleft()
if temp == delim and len(queue) > 0:
queue.append(delim)
print()
else:
print(temp.data, " ")
if temp.left:
queue.append(temp.left)
if temp.right:
queue.append(temp.right)
This is my calling client,
def main():
bst = BST()
bst.root = bst.add(12)
bst.root = bst.add(15)
bst.root = bst.add(9)
bst.levelByLevel(bst.root)
if __name__ == '__main__':
main()
Instead of the expected output of printing the BST level by level I get the following output,
9
9223372036854775807
When I look in the debugger it seems that the every time the add method is called it starts with root as None and then returns the last number as root. I'm not sure why this is happening.
Any help appreciated.
If the root argument of your addHelper is None, you set it to a newly-created Node object and return it. If it is not, then you modify the argument but return nothing, so you end up setting bst.root to None again. Try the following with your code above — it should help your understanding of what your code is doing.
bst = BST()
bst.root = bst.add(12)
try:
print(bst.root.data)
except AttributeError:
print('root is None')
# => 12
# `bst.addHelper(12, self.root)` returned `Node(12)`,
# which `bst.add` returned too, so now `bst.root`
# is `Node(12)`
bst.root = bst.add(15)
try:
print(bst.root.data)
except AttributeError:
print('root is None')
# => root is None
# `bst.addHelper(15, self.root)` returned `None`,
# which `bst.add` returned too, so now `bst.root`
# is `None`.
bst.root = bst.add(9)
try:
print(bst.root.data)
except AttributeError:
print('root is None')
# => 9
# `bst.addHelper(9, self.root)` returned `Node(9)`,
# which `bst.add` returned too, so now `bst.root`
# is `Node(9)`
So you should do two things:
make you addHelper always return its last argument — after the appropriate modifications —, and
have your add function take care of assigning the result to self.root (do not leave it for the class user to do).
Here is the code:
def add(self, item):
self.root = self.addHelper(item, self.root)
self.size += 1 # Otherwise what good is `self.size`?
def addHelper(self, item, node):
if node is None:
node = Node(item)
elif item < node.data:
node.left = self.addHelper(item, node.left)
else:
node.right = self.addHelper(item, node.right)
return node
Notice that I changed the name of the last argument in addHelper to node for clarity (there already is something called root: that of the tree!).
You can now write your main function as follows:
def main():
bst = BST()
bst.add(12)
bst.add(15)
bst.add(9)
bst.levelByLevel(bst.root)
(which is exactly what #AaronTaggart suggests — but you need the modifications in add and addHelper). Its output is:
12
9
15
9223372036854775807
The above gets you to a working binary search tree. A few notes:
I would further modify your levelByLevel to avoid printing that last value, as well as not taking any arguments (besides self, of course) — it should always print from the root of the tree.
bst.add(None) will raise an error. You can guard against it by changing your add method. One possibility is
def add(self, item):
try:
self.root = self.addHelper(item, self.root)
self.size += 1
except TypeError:
pass
Another option (faster, since it refuses to go on processing item if it is None) is
def add(self, item):
if item is not None:
self.root = self.addHelper(item, self.root)
self.size += 1
From the point of view of design, I would expect selecting a node from a binary search tree would give me the subtree below it. In a way it does (the node contains references to all other nodes below), but still: Node and BST objects are different things. You may want to think about a way of unifying the two (this is the point in #YairTwito's answer).
One last thing: in Python, the convention for naming things is to have words in lower case and separated by underscores, not the camelCasing you are using — so add_helper instead of addHelper. I would further add an underscore at the beginning to signal that it is not meant for public use — so _add_helper, or simply _add.
Based on the following, you can see that bst.root in None after the second call to add():
>>> bst.root = bst.add(12)
>>> bst.root
<__main__.Node object at 0x7f9aaa29cfd0>
>>> bst.root = bst.add(15)
>>> type(bst.root)
<type 'NoneType'>
Your addHelper isn't returning the root node. Try this:
def addHelper(self, item, root):
if root is None:
root = Node(item)
return root
if item < root.data:
root.left = self.addHelper(item, root.left)
else:
root.right = self.addHelper(item, root.right)
return root
And then it works as expected:
>>> bst.root = bst.add(12)
>>> bst.root = bst.add(15)
>>> bst.levelByLevel(bst.root)
(12, ' ')
()
(15, ' ')
(9223372036854775807, ' ')
>>> bst.root = bst.add(9)
>>> bst.levelByLevel(bst.root)
(12, ' ')
()
(9, ' ')
(15, ' ')
(9223372036854775807, ' ')
You're using the BST object basically only to hold a root Node and the add function doesn't really operate on the BST object so it's better to have only one class (BtsNode) and implement the add there. Try that and you'll see that the add function would be much simpler.
And, in general, when a member function doesn't use self it shouldn't be a member function (like addHelper), i.e., it shouldn't have self as a parameter (if you'd like I can show you how to write the BtsNode class).
I tried writing a class that uses your idea of how to implement the BST.
class BstNode:
def __init__(self):
self.left = None
self.right = None
self.data = None
def add(self,item):
if not self.data:
self.data = item
elif item >= self.data:
if not self.right:
self.right = BstNode()
self.right.add(item)
else:
if not self.left:
self.left = BstNode()
self.left.add(item)
That way you can create a BST the following way:
bst = BstNode()
bst.add(13)
bst.add(10)
bst.add(20)
The difference is that now the add function actually operates on the object without any need for the user to do anything. The function changes the state of the object by itself.
In general a function should do only what it's expected to do. The add function is expected to add an item to the tree so it shouldn't return the root. The fact that you had to write bst.root = bst.add() each time should signal that there's some fault in your design.
Your add method probably shouldn't return a value. And you most certainly shouldn't assign the root of the tree to what the add method returns.
Try changing your main code to something like this:
def main():
bst = BST()
bst.add(12)
bst.add(15)
bst.add(9)
bst.levelByLevel(bst.root)
if __name__ == '__main__':
main()

How to modify argument to function cleanly in Python?

I have the following code which I use to modify the node that is passed into the function recursively (the node is wrapped in an array so that the modification is persistent after the function returns):
Is there a better or cleaner way to modify the argument?
`
class node(object):
def __init__(self, value, next=None):
self._value = value
self._next = next
def __repr__(self):
return "node({0})".format(self._value)
#property
def next(self):
return self._next
def foo(a, b):
if b == 0:
print "setting to next node",
a[0] = a[0].next
print a
return
print a
foo(a, b-1)
print a
n = node(5, node(8))
foo([n], 2)
`
Question was answered in: How do I pass a variable by reference?
To modify something, that thing has to be mutable. Your node instances are mutable:
n = node(3)
assert n.value == 3
n.value = 5
assert n.value == 5 # it was modified!
Also, your function fails to return any values. In your case it may be a wrong approach. Also, I frankly don't see why you would use number (0, n - 1) where the .next value is referenced. These must be node instances, not numbers.
Apparently you're making a linked list implementation, and your foo function tries to remove n-th node by traversing a list. (Please take care to name your functions descriptively; it helps both you and people answering your question.)
That's how I'd do it:
class Node(object): # class names are usually TitleCase
def __init__(self, value, next=None):
self.value = value
self.next = next # no properties for simplicity
def __repr__(self):
return "node({0})".format(self.value)
def asList(node): # nice for printing
if not node:
return []
return [node.value] + asList(node.next)
def removeNodeAt(head_node, index):
"""Removes a node from a list. Returns the (new) head node of the list."""
if index == 0: # changing head
return head_node.next
i = 1 # we have handled the index == 0 above
scan_node = head_node
while i < index and scan_node.next:
scan_node = scan_node.next
i += 1
# here scan_node.next is the node we want removed, or None
if scan_node.next:
scan_node.next = scan_node.next.next # jump over the removed node
return head_node
It works:
>>> n3 = Node(0, Node(1, Node(2)))
>>> asList(removeNodeAt(n3, 2))
[0, 1]
>>> n3 = Node(0, Node(1, Node(2)))
>>> asList(removeNodeAt(n3, 1))
[0, 2]
Because the parameter your are operating is object. You could use __dict__ to change the whole property of the object. It is equivalent to change every property of the project. You could try the following code:
class node(object):
def __init__(self, value, next=None):
self._value = value
self._next = next
def __repr__(self):
return "node({0})".format(self._value)
#property
def next(self):
return self._next
def foo(a):
print "setting to next node\n",
a.__dict__ = getattr(a.next, '__dict__', None)
return
n = node(5, node(8, node(7)))
print n._value, '->' ,n._next._value
foo(n)
print n._value, '->' ,n._next._value
Hope this could help you.

How can I make MyList iterable?

I want to create a class that behaves like a list. The challenge is doing it without using lists nor dictionaries. So far I've created a node class that goes like this:
class Node:
def __init__(self, value=None):
self.next = None
self.last = None
self.value = valor
def __repr__(self):
return self.value
And MyList class that's basically a chain of nodes, with a head node and a tail node. Thing is, I want to make it iterable so I can run a for with it. I searched how iter and next works and came up with something like this:
class MyList:
def __init__(self):
self.head = None
self.tail = None
def __iter__(self):
return self
def __next__(self):
if self.head:
if self.head.next:
self.head = self.head.next
return self.head.last
aux = self.head
self.head = None
return aux
raise StopIteration
It works but it obviously delete the data inside MyList so I can't use it again. Any advices on how to get the same results without messing up with the info inside the object?
Note that the iterator protocol only requires that the container's __iter__ returns an iterator; you can also implement __iter__ as a generator, rather than returning the instance itself:
def __iter__(self):
node = self.head
while node is not None:
yield node
node = node.next
You need to add a "current" marker to your class to indicate the node currently pointed at by iteration. Something like this:
class MyList:
def __init__(self):
self.head = None
self.tail = None
self.current = self.head
def __iter__(self):
return self
def __next__(self):
if self.current is not None:
it = self.current
self.current = self.current.next
return it
raise StopIteration
Right now, your list doesn't distinguish between its head and its current iteration position, but these are two totally distinct concepts.
Of course, if you do it this way, all iterations over the same MyList will be "linked", so that if you do something like:
x = MyList(1, 2, 3, 4)
for item in x:
print(x)
if item == 2:
break
for item in x:
print(x)
Then the second iteration will pick up where the first left off. If you don't want this behavior, you'll have to create a separate iterator class, and have MyList.__iter__ return an instance of that rather than self. If you return self from __iter__, then the object can't have multiple independent iterations going on, because the iteration state is stored in the object as the data being iterated.

Behavior of del operator in Python

I am wondering why the following doesn't work.
class Node(object):
def __init__(self, data, next=None):
self.data = data
self.next = next
def remove(self, value):
if self is None:
return False
if self.data == value:
if self.next:
self.data = self.next.data
self.next = self.next.next
else:
del self
else:
self.next.remove(value)
node = Node(4)
node.append(Node(3))
node.remove(3)
print node.next.data
#prints 3
del doesn't delete the element from the linked list. I had to modify the delete() function so that I have a pointer to the parent of the target element.
class Node(object):
def __init__(self, data, next=None):
self.data = data
self.next = next
def remove(self, value):
if self is None:
return False
if self.data == value:
if self.next:
self.data = self.next.data
self.next = self.next.next
else:
del self
else:
current = self
while current.next:
if current.next.data == value:
if current.next.next:
current.next = current.next.next
else:
current.next = None
From the console,
node = Node(4)
current = node
del current #node is not deleted because I am only deleting the pointer
del node #node is deleted
This seems logical to me. However, I am not sure why the first code block doesn't work as I expect.
I ll explain why it doesn't work. But first you need to know that you rarely need to create linked lists in Python since the list type already offer you almost everything.
>>> [2*i for i in range(10)]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> list_1=[2*i for i in range(10)]
>>> i=0
>>> list_1[i]#first
0
>>> i=i+1
>>> list_1[i]#next
2
>>> list_1[-1]#last
18
>>> len(list_1)#size
10
>>> del list_1[-1]# del the last
#delete all (list_1 becomes empty)
for i in range(len(list_1)):
del list_1[0]
at the end loop we only delete the first n times but when you del the first in a list the other object go 1 place back.That way you can easily manipulate lists as if they were linked list and delete any elements without worrying about an empty slot. In addition to this, list have several very usefull methods like append, remove, sort, shuffle and more. Look at the docs https://docs.python.org/3.5/library/stdtypes.html?highlight=list#list
Now back to you question:
Let s take a look to your remove method
if self is None:
return False
this is useless, you can only call .remove with an object that has this method.
None.remove() never works. If self is None it will throw an error before any (impossible) call.
else:
del self
When you see self in an Object's method it is just a reference to the object itself, del sel will just delete the local reference. If you really want to destroy an object you must destroy it with del on every reference. Python will forget about your object if no living variable is looking at it. You can also overwrite the reference.
>>> node = Node(4)
>>> node = 3# the Node object is erased because you can t have access to it
Overall, I didn't really understand the goal of your Node class except to create linked lists, but as I said, you can do that with list(). In general you don't need to care about what is deleted or not in Python since Python will overwritte the memory if there is no reference left to an object.

Binary Search Tree - Insert Function

Good evening all,
I've been tasked with designing a function in Python that will build a Binary Search tree.
When I walk through the function myself, it seems to make perfect sense and it SHOULD work. However, for whatever reason, it's only building the last 'tree' and not saving any of the prior tree information. I've included my classes, the constructors and of course the function. Any tips are appreciated! To test the function, I am using the following line:
newMap = mapInsert1('one', 1, mapInsert1('two', 2, mkEmptyMap()))
///CODE///
class EmptyMap():
__slots__ = ()
class NonEmptyMap():
__slots__ = ('left', 'key', 'value', 'right')
def mkEmptyMap():
return EmptyMap()
def mkNonEmptyMap(left, key, value, right):
nonEmptyMap = NonEmptyMap()
nonEmptyMap.left = left
nonEmptyMap.key = key
nonEmptyMap.value = value
nonEmptyMap.right = right
return nonEmptyMap
def mapInsert1(key, value, node):
if isinstance(node, EmptyMap):
node = mkNonEmptyMap(mkEmptyMap(), key, value, mkEmptyMap())
return node
else:
if key > node.key:
return mapInsert1(key, value, node.right)
elif key < node.key:
return mapInsert1(key, value, node.left)
elif key == node.key:
node.value = value
return mapInsert1(key, value, node)
else:
raise TypeError('Bad Map')
Ok, I've got your answer here. There wasn't a problem with your logic, per se, just a problem with how you were trying to implement your algorithm in Python.
And there are several problems with how you're trying to implement your algorithm. The first of these has to do with how variables are passed into functions. I would recommend reading this StackOverflow Question here which discusses how variables are passed into functions Python. The long story short is that due to the way that you are passing and updating variables in your code, you are always updating a local scope copy of the variable, which doesn't actually affect the variable that you want to update.
To see this in your code, try the following:
>>> newMap = mapInsert1('one', 1, mapInsert1('two', 2, mkEmptyMap()))
As you said, this doesn't work. But this does:
>>> newMap = mapInsert1('one', 1, mkEmptyMap())
>>> newMap.right = mapInsert1('two', 2, mkEmptyMap()))
But that's not very helpful, because you have to know what node you want to update before you try and add a new node.
In order to fix your code, what I did was clean up your class implementation. I made the following changes:
First, I started using proper constructors. Python classes use the init function as a constructor. See here for more information.
Second, I added the insert function. This is what actually solves your problem. Using this function means that you're not overwriting a locally-scoped variable, but instead mutating the outer variable. Again, take a look at the variable-passing question I linked above for more details.
Third, I made the empty map just an empty instantiation of the map class, and got rid of the isinstance() check. In Python it's usually best to avoid isinstance whenever possible. Here is more information on avoiding isinstance
Fourth, I fixed an infinite loop bug in the code. If you look at the elif key == node.key condition, you are calling mapInsert1 with the same arguments again, which gives you an infinite recursive loop.
Here's the resulting code:
class Map():
__slots__ = ('left', 'key', 'value', 'right')
def __init__(self, left, key, value, right):
self.left = left
self.key = key
self.value = value
self.right = right
def insert(self, left, key, value, right):
self.left = left
self.key = key
self.value = value
self.right = right
def isEmpty(self):
return self.left == self.right == self.key == self.value == None
def mkEmptyMap():
return Map(None, None, None, None)
def mapInsert1(key, value, node):
if node.isEmpty():
print '0'
node.insert(mkEmptyMap(), key, value, mkEmptyMap())
return node
else:
if key > node.key:
print '1'
return mapInsert1(key, value, node.right)
elif key < node.key:
print '2'
return mapInsert1(key, value, node.left)
elif key == node.key:
print '3'
node.value = value
return node
else:
raise TypeError('Bad Map')
And here's a quick test:
>>> root = mapInsert1('five', 5, mkEmptyMap())
>>> mapInsert1('four', 4, root)
>>> mapInsert1('ace', 1, root)
>>> mapInsert1('five', 'five', root)
>>> root.left.isEmpty()
Out: False
>>> root.left.key
Out: 'ace'
>>> root.left.value
Out: 1
>>> root.right.isEmpty()
Out: False
>>> root.right.key
Out: 'four'
>>> root.right.value
Out: 4
>>> root.key
Out: 'five'
>>> root.value
Out: 'five'

Categories

Resources