I am learning linkedlists in python. In this website, and in one of the methods,
def add_last(self, node):
if self.head is None:
self.head = node
return
for current_node in self:
pass
current_node.next = node
What does the line for current_node in self:
mean? How could one iterate through self
? What does it mean to iterate through self
?
Below is the entire code for the implementation of a linkedlist in python
class Node:
def __init__(self, data):
self.data = data
self.next = None
def __repr__(self):
return self.data
class LinkedList:
def __init__(self):
self.head = None
def __init__(self, nodes=None):
self.head = None
if nodes is not None:
node = Node(data=nodes.pop(0))
self.head = node
for elem in nodes:
node.next = Node(data=elem)
node = node.next
def __repr__(self):
node = self.head
nodes = []
while node is not None:
nodes.append(node.data)
node = node.next
nodes.append("None")
return " -> ".join(nodes)
CodePudding user response:
In python, we can use several different types/classes in for loops (i.e. lists, strings, etc). The python interpreter will accept any object that is an iterable when building a for loop. For a object to be an iterable, its class must implement the __iter__
method. As long as the class implements this method, python doesn't care if you refer to the object using self
or a standard variable.
Here is an example from the wiki page of an iterable object. Note that there is another layer to this with the __next__
method. A class with __next__
defined is called an iterator. The iterator provides the actual logic for producing a sequence of elements for the for loop. An iterable must return an iterator object with __iter__
For more complex types, the iterator may need to be a separate class from the iterable, but in simple cases (like the following example) they can be the same class.
import random
class RandomIterable:
def __iter__(self):
return self
def __next__(self):
if random.choice(["go", "go", "stop"]) == "stop":
raise StopIteration # signals "the end"
return 1
https://wiki.python.org/moin/Iterator
CodePudding user response:
In comments you explain that the LinkedList
class has this method:
def __iter__(self):
node = self.head
while node is not None:
yield node
node = node.next
This makes it possible to iterate an instance of LinkedList
with a for..in
loop. In fact the following piece of code in add_last
:
for current_node in self:
pass
Can be written in more verbose manner like this:
it = iter(self)
try:
while True:
current_node = next(it)
except:
pass
So add_node
could have been written like this:
def add_last(self, node):
if self.head is None:
self.head = node
return
it = iter(self)
try:
while True:
current_node = next(it)
except:
current_node.next = node
it
represents the iterator that the for
loop implicitly creates, but with this code it is easier to follow what is happening.
Let's say you have two nodes in your list with values 1 and 2, then the execution sequence of the more verbose add_node
is as follows:
add_last |
__iter__ |
---|---|
self.head is not None |
|
it = iter(self) |
|
next(it) |
|
node = self.head |
|
node is not None |
|
yield node (with value 1) |
|
current_node = (node with value 1) |
|
next(it) |
|
node = node.next |
|
node is not None |
|
yield node (with value 2) |
|
current_node = (node with value 2) |
|
next(it) |
|
node = node.next |
|
node is None (!) |
|
raise StopIteration() (implicit) |
|
except: |
|
current_node.next = node |
Effectively, the last value of current_node
is the last node in the list, and assigning the new node to its next
attribute is exactly what you want to happen to extend the list with that node.
Other comments
Not your question, but there are some issues in your code:
The second
__init__
definition inLinkedList
overwrites the first. There is no such concept of overloading in Python. But also you don't need the first definition in this case, as the second version has a default value for its second parameter.The indentation of the
for
loop in that__init__
method is wrong. It should be inside theif
block.Calling
.pop(0)
is not nice for the caller of the constructor: it mutates the given list, and this might be an undesired side-effect for the caller.The
__repr__
method onListNode
assumes that the values received fromNode
's__repr__
method are strings, as otherwise thejoin
will fail. If ever you want to use nodes for storing integers (for example), this method will fail. To avoid that, castself.data
to string in theNode
's__repr__
method.