cs_survival_kit.data_structures.singly_linked_list ¶
A singly linked list with head and tail references.
Provides SinglyLinkedList, a sequence built from nodes that each hold one
element and a link to the next node. Keeping a reference to both ends makes
adding at either end O(1). Removing from the front is also O(1), but finding
an element by position or value means walking the chain, so those operations
are O(n).
SinglyLinkedList ¶
Bases: AbstractList[T]
A sequence of nodes, each linked to the next, with head and tail references.
Each element lives in its own node, and each node links only forward to
the next one. The list keeps references to the first node (head) and the
last node (tail), plus a running size count:
headmakesinsertandpopat index 0 O(1), and so the inheritedprependandpop_front.tailmakesinsertat indexlen(self)O(1), and so the inheritedappend. Without it, appending would mean walking the whole chain to find the last node.- The size count makes
lenO(1) instead of a full traversal.
Links only point forward, so there is no fast way to reach a node's
predecessor. Removing the last element means walking from the head to the
second-to-last node, so the inherited pop_back is O(n) even though the
tail reference finds the last node instantly. That limitation is what a
doubly linked list removes.
Compared with DynamicArray, adding or removing at the front is O(1)
instead of O(n), but indexing is O(n) instead of O(1), and every element
pays for an extra node object and link.
Unlike list, indexing accepts only non-negative indices in the range
0 <= index < len(self). Negative indices and slices are not supported.
Complexity
| Operation | Time | Space |
|---|---|---|
a[i], a[i] = x |
O(n) | O(1) |
insert |
O(n) | O(1) |
pop |
O(n) | O(1) |
prepend |
O(1) | O(1) |
append |
O(1) | O(1) |
pop_front |
O(1) | O(1) |
pop_back |
O(n) | O(1) |
remove |
O(n) | O(1) |
reverse |
O(n) | O(1) |
len(a) |
O(1) | O(1) |
item in a |
O(n) | O(1) |
| iteration | O(n) | O(1) |
reversed(a) |
O(n) | O(n) |
insert and pop at index i follow O(i) links, so they are O(1)
at the front. insert is also O(1) at the end, thanks to the tail
reference. reversed(a) is the inherited default, which iterates
once into a buffer and yields it backward. The buffer is O(n) extra
space, and it is the price of linear time: nodes have no backward
link to follow, so reading each index from the last one down would
walk from the head every time and cost O(n²). Total storage is O(n):
one node per element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
Iterable[T]
|
Elements to add to the new list, in order. Defaults to empty. |
()
|
Examples:
>>> a = SinglyLinkedList[int]([2, 3])
>>> a.prepend(1)
>>> a.append(4)
>>> a
SinglyLinkedList([1, 2, 3, 4])
>>> a.pop_front()
1
>>> a[1], 3 in a, len(a)
(3, True, 3)
>>> a.reverse()
>>> list(a)
[4, 3, 2]
- Data Structures Singly Linked List
Source code in cs_survival_kit/data_structures/singly_linked_list.py
·
View on GitHub
__len__ ¶
Return the number of elements in the list.
Returns:
| Type | Description |
|---|---|
int
|
The number of elements in the list. |
Complexity
- Time: O(1), from the stored size count
- Space: O(1)
Source code in cs_survival_kit/data_structures/singly_linked_list.py
·
View on GitHub
__iter__ ¶
Iterate over the elements from head to tail.
Yields:
| Type | Description |
|---|---|
T
|
Each element, in list order. |
Complexity
- Time: O(n) to exhaust the iterator
- Space: O(1)
Source code in cs_survival_kit/data_structures/singly_linked_list.py
·
View on GitHub
__getitem__ ¶
Return the element at index.
Nodes are scattered in memory and each one only knows where the next
node is, so there is no way to compute where element index lives.
The lookup walks index links from the head. An array can jump
straight there because its elements sit at evenly spaced positions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
The position of the element. Must satisfy
|
required |
Returns:
| Type | Description |
|---|---|
T
|
The element at |
Raises:
| Type | Description |
|---|---|
IndexError
|
If |
Complexity
- Time: O(n); O(index) links are followed
- Space: O(1)
Source code in cs_survival_kit/data_structures/singly_linked_list.py
·
View on GitHub
__setitem__ ¶
Replace the element at index with item.
Walks index links from the head, exactly as a[i] does, and
overwrites the element in the node it reaches. No node is added or
unlinked. Use insert to add an element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
The position of the element to replace. Must satisfy
|
required |
item
|
T
|
The new element. |
required |
Raises:
| Type | Description |
|---|---|
IndexError
|
If |
Complexity
- Time: O(n); O(index) links are followed
- Space: O(1)
Source code in cs_survival_kit/data_structures/singly_linked_list.py
·
View on GitHub
insert ¶
Add item at index, after walking to the node before it.
A new node is linked in; no existing element moves. An array must
shift every element after index one slot to the right to make room,
which costs O(n) however far the walk is.
The walk is what costs. Three cases avoid it entirely:
index == 0: the new node links to the current head and becomes the new head. This is the inheritedprepend.index == len(self): the tail reference points straight at the last node, so the new node is linked after it and becomes the new tail. This is the inheritedappend.- Both at once, when the list is empty: the new node is both head and tail.
Anywhere else, the walk follows index - 1 links to the predecessor,
and the new node is linked between it and its successor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
The position the new element will occupy. Must satisfy
|
required |
item
|
T
|
The element to add. |
required |
Raises:
| Type | Description |
|---|---|
IndexError
|
If |
Complexity
- Time: O(n); O(index) links are followed, so O(1) at either end
- Space: O(1) for the new node
Examples:
>>> a = SinglyLinkedList[str](["a", "c"])
>>> a.insert(1, "b")
>>> a
SinglyLinkedList(['a', 'b', 'c'])
>>> a.prepend("_") # insert at 0: no walk
>>> a.append("d") # insert at len(a): no walk, thanks to the tail
>>> a
SinglyLinkedList(['_', 'a', 'b', 'c', 'd'])
Source code in cs_survival_kit/data_structures/singly_linked_list.py
·
View on GitHub
pop ¶
Remove and return the element at index, unlinking its node.
Unlinking a node means pointing its predecessor's next past it. At
index == 0 there is no predecessor: the head simply moves to the
second node, which is why the inherited pop_front is O(1). Anywhere
else, the walk follows index - 1 links to reach the predecessor,
because nodes carry no backward link.
That walk is why the inherited pop_back is O(n). The tail reference
finds the last node instantly, but unlinking it needs the node before
it, and only a walk from the head can find that. If the removed node
was the tail, the predecessor becomes the new tail. If it was the only
node, both references are cleared.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
The position of the element to remove. Must satisfy
|
required |
Returns:
| Type | Description |
|---|---|
T
|
The element that was at |
Raises:
| Type | Description |
|---|---|
IndexError
|
If |
Complexity
- Time: O(n); O(index) links are followed, so O(1) at the front
- Space: O(1)
Examples:
>>> a = SinglyLinkedList[int]([1, 2, 3])
>>> a.pop(1)
2
>>> a.pop_front() # pop at 0: no walk
1
>>> a.pop_back() # pop at len(a) - 1: walks the whole chain
3
>>> a.pop(0)
Traceback (most recent call last):
...
IndexError: index out of range
Source code in cs_survival_kit/data_structures/singly_linked_list.py
·
View on GitHub
reverse ¶
Reverse the list in place.
Walks the list once, re-pointing each node's next at the node before
it. Three references track progress: the previous node, the current
node, and the next node (saved before its link is overwritten). When
the walk ends, the old tail is the new head and the old head is the new
tail. No nodes are allocated or copied.
This overrides the inherited default, which swaps a[i] with its
mirror from both ends inward. That algorithm suits an array, where
every index is O(1) away, but here each index is a walk from the head,
so the default would be O(n²). Rewiring the links is O(n).
Complexity
- Time: O(n)
- Space: O(1)
Examples:
>>> a = SinglyLinkedList[int]([1, 2, 3])
>>> a.reverse()
>>> a
SinglyLinkedList([3, 2, 1])
>>> a.append(0) # the tail reference was updated too
>>> a
SinglyLinkedList([3, 2, 1, 0])
- Data Structures Singly Linked List Reversing