Istnieją pewne subtelne problemy przy implementacji list z wykorzystaniem tylko klasy Node. Przy przejściu z funkcji do metod jest problem z pustą listą. Nie można wywoływać metod, gdy węzeł jest równy None. Rozwiązaniem jest inna implementacja.
Innym problemem są operacje, które są klasy O(n), co powoduje, że dla długich list będą powolne. Są to np. znajdowanie liczby elementów na liście i wstawianie elementu na koniec listy. W podejściu obiektowym możemy rozwiązać te problemy wprowadzając dodatkowe atrybuty, tzn. możemy na bieżąco uaktualniać licznik elementów, oraz możemy przechowywać łącze do końca listy.
Obok klasy Node wprowadzamy klasę SingleList, w której przechowujemy elementy informacyjne poprawiające wydajność operacji na listach.
class Node:
"""Klasa reprezentująca węzeł listy jednokierunkowej."""
def __init__(self, data=None, next=None):
self.data = data
self.next = next
def __str__(self):
return str(self.data) # bardzo ogólnie
def __eq__(self, other):
return self.data == other.data
def __ne__(self, other):
return not self == other
class SingleList:
"""Klasa reprezentująca całą listę jednokierunkową."""
def __init__(self):
self.length = 0 # nie trzeba obliczać za każdym razem
self.head = None
self.tail = None
def is_empty(self):
#return self.length == 0
return self.head is None
def count(self): # tworzymy interfejs do odczytu
return self.length
def insert_head(self, node):
if self.head: # dajemy na koniec listy
node.next = self.head
self.head = node
else: # pusta lista
self.head = self.tail = node
self.length += 1
def insert_tail(self, node): # klasy O(1)
if self.head: # dajemy na koniec listy
self.tail.next = node
self.tail = node
else: # pusta lista
self.head = self.tail = node
self.length += 1
def remove_head(self): # klasy O(1)
if self.is_empty():
raise ValueError("pusta lista")
node = self.head
if self.head == self.tail: # self.length == 1
self.head = self.tail = None
else:
self.head = self.head.next
node.next = None # czyszczenie łącza
self.length -= 1
return node # zwracamy usuwany node
# Zastosowanie.
alist = SingleList()
alist.insert_head(Node(11)) # [11]
alist.insert_head(Node(22)) # [22, 11]
alist.insert_tail(Node(33)) # [22, 11, 33]
print("length {}".format(alist.length)) # odczyt atrybutu
print("length {}".format(alist.count())) # wykorzystujemy interfejs
while not alist.is_empty(): # kolejność 22, 11, 33
print("remove head {}".format(alist.remove_head()))
Istnieją co najmniej dwa sposoby wyposażenia klasy SingleList w możliwość iteracji po elementach listy.
class SingleList: # lista zwróci iterator po sobie
# ... inne metody ...
def __iter__(self): # wykorzystanie funkcji generatora
node = self.head
while node:
yield node.data
node = node.next
class SingleList: # lista jest jednocześnie iteratorem
# ... inne metody ...
def __iter__(self):
# Przy tworzeniu iteratora trzeba mieć zmienną, która będzie pamiętać stan.
# Przy kolejnym tworzeniu iteratora będzie ustawianie na początek.
# Iterator będzie odświeżony do nowej iteracji po liście.
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__ # kompatybilność Py2 i Py3
# Zastosowanie.
slist = SingleList()
slist.insert_head(Node(11)) # [11]
slist.insert_head(Node(22)) # [22, 11]
slist.insert_tail(Node(33)) # [22, 11, 33]
for item in slist: # kolejność 22, 11, 33
print(item)