pkstruct 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pkstruct/__init__.py +167 -0
- pkstruct/graphs/__init__.py +127 -0
- pkstruct/graphs/connectivity.py +157 -0
- pkstruct/graphs/directed.py +95 -0
- pkstruct/graphs/exceptions.py +63 -0
- pkstruct/graphs/graph.py +262 -0
- pkstruct/graphs/mst.py +118 -0
- pkstruct/graphs/scc.py +138 -0
- pkstruct/graphs/shortest_path.py +250 -0
- pkstruct/graphs/topo_sort.py +108 -0
- pkstruct/graphs/traversal.py +175 -0
- pkstruct/graphs/visualization.py +90 -0
- pkstruct/graphs/weighted.py +37 -0
- pkstruct/linear/__init__.py +95 -0
- pkstruct/linear/deques/__init__.py +33 -0
- pkstruct/linear/deques/deque.py +194 -0
- pkstruct/linear/deques/linked_deque.py +198 -0
- pkstruct/linear/exceptions.py +26 -0
- pkstruct/linear/linked_lists/__init__.py +5 -0
- pkstruct/linear/linked_lists/_base.py +608 -0
- pkstruct/linear/linked_lists/circular_linked_list.py +230 -0
- pkstruct/linear/linked_lists/doubly_linked_list.py +151 -0
- pkstruct/linear/linked_lists/nodes.py +68 -0
- pkstruct/linear/linked_lists/singly_linked_list.py +136 -0
- pkstruct/linear/queues/__init__.py +44 -0
- pkstruct/linear/queues/circular_queue.py +258 -0
- pkstruct/linear/queues/linked_queue.py +186 -0
- pkstruct/linear/queues/priority_queue.py +202 -0
- pkstruct/linear/queues/queue.py +174 -0
- pkstruct/linear/stacks/__init__.py +38 -0
- pkstruct/linear/stacks/array_stack.py +165 -0
- pkstruct/linear/stacks/linked_stack.py +168 -0
- pkstruct/linear/stacks/stack.py +158 -0
- pkstruct/linear/utils/__init__.py +18 -0
- pkstruct/linear/utils/benchmark.py +255 -0
- pkstruct/linear/utils/debug_tools.py +239 -0
- pkstruct/linear/utils/helpers.py +143 -0
- pkstruct/linear/utils/iterators.py +148 -0
- pkstruct/linear/visualization/__init__.py +0 -0
- pkstruct/linear/visualization/ascii_visualizer.py +114 -0
- pkstruct/linear/visualization/linked_list_visualizer.py +126 -0
- pkstruct/shared/__init__.py +67 -0
- pkstruct/shared/benchmarking/__init__.py +78 -0
- pkstruct/shared/debugging/__init__.py +69 -0
- pkstruct/shared/exceptions/__init__.py +59 -0
- pkstruct/shared/serializers/__init__.py +65 -0
- pkstruct/shared/threading/__init__.py +43 -0
- pkstruct/shared/validators/__init__.py +98 -0
- pkstruct/shared/visualization/__init__.py +21 -0
- pkstruct/trees/__init__.py +92 -0
- pkstruct/trees/avl.py +321 -0
- pkstruct/trees/balancing.py +253 -0
- pkstruct/trees/bplus.py +425 -0
- pkstruct/trees/bst.py +948 -0
- pkstruct/trees/btree.py +504 -0
- pkstruct/trees/exceptions.py +96 -0
- pkstruct/trees/fenwick_tree.py +312 -0
- pkstruct/trees/interval_tree.py +541 -0
- pkstruct/trees/node.py +356 -0
- pkstruct/trees/red_black.py +710 -0
- pkstruct/trees/segment_tree.py +398 -0
- pkstruct/trees/traversal.py +456 -0
- pkstruct/trees/tree_helpers.py +366 -0
- pkstruct/trees/utils/__init__.py +15 -0
- pkstruct/trees/utils/complexity_helpers.py +231 -0
- pkstruct/trees/visualization/__init__.py +0 -0
- pkstruct/trees/visualization/ascii_renderer.py +220 -0
- pkstruct/trees/visualization/tree_printer.py +129 -0
- pkstruct-0.1.0.dist-info/METADATA +482 -0
- pkstruct-0.1.0.dist-info/RECORD +72 -0
- pkstruct-0.1.0.dist-info/WHEEL +4 -0
- pkstruct-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pkstruct.linear.queues.queue
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
Abstract base class for all queue implementations.
|
|
5
|
+
|
|
6
|
+
Provides a formal interface contract. Every concrete queue in ``pkstruct``
|
|
7
|
+
derives from this class and implements all abstract methods.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from collections.abc import Iterator
|
|
14
|
+
from typing import Generic, TypeVar
|
|
15
|
+
|
|
16
|
+
T = TypeVar("T")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Queue(ABC, Generic[T]):
|
|
20
|
+
"""
|
|
21
|
+
Abstract base class for FIFO (first-in, first-out) queue structures.
|
|
22
|
+
|
|
23
|
+
Subclasses must implement all abstract methods. The interface is
|
|
24
|
+
consistent across array-backed, linked-list-backed, and heap-backed
|
|
25
|
+
implementations.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
No parameters required at the abstract level.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
__slots__ = ()
|
|
33
|
+
|
|
34
|
+
# ------------------------------------------------------------------ #
|
|
35
|
+
# Required operations #
|
|
36
|
+
# ------------------------------------------------------------------ #
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def enqueue(self, value: T) -> None:
|
|
40
|
+
"""
|
|
41
|
+
Add *value* to the rear of the queue.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
value:
|
|
46
|
+
The element to enqueue.
|
|
47
|
+
"""
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def dequeue(self) -> T:
|
|
52
|
+
"""
|
|
53
|
+
Remove and return the front element.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
T
|
|
58
|
+
The element at the front of the queue.
|
|
59
|
+
|
|
60
|
+
Raises
|
|
61
|
+
------
|
|
62
|
+
EmptyStructureError
|
|
63
|
+
If the queue is empty.
|
|
64
|
+
"""
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def front(self) -> T:
|
|
69
|
+
"""
|
|
70
|
+
Return the front element without removing it.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
T
|
|
75
|
+
|
|
76
|
+
Raises
|
|
77
|
+
------
|
|
78
|
+
EmptyStructureError
|
|
79
|
+
If the queue is empty.
|
|
80
|
+
"""
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def rear(self) -> T:
|
|
85
|
+
"""
|
|
86
|
+
Return the rear element without removing it.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
T
|
|
91
|
+
|
|
92
|
+
Raises
|
|
93
|
+
------
|
|
94
|
+
EmptyStructureError
|
|
95
|
+
If the queue is empty.
|
|
96
|
+
"""
|
|
97
|
+
...
|
|
98
|
+
|
|
99
|
+
# ------------------------------------------------------------------ #
|
|
100
|
+
# Query operations #
|
|
101
|
+
# ------------------------------------------------------------------ #
|
|
102
|
+
|
|
103
|
+
@abstractmethod
|
|
104
|
+
def is_empty(self) -> bool:
|
|
105
|
+
"""
|
|
106
|
+
Return ``True`` if the queue contains no elements.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
bool
|
|
111
|
+
"""
|
|
112
|
+
...
|
|
113
|
+
|
|
114
|
+
@abstractmethod
|
|
115
|
+
def size(self) -> int:
|
|
116
|
+
"""
|
|
117
|
+
Return the number of elements in the queue.
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
int
|
|
122
|
+
"""
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
def clear(self) -> None:
|
|
127
|
+
"""Remove all elements from the queue."""
|
|
128
|
+
...
|
|
129
|
+
|
|
130
|
+
@abstractmethod
|
|
131
|
+
def copy(self) -> Queue[T]:
|
|
132
|
+
"""
|
|
133
|
+
Return a shallow copy of this queue.
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
Queue[T]
|
|
138
|
+
"""
|
|
139
|
+
...
|
|
140
|
+
|
|
141
|
+
@abstractmethod
|
|
142
|
+
def to_list(self) -> list[T]:
|
|
143
|
+
"""
|
|
144
|
+
Return a list of all elements, from front to rear.
|
|
145
|
+
|
|
146
|
+
Returns
|
|
147
|
+
-------
|
|
148
|
+
list[T]
|
|
149
|
+
"""
|
|
150
|
+
...
|
|
151
|
+
|
|
152
|
+
# ------------------------------------------------------------------ #
|
|
153
|
+
# Dunder methods #
|
|
154
|
+
# ------------------------------------------------------------------ #
|
|
155
|
+
|
|
156
|
+
@abstractmethod
|
|
157
|
+
def __iter__(self) -> Iterator[T]:
|
|
158
|
+
...
|
|
159
|
+
|
|
160
|
+
@abstractmethod
|
|
161
|
+
def __len__(self) -> int:
|
|
162
|
+
...
|
|
163
|
+
|
|
164
|
+
@abstractmethod
|
|
165
|
+
def __bool__(self) -> bool:
|
|
166
|
+
...
|
|
167
|
+
|
|
168
|
+
@abstractmethod
|
|
169
|
+
def __repr__(self) -> str:
|
|
170
|
+
...
|
|
171
|
+
|
|
172
|
+
@abstractmethod
|
|
173
|
+
def __eq__(self, other: object) -> bool:
|
|
174
|
+
...
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pkstruct.linear.stacks
|
|
3
|
+
======================
|
|
4
|
+
|
|
5
|
+
LIFO (last-in, first-out) stack data structures for the pkstruct ecosystem.
|
|
6
|
+
|
|
7
|
+
Provides two concrete stack implementations and a shared abstract base.
|
|
8
|
+
|
|
9
|
+
Classes
|
|
10
|
+
-------
|
|
11
|
+
Stack
|
|
12
|
+
Abstract base class defining the stack interface.
|
|
13
|
+
LinkedStack
|
|
14
|
+
Stack backed by ``SinglyLinkedList`` (node-based, thread-safe).
|
|
15
|
+
ArrayStack
|
|
16
|
+
Stack backed by a Python ``list`` (compact, cache-local).
|
|
17
|
+
|
|
18
|
+
Example
|
|
19
|
+
-------
|
|
20
|
+
>>> from pkstruct.linear.stacks import LinkedStack
|
|
21
|
+
>>> s = LinkedStack([1, 2, 3])
|
|
22
|
+
>>> s.push(4)
|
|
23
|
+
>>> s.pop()
|
|
24
|
+
4
|
|
25
|
+
>>> list(s)
|
|
26
|
+
[3, 2, 1]
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from pkstruct.linear.stacks.array_stack import ArrayStack
|
|
30
|
+
from pkstruct.linear.stacks.linked_stack import LinkedStack
|
|
31
|
+
from pkstruct.linear.stacks.stack import Stack
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
# Stack classes
|
|
35
|
+
"ArrayStack",
|
|
36
|
+
"LinkedStack",
|
|
37
|
+
"Stack",
|
|
38
|
+
]
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pkstruct.linear.stacks.array_stack
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
A LIFO stack backed by a dynamic Python ``list``.
|
|
5
|
+
|
|
6
|
+
``ArrayStack`` offers compact memory (no per-node overhead) and excellent
|
|
7
|
+
cache locality. All operations are O(1) amortised.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Iterator
|
|
13
|
+
from typing import TypeVar
|
|
14
|
+
|
|
15
|
+
from pkstruct.linear.exceptions import EmptyStructureError
|
|
16
|
+
from pkstruct.linear.stacks.stack import Stack
|
|
17
|
+
from pkstruct.shared.threading import StructureLock
|
|
18
|
+
|
|
19
|
+
T = TypeVar("T")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ArrayStack(Stack[T]):
|
|
23
|
+
"""
|
|
24
|
+
A thread-safe stack backed by a Python ``list``.
|
|
25
|
+
|
|
26
|
+
The underlying list grows and shrinks dynamically. Push and pop at the
|
|
27
|
+
end of the list are O(1) amortised.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
items:
|
|
32
|
+
Optional iterable of initial values (bottom-to-top order).
|
|
33
|
+
If omitted the stack starts empty.
|
|
34
|
+
|
|
35
|
+
Examples
|
|
36
|
+
--------
|
|
37
|
+
>>> s = ArrayStack([1, 2, 3])
|
|
38
|
+
>>> s.push(4)
|
|
39
|
+
>>> s.pop()
|
|
40
|
+
4
|
|
41
|
+
>>> s.peek()
|
|
42
|
+
3
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
__slots__ = ("_data", "_lock")
|
|
46
|
+
|
|
47
|
+
def __init__(self, items: list[T] | None = None) -> None:
|
|
48
|
+
self._data: list[T] = list(items) if items is not None else []
|
|
49
|
+
self._lock: StructureLock = StructureLock()
|
|
50
|
+
|
|
51
|
+
# ------------------------------------------------------------------ #
|
|
52
|
+
# Required operations #
|
|
53
|
+
# ------------------------------------------------------------------ #
|
|
54
|
+
|
|
55
|
+
def push(self, value: T) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Push *value* onto the top of the stack.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
value:
|
|
62
|
+
The element to push.
|
|
63
|
+
"""
|
|
64
|
+
with self._lock:
|
|
65
|
+
self._data.append(value)
|
|
66
|
+
|
|
67
|
+
def pop(self) -> T:
|
|
68
|
+
"""
|
|
69
|
+
Remove and return the top element.
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
T
|
|
74
|
+
|
|
75
|
+
Raises
|
|
76
|
+
------
|
|
77
|
+
EmptyStructureError
|
|
78
|
+
If the stack is empty.
|
|
79
|
+
"""
|
|
80
|
+
with self._lock:
|
|
81
|
+
if not self._data:
|
|
82
|
+
raise EmptyStructureError("pop from an empty stack")
|
|
83
|
+
return self._data.pop()
|
|
84
|
+
|
|
85
|
+
def peek(self) -> T:
|
|
86
|
+
"""
|
|
87
|
+
Return the top element without removing it.
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
T
|
|
92
|
+
|
|
93
|
+
Raises
|
|
94
|
+
------
|
|
95
|
+
EmptyStructureError
|
|
96
|
+
If the stack is empty.
|
|
97
|
+
"""
|
|
98
|
+
with self._lock:
|
|
99
|
+
if not self._data:
|
|
100
|
+
raise EmptyStructureError("peek at an empty stack")
|
|
101
|
+
return self._data[-1]
|
|
102
|
+
|
|
103
|
+
# ------------------------------------------------------------------ #
|
|
104
|
+
# Query operations #
|
|
105
|
+
# ------------------------------------------------------------------ #
|
|
106
|
+
|
|
107
|
+
def is_empty(self) -> bool:
|
|
108
|
+
"""Return ``True`` if the stack contains no elements."""
|
|
109
|
+
return len(self._data) == 0
|
|
110
|
+
|
|
111
|
+
def size(self) -> int:
|
|
112
|
+
"""Return the number of elements in the stack."""
|
|
113
|
+
return len(self._data)
|
|
114
|
+
|
|
115
|
+
def clear(self) -> None:
|
|
116
|
+
"""Remove all elements from the stack."""
|
|
117
|
+
with self._lock:
|
|
118
|
+
self._data.clear()
|
|
119
|
+
|
|
120
|
+
def copy(self) -> ArrayStack[T]:
|
|
121
|
+
"""
|
|
122
|
+
Return a shallow copy of this stack.
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
ArrayStack[T]
|
|
127
|
+
"""
|
|
128
|
+
with self._lock:
|
|
129
|
+
new = ArrayStack[T]()
|
|
130
|
+
new._data = self._data.copy()
|
|
131
|
+
return new
|
|
132
|
+
|
|
133
|
+
def to_list(self) -> list[T]:
|
|
134
|
+
"""
|
|
135
|
+
Return a list of all elements, from top to bottom.
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
list[T]
|
|
140
|
+
"""
|
|
141
|
+
with self._lock:
|
|
142
|
+
return list(reversed(self._data))
|
|
143
|
+
|
|
144
|
+
# ------------------------------------------------------------------ #
|
|
145
|
+
# Dunder methods #
|
|
146
|
+
# ------------------------------------------------------------------ #
|
|
147
|
+
|
|
148
|
+
def __iter__(self) -> Iterator[T]:
|
|
149
|
+
with self._lock:
|
|
150
|
+
snapshot = self.to_list()
|
|
151
|
+
return iter(snapshot)
|
|
152
|
+
|
|
153
|
+
def __len__(self) -> int:
|
|
154
|
+
return self.size()
|
|
155
|
+
|
|
156
|
+
def __bool__(self) -> bool:
|
|
157
|
+
return not self.is_empty()
|
|
158
|
+
|
|
159
|
+
def __repr__(self) -> str:
|
|
160
|
+
return f"ArrayStack({self.to_list()!r})"
|
|
161
|
+
|
|
162
|
+
def __eq__(self, other: object) -> bool:
|
|
163
|
+
if not isinstance(other, ArrayStack):
|
|
164
|
+
return NotImplemented
|
|
165
|
+
return self.to_list() == other.to_list()
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pkstruct.linear.stacks.linked_stack
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
A LIFO stack backed by a ``SinglyLinkedList``.
|
|
5
|
+
|
|
6
|
+
``LinkedStack`` composes the existing production-grade linked list rather than
|
|
7
|
+
reimplementing node management. Push is O(1) amortised via head insertion;
|
|
8
|
+
pop and peek are O(1).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from collections.abc import Iterator
|
|
14
|
+
from typing import TypeVar, cast
|
|
15
|
+
|
|
16
|
+
from pkstruct.linear.exceptions import EmptyStructureError
|
|
17
|
+
from pkstruct.linear.linked_lists.singly_linked_list import SinglyLinkedList
|
|
18
|
+
from pkstruct.linear.stacks.stack import Stack
|
|
19
|
+
from pkstruct.shared.threading import StructureLock
|
|
20
|
+
|
|
21
|
+
T = TypeVar("T")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LinkedStack(Stack[T]):
|
|
25
|
+
"""
|
|
26
|
+
A thread-safe stack implemented on top of ``SinglyLinkedList``.
|
|
27
|
+
|
|
28
|
+
The internal linked list stores elements with the top of the stack at
|
|
29
|
+
head position, so ``push``, ``pop``, and ``peek`` all operate at index 0.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
items:
|
|
34
|
+
Optional iterable of initial values (bottom-to-top order).
|
|
35
|
+
If omitted the stack starts empty.
|
|
36
|
+
|
|
37
|
+
Examples
|
|
38
|
+
--------
|
|
39
|
+
>>> s = LinkedStack([1, 2, 3])
|
|
40
|
+
>>> s.push(4)
|
|
41
|
+
>>> s.pop()
|
|
42
|
+
4
|
|
43
|
+
>>> s.peek()
|
|
44
|
+
3
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
__slots__ = ("_list", "_lock")
|
|
48
|
+
|
|
49
|
+
def __init__(self, items: list[T] | None = None) -> None:
|
|
50
|
+
self._list: SinglyLinkedList[T] = SinglyLinkedList()
|
|
51
|
+
self._lock: StructureLock = StructureLock()
|
|
52
|
+
if items is not None:
|
|
53
|
+
for item in items:
|
|
54
|
+
self._list.insert(item, position=0)
|
|
55
|
+
|
|
56
|
+
# ------------------------------------------------------------------ #
|
|
57
|
+
# Required operations #
|
|
58
|
+
# ------------------------------------------------------------------ #
|
|
59
|
+
|
|
60
|
+
def push(self, value: T) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Push *value* onto the top of the stack (head of the list).
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
value:
|
|
67
|
+
The element to push.
|
|
68
|
+
"""
|
|
69
|
+
with self._lock:
|
|
70
|
+
self._list.insert(value, position=0)
|
|
71
|
+
|
|
72
|
+
def pop(self) -> T:
|
|
73
|
+
"""
|
|
74
|
+
Remove and return the top element.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
T
|
|
79
|
+
|
|
80
|
+
Raises
|
|
81
|
+
------
|
|
82
|
+
EmptyStructureError
|
|
83
|
+
If the stack is empty.
|
|
84
|
+
"""
|
|
85
|
+
with self._lock:
|
|
86
|
+
if self._list.is_empty():
|
|
87
|
+
raise EmptyStructureError("pop from an empty stack")
|
|
88
|
+
return cast(T, self._list.delete(position=0))
|
|
89
|
+
|
|
90
|
+
def peek(self) -> T:
|
|
91
|
+
"""
|
|
92
|
+
Return the top element without removing it.
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
T
|
|
97
|
+
|
|
98
|
+
Raises
|
|
99
|
+
------
|
|
100
|
+
EmptyStructureError
|
|
101
|
+
If the stack is empty.
|
|
102
|
+
"""
|
|
103
|
+
with self._lock:
|
|
104
|
+
if self._list.is_empty():
|
|
105
|
+
raise EmptyStructureError("peek at an empty stack")
|
|
106
|
+
return self._list.get(0)
|
|
107
|
+
|
|
108
|
+
# ------------------------------------------------------------------ #
|
|
109
|
+
# Query operations #
|
|
110
|
+
# ------------------------------------------------------------------ #
|
|
111
|
+
|
|
112
|
+
def is_empty(self) -> bool:
|
|
113
|
+
"""Return ``True`` if the stack contains no elements."""
|
|
114
|
+
return self._list.is_empty()
|
|
115
|
+
|
|
116
|
+
def size(self) -> int:
|
|
117
|
+
"""Return the number of elements in the stack."""
|
|
118
|
+
return self._list.size()
|
|
119
|
+
|
|
120
|
+
def clear(self) -> None:
|
|
121
|
+
"""Remove all elements from the stack."""
|
|
122
|
+
self._list.clear()
|
|
123
|
+
|
|
124
|
+
def copy(self) -> LinkedStack[T]:
|
|
125
|
+
"""
|
|
126
|
+
Return a shallow copy of this stack.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
LinkedStack[T]
|
|
131
|
+
"""
|
|
132
|
+
with self._lock:
|
|
133
|
+
new = LinkedStack[T]()
|
|
134
|
+
new._list = cast(SinglyLinkedList[T], self._list.copy())
|
|
135
|
+
return new
|
|
136
|
+
|
|
137
|
+
def to_list(self) -> list[T]:
|
|
138
|
+
"""
|
|
139
|
+
Return a list of all elements, from top to bottom.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
list[T]
|
|
144
|
+
"""
|
|
145
|
+
return self._list.to_list()
|
|
146
|
+
|
|
147
|
+
# ------------------------------------------------------------------ #
|
|
148
|
+
# Dunder methods #
|
|
149
|
+
# ------------------------------------------------------------------ #
|
|
150
|
+
|
|
151
|
+
def __iter__(self) -> Iterator[T]:
|
|
152
|
+
with self._lock:
|
|
153
|
+
snapshot = self.to_list()
|
|
154
|
+
return iter(snapshot)
|
|
155
|
+
|
|
156
|
+
def __len__(self) -> int:
|
|
157
|
+
return self.size()
|
|
158
|
+
|
|
159
|
+
def __bool__(self) -> bool:
|
|
160
|
+
return not self.is_empty()
|
|
161
|
+
|
|
162
|
+
def __repr__(self) -> str:
|
|
163
|
+
return f"LinkedStack({self.to_list()!r})"
|
|
164
|
+
|
|
165
|
+
def __eq__(self, other: object) -> bool:
|
|
166
|
+
if not isinstance(other, LinkedStack):
|
|
167
|
+
return NotImplemented
|
|
168
|
+
return self.to_list() == other.to_list()
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pkstruct.linear.stacks.stack
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
Abstract base class for all stack implementations.
|
|
5
|
+
|
|
6
|
+
Provides a formal interface contract. Every concrete stack in ``pkstruct``
|
|
7
|
+
derives from this class and implements all abstract methods.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from collections.abc import Iterator
|
|
14
|
+
from typing import Generic, TypeVar
|
|
15
|
+
|
|
16
|
+
T = TypeVar("T")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Stack(ABC, Generic[T]):
|
|
20
|
+
"""
|
|
21
|
+
Abstract base class for LIFO (last-in, first-out) stack structures.
|
|
22
|
+
|
|
23
|
+
Subclasses must implement all abstract methods. The interface is designed
|
|
24
|
+
to be consistent across both array-backed and linked-list-backed stacks.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
No parameters required at the abstract level.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
__slots__ = ()
|
|
32
|
+
|
|
33
|
+
# ------------------------------------------------------------------ #
|
|
34
|
+
# Required operations #
|
|
35
|
+
# ------------------------------------------------------------------ #
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def push(self, value: T) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Push *value* onto the top of the stack.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
value:
|
|
45
|
+
The element to push.
|
|
46
|
+
"""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def pop(self) -> T:
|
|
51
|
+
"""
|
|
52
|
+
Remove and return the top element.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
T
|
|
57
|
+
The element at the top of the stack.
|
|
58
|
+
|
|
59
|
+
Raises
|
|
60
|
+
------
|
|
61
|
+
EmptyStructureError
|
|
62
|
+
If the stack is empty.
|
|
63
|
+
"""
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def peek(self) -> T:
|
|
68
|
+
"""
|
|
69
|
+
Return the top element without removing it.
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
T
|
|
74
|
+
The element at the top of the stack.
|
|
75
|
+
|
|
76
|
+
Raises
|
|
77
|
+
------
|
|
78
|
+
EmptyStructureError
|
|
79
|
+
If the stack is empty.
|
|
80
|
+
"""
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
# ------------------------------------------------------------------ #
|
|
84
|
+
# Query operations #
|
|
85
|
+
# ------------------------------------------------------------------ #
|
|
86
|
+
|
|
87
|
+
@abstractmethod
|
|
88
|
+
def is_empty(self) -> bool:
|
|
89
|
+
"""
|
|
90
|
+
Return ``True`` if the stack contains no elements.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
bool
|
|
95
|
+
"""
|
|
96
|
+
...
|
|
97
|
+
|
|
98
|
+
@abstractmethod
|
|
99
|
+
def size(self) -> int:
|
|
100
|
+
"""
|
|
101
|
+
Return the number of elements in the stack.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
int
|
|
106
|
+
"""
|
|
107
|
+
...
|
|
108
|
+
|
|
109
|
+
@abstractmethod
|
|
110
|
+
def clear(self) -> None:
|
|
111
|
+
"""Remove all elements from the stack."""
|
|
112
|
+
...
|
|
113
|
+
|
|
114
|
+
@abstractmethod
|
|
115
|
+
def copy(self) -> Stack[T]:
|
|
116
|
+
"""
|
|
117
|
+
Return a shallow copy of this stack.
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
Stack[T]
|
|
122
|
+
"""
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
def to_list(self) -> list[T]:
|
|
127
|
+
"""
|
|
128
|
+
Return a list of all elements, from top to bottom.
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
list[T]
|
|
133
|
+
"""
|
|
134
|
+
...
|
|
135
|
+
|
|
136
|
+
# ------------------------------------------------------------------ #
|
|
137
|
+
# Dunder methods #
|
|
138
|
+
# ------------------------------------------------------------------ #
|
|
139
|
+
|
|
140
|
+
@abstractmethod
|
|
141
|
+
def __iter__(self) -> Iterator[T]:
|
|
142
|
+
...
|
|
143
|
+
|
|
144
|
+
@abstractmethod
|
|
145
|
+
def __len__(self) -> int:
|
|
146
|
+
...
|
|
147
|
+
|
|
148
|
+
@abstractmethod
|
|
149
|
+
def __bool__(self) -> bool:
|
|
150
|
+
...
|
|
151
|
+
|
|
152
|
+
@abstractmethod
|
|
153
|
+
def __repr__(self) -> str:
|
|
154
|
+
...
|
|
155
|
+
|
|
156
|
+
@abstractmethod
|
|
157
|
+
def __eq__(self, other: object) -> bool:
|
|
158
|
+
...
|