How can I make MyList iterable? - python

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.

Related

How to create a recursive function to delete node from linked list with given key in python?

I tried program to delete node from linked list recursively. My program is given below
class node:
def __init__(self, data=None):
self.data = data
self.next = None
class linkedList:
def __init__(self):
self.head=None
def printList(self):
cur = self.head
while(cur != None):
print(cur.data,end="->")
cur=cur.next
print("null")
def push(self,dat):
newNode = node(dat)
temp = self.head
self.head = newNode
newNode.next = temp
#staticmethod
def dnr(head, key):
if head is not None:
if head.data == key:
head=head.next
return head
if head is not None:
head.next = linkedList.dnr(head.next, key)
return head
return head
if __name__ == '__main__':
ll=linkedList()
ll.push(3)
ll.push(6)
ll.push(9)
ll.push(12)
ll.push(15)
ll.printList()
print("*******")
# ll.head = linkedList.dnr(ll.head,2)
linkedList.dnr(ll.head,9)
ll.printList()
The problem with this is that this does not work for first element.To make it work for first element I have to call the function like this
ll.head = linkedList.dnr(ll.head,2)
second thing is that I wanted my function to call this way
ll.dnr(2)
please tell me how to create a recursive function to delete node in linked list in python
I rewrote your code:
class node:
def __init__(self, data=None):
self.data = data
self.next = None
class linkedList:
def __init__(self):
self.__head=None
def printList(self):
cur = self.__head
while(cur != None):
print(cur.data,end="->")
cur=cur.next
print("null")
def push(self,dat):
newNode = node(dat)
temp = self.__head
self.__head = newNode
newNode.next = temp
#staticmethod
def __dnr(head, key):
if head is None:
return head
if head.data == key:
head = head.next
return head
head.next = linkedList.__dnr(head.next, key)
return head
#staticmethod
def dnr(listObj, key):
if listObj is None or listObj.__head is None:
return listObj
if listObj.__head.data == key:
listObj.__head = listObj.__head
listObj.__head = linkedList.__dnr(listObj.__head, key)
def deleteKey(self, key):
linkedList.dnr(self, key)
if __name__ == '__main__':
ll=linkedList()
ll.push(3)
ll.push(6)
ll.push(9)
ll.push(12)
ll.push(15)
ll.printList()
print("*******")
linkedList.dnr(ll, 9)
ll.deleteKey(12)
ll.printList()
I made head variable inside linkedList class private, it's not smart to give access outer world to class core components. And class core components should never leak to the outer world because if it's that not used properly that can cause errors. So I rewrote your dnr function and now it's more clear and it doesn't return head object which is core component of linkedList class. Now dnr function just check if passed listObj is valid and check if head is that node that should be deleted. After that it calls private static __dnr function to delete node with given key. Function deleteKey can be called like this ll.deleteKey(12), that is what you wanted.
Giving core component accessible through outer world is like giving bank customer access to a bank vault. Not everybody will try to steal money from it, but there will be someone who will try.
If you don't understand private variables follow this link.
I wanted my function to call this way ll.dnr(2)
Then you need to define an instance method. Your static function can serve a purpose, but as you noted, you really need to assign its return value back to your list's head attribute to be sure it also works when the original head node is removed. With your static method you cannot avoid this overhead, since that method has no knowledge about your linkedList instance.
You can achieve what you want simply by adding an instance method, that will rely on the existing static method and will deal with this assignment back to the instance's head attribute.
Add this to your linkedList class:
def remove(self, key):
self.head = linkedList.dnr(self.head, key)
Now in your main program you can do:
ll.remove(15)
Side note: you don't need the second if head is not None: check in your static method, as this condition will always be true when the execution reaches that point in your code. Just do the assignment to head.next unconditionally.
Addendum
If you want dnr itself to become an instance method (without addition of a remove method), then you need to temporarily cut off the head node from the list (even if you want to keep it), recur, and then conditionally add that cut-off node again (if it's key is not the one to delete).
It would look like this:
def dnr(self, key):
head = self.head
if head:
self.head = head.next # Skip
if head.data != key: # Need to restore it
self.dnr(key) # But first look further...
head.next = self.head # Prefix the removed node
self.head = head # ...and make it the head again
You would call like:
ll.dnr(15)

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())

Why creating an instance of a class within a class method changes the 'self' argument?

I'm writing a linked list in Python and I've come across an issue which is really troublesome and terrible for debugging and I feel like I'm missing something about Python. I'm supposed to create a singly linked list with some basic functionalities. One of them is the take() function, which is meant to create a new list of n first elements of the original list.
However, creating a new instance of the LinkedList class seems to change the .self parameter and the variable node is modified, as the attribute .next is turned to None. In result, when creating a list and then trying to make a new one out of the n elements of it, the program runs indefinitely, but no matter which part I look at, I cannot find the loop or the reason behind it.
class LinkedList:
def __init__(self, head=None):
self.head = head
def is_empty(self):
if self.head == None:
return True
else:
return False
def add_last(self, node):
if self.is_empty():
self.head = node
return
nextEl = self.head
while True:
if nextEl.next is None:
nextEl.next = node
return
nextEl = nextEl.next
def take(self, n):
node = self.head
newHead = self.head
newHead.next = None
newList = LinkedList(newHead)
count = 0
while count < n:
newList.add_last(node.next)
node = node.next
count += 1
return newList
class Node:
def __init__(self, data, next=None):
self.data = data
self.next = next
Thank you for all your help.
In the take() function the line
newHead.next = None
modifies a node of the linked list, breaking this list. You can fix this as follows:
def take(self, n):
node = self.head
newHead = Node(self.head.data)
newList = LinkedList(newHead)
count = 0
while count < n:
newList.add_last(node.next)
node = node.next
count += 1
return newList
This, however, still will not work correctly since there is a problem with the add_last() function too. This function, I think, is supposed to add a node as the last element of a linked list, but since you do not modify the next attribute of the node, you actually append a whole linked list starting with that node. This can be fixed in the following way:
def add_last(self, node):
if self.is_empty():
self.head = node
return
nextEl = self.head
while True:
if nextEl.next is None:
nextEl.next = Node(node.data)
return
nextEl = nextEl.next
There are more issues. For example, take(sefl, n) will actually create a list of n+1 elements and will throw an exception if it is applied to a linked list that does not have that many elements.

Not understanding some composition aspects (not composition)

I am brushing up on some data structures and algorithms with Python, so I am implementing an Unordered Linked list. Within the same file I first wrote a Node class followed by a List class. What I don't get is how the "current" variable in my search_item() method seems to be a node object or at least able to access the Node class methods and attributes. I noticed that if I comment out my add_node() method then "current" no longer has access to Node's methods. Now I am not explicitly using neither inheritance nor composition, so I am having a hard time seeing how current just gets to call get_next() the way the code is written below. I would think I'd have to declare current as: current = Node(self.head) but just current = self.head seems to work?
Your help would be greatly appreciated.
class Node:
def __init__(self, data):
self.data = data
self.next = None
def get_data(self):
return self.data
def set_data(self, d):
self.data = d
def get_next(self):
return self .next
def set_next(self, n):
self.next = n
class UnorderedList:
def __init__(self):
self.head = None
def add_node(self, item):
tmp = Node(item)
tmp.set_next(self.head)
self.head = tmp
def search_item(self, item):
current = self.head
# current = Node(self.head)
found = False
while current != None and not found:
if current.get_data() == item:
found = True
else:
current = current.get_next()
return found
Well if you comment out add_node then you do not add nodes to your linked list any longer therefore search_item will always see the initial value of self.head which is None.
Calling current.get_next() just works because via add_node you always ensure that self.head points either to None or to an instance of Node as tmp is created by tmp = Node(item) and then assigned to self.head = tmp. Therefore when setting current = self.head it will already refer to an instance of Node (or None) so you do not need to call current = Node(self.head).
I just recently came across the concept of duck typing and this old post of mine came to mind. It seems that this is what's at play and just didn't understand it at the time. 'current' is set to None by default, by when invoking any of the methods defined in the Node class, it is automatically defined as Node object.

How would a linked list be faster in iteration? with __iter__ and __next__ or with __getitem__?

I am basically trying to emulate some characteristics of python's lists with the following linked list:
class List:
def __init__(self):
self.head = None
self.tail = None
self.length = 0
def append(self, value):
node = Node(value)
if not self.head:
self.head = self.tail = node
else:
tail = self.tail
tail.next = node
self.tail = node
self.length += 1
def __len__(self):
return self.length
def __getitem__(self, i):
if i >= len(self):
raise IndexError("Index out of range.")
elif i < len(self):
index = 0
current = self.head
while index <= i:
if index == i:
return current
current = current.next
index += 1
def __contains__(self, value):
for node in self:
if value == node.value:
return True
return False
class Node:
def __init__(self, value, n=None):
self.next = n
self.value = value
So I need to make this linked list as efficient as possible, specially when looping through a big instance of it. Is there any way to improve my getitem implementation or an alternative use of iter and next to maximize performance without using any kind of python's built-in data structures, such as lists or tuples? If anyone could come up with any code examples, I would be grateful.
This would be slightly more efficient, since it gets rid of some variable assignments and comparisons.
def __getitem__(self, i):
if i >= len(self):
raise IndexError("Index out of range.")
current = self.head
for _ in xrange(i):
current = current.next
return current
However, random access on a linked list will be always at least O(n). If you are not looking for random access but pure iteration, __iter__ is the way to go, because iterating with the combination of __len__ and __getitem__ will be O(n^2), which is quite horrible. You can do something like this:
def __iter__(self):
current = self.head
while current:
yield current
current = current.next
You don't have to provide any __next__ or next method. yielding from a method is enough.
Define __iter__: return an iterator that keeps a reference to the current node. Then it can get the next node in O(1). __getitem__ traverses to the requested node in O(n).
Define a __iter__ method:
def __iter__(self):
current = self.head
while current:
yield current
current = current.next

Categories

Resources