11"""
2- - A linked list is similar to an array, it holds values. However, links in a linked
3- list do not have indexes.
4- - This is an example of a double ended, doubly linked list.
5- - Each link references the next link and the previous one.
6- - A Doubly Linked List (DLL) contains an extra pointer, typically called previous
7- pointer, together with next pointer and data which are there in singly linked list.
8- - Advantages over SLL - It can be traversed in both forward and backward direction.
9- Delete operation is more efficient"""
10-
2+ https://en.wikipedia.org/wiki/Doubly_linked_list
3+ """
114
12- class LinkedList :
13- """
14- >>> linked_list = LinkedList()
15- >>> linked_list.insert_at_head("a")
16- >>> linked_list.insert_at_tail("b")
17- >>> linked_list.delete_tail()
18- 'b'
19- >>> linked_list.is_empty
20- False
21- >>> linked_list.delete_head()
22- 'a'
23- >>> linked_list.is_empty
24- True
25- """
265
27- def __init__ (self ):
28- self .head = None # First node in list
29- self .tail = None # Last node in list
6+ class Node :
7+ def __init__ (self , data ):
8+ self .data = data
9+ self .previous = None
10+ self .next = None
3011
3112 def __str__ (self ):
32- current = self .head
33- nodes = []
34- while current is not None :
35- nodes .append (current )
36- current = current .next
37- return " " .join (str (node ) for node in nodes )
38-
39- def insert_at_head (self , data ):
40- new_node = Node (data )
41- if self .is_empty :
42- self .tail = new_node
43- self .head = new_node
44- else :
45- self .head .previous = new_node
46- new_node .next = self .head
47- self .head = new_node
13+ return f"{ self .data } "
4814
49- def delete_head (self ) -> str :
50- if self .is_empty :
51- return "List is empty"
5215
53- head_data = self .head .data
54- if self .head .next :
55- self .head = self .head .next
56- self .head .previous = None
16+ class DoublyLinkedList :
17+ def __init__ (self ):
18+ self .head = None
19+ self .tail = None
20+
21+ def __iter__ (self ):
22+ """
23+ >>> linked_list = DoublyLinkedList()
24+ >>> linked_list.insert_at_head('b')
25+ >>> linked_list.insert_at_head('a')
26+ >>> linked_list.insert_at_tail('c')
27+ >>> tuple(linked_list)
28+ ('a', 'b', 'c')
29+ """
30+ node = self .head
31+ while node :
32+ yield node .data
33+ node = node .next
5734
58- else : # If there is no next previous node
59- self .head = None
60- self .tail = None
35+ def __str__ (self ):
36+ """
37+ >>> linked_list = DoublyLinkedList()
38+ >>> linked_list.insert_at_tail('a')
39+ >>> linked_list.insert_at_tail('b')
40+ >>> linked_list.insert_at_tail('c')
41+ >>> str(linked_list)
42+ 'a->b->c'
43+ """
44+ return "->" .join ([str (item ) for item in self ])
45+
46+ def __len__ (self ):
47+ """
48+ >>> linked_list = DoublyLinkedList()
49+ >>> for i in range(0, 5):
50+ ... linked_list.insert_at_nth(i, i + 1)
51+ >>> len(linked_list) == 5
52+ True
53+ """
54+ return len (tuple (iter (self )))
6155
62- return head_data
56+ def insert_at_head (self , data ):
57+ self .insert_at_nth (0 , data )
6358
6459 def insert_at_tail (self , data ):
60+ self .insert_at_nth (len (self ), data )
61+
62+ def insert_at_nth (self , index : int , data ):
63+ """
64+ >>> linked_list = DoublyLinkedList()
65+ >>> linked_list.insert_at_nth(-1, 666)
66+ Traceback (most recent call last):
67+ ....
68+ IndexError: list index out of range
69+ >>> linked_list.insert_at_nth(1, 666)
70+ Traceback (most recent call last):
71+ ....
72+ IndexError: list index out of range
73+ >>> linked_list.insert_at_nth(0, 2)
74+ >>> linked_list.insert_at_nth(0, 1)
75+ >>> linked_list.insert_at_nth(2, 4)
76+ >>> linked_list.insert_at_nth(2, 3)
77+ >>> str(linked_list)
78+ '1->2->3->4'
79+ >>> linked_list.insert_at_nth(5, 5)
80+ Traceback (most recent call last):
81+ ....
82+ IndexError: list index out of range
83+ """
84+ if not 0 <= index <= len (self ):
85+ raise IndexError ("list index out of range" )
6586 new_node = Node (data )
66- if self .is_empty :
67- self .tail = new_node
87+ if self .head is None :
88+ self .head = self .tail = new_node
89+ elif index == 0 :
90+ self .head .previous = new_node
91+ new_node .next = self .head
6892 self .head = new_node
69- else :
93+ elif index == len ( self ) :
7094 self .tail .next = new_node
7195 new_node .previous = self .tail
7296 self .tail = new_node
73-
74- def delete_tail (self ) -> str :
75- if self .is_empty :
76- return "List is empty"
77-
78- tail_data = self .tail .data
79- if self .tail .previous :
97+ else :
98+ temp = self .head
99+ for i in range (0 , index ):
100+ temp = temp .next
101+ temp .previous .next = new_node
102+ new_node .previous = temp .previous
103+ new_node .next = temp
104+ temp .previous = new_node
105+
106+ def delete_head (self ):
107+ return self .delete_at_nth (0 )
108+
109+ def delete_tail (self ):
110+ return self .delete_at_nth (len (self ) - 1 )
111+
112+ def delete_at_nth (self , index : int ):
113+ """
114+ >>> linked_list = DoublyLinkedList()
115+ >>> linked_list.delete_at_nth(0)
116+ Traceback (most recent call last):
117+ ....
118+ IndexError: list index out of range
119+ >>> for i in range(0, 5):
120+ ... linked_list.insert_at_nth(i, i + 1)
121+ >>> linked_list.delete_at_nth(0) == 1
122+ True
123+ >>> linked_list.delete_at_nth(3) == 5
124+ True
125+ >>> linked_list.delete_at_nth(1) == 3
126+ True
127+ >>> str(linked_list)
128+ '2->4'
129+ >>> linked_list.delete_at_nth(2)
130+ Traceback (most recent call last):
131+ ....
132+ IndexError: list index out of range
133+ """
134+ if not 0 <= index <= len (self ) - 1 :
135+ raise IndexError ("list index out of range" )
136+ delete_node = self .head # default first node
137+ if len (self ) == 1 :
138+ self .head = self .tail = None
139+ elif index == 0 :
140+ self .head = self .head .next
141+ self .head .previous = None
142+ elif index == len (self ) - 1 :
143+ delete_node = self .tail
80144 self .tail = self .tail .previous
81145 self .tail .next = None
82- else : # if there is no previous node
83- self .head = None
84- self .tail = None
85-
86- return tail_data
146+ else :
147+ temp = self .head
148+ for i in range (0 , index ):
149+ temp = temp .next
150+ delete_node = temp
151+ temp .next .previous = temp .previous
152+ temp .previous .next = temp .next
153+ return delete_node .data
87154
88155 def delete (self , data ) -> str :
89156 current = self .head
@@ -105,16 +172,55 @@ def delete(self, data) -> str:
105172 current .next .previous = current .previous # 1 <--> 3
106173 return data
107174
108- @property
109- def is_empty (self ): # return True if the list is empty
110- return self .head is None
175+ def is_empty (self ):
176+ """
177+ >>> linked_list = DoublyLinkedList()
178+ >>> linked_list.is_empty()
179+ True
180+ >>> linked_list.insert_at_tail(1)
181+ >>> linked_list.is_empty()
182+ False
183+ """
184+ return len (self ) == 0
111185
112186
113- class Node :
114- def __init__ (self , data ):
115- self .data = data
116- self .previous = None
117- self .next = None
118-
119- def __str__ (self ):
120- return f"{ self .data } "
187+ def test_doubly_linked_list () -> None :
188+ """
189+ >>> test_doubly_linked_list()
190+ """
191+ linked_list = DoublyLinkedList ()
192+ assert linked_list .is_empty () is True
193+ assert str (linked_list ) == ""
194+
195+ try :
196+ linked_list .delete_head ()
197+ assert False # This should not happen.
198+ except IndexError :
199+ assert True # This should happen.
200+
201+ try :
202+ linked_list .delete_tail ()
203+ assert False # This should not happen.
204+ except IndexError :
205+ assert True # This should happen.
206+
207+ for i in range (10 ):
208+ assert len (linked_list ) == i
209+ linked_list .insert_at_nth (i , i + 1 )
210+ assert str (linked_list ) == "->" .join (str (i ) for i in range (1 , 11 ))
211+
212+ linked_list .insert_at_head (0 )
213+ linked_list .insert_at_tail (11 )
214+ assert str (linked_list ) == "->" .join (str (i ) for i in range (0 , 12 ))
215+
216+ assert linked_list .delete_head () == 0
217+ assert linked_list .delete_at_nth (9 ) == 10
218+ assert linked_list .delete_tail () == 11
219+ assert len (linked_list ) == 9
220+ assert str (linked_list ) == "->" .join (str (i ) for i in range (1 , 10 ))
221+
222+
223+ if __name__ == "__main__" :
224+ from doctest import testmod
225+
226+ testmod ()
0 commit comments