Skip to content

Commit 5ddfab1

Browse files
feat: add linked list implementations and rename stack module
- Add SinglyLinkedList and DoublyLinkedList abstract interfaces - Add SinglyLinkedListImpl and DoublyLinkedListImpl implementations - Add tests for both linked list types (32 tests) - Rename stacks/ to stack/ for consistency - Update README with linked list docs and project structure
1 parent 9e68267 commit 5ddfab1

10 files changed

Lines changed: 410 additions & 2 deletions

File tree

README.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,32 @@ ms.push(7)
2929
ms.get_min() # 3
3030
```
3131

32+
### Linked Lists
33+
34+
| Implementation | Description |
35+
|---|---|
36+
| `SinglyLinkedListImpl` | Singly linked list with O(1) push/pop at the head. |
37+
| `DoublyLinkedListImpl` | Doubly linked list with O(1) push/pop at both head and tail. |
38+
39+
```python
40+
from data_structures.linked_list import SinglyLinkedListImpl, DoublyLinkedListImpl
41+
42+
sll = SinglyLinkedListImpl()
43+
sll.push_front(1)
44+
sll.push_front(2)
45+
sll.peek_front() # 2
46+
sll.pop_front() # 2
47+
list(sll) # [1]
48+
49+
dll = DoublyLinkedListImpl()
50+
dll.push_front(1)
51+
dll.push_back(2)
52+
dll.push_back(3)
53+
dll.peek_back() # 3
54+
dll.pop_back() # 3
55+
list(dll) # [1, 2]
56+
```
57+
3258
## Requirements
3359

3460
- Python >= 3.13
@@ -48,10 +74,15 @@ uv run pytest tests/ -v
4874

4975
```
5076
data_structures/
51-
stacks/
77+
stack/
5278
base.py # Stack ABC and StackEmptyError
5379
array_stack.py # ArrayStack implementation
5480
min_stack.py # MinStack implementation
81+
linked_list/
82+
base.py # SinglyLinkedList & DoublyLinkedList ABCs
83+
singly_linked_list.py # SinglyLinkedListImpl
84+
doubly_linked_list.py # DoublyLinkedListImpl
5585
tests/
56-
test_stacks.py # pytest suite for all stack implementations
86+
test_stacks.py # pytest suite for stack implementations
87+
test_linked_lists.py # pytest suite for linked list implementations
5788
```
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from .base import DoublyLinkedList, LinkedListEmptyError, SinglyLinkedList
2+
from .doubly_linked_list import DoublyLinkedListImpl
3+
from .singly_linked_list import SinglyLinkedListImpl
4+
5+
__all__ = [
6+
"DoublyLinkedList",
7+
"DoublyLinkedListImpl",
8+
"LinkedListEmptyError",
9+
"SinglyLinkedList",
10+
"SinglyLinkedListImpl",
11+
]
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from abc import ABC, abstractmethod
2+
from typing import Iterator
3+
4+
5+
class LinkedListEmptyError(Exception):
6+
"""Raised when attempting to access an element from an empty linked list."""
7+
8+
def __init__(self) -> None:
9+
super().__init__("Linked list is empty")
10+
11+
12+
class SinglyLinkedList[T](ABC):
13+
"""Abstract Singly Linked List interface.
14+
15+
Each node holds a reference only to the *next* node.
16+
"""
17+
18+
@abstractmethod
19+
def push_front(self, item: T) -> None:
20+
"""Insert *item* at the head. O(1)."""
21+
22+
@abstractmethod
23+
def pop_front(self) -> T:
24+
"""Remove and return the head element. O(1)."""
25+
26+
@abstractmethod
27+
def peek_front(self) -> T:
28+
"""Return the head element without removing it. O(1)."""
29+
30+
@abstractmethod
31+
def is_empty(self) -> bool:
32+
"""Return ``True`` if the list contains no elements. O(1)."""
33+
34+
@abstractmethod
35+
def size(self) -> int:
36+
"""Return the number of elements. O(1)."""
37+
38+
@abstractmethod
39+
def __iter__(self) -> Iterator[T]:
40+
"""Iterate over all elements from head to tail."""
41+
42+
43+
class DoublyLinkedList[T](SinglyLinkedList[T]):
44+
"""Abstract Doubly Linked List interface.
45+
46+
Each node holds references to both *next* and *prev* nodes.
47+
Adds O(1) tail operations on top of ``SinglyLinkedList``.
48+
"""
49+
50+
@abstractmethod
51+
def push_back(self, item: T) -> None:
52+
"""Insert *item* at the tail. O(1)."""
53+
54+
@abstractmethod
55+
def pop_back(self) -> T:
56+
"""Remove and return the tail element. O(1)."""
57+
58+
@abstractmethod
59+
def peek_back(self) -> T:
60+
"""Return the tail element without removing it. O(1)."""
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from typing import Iterator
2+
3+
from .base import DoublyLinkedList, LinkedListEmptyError
4+
5+
6+
class _Node[T]:
7+
__slots__ = ("value", "prev", "next")
8+
9+
def __init__(
10+
self,
11+
value: T,
12+
prev: "_Node[T] | None" = None,
13+
next: "_Node[T] | None" = None,
14+
) -> None:
15+
self.value = value
16+
self.prev = prev
17+
self.next = next
18+
19+
20+
class DoublyLinkedListImpl[T](DoublyLinkedList[T]):
21+
"""Doubly linked list backed by ``_Node`` objects."""
22+
23+
def __init__(self) -> None:
24+
self._head: _Node[T] | None = None
25+
self._tail: _Node[T] | None = None
26+
self._size: int = 0
27+
28+
def push_front(self, item: T) -> None:
29+
node = _Node(item, next=self._head)
30+
if self._head is not None:
31+
self._head.prev = node
32+
else:
33+
self._tail = node
34+
self._head = node
35+
self._size += 1
36+
37+
def pop_front(self) -> T:
38+
if self._head is None:
39+
raise LinkedListEmptyError
40+
value = self._head.value
41+
self._head = self._head.next
42+
if self._head is not None:
43+
self._head.prev = None
44+
else:
45+
self._tail = None
46+
self._size -= 1
47+
return value
48+
49+
def peek_front(self) -> T:
50+
if self._head is None:
51+
raise LinkedListEmptyError
52+
return self._head.value
53+
54+
def push_back(self, item: T) -> None:
55+
node = _Node(item, prev=self._tail)
56+
if self._tail is not None:
57+
self._tail.next = node
58+
else:
59+
self._head = node
60+
self._tail = node
61+
self._size += 1
62+
63+
def pop_back(self) -> T:
64+
if self._tail is None:
65+
raise LinkedListEmptyError
66+
value = self._tail.value
67+
self._tail = self._tail.prev
68+
if self._tail is not None:
69+
self._tail.next = None
70+
else:
71+
self._head = None
72+
self._size -= 1
73+
return value
74+
75+
def peek_back(self) -> T:
76+
if self._tail is None:
77+
raise LinkedListEmptyError
78+
return self._tail.value
79+
80+
def is_empty(self) -> bool:
81+
return self._size == 0
82+
83+
def size(self) -> int:
84+
return self._size
85+
86+
def __iter__(self) -> Iterator[T]:
87+
node = self._head
88+
while node is not None:
89+
yield node.value
90+
node = node.next
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from typing import Iterator
2+
3+
from .base import LinkedListEmptyError, SinglyLinkedList
4+
5+
6+
class _Node[T]:
7+
__slots__ = ("value", "next")
8+
9+
def __init__(self, value: T, next: "_Node[T] | None" = None) -> None:
10+
self.value = value
11+
self.next = next
12+
13+
14+
class SinglyLinkedListImpl[T](SinglyLinkedList[T]):
15+
"""Singly linked list backed by ``_Node`` objects."""
16+
17+
def __init__(self) -> None:
18+
self._head: _Node[T] | None = None
19+
self._size: int = 0
20+
21+
def push_front(self, item: T) -> None:
22+
self._head = _Node(item, self._head)
23+
self._size += 1
24+
25+
def pop_front(self) -> T:
26+
if self._head is None:
27+
raise LinkedListEmptyError
28+
value = self._head.value
29+
self._head = self._head.next
30+
self._size -= 1
31+
return value
32+
33+
def peek_front(self) -> T:
34+
if self._head is None:
35+
raise LinkedListEmptyError
36+
return self._head.value
37+
38+
def is_empty(self) -> bool:
39+
return self._size == 0
40+
41+
def size(self) -> int:
42+
return self._size
43+
44+
def __iter__(self) -> Iterator[T]:
45+
node = self._head
46+
while node is not None:
47+
yield node.value
48+
node = node.next

0 commit comments

Comments
 (0)