Behavior of del operator in Python - 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.

Related

Trying to insert and print the data in linked list, got an error 'int' object has no attribute 'data'

creating a linked list
inserting new data in linked list at end
printing linked list in list
Expected output: [5,6,2,8]
class node():
def __init__(self, data):
self.data = data
self.next = None
class linkedlist():
def add_at_end(self,newval,head):
newnode = node(newval)
while head.next:
head = head.next
head.next = newval
def print_list(self,head):
LL = []
while head:
LL.append(head.data)
head = head.next
return LL
x = node(5)
y = node(6)
z = node(2)
x.next = y
y.next = z
vals = linkedlist()
print(vals.add_at_end(8,x))
print(vals.print_list(x))
The error occurs because you have head.next = newval where newval is a number, but you should be assigning a node instance, i.e. newnode. So the next time you iterate the list, like in print_list you'll bump into that integer, and access its data attribute, as if it is a node...
Some other remarks on your code:
Your linkedlist class has no state. It merely provides some functions that act on the head argument. The OOP way to do this, is to give a linkedlist instance its own head attribute. And then rely on the self argument, which in your current code is not used in the functions.
Your main code should not have to deal with node instances. It should just have to create a linkedlist, and then populate it by calling its method(s). For that to work you'll have to deal with the case where the list is empty, and a first value is added to it, because then its head attribute needs to be set to it.
Use PascalCase for naming your classes.
The name of the print_list function is misleading: it doesn't print, but returns a list. So I would name it to_list instead.
Don't print the result of calling vals.add_at_end, because it is not designed to return anything, so you'll be printing None.
Corrected:
class Node(): # Use PascalCase
def __init__(self, data):
self.data = data
self.next = None
class LinkedList():
def __init__(self):
self.head = None # Give each instance its own head attribute
def add_at_end(self, newval):
if not self.head: # Deal with this case
self.head = Node(newval)
else:
head = self.head # Use attribute
while head.next:
head = head.next
head.next = Node(newval) # corrected
def to_list(self): # Better name
LL = []
head = self.head # Use attribute
while head:
LL.append(head.data)
head = head.next
return LL
vals = LinkedList()
vals.add_at_end(5) # Use method to add nodes
vals.add_at_end(6)
vals.add_at_end(2)
vals.add_at_end(8) # Nothing to print here
print(vals.to_list())

My self-defined queue not printing properly

I'm currently doing a class assignment to create a Class Queue using linked list. My code is as follows:
class Node:
def __init__(self, data):
self.data = data
self.next = None
self.prev = None
class Queue:
def __init__(self):
self.front = None
self.rear = None
def enqueue(self, value):
newNode = Node(value)
if self.rear is None:
self.front = newNode
self.rear = self.front
else:
self.rear.next = newNode
self.rear.next.prev = self.rear
self.rear = self.rear.next
def dequeue(self):
if self.front is None:
return None
else:
to_remove = self.front.data
self.front = self.front.next
self.front.prev = None
return to_remove
def printQueue(self):
print('The elements in the queue are ')
element = self.front
while element is not None:
print(element, end = "")
element = element.next
myqueue = Queue()
for i in range(21):
if i % 3 == 0:
myqueue.enqueue(i)
elif i % 5 == 0:
myqueue.dequeue()
myqueue.printQueue()
But when I tried printing, it comes up like this:
<__main__.Node object at 0x000001EB465D8048><__main__.Node object at 0x000001EB465D8128><__main__.Node object at 0x000001EB465D8080><__main__.Node object at 0x000001EB465D80F0>
I searched on the internet and tried to change my code but it still shows up the same. I don't understand why though.
As the comment says, and I modified your questions itself, too. You have to print the value of the objects, not the objects themselves. It's not an error, just a little confusion on your part.
When you print out element you print the location of the object's memory.
<__main__.Node object at 0x000001EB465D8048><__main__.Node object at 0x000001EB465D8128><__main__.Node object at 0x000001EB465D8080><__main__.Node object at 0x000001EB465D80F0>
The hexadecimal values are addresses.
In order to access the value present at these locations use element.data instead of element.
First of all, that is not an error. Your printQueue function iterates the elements and prints each element. The type of each element is Node.
For example, your output contains the representation of the nodes:
The object below is a single node, defined in the __main__ function. Its type is Node Object and its memory address is 0x000001EB465D8048
<__main__.Node object at 0x000001EB465D8048>
To print a user friendly version of each node, the node class needs to override the __str__ method.
For example, if you would modify your Node class to have the following structure, then you could just use print(element, end = "\n") inside your printQueue method.
class Node:
def __init__(self, data):
self.data = data
self.next = None
self.prev = None
def __str__(self):
return str(self.data)
The output then would be
The elements in the queue are
9
12
15
18
Furthermore, while not directly related to your question, it's tangential enough that you might find it helpful. Python objects have both a __str__ and a __repr__method.
Simply put, the __str__ is used to represent the readable form of your object, and the __repr__ the unambiguous representation.
For more information on those 2 methods, this stackoverflow post does a good job at explaining them.

Python: passing parameters over functions

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)

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.

Python tree operations

I need to implement (or just use) a tree data structure on which I can perform:
1. Child additions at any specified position. The new child can itself be a big tree (need not be a singleton)
2. Subtree deletions and moving (to another node in the same tree)
3. Common traversal operations.
4. Access parent from child node.
First, is there any module I can use for this?
Second, if I were to implement this by myself, I've this concern:
When I do tree manipulations like moving subtrees, removing subtrees or adding new subtrees, I only wish to move the "references" to these tree nodes. For example, in C/C++ these operations can be performed by pointer manipulations and I can be assured that only the references are being moved.
Similarly, when I do tree "movements" I need to move only the reference - aka, a new copy of the tree should not be created at the destination.
I'm still in a "pointers" frame of thinking, and hence the question. May be, I don't need to do all this?
You can easily make your own tree with operator overloading. For example, here is a basic class with __add__ implemented :
class Node(object):
def __init__(self, value):
self.value = value
self.child = []
def add_child(self, child):
self.child.append(child)
def __add__(self, element):
if type(element) != Node:
raise NotImplementedError("Addition is possible only between 2 nodes")
self.value += element.value # i didn't get if you have to add also childs
return self # return the NODE object
So to answer to your second question, there is a python trick here. In __add__ you return self. Then, this return True:
a = Node(1)
b = Node(2)
print a is a + b
If you use a + b, this will modify the value a. a and b are, in fact, pointers. Then if you pass it as argument in a function, and you modify them in the function, the a and b instances will be modified. There is two different way to avoid this (maybe more, but this is the two i use) :
The first one is to directly modify the definition of __add__ :
def __add__(self, element):
# .../...
value = self.value + element.value
return Node(value) # you may add rows in order to copy childs
The second one is to add a copy method :
def copy(self):
# .../...
n = Node(self.value)
n.child = self.child[:] # Copy the list, in order to have 2 different instance of this list.
return n
This will allow you to do something like c = a.copy() + b and the assertion c is a will be false.
Hope I answered to your question.
Thi is an example for you:
class BinaryTree:
def __init__(self,rootObj):
self.key = rootObj
self.leftChild = None
self.rightChild = None
def insertLeft(self,newNode):
if self.leftChild == None:
self.leftChild = BinaryTree(newNode)
else:
t = BinaryTree(newNode)
t.leftChild = self.leftChild
self.leftChild = t
def insertRight(self,newNode):
if self.rightChild == None:
self.rightChild = BinaryTree(newNode)
else:
t = BinaryTree(newNode)
t.rightChild = self.rightChild
self.rightChild = t
def getRightChild(self):
return self.rightChild
def getLeftChild(self):
return self.leftChild
def setRootVal(self,obj):
self.key = obj
def getRootVal(self):
return self.key

Categories

Resources