cs_survival_kit.data_structures ¶
Data structures, each written by hand for study.
GrowthPolicy ¶
Maps the current capacity to the new capacity after an array resize.
A valid policy must return a value strictly greater than its input.
DynamicArray.append raises ValueError if a policy does not.
AbstractList ¶
Bases: ABC
An ordered collection of elements, each at a position from 0 upward.
This class defines the operations every list implementation provides. It
stores nothing and implements nothing itself: a subclass supplies the
storage and all six operations, and may add operations of its own that
suit its storage, such as an O(1) prepend on a linked list.
A subclass that leaves out any of these operations cannot be instantiated.
Complexity
The interface fixes behaviour only. What each operation costs is set by the implementation, and comparing those costs is what the implementations are for. Each one documents its own.
| Operation | Required behaviour |
|---|---|
len(a) |
the number of elements |
| iteration | every element, in position order |
a[i] |
the element at position i |
item in a |
whether any element equals item |
append |
add an element after the current last one |
repr(a) |
the class name and the elements, in order |
Examples:
The base class cannot be instantiated; an implementation can.
>>> AbstractList()
Traceback (most recent call last):
...
TypeError: Can't instantiate abstract class AbstractList...
>>> from cs_survival_kit.data_structures import DynamicArray
>>> isinstance(DynamicArray[int](), AbstractList)
True
- Data Structures Singly Linked List
- Reference (cs-survival-kit 0.6.0) cs_survival_kit data_structures
__len__
abstractmethod
¶
Return the number of elements in the list.
Returns:
| Type | Description |
|---|---|
int
|
The number of elements, which is 0 for an empty list. |
__iter__
abstractmethod
¶
Iterate over the elements in position order.
Yields:
| Type | Description |
|---|---|
T
|
Each element, starting with the one at position 0. |
__getitem__
abstractmethod
¶
Return the element at position index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
The position of the element. Every implementation accepts
|
required |
Returns:
| Type | Description |
|---|---|
T
|
The element at |
Raises:
| Type | Description |
|---|---|
IndexError
|
If there is no element at |
Source code in lib/cs_survival_kit/data_structures/abstract_list.py
__contains__
abstractmethod
¶
__repr__
abstractmethod
¶
Return a string showing the class name and the elements in order.
Returns:
| Type | Description |
|---|---|
str
|
A string such as |
append
abstractmethod
¶
Add item to the end of the list.
Afterwards item is the last element and len(self) is one greater.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item
|
T
|
The element to add. |
required |
Source code in lib/cs_survival_kit/data_structures/abstract_list.py
DynamicArray ¶
Bases: AbstractList[T]
A resizable array backed by fixed-capacity storage.
Elements are stored in a fixed-size backing list of capacity slots,
where only the first len(self) slots are in use. When an append finds
no free slot, the array first resizes: it allocates a larger backing list,
sized by the growth policy, and copies every element into it. The backing
storage never shrinks, so popping elements leaves capacity unchanged.
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 |
|---|---|---|
append |
O(1) amortized, O(n) worst | O(1) amortized, O(n) worst |
pop |
O(1) | O(1) |
a[i], a[i] = x |
O(1) | O(1) |
len(a) |
O(1) | O(1) |
item in a |
O(n) | O(1) |
| iteration | O(n) | O(1) |
| resize | O(n) | O(n) |
The append bounds assume a geometric growth policy such as the
default doubling. With an additive policy, append is amortized
O(n). Total storage is O(capacity). For an array built by appends
alone, doubling keeps capacity below 2n once the array has grown past
its initial capacity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
capacity
|
int
|
The number of slots to allocate up front. Must be at least 1. |
4
|
growth
|
GrowthPolicy
|
The growth policy that computes the new capacity on each
resize. Must return a value greater than its input. Defaults to
|
doubling
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> a = DynamicArray[int](capacity=2)
>>> a.append(1)
>>> a.append(2)
>>> len(a), a.capacity
(2, 2)
>>> a.append(3) # no free slot, so the array doubles first
>>> len(a), a.capacity
(3, 4)
>>> a
DynamicArray([1, 2, 3])
>>> a[0] = 10
>>> a.pop()
3
>>> list(a)
[10, 2]
- Data Structures Dynamic Array
Source code in lib/cs_survival_kit/data_structures/dynamic_array.py
capacity
property
¶
The number of allocated slots, used or not.
Always at least len(self). The two are equal when the array is full,
and the next append will trigger a resize.
Complexity
- Time: O(1)
- Space: O(1)
__len__ ¶
Return the number of elements in the array.
Returns:
| Type | Description |
|---|---|
int
|
The number of elements in the array. |
Complexity
- Time: O(1)
- Space: O(1)
__getitem__ ¶
Return the element at index.
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(1)
- Space: O(1)
Source code in lib/cs_survival_kit/data_structures/dynamic_array.py
__setitem__ ¶
Replace the element at index with item.
Only overwrites an existing element. Use append to add one.
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(1)
- Space: O(1)
Source code in lib/cs_survival_kit/data_structures/dynamic_array.py
__iter__ ¶
Iterate over the elements from index 0 to len(self) - 1.
Yields:
| Type | Description |
|---|---|
T
|
Each element, in index order. |
Complexity
- Time: O(n) to exhaust the iterator
- Space: O(1)
Source code in lib/cs_survival_kit/data_structures/dynamic_array.py
__contains__ ¶
Return whether item is in the array.
Checks the elements in index order and stops at the first match. An
element matches if it is item or equals it, the same rule that
list uses. Unused slots are never examined, so None in a is
True only if None was actually stored.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item
|
object
|
The value to look for. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Complexity
- Time: O(n), since the elements are not ordered and each one may have to be checked; O(1) if the first element matches
- Space: O(1)
Examples:
Source code in lib/cs_survival_kit/data_structures/dynamic_array.py
__repr__ ¶
Return a string showing the class name and the elements, like list.
Unused slots and the capacity are not shown. Use capacity to inspect
the backing storage.
Returns:
| Type | Description |
|---|---|
str
|
A string such as |
Source code in lib/cs_survival_kit/data_structures/dynamic_array.py
append ¶
Add item to the end of the array.
If the array is full, it first resizes to the capacity returned by the growth policy, copying every element, and then stores the item in the first free slot. Resizing only when there is no room means every allocated slot gets used before the array grows.
The growth policy's result is checked before anything changes. If it
is not greater than the current capacity, ValueError is raised and
the array is left exactly as it was.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item
|
T
|
The element to add. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the array is full and the growth policy returns a capacity that is not greater than the current capacity. |
Complexity
- Time: O(1) amortized with a geometric growth policy; O(n) for the append that triggers a resize
- Space: O(1) amortized; O(n) for the append that triggers a resize
Examples:
>>> a = DynamicArray[str](capacity=2)
>>> a.append("x")
>>> a.append("y") # fills the last free slot; no resize yet
>>> a.capacity
2
>>> a.append("z") # no free slot, so the array grows first
>>> a.capacity
4
A growth policy that doesn't grow is rejected:
>>> stuck = DynamicArray[int](capacity=1, growth=lambda c: c)
>>> stuck.append(1)
>>> stuck.append(2)
Traceback (most recent call last):
...
ValueError: growth policy must increase capacity (1 -> 1)
>>> list(stuck), stuck.capacity
([1], 1)
Source code in lib/cs_survival_kit/data_structures/dynamic_array.py
pop ¶
Remove and return the last element.
The freed slot is cleared, so the array no longer holds a reference to the element. Capacity is unchanged.
Returns:
| Type | Description |
|---|---|
T
|
The element that was at index |
Raises:
| Type | Description |
|---|---|
IndexError
|
If the array is empty. |
Complexity
- Time: O(1)
- Space: O(1)
Examples:
>>> a = DynamicArray[int]()
>>> a.append(1)
>>> a.pop()
1
>>> a.pop()
Traceback (most recent call last):
...
IndexError: pop from empty array
Source code in lib/cs_survival_kit/data_structures/dynamic_array.py
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:
headmakesprependandpop_frontO(1).tailmakesappendO(1). 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 would require walking from the head
to the second-to-last node, so the list offers no O(1) pop_back. That
limitation is what a doubly linked list removes.
Compared with DynamicArray, adding 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 |
|---|---|---|
prepend |
O(1) | O(1) |
append |
O(1) | O(1) |
pop_front |
O(1) | O(1) |
a[i] |
O(n) | O(1) |
item in a |
O(n) | O(1) |
remove |
O(n) | O(1) |
reverse |
O(n) | O(1) |
len(a) |
O(1) | O(1) |
| iteration | O(n) | O(1) |
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 lib/cs_survival_kit/data_structures/singly_linked_list.py
__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 lib/cs_survival_kit/data_structures/singly_linked_list.py
__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 lib/cs_survival_kit/data_structures/singly_linked_list.py
__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 lib/cs_survival_kit/data_structures/singly_linked_list.py
__contains__ ¶
Return whether item is in the list.
Checks the elements from the head onward and stops at the first match.
An element matches if it is item or equals it, the same rule that
list uses.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item
|
object
|
The value to look for. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Complexity
- Time: O(n)
- Space: O(1)
Source code in lib/cs_survival_kit/data_structures/singly_linked_list.py
__repr__ ¶
Return a string showing the class name and the elements, like list.
Returns:
| Type | Description |
|---|---|
str
|
A string such as |
Source code in lib/cs_survival_kit/data_structures/singly_linked_list.py
prepend ¶
Add item to the front of the list.
The new node links to the current head and becomes the new head. No existing element moves. An array must shift every element one slot to the right to make room at index 0, which costs O(n).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item
|
T
|
The element to add. |
required |
Complexity
- Time: O(1)
- Space: O(1) for the new node
Examples:
- Data Structures Singly Linked List Working at the front
Source code in lib/cs_survival_kit/data_structures/singly_linked_list.py
append ¶
Add item to the end of the list.
The tail reference points straight at the last node, so the new node
is linked after it and becomes the new tail. Without a tail reference,
finding the last node would mean walking the whole chain, making
append O(n).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item
|
T
|
The element to add. |
required |
Complexity
- Time: O(1)
- Space: O(1) for the new node
Examples:
- Data Structures Singly Linked List Working at the end
Source code in lib/cs_survival_kit/data_structures/singly_linked_list.py
pop_front ¶
Remove and return the first element.
The head moves to the second node. If that empties the list, the tail is cleared too.
Returns:
| Type | Description |
|---|---|
T
|
The element that was at the front of the list. |
Raises:
| Type | Description |
|---|---|
IndexError
|
If the list is empty. |
Complexity
- Time: O(1)
- Space: O(1)
Examples:
>>> a = SinglyLinkedList[int]([1])
>>> a.pop_front()
1
>>> a.pop_front()
Traceback (most recent call last):
...
IndexError: pop_front from empty list
- Data Structures Singly Linked List Working at the front
Source code in lib/cs_survival_kit/data_structures/singly_linked_list.py
remove ¶
Remove the first element that matches item.
Only the first match, scanning from the head, is removed. An element
matches if it is item or equals it, as in item in a. Unlinking a
node means pointing its predecessor's next past it, and nodes have no
backward link, so the scan carries a reference to the previous node as
it goes. The head and tail references are updated when the removed node
is at either end.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item
|
T
|
The value to remove. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no element matches |
Complexity
- Time: O(n)
- Space: O(1)
Examples:
>>> a = SinglyLinkedList[int]([1, 2, 1])
>>> a.remove(1)
>>> a
SinglyLinkedList([2, 1])
>>> a.remove(5)
Traceback (most recent call last):
...
ValueError: item not in list
- Data Structures Singly Linked List Finding an element
Source code in lib/cs_survival_kit/data_structures/singly_linked_list.py
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.
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
Source code in lib/cs_survival_kit/data_structures/singly_linked_list.py
additive ¶
Build a growth policy that adds a fixed number of slots on each resize.
This policy is a counterexample to geometric growth. A resize happens
every step appends and copies every element, so n appends perform about
n² / (2 · step) copies in total. That is amortized O(n) per append, and
O(n²) to build an array of n elements. In exchange, a resize never leaves
more than step unused slots.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
step
|
int
|
The number of slots to add on each resize. Must be at least 1. |
required |
Returns:
| Type | Description |
|---|---|
GrowthPolicy
|
A growth policy that returns |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
- Data Structures Dynamic Array Growth policies
Source code in lib/cs_survival_kit/data_structures/dynamic_array.py
doubling ¶
Double the capacity on each resize.
The default growth policy for DynamicArray. Equivalent to
geometric(2.0).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
curr_capacity
|
int
|
The current capacity of the array. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Twice the current capacity. |
Examples:
- Data Structures Dynamic Array Growth policies
- Reference (cs-survival-kit 0.6.0) cs_survival_kit data_structures
Source code in lib/cs_survival_kit/data_structures/dynamic_array.py
geometric ¶
Build a growth policy that multiplies the capacity by a constant factor.
Any factor greater than 1 gives amortized O(1) appends. The factor trades
memory for copying: a resize copies every element, so a larger factor means
fewer resizes, but right after a resize the capacity is about factor times
the number of elements. CPython's list over-allocates by roughly ⅛,
choosing low memory overhead over fewer resizes.
Because the new capacity is truncated to an integer, a small factor applied to a small capacity could round back down to the current capacity. The returned policy always grows the capacity by at least 1 to prevent that.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
factor
|
float
|
The multiplier applied to the current capacity. Must be finite and greater than 1. |
required |
Returns:
| Type | Description |
|---|---|
GrowthPolicy
|
A growth policy that returns |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
- Data Structures Dynamic Array Growth policies