__next__ and next with arguments - python

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.

Related

Python 3 - Using recursion in a linked list

Python 3 - I am new to coding and am finding recursion difficult. I'm making a linked list class with recursive methods for adding and removing items from the list. Right now, I am unable to remove an item if it happens to be the first item in my list. I wrote some alternative code which could remove the first item from the list if I included another parameter (previous) and another base case, but then I could only remove the first item and spent way too long trying to figure out why so I scrapped that entirely. I would appreciate a hint!
Also, I am already aware that I have getters and am not using them properly.
class Node:
"""
Represents a node in a linked list
"""
def __init__(self, data):
self._data = data
self._next = None
def get_data(self):
"""getter method for data in Node class"""
return self._data
def get_next(self):
"""getter method for next in Node class"""
return self._next
class LinkedList:
"""
A linked list implementation of the List ADT
"""
def __init__(self):
self._head = None
def get_head(self):
"""getter function for head of list"""
return self._head
def add(self, val):
""" Adds a node containing val to the linked list - helper function"""
self._head = self.recursive_add(self._head, val)
def recursive_add(self, node1, val):
""" Adds a node containing val to the linked list """
if node1 is None:
return Node(val)
else:
node1._next = self.recursive_add(node1._next, val)
return node1
def remove(self, val):
"""removed the node containing val from the linked list - helper function"""
self.recursive_remove(self._head, val)
def recursive_remove(self, node1, val):
"""
Removes the node containing val from the linked list
"""
if node1 is None:
return node1
elif node1._data == val:
return node1._next
else:
node1._next = self.recursive_remove(node1._next, val)
return node1
def main():
my_list = LinkedList()
my_list.add(13)
my_list.add(9)
my_list.add(5)
my_list.remove(9)
if __name__ == '__main__':
main()
def remove(self, val):
"""removed the node containing val from the linked list - helper function"""
if self._head and self._head._data == val:
self._head = self._head._next
return
self.recursive_remove(self._head, val)
if its at the start, the head needs to be changed.
In remove you call recursive_remove, but ignore its return value. You should use it as the (potentially different) _head reference, must like is done in the recursive method itself, where you have:
node1._next = self.recursive_remove(node1._next, val)
# ^ │
# └───────────────────────────────────┘
Note how node1._next is passed as argument, and the method's return value is the (potentially different) reference that node1._next should end up with. The same pattern should be applied in the initial call in remove:
def remove(self, val):
self._head = self.recursive_remove(self._head, val)
# ^ │
# └──────────────────────────────────┘
NB: the same pattern is used in add, where you do it correctly.

Reversing Single Linked list using Recursion

I have made a function that reverses the singly linked list using Recursive method.
However I am having some difficulty executing my below code:
class node:
def __init__(self,data=None):
self.next=None
self.data=data
class linked_list:
def __init__(self):
self.head=node()
def append(self,data):
new_node=node(data)
cur_node=self.head
while (cur_node.next!=None):
cur_node=cur_node.next
cur_node.next=new_node
return cur_node.data
def display(self):
elements=[]
cur_node=self.head
while(cur_node.next!=None):
cur_node=cur_node.next
elements.append(cur_node.data)
print(elements)
def reverseRecursive(self,prev_code,cur_node):
if cur_node.next!=None:
reverseRecursive(cur_node,cur_node.next)
cur_node.next=prev_node
else:
self.head=cur_node
return
lst1=linked_list()
lst1.display()
lst1.append(1)
lst1.append(3)
lst1.append(5)
lst1.append(7)
lst1.display()
lst1.reverseRecursive(None,_____)
lst1.display()
What should I pass the second argument in reverseRecursive function/method so that I can execute it?
As a second argument, I want to simply pass the head node of a linked list. But I don't know how to get the head node from the init method of the class linked_list
I have tried several things but I'm not able to resolve it. Maybe I am not very good at OOP concepts. Can anyone please help me in resolving this?
The linked list is a recursive structure, and so using it with functional style will yield the best results. In your program, you have implemented a linked list using procedural style and mutable nodes – you change the values of data and next over time. While this might feel like an intuitive approach, I'd like to focus on an immutable discipline that frees us from crippling state complexity.
First, we fix the node constructor function. We set both properties when constructing new nodes because they will not change later in the program –
class node:
def __init__ (self, data, next):
self.data = data
self.next = next
Then a linked_list is just a single node constructed by a particular convention:
node.data hold's the node's data
node.next is either:
another linked_list
or, None, representing the end of the list
We begin with the constructor for linked_list –
class linked_list:
def __init__ (self, node = None):
self.node = node
# ...
And implement is_empty, head, and tail properties to abstract away node –
class linked_list:
def __init__ (self, node = None):
self.node = node
#property
def is_empty (self):
return self.node is None
#property
def head (self):
if self.is_empty:
raise Exception ("cannot get head of an empty list")
else:
return self.node.data
#property
def tail (self):
if self.is_empty:
raise Exception ("cannot get tail of an empty list")
else:
return self.node.next
Now the use of a node is completely abstracted, and we can write higher level list behaviors by using our new properties –
class linked_list:
...
def length (self):
if self.is_empty:
return 0
else:
return 1 + self.tail.length()
Above, we see it's very easy to talk about our list through use of its properties. Before we go further, let's see how we can construct lists and visualize them using print. For object-to-string conversion, we use __str__ –
class linked_list:
...
def add (self, x):
return linked_list (node (x, self))
def __str__ (self):
if self.is_empty:
return "None"
else:
return str (self.head) + " -> " + str (self.tail)
ls = linked_list().add('A').add('B').add('C')
print (ls)
# C -> B -> A -> None
print (ls.length())
# 3
Remember, because we've built an immutable linked list, add does not change the list it was called upon –
ls = linked_list().add('A').add('B').add('C')
print (ls)
# C -> B -> A -> None
print (ls.add('D'))
# D -> C -> B -> A -> None
print (ls)
# C -> B -> A -> None
Finally, we can implement reverse –
class linked_list:
# ...
def reverse (self):
def loop (ls, acc):
if ls.is_empty:
return acc
else:
return loop (ls.tail, acc.add(ls.head))
return loop (self, linked_list())
ls = linked_list().add('A').add('B').add('C')
print (ls)
# C -> B -> A -> None
print (ls.reverse())
# A -> B -> C -> None
Reversing the list does not mutate it
print (ls)
# C -> B -> A -> None
print (ls.reverse())
# A -> B -> C -> None
print (ls)
# C -> B -> A -> None

default argument in recursive class method of python

Inside a recursive class function, how do i use a class member as default argument?
I am currently using following snippet that works (python 3). height is recursive class function. Default argument that i want to use for node is self._root. In order to achieve this, i do something like following but thats a very round about way where i have to define another function height_tree. Is there a more direct approach?
# following code works #
class BTree:
def __init__(self, node=None):
self._root = node
def height_tree(self):
return self.height(self._root)
def height(self, node):
if node is not None:
height = max(self.height(node.get_left()), self.height(node.get_right())) + 1
else:
height = 0
return height
I want to do something like following but it obviously doesn't works !
def height(self, node=self._root)
# code from above follows
I however cannot use alternate trick of assigning root to node when node is None since that is one of the end condition in recursion
def height(self, node)
if node is None:
node = self._root
# this will conflict with logic in code block above#
Are there any recommended ways to handle such a situation?
You can use a sentinel object:
sentinel = object()
def height(self, node=sentinel)
if node is sentinel:
node = self._root
...
Since such object would have a unique memory address, it would never be identical to any other object (including None).
You can always create arbitrary sentinels:
SENTINEL = object()
class BTree:
def __init__(self, node=None):
self._root = node
def height_tree(self):
return self.height(self._root)
def height(self, node=SENTINEL):
if node is SENTINEL:
node = self._root
elif node is not None:
height = max(self.height(node.get_left()), self.height(node.get_right())) + 1
else:
height = 0
return height

__next__ and __iter__ methods in doubly linked sentinel list

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)

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