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,230 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, TypeVar, cast
|
|
4
|
+
|
|
5
|
+
from pkstruct.linear.exceptions import (
|
|
6
|
+
EmptyStructureError,
|
|
7
|
+
ValidationError,
|
|
8
|
+
)
|
|
9
|
+
from pkstruct.linear.linked_lists._base import _LinkedListBase
|
|
10
|
+
from pkstruct.linear.linked_lists.nodes import CircularNode
|
|
11
|
+
from pkstruct.shared.validators import validate_index
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CircularLinkedList(_LinkedListBase[T]):
|
|
17
|
+
__slots__ = ()
|
|
18
|
+
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
super().__init__()
|
|
21
|
+
|
|
22
|
+
def _append(self, value: T) -> None:
|
|
23
|
+
new_node: CircularNode[T] = CircularNode(value)
|
|
24
|
+
if self._tail is None:
|
|
25
|
+
new_node.next = new_node
|
|
26
|
+
self._head = new_node
|
|
27
|
+
self._tail = new_node
|
|
28
|
+
else:
|
|
29
|
+
new_node.next = self._head
|
|
30
|
+
self._tail.next = new_node
|
|
31
|
+
self._tail = new_node
|
|
32
|
+
self._size += 1
|
|
33
|
+
|
|
34
|
+
def _prepend(self, value: T) -> None:
|
|
35
|
+
new_node: CircularNode[T] = CircularNode(value)
|
|
36
|
+
if self._tail is None:
|
|
37
|
+
new_node.next = new_node
|
|
38
|
+
self._head = new_node
|
|
39
|
+
self._tail = new_node
|
|
40
|
+
else:
|
|
41
|
+
new_node.next = self._head
|
|
42
|
+
self._tail.next = new_node
|
|
43
|
+
self._head = new_node
|
|
44
|
+
self._size += 1
|
|
45
|
+
|
|
46
|
+
def _node_at(self, index: int) -> CircularNode[T]:
|
|
47
|
+
validate_index(index, self._size)
|
|
48
|
+
node: CircularNode[T] | None = self._head
|
|
49
|
+
for _ in range(index):
|
|
50
|
+
if node is None:
|
|
51
|
+
raise RuntimeError("internal invariant violated: unexpected None in linked list chain")
|
|
52
|
+
node = node.next
|
|
53
|
+
if node is None:
|
|
54
|
+
raise RuntimeError("internal invariant violated: unexpected None in linked list chain")
|
|
55
|
+
return node
|
|
56
|
+
|
|
57
|
+
def _node_before(self, target: CircularNode[T]) -> CircularNode[T]:
|
|
58
|
+
node: CircularNode[T] | None = self._tail
|
|
59
|
+
while node is not None and node.next is not target:
|
|
60
|
+
node = node.next
|
|
61
|
+
if node is None:
|
|
62
|
+
raise RuntimeError("internal invariant violated: unexpected None in linked list chain")
|
|
63
|
+
return node
|
|
64
|
+
|
|
65
|
+
def _find_node(self, value: object) -> CircularNode[T] | None:
|
|
66
|
+
if self._head is None:
|
|
67
|
+
return None
|
|
68
|
+
node: CircularNode[T] | None = self._head
|
|
69
|
+
for _ in range(self._size):
|
|
70
|
+
if node is None:
|
|
71
|
+
return None
|
|
72
|
+
if node.value == value:
|
|
73
|
+
return node
|
|
74
|
+
node = node.next
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def _remove_node(self, node: CircularNode[T]) -> T:
|
|
78
|
+
if self._size == 1:
|
|
79
|
+
self._head = None
|
|
80
|
+
self._tail = None
|
|
81
|
+
node.next = None
|
|
82
|
+
self._size -= 1
|
|
83
|
+
return node.value
|
|
84
|
+
prev = self._node_before(node)
|
|
85
|
+
prev.next = node.next
|
|
86
|
+
if node is self._head:
|
|
87
|
+
self._head = node.next
|
|
88
|
+
if node is self._tail:
|
|
89
|
+
self._tail = prev
|
|
90
|
+
node.next = None
|
|
91
|
+
self._size -= 1
|
|
92
|
+
return node.value
|
|
93
|
+
|
|
94
|
+
def _insert_before_node(self, node: CircularNode[T], value: T) -> None:
|
|
95
|
+
if node is self._head:
|
|
96
|
+
self._prepend(value)
|
|
97
|
+
else:
|
|
98
|
+
prev = self._node_before(node)
|
|
99
|
+
new_node: CircularNode[T] = CircularNode(value)
|
|
100
|
+
new_node.next = node
|
|
101
|
+
prev.next = new_node
|
|
102
|
+
self._size += 1
|
|
103
|
+
|
|
104
|
+
def _insert_after_node(self, node: CircularNode[T], value: T) -> None:
|
|
105
|
+
new_node: CircularNode[T] = CircularNode(value)
|
|
106
|
+
new_node.next = node.next
|
|
107
|
+
node.next = new_node
|
|
108
|
+
if node is self._tail:
|
|
109
|
+
self._tail = new_node
|
|
110
|
+
self._size += 1
|
|
111
|
+
|
|
112
|
+
def _to_list_unsafe(self) -> list[T]:
|
|
113
|
+
if self._head is None:
|
|
114
|
+
return []
|
|
115
|
+
result: list[T] = []
|
|
116
|
+
node = self._head
|
|
117
|
+
for _ in range(self._size):
|
|
118
|
+
result.append(node.value)
|
|
119
|
+
node = node.next
|
|
120
|
+
return result
|
|
121
|
+
|
|
122
|
+
# ------------------------------------------------------------------ #
|
|
123
|
+
# Rotation overrides (pointer-based for full list) #
|
|
124
|
+
# ------------------------------------------------------------------ #
|
|
125
|
+
|
|
126
|
+
def _rotate_full_list(self, shift: int) -> None:
|
|
127
|
+
if shift == 0 or self._size <= 1:
|
|
128
|
+
return
|
|
129
|
+
steps = (self._size - shift) % self._size
|
|
130
|
+
for _ in range(steps):
|
|
131
|
+
self._head = self._head.next
|
|
132
|
+
self._tail = self._tail.next
|
|
133
|
+
|
|
134
|
+
def rotate_head(self, steps: int = 1, direction: bool = True) -> None:
|
|
135
|
+
with self._lock:
|
|
136
|
+
if self._size == 0:
|
|
137
|
+
raise EmptyStructureError("rotate_head on an empty list")
|
|
138
|
+
self._tracer.record("rotate_head", steps=steps, direction=direction)
|
|
139
|
+
steps = steps % self._size
|
|
140
|
+
if steps == 0:
|
|
141
|
+
return
|
|
142
|
+
if not direction:
|
|
143
|
+
steps = self._size - steps
|
|
144
|
+
for _ in range(steps):
|
|
145
|
+
self._tail = self._head
|
|
146
|
+
self._head = self._head.next
|
|
147
|
+
|
|
148
|
+
# ------------------------------------------------------------------ #
|
|
149
|
+
# Josephus problem (circular-unique) #
|
|
150
|
+
# ------------------------------------------------------------------ #
|
|
151
|
+
|
|
152
|
+
def josephus(self, step: int) -> T:
|
|
153
|
+
with self._lock:
|
|
154
|
+
if self._size == 0:
|
|
155
|
+
raise EmptyStructureError("josephus on an empty list")
|
|
156
|
+
if step < 1:
|
|
157
|
+
raise ValidationError("'step' must be >= 1.")
|
|
158
|
+
self._tracer.record("josephus", step=step)
|
|
159
|
+
current = self._head
|
|
160
|
+
while self._size > 1:
|
|
161
|
+
for _ in range(step - 1):
|
|
162
|
+
current = current.next
|
|
163
|
+
next_node = current.next
|
|
164
|
+
self._remove_node(current)
|
|
165
|
+
current = next_node
|
|
166
|
+
survivor = cast(T, self._head.value)
|
|
167
|
+
self.clear()
|
|
168
|
+
return survivor
|
|
169
|
+
|
|
170
|
+
# ------------------------------------------------------------------ #
|
|
171
|
+
# Visualization & Debug #
|
|
172
|
+
# ------------------------------------------------------------------ #
|
|
173
|
+
|
|
174
|
+
def visualize(self, style: str = "ascii") -> str:
|
|
175
|
+
with self._lock:
|
|
176
|
+
if self._size == 0:
|
|
177
|
+
return "(empty circular list)"
|
|
178
|
+
values = self._to_list_unsafe()
|
|
179
|
+
if style == "simple":
|
|
180
|
+
return ", ".join(str(v) for v in values)
|
|
181
|
+
if style == "ring":
|
|
182
|
+
nodes_str = " -> ".join(f"[{v}]" for v in values)
|
|
183
|
+
return f"{nodes_str} -> \u21ba"
|
|
184
|
+
inner = " -> ".join(str(v) for v in values)
|
|
185
|
+
return f"{inner} -> (back to head)"
|
|
186
|
+
|
|
187
|
+
def debug(self) -> dict[str, Any]:
|
|
188
|
+
with self._lock:
|
|
189
|
+
invariant = (
|
|
190
|
+
(self._tail is not None and self._tail.next is self._head)
|
|
191
|
+
if self._size > 0
|
|
192
|
+
else True
|
|
193
|
+
)
|
|
194
|
+
return {
|
|
195
|
+
"type": "CircularLinkedList",
|
|
196
|
+
"length": self._size,
|
|
197
|
+
"size": self._size,
|
|
198
|
+
"head": self._head.value if self._head else None,
|
|
199
|
+
"tail": self._tail.value if self._tail else None,
|
|
200
|
+
"tail_next": (self._tail.next.value if self._tail and self._tail.next else None),
|
|
201
|
+
"invariant_ok": invariant,
|
|
202
|
+
"values": self._to_list_unsafe(),
|
|
203
|
+
"events": self._tracer.get_events(),
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
def detect_cycle(self, return_start: bool = False) -> bool | tuple[bool, Any]:
|
|
207
|
+
with self._lock:
|
|
208
|
+
if self._head is None:
|
|
209
|
+
return (False, None) if return_start else False
|
|
210
|
+
slow = self._head
|
|
211
|
+
fast = self._head
|
|
212
|
+
has_cycle = False
|
|
213
|
+
max_steps = self._size * 2
|
|
214
|
+
for _ in range(max_steps):
|
|
215
|
+
if fast.next is None or fast.next.next is None:
|
|
216
|
+
break
|
|
217
|
+
slow = slow.next
|
|
218
|
+
fast = fast.next.next
|
|
219
|
+
if slow is fast:
|
|
220
|
+
has_cycle = True
|
|
221
|
+
break
|
|
222
|
+
if not return_start:
|
|
223
|
+
return has_cycle
|
|
224
|
+
if not has_cycle:
|
|
225
|
+
return False, None
|
|
226
|
+
slow = self._head
|
|
227
|
+
while slow is not fast:
|
|
228
|
+
slow = slow.next
|
|
229
|
+
fast = fast.next
|
|
230
|
+
return True, slow.value
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, TypeVar, cast
|
|
4
|
+
|
|
5
|
+
from pkstruct.linear.exceptions import EmptyStructureError
|
|
6
|
+
from pkstruct.linear.linked_lists._base import _LinkedListBase
|
|
7
|
+
from pkstruct.linear.linked_lists.nodes import DoublyNode
|
|
8
|
+
from pkstruct.shared.validators import validate_range
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DoublyLinkedList(_LinkedListBase[T]):
|
|
14
|
+
__slots__ = ()
|
|
15
|
+
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
super().__init__()
|
|
18
|
+
|
|
19
|
+
def _append(self, value: T) -> None:
|
|
20
|
+
new_node: DoublyNode[T] = DoublyNode(value)
|
|
21
|
+
if self._tail is None:
|
|
22
|
+
self._head = new_node
|
|
23
|
+
self._tail = new_node
|
|
24
|
+
else:
|
|
25
|
+
new_node.prev = self._tail
|
|
26
|
+
self._tail.next = new_node
|
|
27
|
+
self._tail = new_node
|
|
28
|
+
self._size += 1
|
|
29
|
+
|
|
30
|
+
def _prepend(self, value: T) -> None:
|
|
31
|
+
new_node: DoublyNode[T] = DoublyNode(value)
|
|
32
|
+
if self._head is None:
|
|
33
|
+
self._head = new_node
|
|
34
|
+
self._tail = new_node
|
|
35
|
+
else:
|
|
36
|
+
new_node.next = self._head
|
|
37
|
+
self._head.prev = new_node
|
|
38
|
+
self._head = new_node
|
|
39
|
+
self._size += 1
|
|
40
|
+
|
|
41
|
+
def _node_at(self, index: int) -> DoublyNode[T]:
|
|
42
|
+
if index <= self._size // 2:
|
|
43
|
+
node: DoublyNode[T] | None = self._head
|
|
44
|
+
for _ in range(index):
|
|
45
|
+
if node is None:
|
|
46
|
+
raise RuntimeError("internal invariant violated: unexpected None in linked list chain")
|
|
47
|
+
node = node.next
|
|
48
|
+
else:
|
|
49
|
+
node = self._tail
|
|
50
|
+
for _ in range(self._size - 1 - index):
|
|
51
|
+
if node is None:
|
|
52
|
+
raise RuntimeError("internal invariant violated: unexpected None in linked list chain")
|
|
53
|
+
node = node.prev
|
|
54
|
+
if node is None:
|
|
55
|
+
raise RuntimeError("internal invariant violated: unexpected None in linked list chain")
|
|
56
|
+
return node
|
|
57
|
+
|
|
58
|
+
def _find_node(self, value: object) -> DoublyNode[T] | None:
|
|
59
|
+
node: DoublyNode[T] | None = self._head
|
|
60
|
+
while node is not None:
|
|
61
|
+
if node.value == value:
|
|
62
|
+
return node
|
|
63
|
+
node = node.next
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
def _remove_node(self, node: DoublyNode[T]) -> T:
|
|
67
|
+
prev_node = node.prev
|
|
68
|
+
next_node = node.next
|
|
69
|
+
if prev_node is not None:
|
|
70
|
+
prev_node.next = next_node
|
|
71
|
+
else:
|
|
72
|
+
self._head = next_node
|
|
73
|
+
if next_node is not None:
|
|
74
|
+
next_node.prev = prev_node
|
|
75
|
+
else:
|
|
76
|
+
self._tail = prev_node
|
|
77
|
+
node.next = None
|
|
78
|
+
node.prev = None
|
|
79
|
+
self._size -= 1
|
|
80
|
+
return node.value
|
|
81
|
+
|
|
82
|
+
def _insert_before_node(self, node: DoublyNode[T], value: T) -> None:
|
|
83
|
+
new_node: DoublyNode[T] = DoublyNode(value)
|
|
84
|
+
prev_node = node.prev
|
|
85
|
+
new_node.next = node
|
|
86
|
+
new_node.prev = prev_node
|
|
87
|
+
node.prev = new_node
|
|
88
|
+
if prev_node is None:
|
|
89
|
+
self._head = new_node
|
|
90
|
+
else:
|
|
91
|
+
prev_node.next = new_node
|
|
92
|
+
self._size += 1
|
|
93
|
+
|
|
94
|
+
def _insert_after_node(self, node: DoublyNode[T], value: T) -> None:
|
|
95
|
+
new_node: DoublyNode[T] = DoublyNode(value)
|
|
96
|
+
next_node = node.next
|
|
97
|
+
new_node.prev = node
|
|
98
|
+
new_node.next = next_node
|
|
99
|
+
node.next = new_node
|
|
100
|
+
if next_node is None:
|
|
101
|
+
self._tail = new_node
|
|
102
|
+
else:
|
|
103
|
+
next_node.prev = new_node
|
|
104
|
+
self._size += 1
|
|
105
|
+
|
|
106
|
+
def _to_list_unsafe(self) -> list[T]:
|
|
107
|
+
result: list[T] = []
|
|
108
|
+
node = self._head
|
|
109
|
+
while node is not None:
|
|
110
|
+
result.append(node.value)
|
|
111
|
+
node = node.next
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
def reverse(self, start: int | None = None, end: int | None = None) -> None:
|
|
115
|
+
with self._lock:
|
|
116
|
+
if self._size == 0:
|
|
117
|
+
raise EmptyStructureError("reverse an empty list")
|
|
118
|
+
lo = 0 if start is None else start
|
|
119
|
+
hi = self._size - 1 if end is None else end
|
|
120
|
+
if lo >= hi:
|
|
121
|
+
return
|
|
122
|
+
validate_range(lo, hi, self._size)
|
|
123
|
+
self._tracer.record("reverse", start=lo, end=hi)
|
|
124
|
+
left = self._node_at(lo)
|
|
125
|
+
right = self._node_at(hi)
|
|
126
|
+
while left is not right and left is not cast(DoublyNode[T], right.next):
|
|
127
|
+
left.value, right.value = right.value, left.value
|
|
128
|
+
left = cast(DoublyNode[T], left.next)
|
|
129
|
+
right = cast(DoublyNode[T], right.prev)
|
|
130
|
+
|
|
131
|
+
def visualize(self, style: str = "ascii") -> str:
|
|
132
|
+
with self._lock:
|
|
133
|
+
if self._size == 0:
|
|
134
|
+
return "None <- (empty) -> None"
|
|
135
|
+
values = self._to_list_unsafe()
|
|
136
|
+
if style == "simple":
|
|
137
|
+
return ", ".join(str(v) for v in values)
|
|
138
|
+
inner = " <-> ".join(str(v) for v in values)
|
|
139
|
+
return f"None <- {inner} -> None"
|
|
140
|
+
|
|
141
|
+
def debug(self) -> dict[str, Any]:
|
|
142
|
+
with self._lock:
|
|
143
|
+
return {
|
|
144
|
+
"type": "DoublyLinkedList",
|
|
145
|
+
"length": self._size,
|
|
146
|
+
"size": self._size,
|
|
147
|
+
"head": self._head.value if self._head else None,
|
|
148
|
+
"tail": self._tail.value if self._tail else None,
|
|
149
|
+
"values": self._to_list_unsafe(),
|
|
150
|
+
"events": self._tracer.get_events(),
|
|
151
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pkstruct.linear.linked_lists.nodes
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
Node definitions for all linked-list variants.
|
|
5
|
+
|
|
6
|
+
All nodes use ``__slots__`` to minimise per-object memory overhead.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Generic, TypeVar
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SinglyNode(Generic[T]):
|
|
16
|
+
"""A node in a singly-linked list.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
value: The stored element.
|
|
20
|
+
next: Reference to the next node, or ``None`` if tail.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
__slots__ = ("value", "next")
|
|
24
|
+
|
|
25
|
+
def __init__(self, value: T) -> None:
|
|
26
|
+
self.value: T = value
|
|
27
|
+
self.next: SinglyNode[T] | None = None
|
|
28
|
+
|
|
29
|
+
def __repr__(self) -> str:
|
|
30
|
+
return f"SinglyNode({self.value!r})"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DoublyNode(Generic[T]):
|
|
34
|
+
"""A node in a doubly-linked list.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
value: The stored element.
|
|
38
|
+
next: Reference to the next node, or ``None`` if tail.
|
|
39
|
+
prev: Reference to the previous node, or ``None`` if head.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
__slots__ = ("value", "next", "prev")
|
|
43
|
+
|
|
44
|
+
def __init__(self, value: T) -> None:
|
|
45
|
+
self.value: T = value
|
|
46
|
+
self.next: DoublyNode[T] | None = None
|
|
47
|
+
self.prev: DoublyNode[T] | None = None
|
|
48
|
+
|
|
49
|
+
def __repr__(self) -> str:
|
|
50
|
+
return f"DoublyNode({self.value!r})"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class CircularNode(Generic[T]):
|
|
54
|
+
"""A node in a circular singly-linked list.
|
|
55
|
+
|
|
56
|
+
Attributes:
|
|
57
|
+
value: The stored element.
|
|
58
|
+
next: Always points to a valid node (wraps to head at tail).
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
__slots__ = ("value", "next")
|
|
62
|
+
|
|
63
|
+
def __init__(self, value: T) -> None:
|
|
64
|
+
self.value: T = value
|
|
65
|
+
self.next: CircularNode[T] | None = None # set to self or next during build
|
|
66
|
+
|
|
67
|
+
def __repr__(self) -> str:
|
|
68
|
+
return f"CircularNode({self.value!r})"
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, TypeVar
|
|
4
|
+
|
|
5
|
+
from pkstruct.linear.linked_lists._base import _LinkedListBase
|
|
6
|
+
from pkstruct.linear.linked_lists.nodes import SinglyNode
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SinglyLinkedList(_LinkedListBase[T]):
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
super().__init__()
|
|
14
|
+
|
|
15
|
+
def _append(self, value: T) -> None:
|
|
16
|
+
node: SinglyNode[T] = SinglyNode(value)
|
|
17
|
+
if self._head is None:
|
|
18
|
+
self._head = node
|
|
19
|
+
self._tail = node
|
|
20
|
+
else:
|
|
21
|
+
self._tail.next = node
|
|
22
|
+
self._tail = node
|
|
23
|
+
self._size += 1
|
|
24
|
+
|
|
25
|
+
def _prepend(self, value: T) -> None:
|
|
26
|
+
node: SinglyNode[T] = SinglyNode(value)
|
|
27
|
+
node.next = self._head
|
|
28
|
+
self._head = node
|
|
29
|
+
if self._tail is None:
|
|
30
|
+
self._tail = node
|
|
31
|
+
self._size += 1
|
|
32
|
+
|
|
33
|
+
def _node_at(self, index: int) -> SinglyNode[T]:
|
|
34
|
+
cur: SinglyNode[T] | None = self._head
|
|
35
|
+
for _ in range(index):
|
|
36
|
+
if cur is None:
|
|
37
|
+
raise RuntimeError("internal invariant violated: unexpected None in linked list chain")
|
|
38
|
+
cur = cur.next
|
|
39
|
+
if cur is None:
|
|
40
|
+
raise RuntimeError("internal invariant violated: unexpected None in linked list chain")
|
|
41
|
+
return cur
|
|
42
|
+
|
|
43
|
+
def _find_node(self, value: object) -> SinglyNode[T] | None:
|
|
44
|
+
cur: SinglyNode[T] | None = self._head
|
|
45
|
+
while cur is not None:
|
|
46
|
+
if cur.value == value:
|
|
47
|
+
return cur
|
|
48
|
+
cur = cur.next
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
def _remove_node(self, node: SinglyNode[T]) -> T:
|
|
52
|
+
if node is self._head:
|
|
53
|
+
self._head = node.next
|
|
54
|
+
if self._head is None:
|
|
55
|
+
self._tail = None
|
|
56
|
+
else:
|
|
57
|
+
prev = self._head
|
|
58
|
+
while prev is not None and prev.next is not node:
|
|
59
|
+
prev = prev.next
|
|
60
|
+
if prev is not None:
|
|
61
|
+
prev.next = node.next
|
|
62
|
+
if prev.next is None:
|
|
63
|
+
self._tail = prev
|
|
64
|
+
node.next = None
|
|
65
|
+
self._size -= 1
|
|
66
|
+
return node.value
|
|
67
|
+
|
|
68
|
+
def _insert_before_node(self, node: SinglyNode[T], value: T) -> None:
|
|
69
|
+
new_node: SinglyNode[T] = SinglyNode(value)
|
|
70
|
+
if node is self._head:
|
|
71
|
+
new_node.next = self._head
|
|
72
|
+
self._head = new_node
|
|
73
|
+
else:
|
|
74
|
+
prev = self._head
|
|
75
|
+
while prev is not None and prev.next is not node:
|
|
76
|
+
prev = prev.next
|
|
77
|
+
if prev is not None:
|
|
78
|
+
new_node.next = node
|
|
79
|
+
prev.next = new_node
|
|
80
|
+
self._size += 1
|
|
81
|
+
|
|
82
|
+
def _insert_after_node(self, node: SinglyNode[T], value: T) -> None:
|
|
83
|
+
new_node: SinglyNode[T] = SinglyNode(value)
|
|
84
|
+
new_node.next = node.next
|
|
85
|
+
node.next = new_node
|
|
86
|
+
if node is self._tail:
|
|
87
|
+
self._tail = new_node
|
|
88
|
+
self._size += 1
|
|
89
|
+
|
|
90
|
+
def _to_list_unsafe(self) -> list[T]:
|
|
91
|
+
result: list[T] = []
|
|
92
|
+
cur = self._head
|
|
93
|
+
while cur is not None:
|
|
94
|
+
result.append(cur.value)
|
|
95
|
+
cur = cur.next
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
def intersection_node(self, other: SinglyLinkedList[T]) -> T | None:
|
|
99
|
+
with self._lock:
|
|
100
|
+
len_a = self._size
|
|
101
|
+
len_b = other._size
|
|
102
|
+
cur_a: SinglyNode[T] | None = self._head
|
|
103
|
+
cur_b: SinglyNode[T] | None = other._head
|
|
104
|
+
while len_a > len_b:
|
|
105
|
+
cur_a = cur_a.next if cur_a else None
|
|
106
|
+
len_a -= 1
|
|
107
|
+
while len_b > len_a:
|
|
108
|
+
cur_b = cur_b.next if cur_b else None
|
|
109
|
+
len_b -= 1
|
|
110
|
+
while cur_a is not cur_b:
|
|
111
|
+
cur_a = cur_a.next if cur_a else None
|
|
112
|
+
cur_b = cur_b.next if cur_b else None
|
|
113
|
+
return cur_a.value if cur_a is not None else None
|
|
114
|
+
|
|
115
|
+
def visualize(self, style: str = "ascii") -> str:
|
|
116
|
+
with self._lock:
|
|
117
|
+
items = self._to_list_unsafe()
|
|
118
|
+
if style != "ascii":
|
|
119
|
+
from pkstruct.linear.exceptions import ValidationError
|
|
120
|
+
raise ValidationError(f"Unknown style {style!r}. Only 'ascii' is supported.")
|
|
121
|
+
if not items:
|
|
122
|
+
return "NULL (empty)"
|
|
123
|
+
parts = [f"[{v!r}]" for v in items]
|
|
124
|
+
return " -> ".join(parts) + " -> NULL"
|
|
125
|
+
|
|
126
|
+
def debug(self) -> dict[str, Any]:
|
|
127
|
+
with self._lock:
|
|
128
|
+
return {
|
|
129
|
+
"type": "SinglyLinkedList",
|
|
130
|
+
"length": self._size,
|
|
131
|
+
"size": self._size,
|
|
132
|
+
"head": repr(self._head),
|
|
133
|
+
"tail": repr(self._node_at(self._size - 1)) if self._size > 0 else None,
|
|
134
|
+
"values": self._to_list_unsafe(),
|
|
135
|
+
"tracer": self._tracer.summary(),
|
|
136
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pkstruct.linear.queues
|
|
3
|
+
======================
|
|
4
|
+
|
|
5
|
+
FIFO (first-in, first-out) queue data structures for the pkstruct ecosystem.
|
|
6
|
+
|
|
7
|
+
Provides multiple concrete queue implementations and a shared abstract base.
|
|
8
|
+
|
|
9
|
+
Classes
|
|
10
|
+
-------
|
|
11
|
+
Queue
|
|
12
|
+
Abstract base class defining the queue interface.
|
|
13
|
+
LinkedQueue
|
|
14
|
+
Queue backed by ``SinglyLinkedList`` (node-based, thread-safe).
|
|
15
|
+
CircularQueue
|
|
16
|
+
Fixed-capacity queue backed by a ring buffer.
|
|
17
|
+
PriorityQueue
|
|
18
|
+
Min-heap priority queue backed by ``heapq``.
|
|
19
|
+
|
|
20
|
+
Example
|
|
21
|
+
-------
|
|
22
|
+
>>> from pkstruct.linear.queues import LinkedQueue
|
|
23
|
+
>>> q = LinkedQueue([1, 2, 3])
|
|
24
|
+
>>> q.enqueue(4)
|
|
25
|
+
>>> q.dequeue()
|
|
26
|
+
1
|
|
27
|
+
>>> list(q)
|
|
28
|
+
[2, 3, 4]
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from pkstruct.linear.queues.circular_queue import CircularQueue, QueueFullError
|
|
32
|
+
from pkstruct.linear.queues.linked_queue import LinkedQueue
|
|
33
|
+
from pkstruct.linear.queues.priority_queue import PriorityQueue
|
|
34
|
+
from pkstruct.linear.queues.queue import Queue
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
# Queue classes
|
|
38
|
+
"CircularQueue",
|
|
39
|
+
"LinkedQueue",
|
|
40
|
+
"PriorityQueue",
|
|
41
|
+
"Queue",
|
|
42
|
+
# Exceptions
|
|
43
|
+
"QueueFullError",
|
|
44
|
+
]
|