__next__ and __iter__ methods in doubly linked sentinel list - python

I'm working on a doubly linked list class right now and I'm running into trouble with my next and iter methods. This is for a project for my class that I've already turned in and now just want to understand how to actually fix it to make it useful.
What I want my code to do is set a current pointer, starting at the header, and then continue advancing through until instructed to terminate or when it reaches the trailer. I want to access the value stored at each node. The node class is a subclass of the main linked list class. Here is the code I have. My problem appears when I call upon my methods (posting my append method); the current pointer is not recognized. Any ideas on how to fix this?
class Linked_List:
class __Node:
def __init__(self, val):
self.val = val
self.size = 0
def __init__(self):
self.header = Linked_List.__Node('header')
self.trailer = Linked_List.__Node('trailer')
self.header.next = self.trailer
self.trailer.prev = self.header
self.size = 0
self.current = self.header
self.current.next = self.trailer
def __iter__(self):
self.current = self.header
return self
def __next__(self):
if self.current == self.trailer:
raise StopIteration
result = self.Linked_List.__Node[self.current]
self.current = self.current.next
return result
def append(self, val):
new_node = Linked_List.__Node(val)
if self.header.next is self.trailer:
self.header.next = new_node
self.trailer.prev = new_node
self.current = self.header
else:
while self.current is not self.trailer:
self.current = self.current.next
self.current.next = new_node
new_node.next = self.trailer
new_node.prev = self.current
self.size += 1
I'm newer to python (and coding in general) so any advice would be amazing.

Your code has multiple issues, which get apparent as you attempt to use it. Let’s assume the following code to test it:
l = Linked_List()
l.append('foo')
l.append('bar')
l.append('baz')
print([x.val for x in l])
AttributeError: '__Node' object has no attribute 'next'
First issue: Your __Node type does not have fields for next and prev:
class __Node:
def __init__(self, val):
self.val = val
self.size = 0
self.prev = None
self.next = None
AttributeError: 'NoneType' object has no attribute 'next'
Second issue: next is not always being filled for appended nodes. In one of your paths in append, you do not set next and prev of the new node:
def append(self, val):
new_node = Linked_List.__Node(val)
if self.header.next is self.trailer:
# set the attributes on new_node
new_node.prev = self.header
new_node.next = self.trailer
self.header.next = new_node
self.trailer.prev = new_node
self.current = self.header
# …
AttributeError: 'Linked_List' object has no attribute 'Linked_List'
Third issue: No idea what you were trying to do in __next__ there. You should simply access self.current there:
def __next__(self):
if self.current == self.trailer:
raise StopIteration
result = self.current
self.current = self.current.next
return result
Once we have fixed all that, we have a code that runs successfully. But we only get the following output: ['header', 'foo']. Of course, that’s not what we want.
The reason this happens is because the actual order of the items is the following:
header
foo
trailer
baz
trailer
(Yes, there is a recursion) So apparently, the append did not work correctly after all. If you just append two elements, you can see that the element is being added after the trailer element. This means that self.current does hit the trailer element in the append loop after all:
while self.current is not self.trailer:
self.current = self.current.next
And if you look at it, it makes sense that this happens: self.current is updated first, and then the check is made to eventually cancel the loop. At that time self.current is self.trailer. So we should check self.current.next instead:
while self.current.next is not self.trailer:
self.current = self.current.next
Once we have that fixed, we get the following output: ['header', 'foo', 'bar', 'baz']. That’s almost what we would like to see. All we need to do now is to skip the header element as well. We do that by simply starting from the element after the header:
def __iter__(self):
self.current = self.header.next
return self
And then it works.
This is all it takes to get your code running. However, I would generally advise against this approach. You are storing iteration state inside the list, which is very fragile. You should really have this state as local as you possibly can.
In particular, the linked list does not need to be both enumerable and an enumerator. Implementing __iter__ does the former, implementing __next__ does the latter. Enumerable means “you can iterate this thing” while the enumerator is the thing that is performing the iteration and which has the iteration state.
Try moving the iteration state off the linked list, by making it only enumerable and not an enumerator. To do this, add a LinkedListEnumerator type that has a reference to your list and keeps track of the current element:
class LinkedListEnumerator:
def __init__ (self, lst):
self.lst = lst
self.current = lst.header.next
def __iter__ (self):
return self
def __next__ (self):
if self.current == self.lst.trailer:
raise StopIteration
result = self.current
self.current = self.current.next
return result
You can then delete the __next__ method in your linked list, and replace the __iter__ method by the following:
def __iter__(self):
return LinkedListEnumerator(self)
And then you no longer have the state in the linked list. (At this point, you should also make current a local variable in append and get rid of self.current completely)

Related

how is the object using a variable which is not inside the class or defined anywhere

In this code the object of class Node is using a variable next which is not defined anywhere and the code is still working HOW?How is the object using a variable which is not defined in its class
class Node:
def __init__(self, data):
self.data = data
class LinkedList:
# Function to initialize head
def __init__(self):
self.head = None
# Function to reverse the linked list
def reverse(self):
prev = None
current = self.head
while(current is not None):
next = current.next
current.next = prev
prev = current
current = next
self.head = prev
# Function to insert a new node at the beginning
def push(self, new_data):
new_node = Node(new_data)
new_node.next = self.head
self.head = new_node
# Utility function to print the linked LinkedList
def printList(self):
temp = self.head
while(temp):
print(temp.data)
temp = temp.next
llist = LinkedList()
llist.push(20)
llist.push(4)
llist.push(15)
llist.push(85)
print( "Given Linked List")
llist.printList()
llist.reverse()
print ("\nReversed Linked List")
llist.printList()
While in most strongly typed languages this is not possible, Python allows instance attributes to be defined even after the instance has been created and the constructor has run. As long as code does not reference an attribute before it has been defined, there is no issue. See also: Can I declare Python class fields outside the constructor method?
In this particular case the following code would produce an error:
node = Node(42)
if node.next: # Attribute error
print("42 is not the last node")
else:
print("42 is the last node")
However, the only place where new node instances are created is in the push method of the LinkedList class:
def push(self, new_data):
new_node = Node(new_data)
new_node.next = self.head
self.head = new_node
As you can see, the next attribute is defined immediately after the node is constructed. So in practice, every node in a linked list will have a next attribute.
Best Practice?
It is open for debate whether this coding practice is advisable. For instance, Pylint has a rule defining-attr-methods which by default will raise a warning when attributes are defined outside of __init__, __new__, setUp, or __post_init__.
Alternative
In this scenario I would certainly prefer to define the next attribute in the constructor, and give the constructor an extra, optional parameter with which next can be initialised:
class Node:
def __init__(self, data, nxt=None):
self.data = data
self.next = nxt
With this change, the push method of the LinkedList class can be reduce to just:
class LinkedList:
# ...
def push(self, new_data):
self.head = Node(new_data, self.head)
That looks a lot more elegant.
Unrelated, but I would also let the constructor of LinkedList accept any number of values to initialise the list with:
class LinkedList:
def __init__(self, *values):
self.head = None
for value in reversed(values):
self.push(value)
Now the main code could create a list with 4 values in one go:
llist = LinkedList(85, 15, 4, 20)

Can you check if two objects instances are same

In a LinkedList Class defined here, I wanted to check if you can check self.head == node or you need to compare the node with all the attributes and define a equals method explicitly? I saw code where someone was using this without equals method
class Node(object):
def __init__(self,key=None,value=None):
self.key = key
self.value = value
self.previous = None
self.next = None
class LinkedList(object):
def __init__(self):
self.head = None
self.tail = None
self.count = 0
def prepend(self,value):
node = Node(value)
if self.head is None:
self.head = node
self.tail = self.head
self.count = 1
return
self.head.previous = node
node.next = self.head
self.head = node
self.count += 1
To see if the address of obj1 matches the address of obj2, use the is operator.
You are doing that already in this test:
if self.head is None:
There is exactly one object (a singleton) in the NoneType class,
and you are essentially asking if id(self.head) matches the id(), or address, of None.
Feel free to do that with other linked list node objects.
If, OTOH, you were to ask if self.head == some_node,
that might well be asking if node attribute a matches in both,
and attribute b matches in both,
depending on your class methods,
e.g. using def __eq__.
A node created by a shallow copy might be == equal to original,
but is will reveal that separate storage is allocated for it.

Python: How to check if an input is an instance of a class within that class?

I tried to write a code that protects the pointer of a linked list. The setter should point only to an instance which belongs to the same class. Usually isinstance() or type() work after the class is defined. But the setter is a method inside that class, hence the class is not fully defined yet.
I have no doubt that type() gives out error. But I wonder why isinstance(instance, class) yields False if calling from another class.
#define the Node class
class Node:
"""Class Node contain only data and a next pointer variables."""
def __init__(self, Data = None):
self.data = Data
self.__next = None
def getNext(self):
"""The getter"""
return self.__next
def setNext(self, NextNode):
"""The setter"""
#if possible check if NewNode is an instance of Node before pointing to it.
#I have tried "isinstance(), type(), etc. but failed.
if isinstance(NextNode, Node):
self.__next = NextNode
else:
print('The Next pointer should point to "Node" only.')
Then check if isinstance is working
ANode = Node((1,2,3))
BNode = Node((5,6))
ANode.setNext(BNode)
print(BNode)
print(ANode.getNext())
Both prints yield the same address
<__main__.Node object at 0x112162828>
<__main__.Node object at 0x112162828>
So everything looks fine. But When I call from the LinkedList class, printed below, the isinstance yields False, as seen from my warning.
class LinkedList:
"""This class is the Linked List of Node."""
def __init__(self, FirstNode = None):
"""Initialize by creating an empty List. __First hold the pointer that point to the first node."""
if FirstNode is None:
self.__first = Node(None)
self.__last = self.__first
elif type(FirstNode) is Node:
self.__first = FirstNode
self.__last = self.__first
else:
print('To create a linked-list enter nothing or a Node.')
raise TypeError
def getFirst(self):
return self.__first
def append(self, NewLastNode):
"""Add LastNode to the end of the list."""
if not isinstance(NewLastNode,Node):
raise TypeError
OldLast = self.__last
OldLast.setNext(NewLastNode)
self.__last = NewLastNode
NewLastNode.setNext(None)
def removeFirstNode(self):
"""Remove the first node (when the buffer is full)."""
OldFirst = self.__first
NewFirst = OldFirst.getNext()
if NewFirst == None:
# just clear data
OldFirst.data = None
else:
self.__first = NewFirst
del OldFirst
Then I create an instance of the LinkedList class
LL = LinkedList(Node((1,2)))
NewNode = Node((2.0, 3.0, -10))
Surely isinstance works fine here
isinstance(NewNode,Node)
yields True, but
LL.append(NewNode)
which will call Node.setNext() and there the isinstance() yields False as the else in Node.setNext() prints out
The Next pointer should point to "Node" only.
The piece of code that's giving you the error is this:
NewLastNode.setNext(None)
because you're trying to set the next element to an object that it's not a Node instance, hence the error.
I think you could simply remove this statement, as your self.__last is now correctly pointing to your NewLastNode. So your code will become:
def append(self, NewLastNode):
"""Add LastNode to the end of the list."""
if not isinstance(NewLastNode,Node):
raise TypeError
OldLast = self.__last
OldLast.setNext(NewLastNode)
self.__last = NewLastNode

__next__ and next with arguments

I'm writing an implementation of doubly linked lists. In order to traverse the list, I'm using something like:
class Node:
""" A node in our linked list """
def __init__(self, value: Any, next: Union['Node', None] =None,
previous: Union['Node', None] =None) -> None:
self.value = value
self.next = next
self.previous = previous
...
def __next__(self, direction: int =1) -> Union['Node', None]:
if direction == 1:
return self.get_next()
else:
return self.get_previous()
...
where get_next and get_previous are just getters of self.next and self.previous.
However, PyCharm yells at me for trying to call next as
next(some_node, direction=-1). What's the proper way to do this?
Besides __iter__ there is also __reversed__. Both are required to return iterators. The __next__ method should be implemented on iterators (not on node-classes). Note that all magic methods (when called by a function like next instead of directly invoked) need to implement the expected arguments not more - not less.
For example a doubly linked list could just implement __iter__ and __reversed__ and rely on next and previous attribute of the Node:
class Node(object):
def __init__(self, val, nxt, prv):
self.val = val
self.nxt = nxt
self.prv = prv
class DoublyLinkedList(object):
def __init__(self, base=None, last=None):
self.base = base
self.last = last
def prepend(self, val):
new = Node(val, self.base, None)
if self.base is None:
self.base = new
self.last = new
else:
self.base.prv = new
self.base = new
def append(self, val):
new = Node(val, None, self.last)
if self.last is None:
self.base = new
self.last = new
else:
self.last.nxt = new
self.last = new
def __iter__(self):
current = self.base
while current is not None:
yield current
current = current.nxt
def __reversed__(self):
current = self.last
while current is not None:
yield current
current = current.prv
For example:
dl = DoublyLinkedList()
dl.prepend(10)
dl.prepend(20)
dl.prepend(30)
for i in dl:
print(i.val)
gives:
30
20
10
similar for reversed:
for i in reversed(dl):
print(i.val)
# prints:
10
20
30
__next__ is part of the iterator protocol and should be used as described in said protocol, doing otherwise only make problems with the rest python.
In your case just rename the function to simple next and use as some_node.next(-1), though I would change the direction argument to a boolean, as that is how you use it, and its name too. Like this for example
class None:
...
def next(self, forward:bool=True) -> Union['Node', None]:
if forward:
return self.get_next()
else:
return self.get_previous()
and use as some_node.next(), some_node.next(False) or even some_node.next(0) (using 0 instead of False for the same effect)
The extra argument to next is a default value, and __next__ doesn't take any extra arguments. Python doesn't have any sort of two-way iterators. If your interface is not exactly the same as for i in obj:, then you should write your own.

Python Linked List with Nodes. Iterable

I need some help writing an __iter__() method for my UnorderedList() class. I tried this:
def __iter__(self):
current = self
while current != None:
yield current
But the while loop doesn't stop. Here is the rest of my classes and code:
class Node:
def __init__(self,initdata):
self.data = initdata
self.next = None
def getData(self):
return self.data
def getNext(self):
return self.next
def setData(self,newdata):
self.data = newdata
def setNext(self,newnext):
self.next = newnext
class UnorderedList:
def __init__(self):
self.head = None
self.count = 0
If you want to iterate all items succeedingly, you should do
def __iter__(self):
# Remember, self is our UnorderedList.
# In order to get to the first Node, we must do
current = self.head
# and then, until we have reached the end:
while current is not None:
yield current
# in order to get from one Node to the next one:
current = current.next
so that in every step you go one step further.
BTW, setters and getters aren't used in Python in the form of methods. If you need them, use properties, otherwise omit them altogether.
So just do
class Node(object):
def __init__(self, initdata):
self.data = initdata
self.next = None
class UnorderedList(object):
def __init__(self):
self.head = None
self.count = 0
def __iter__(self):
current = self.head
while current is not None:
yield current
current = current.next

Categories