Skip to content

Commit d5bdca1

Browse files
authored
Added more documentation, type hints and test case
This is a followup to TheAlgorithms#4973 (comment) As per given suggestion, I've added type hints to certain methods that don't have them. I have also added documentation and example doctests as a usage example for (most of) those that don't have them. I have also added another test case following the previous test case's format. I noticed that the existing test case from previous pull request might be redundant with the ones I've made, so I decided to create a specific situation where the linked list would have to keep different kinds of data types for each node, in `test_singly_linked_list_2` test function.
1 parent 90db983 commit d5bdca1

File tree

1 file changed

+268
-15
lines changed

1 file changed

+268
-15
lines changed

data_structures/linked_list/singly_linked_list.py

+268-15
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,53 @@
1+
from typing import Any
2+
3+
14
class Node:
2-
def __init__(self, data):
5+
def __init__(self, data: Any):
6+
"""
7+
Create and initialize Node class instance.
8+
>>> Node(20)
9+
Node(20)
10+
>>> Node("Hello, world!")
11+
Node(Hello, world!)
12+
>>> Node(None)
13+
Node(None)
14+
>>> Node(True)
15+
Node(True)
16+
"""
317
self.data = data
418
self.next = None
519

6-
def __repr__(self):
20+
def __repr__(self) -> str:
21+
"""
22+
Get the string representation of this node.
23+
>>> Node(10).__repr__()
24+
'Node(10)'
25+
"""
726
return f"Node({self.data})"
827

928

1029
class LinkedList:
1130
def __init__(self):
31+
"""
32+
Create and initialize LinkedList class instance.
33+
>>> linked_list = LinkedList()
34+
"""
1235
self.head = None
1336

14-
def __iter__(self):
37+
def __iter__(self) -> Any:
38+
"""
39+
This function is intended for iterators to access
40+
and iterate through data inside linked list.
41+
>>> linked_list = LinkedList()
42+
>>> linked_list.insert_tail("tail")
43+
>>> linked_list.insert_tail("tail_1")
44+
>>> linked_list.insert_tail("tail_2")
45+
>>> for node in linked_list: # __iter__ used here.
46+
... node
47+
'tail'
48+
'tail_1'
49+
'tail_2'
50+
"""
1551
node = self.head
1652
while node:
1753
yield node.data
@@ -23,7 +59,7 @@ def __len__(self) -> int:
2359
>>> linked_list = LinkedList()
2460
>>> len(linked_list)
2561
0
26-
>>> linked_list.insert_tail("head")
62+
>>> linked_list.insert_tail("tail")
2763
>>> len(linked_list)
2864
1
2965
>>> linked_list.insert_head("head")
@@ -38,13 +74,18 @@ def __len__(self) -> int:
3874
"""
3975
return len(tuple(iter(self)))
4076

41-
def __repr__(self):
77+
def __repr__(self) -> str:
4278
"""
4379
String representation/visualization of a Linked Lists
80+
>>> linked_list = LinkedList()
81+
>>> linked_list.insert_tail(1)
82+
>>> linked_list.insert_tail(3)
83+
>>> linked_list.__repr__()
84+
'1->3'
4485
"""
4586
return "->".join([str(item) for item in self])
4687

47-
def __getitem__(self, index):
88+
def __getitem__(self, index: int) -> Any:
4889
"""
4990
Indexing Support. Used to get a node at particular position
5091
>>> linked_list = LinkedList()
@@ -68,7 +109,7 @@ def __getitem__(self, index):
68109
return node
69110

70111
# Used to change the data of a particular node
71-
def __setitem__(self, index, data):
112+
def __setitem__(self, index: int, data: Any) -> None:
72113
"""
73114
>>> linked_list = LinkedList()
74115
>>> for i in range(0, 10):
@@ -95,13 +136,54 @@ def __setitem__(self, index, data):
95136
current = current.next
96137
current.data = data
97138

98-
def insert_tail(self, data) -> None:
139+
def insert_tail(self, data: Any) -> None:
140+
"""
141+
Insert data to the tail end of linked list.
142+
>>> linked_list = LinkedList()
143+
>>> linked_list.insert_tail("tail")
144+
>>> linked_list
145+
tail
146+
>>> linked_list.insert_tail("tail_2")
147+
>>> linked_list
148+
tail->tail_2
149+
>>> linked_list.insert_tail("tail_3")
150+
>>> linked_list
151+
tail->tail_2->tail_3
152+
"""
99153
self.insert_nth(len(self), data)
100154

101-
def insert_head(self, data) -> None:
155+
def insert_head(self, data: Any) -> None:
156+
"""
157+
Insert data to the head first of linked list.
158+
>>> linked_list = LinkedList()
159+
>>> linked_list.insert_head("head")
160+
>>> linked_list
161+
head
162+
>>> linked_list.insert_head("head_2")
163+
>>> linked_list
164+
head_2->head
165+
>>> linked_list.insert_head("head_3")
166+
>>> linked_list
167+
head_3->head_2->head
168+
"""
102169
self.insert_nth(0, data)
103170

104-
def insert_nth(self, index: int, data) -> None:
171+
def insert_nth(self, index: int, data: Any) -> None:
172+
"""
173+
Insert data at given index.
174+
>>> linked_list = LinkedList()
175+
>>> linked_list.insert_tail("first")
176+
>>> linked_list.insert_tail("second")
177+
>>> linked_list.insert_tail("third")
178+
>>> linked_list
179+
first->second->third
180+
>>> linked_list.insert_nth(1, "fourth")
181+
>>> linked_list
182+
first->fourth->second->third
183+
>>> linked_list.insert_nth(3, "fifth")
184+
>>> linked_list
185+
first->fourth->second->fifth->third
186+
"""
105187
if not 0 <= index <= len(self):
106188
raise IndexError("list index out of range")
107189
new_node = Node(data)
@@ -118,17 +200,84 @@ def insert_nth(self, index: int, data) -> None:
118200
temp.next = new_node
119201

120202
def print_list(self) -> None: # print every node data
203+
"""
204+
This method prints every node data.
205+
>>> linked_list = LinkedList()
206+
>>> linked_list.insert_tail("first")
207+
>>> linked_list.insert_tail("second")
208+
>>> linked_list.insert_tail("third")
209+
>>> linked_list
210+
first->second->third
211+
"""
121212
print(self)
122213

123-
def delete_head(self):
214+
def delete_head(self) -> Any:
215+
"""
216+
Delete the head first node and return the deleted
217+
node's data.
218+
>>> linked_list = LinkedList()
219+
>>> linked_list.insert_tail("first")
220+
>>> linked_list.insert_tail("second")
221+
>>> linked_list.insert_tail("third")
222+
>>> linked_list
223+
first->second->third
224+
>>> linked_list.delete_head()
225+
'first'
226+
>>> linked_list
227+
second->third
228+
>>> linked_list.delete_head()
229+
'second'
230+
>>> linked_list
231+
third
232+
"""
124233
return self.delete_nth(0)
125234

126-
def delete_tail(self): # delete from tail
235+
def delete_tail(self) -> Any: # delete from tail
236+
"""
237+
Delete the tail end node and return the deleted
238+
node's data.
239+
>>> linked_list = LinkedList()
240+
>>> linked_list.insert_tail("first")
241+
>>> linked_list.insert_tail("second")
242+
>>> linked_list.insert_tail("third")
243+
>>> linked_list
244+
first->second->third
245+
>>> linked_list.delete_tail()
246+
'third'
247+
>>> linked_list
248+
first->second
249+
>>> linked_list.delete_tail()
250+
'second'
251+
>>> linked_list
252+
first
253+
"""
127254
return self.delete_nth(len(self) - 1)
128255

129-
def delete_nth(self, index: int = 0):
256+
def delete_nth(self, index: int = 0) -> Any:
257+
"""
258+
Delete node at given index and return the deleted
259+
node's data.
260+
>>> linked_list = LinkedList()
261+
>>> linked_list.insert_tail("first")
262+
>>> linked_list.insert_tail("second")
263+
>>> linked_list.insert_tail("third")
264+
>>> linked_list
265+
first->second->third
266+
>>> linked_list.delete_nth(1) # delete middle
267+
'second'
268+
>>> linked_list
269+
first->third
270+
>>> linked_list.delete_nth(5) # this raises error
271+
Traceback (most recent call last):
272+
...
273+
IndexError: List index out of range.
274+
>>> linked_list.delete_nth(-1) # this also raises error
275+
Traceback (most recent call last):
276+
...
277+
IndexError: List index out of range.
278+
"""
130279
if not 0 <= index <= len(self) - 1: # test if index is valid
131-
raise IndexError("list index out of range")
280+
raise IndexError("List index out of range.")
132281
delete_node = self.head # default first node
133282
if index == 0:
134283
self.head = self.head.next
@@ -141,9 +290,30 @@ def delete_nth(self, index: int = 0):
141290
return delete_node.data
142291

143292
def is_empty(self) -> bool:
293+
"""
294+
Check if linked list is empty.
295+
>>> linked_list = LinkedList()
296+
>>> linked_list.is_empty()
297+
True
298+
>>> linked_list.insert_head("first")
299+
>>> linked_list.is_empty()
300+
False
301+
"""
144302
return self.head is None
145303

146-
def reverse(self):
304+
def reverse(self) -> None:
305+
"""
306+
This reverses the linked list order.
307+
>>> linked_list = LinkedList()
308+
>>> linked_list.insert_tail("first")
309+
>>> linked_list.insert_tail("second")
310+
>>> linked_list.insert_tail("third")
311+
>>> linked_list
312+
first->second->third
313+
>>> linked_list.reverse()
314+
>>> linked_list
315+
third->second->first
316+
"""
147317
prev = None
148318
current = self.head
149319

@@ -201,6 +371,89 @@ def test_singly_linked_list() -> None:
201371
linked_list[i] = -i
202372
assert all(linked_list[i] == -i for i in range(0, 9)) is True
203373

374+
linked_list.reverse()
375+
assert str(linked_list) == "->".join(str(i) for i in range(-8, 1))
376+
377+
378+
def test_singly_linked_list_2() -> None:
379+
"""
380+
This section of the test checks for certain conditions.
381+
>>> test_singly_linked_list_2()
382+
"""
383+
input = [
384+
-9,
385+
100,
386+
Node(77345112),
387+
"dlrow olleH",
388+
7,
389+
5555,
390+
0,
391+
-192.55555,
392+
"Hello, world!",
393+
77.9,
394+
Node(10),
395+
None,
396+
None,
397+
12.20,
398+
]
399+
linked_list = LinkedList()
400+
[linked_list.insert_tail(i) for i in input]
401+
402+
# Check if it's empty or not
403+
assert linked_list.is_empty() is False
404+
assert (
405+
str(linked_list) == "-9->100->Node(77345112)->dlrow olleH->7->5555->0->"
406+
"-192.55555->Hello, world!->77.9->Node(10)->None->None->12.2"
407+
)
408+
409+
# Delete the head
410+
result = linked_list.delete_head()
411+
assert result == -9
412+
assert (
413+
str(linked_list) == "100->Node(77345112)->dlrow olleH->7->5555->0->-192.55555->"
414+
"Hello, world!->77.9->Node(10)->None->None->12.2"
415+
)
416+
417+
# Delete the tail
418+
result = linked_list.delete_tail()
419+
assert result == 12.2
420+
assert (
421+
str(linked_list) == "100->Node(77345112)->dlrow olleH->7->5555->0->-192.55555->"
422+
"Hello, world!->77.9->Node(10)->None->None"
423+
)
424+
425+
# Delete a node in specific location in linked list
426+
result = linked_list.delete_nth(10)
427+
assert result is None
428+
assert (
429+
str(linked_list) == "100->Node(77345112)->dlrow olleH->7->5555->0->-192.55555->"
430+
"Hello, world!->77.9->Node(10)->None"
431+
)
432+
433+
# Add a Node instance to its head
434+
linked_list.insert_head(Node("Hello again, world!"))
435+
assert (
436+
str(linked_list)
437+
== "Node(Hello again, world!)->100->Node(77345112)->dlrow olleH->"
438+
"7->5555->0->-192.55555->Hello, world!->77.9->Node(10)->None"
439+
)
440+
441+
# Add None to its tail
442+
linked_list.insert_tail(None)
443+
assert (
444+
str(linked_list)
445+
== "Node(Hello again, world!)->100->Node(77345112)->dlrow olleH->"
446+
"7->5555->0->-192.55555->Hello, world!->77.9->Node(10)->None->None"
447+
)
448+
449+
# Reverse the linked list
450+
linked_list.reverse()
451+
assert (
452+
str(linked_list)
453+
== "None->None->Node(10)->77.9->Hello, world!->-192.55555->0->5555->"
454+
"7->dlrow olleH->Node(77345112)->100->Node(Hello again, world!)"
455+
)
456+
204457

205458
def main():
206459
from doctest import testmod

0 commit comments

Comments
 (0)