Linked lists with iterators

https://en.wikipedia.org/wiki/Linked_list

INTRODUCTION

A linked list is a linear collection of items.


class Node:

    def __init__(self, data=None, next=None):
        self.data = data
        self.next = next

    def __str__(self):
        return str(self.data)

class SingleList:

    def __init__(self):
        self.head = None
        self.tail = None

    def is_empty(self):
        return self.head is None

    def insert_head(self, data):   # O(1) time
        node = Node(data)
        if self.is_empty():
            self.head = self.tail = node
        else:
            node.next = self.head
            self.head = node

    def insert_tail(self, data):   # O(1) time
        node = Node(data)
        if self.is_empty():
            self.head = self.tail = node
        else:
            self.tail.next = node
            self.tail = node

    def remove_head(self):          # O(1) time
        if self.is_empty():
            raise ValueError("empty list")
        node = self.head
        if self.head == self.tail:
            self.head = self.tail = None
        else:
            self.head = self.head.next
        node.next = None   # cleaning
        return node.data

# Usage.
SL = SingleList()   # head = tail = None
SL.insert_head(11)         # [11]

# head=tail
# +----++
# | 11 ||
# +----++

SL.insert_head(22)         # [22, 11]

# head      tail
# +----++   +----++
# | 22 |+--o| 11 ||
# +----++   +----++

SL.insert_tail(33)         # [22, 11, 33]

# head                tail
# +----++   +----++   +----++
# | 22 |+--o| 11 |+--o| 33 ||
# +----++   +----++   +----++

while not SL.is_empty():   # order 22, 11, 33
    print(SL.remove_head())

ITERATORS


# Solution 1.
# SL = SingleList()
# assert not isinstance(iter(SL), SingleList)
# assert id(iter(SL)) != id(SL)
class SingleList:
    # ... other methods ...

    def __iter__(self):   # using a generator function
        node = self.head
        while node:
            yield node.data
            node = node.next

# Solution 2.
# SL = SingleList()
# assert isinstance(iter(SL), SingleList)
# assert id(iter(SL)) == id(SL)
class SingleList: 
    # ... other methods ...

    def __iter__(self):
        self.current = self.head
        return self

    def __next__(self):
        if self.current:
            node = self.current
            self.current = self.current.next
            return node.data
        else:   # self.current is None
            raise StopIteration

    next = __next__   # compatibility

# Usage.
SL = SingleList()
SL.insert_head(11)         # [11]
SL.insert_head(22)         # [22, 11]
SL.insert_tail(33)         # [22, 11, 33]
for item in SL:   # order 22, 11, 33
    print(item)