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.
Files changed (72) hide show
  1. pkstruct/__init__.py +167 -0
  2. pkstruct/graphs/__init__.py +127 -0
  3. pkstruct/graphs/connectivity.py +157 -0
  4. pkstruct/graphs/directed.py +95 -0
  5. pkstruct/graphs/exceptions.py +63 -0
  6. pkstruct/graphs/graph.py +262 -0
  7. pkstruct/graphs/mst.py +118 -0
  8. pkstruct/graphs/scc.py +138 -0
  9. pkstruct/graphs/shortest_path.py +250 -0
  10. pkstruct/graphs/topo_sort.py +108 -0
  11. pkstruct/graphs/traversal.py +175 -0
  12. pkstruct/graphs/visualization.py +90 -0
  13. pkstruct/graphs/weighted.py +37 -0
  14. pkstruct/linear/__init__.py +95 -0
  15. pkstruct/linear/deques/__init__.py +33 -0
  16. pkstruct/linear/deques/deque.py +194 -0
  17. pkstruct/linear/deques/linked_deque.py +198 -0
  18. pkstruct/linear/exceptions.py +26 -0
  19. pkstruct/linear/linked_lists/__init__.py +5 -0
  20. pkstruct/linear/linked_lists/_base.py +608 -0
  21. pkstruct/linear/linked_lists/circular_linked_list.py +230 -0
  22. pkstruct/linear/linked_lists/doubly_linked_list.py +151 -0
  23. pkstruct/linear/linked_lists/nodes.py +68 -0
  24. pkstruct/linear/linked_lists/singly_linked_list.py +136 -0
  25. pkstruct/linear/queues/__init__.py +44 -0
  26. pkstruct/linear/queues/circular_queue.py +258 -0
  27. pkstruct/linear/queues/linked_queue.py +186 -0
  28. pkstruct/linear/queues/priority_queue.py +202 -0
  29. pkstruct/linear/queues/queue.py +174 -0
  30. pkstruct/linear/stacks/__init__.py +38 -0
  31. pkstruct/linear/stacks/array_stack.py +165 -0
  32. pkstruct/linear/stacks/linked_stack.py +168 -0
  33. pkstruct/linear/stacks/stack.py +158 -0
  34. pkstruct/linear/utils/__init__.py +18 -0
  35. pkstruct/linear/utils/benchmark.py +255 -0
  36. pkstruct/linear/utils/debug_tools.py +239 -0
  37. pkstruct/linear/utils/helpers.py +143 -0
  38. pkstruct/linear/utils/iterators.py +148 -0
  39. pkstruct/linear/visualization/__init__.py +0 -0
  40. pkstruct/linear/visualization/ascii_visualizer.py +114 -0
  41. pkstruct/linear/visualization/linked_list_visualizer.py +126 -0
  42. pkstruct/shared/__init__.py +67 -0
  43. pkstruct/shared/benchmarking/__init__.py +78 -0
  44. pkstruct/shared/debugging/__init__.py +69 -0
  45. pkstruct/shared/exceptions/__init__.py +59 -0
  46. pkstruct/shared/serializers/__init__.py +65 -0
  47. pkstruct/shared/threading/__init__.py +43 -0
  48. pkstruct/shared/validators/__init__.py +98 -0
  49. pkstruct/shared/visualization/__init__.py +21 -0
  50. pkstruct/trees/__init__.py +92 -0
  51. pkstruct/trees/avl.py +321 -0
  52. pkstruct/trees/balancing.py +253 -0
  53. pkstruct/trees/bplus.py +425 -0
  54. pkstruct/trees/bst.py +948 -0
  55. pkstruct/trees/btree.py +504 -0
  56. pkstruct/trees/exceptions.py +96 -0
  57. pkstruct/trees/fenwick_tree.py +312 -0
  58. pkstruct/trees/interval_tree.py +541 -0
  59. pkstruct/trees/node.py +356 -0
  60. pkstruct/trees/red_black.py +710 -0
  61. pkstruct/trees/segment_tree.py +398 -0
  62. pkstruct/trees/traversal.py +456 -0
  63. pkstruct/trees/tree_helpers.py +366 -0
  64. pkstruct/trees/utils/__init__.py +15 -0
  65. pkstruct/trees/utils/complexity_helpers.py +231 -0
  66. pkstruct/trees/visualization/__init__.py +0 -0
  67. pkstruct/trees/visualization/ascii_renderer.py +220 -0
  68. pkstruct/trees/visualization/tree_printer.py +129 -0
  69. pkstruct-0.1.0.dist-info/METADATA +482 -0
  70. pkstruct-0.1.0.dist-info/RECORD +72 -0
  71. pkstruct-0.1.0.dist-info/WHEEL +4 -0
  72. pkstruct-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,456 @@
1
+ """
2
+ pkstruct.trees.traversal
3
+ ========================
4
+ Unified traversal engine for binary trees.
5
+
6
+ A single :func:`traverse` entry-point supports eight traversal orders,
7
+ both recursive and iterative implementations, and an option to yield
8
+ raw :class:`~pkstruct.trees.node.TreeNode` objects instead of
9
+ ``(key, value)`` tuples.
10
+
11
+ Supported orders
12
+ ----------------
13
+ ``inorder``
14
+ Left → Root → Right (produces sorted output for BSTs).
15
+ ``preorder``
16
+ Root → Left → Right.
17
+ ``postorder``
18
+ Left → Right → Root.
19
+ ``levelorder``
20
+ Breadth-first, level by level.
21
+ ``reverse_inorder``
22
+ Right → Root → Left (produces descending output for BSTs).
23
+ ``zigzag``
24
+ Level-order with alternating left-right / right-left direction.
25
+ ``boundary``
26
+ Left boundary + leaves + right boundary (anti-clockwise).
27
+ ``vertical``
28
+ Nodes grouped by horizontal distance from the root.
29
+
30
+ All public functions are generator-based for memory efficiency.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ from collections import defaultdict, deque
36
+ from collections.abc import Generator
37
+ from typing import (
38
+ Any,
39
+ )
40
+
41
+ from .node import TreeNode
42
+
43
+ __all__ = [
44
+ "traverse",
45
+ "inorder",
46
+ "preorder",
47
+ "postorder",
48
+ "levelorder",
49
+ "reverse_inorder",
50
+ "zigzag",
51
+ "boundary",
52
+ "vertical",
53
+ ]
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # Type aliases
57
+ # ---------------------------------------------------------------------------
58
+
59
+ _Node = TreeNode | None
60
+ _Yield = TreeNode | tuple[Any, Any]
61
+
62
+ _ORDER_DISPATCH: dict[str, str] = {
63
+ "inorder": "_inorder",
64
+ "preorder": "_preorder",
65
+ "postorder": "_postorder",
66
+ "levelorder": "_levelorder",
67
+ "reverse_inorder": "_reverse_inorder",
68
+ "zigzag": "_zigzag",
69
+ "boundary": "_boundary",
70
+ "vertical": "_vertical",
71
+ }
72
+
73
+
74
+ # ---------------------------------------------------------------------------
75
+ # Public entry-point
76
+ # ---------------------------------------------------------------------------
77
+
78
+
79
+ def traverse(
80
+ root: _Node,
81
+ order: str = "inorder",
82
+ *,
83
+ recursive: bool = False,
84
+ yield_nodes: bool = False,
85
+ ) -> Generator[_Yield, None, None]:
86
+ """
87
+ Unified traversal generator.
88
+
89
+ Parameters
90
+ ----------
91
+ root:
92
+ Root node of the tree (or sub-tree).
93
+ order:
94
+ One of ``"inorder"``, ``"preorder"``, ``"postorder"``,
95
+ ``"levelorder"``, ``"reverse_inorder"``, ``"zigzag"``,
96
+ ``"boundary"``, ``"vertical"``.
97
+ recursive:
98
+ If *True* use the recursive implementation (may hit Python's
99
+ default recursion limit for very deep trees). Ignored for orders
100
+ that are inherently iterative (``levelorder``, ``zigzag``,
101
+ ``boundary``, ``vertical``).
102
+ yield_nodes:
103
+ If *True* yield raw :class:`~pkstruct.trees.node.TreeNode`
104
+ objects; otherwise yield ``(key, value)`` tuples.
105
+
106
+ Yields
107
+ ------
108
+ :class:`~pkstruct.trees.node.TreeNode` | tuple
109
+ Nodes or ``(key, value)`` pairs in the requested order.
110
+
111
+ Raises
112
+ ------
113
+ ValueError
114
+ If *order* is not one of the supported strings.
115
+ """
116
+ if order not in _ORDER_DISPATCH:
117
+ raise ValueError(
118
+ f"Unknown traversal order {order!r}. "
119
+ f"Choose from: {sorted(_ORDER_DISPATCH)}"
120
+ )
121
+
122
+ gen_func_name = _ORDER_DISPATCH[order]
123
+ # Select recursive vs iterative variant
124
+ rec_orders = {"inorder", "preorder", "postorder", "reverse_inorder"}
125
+ use_recursive = recursive and order in rec_orders
126
+ suffix = "_rec" if use_recursive else "_iter"
127
+ func_name = gen_func_name + suffix
128
+ # Fallback: some orders are iterative-only
129
+ gen_func = globals().get(func_name) or globals()[gen_func_name + "_iter"]
130
+
131
+ for node in gen_func(root): # type: ignore[operator]
132
+ if node is None:
133
+ continue
134
+ yield node if yield_nodes else (node.key, node.value)
135
+
136
+
137
+ # ---------------------------------------------------------------------------
138
+ # Public convenience wrappers
139
+ # ---------------------------------------------------------------------------
140
+
141
+
142
+ def inorder(
143
+ root: _Node, *, recursive: bool = False, yield_nodes: bool = False
144
+ ) -> Generator[_Yield, None, None]:
145
+ """Inorder (Left → Root → Right) traversal."""
146
+ yield from traverse(root, "inorder", recursive=recursive, yield_nodes=yield_nodes)
147
+
148
+
149
+ def preorder(
150
+ root: _Node, *, recursive: bool = False, yield_nodes: bool = False
151
+ ) -> Generator[_Yield, None, None]:
152
+ """Preorder (Root → Left → Right) traversal."""
153
+ yield from traverse(root, "preorder", recursive=recursive, yield_nodes=yield_nodes)
154
+
155
+
156
+ def postorder(
157
+ root: _Node, *, recursive: bool = False, yield_nodes: bool = False
158
+ ) -> Generator[_Yield, None, None]:
159
+ """Postorder (Left → Right → Root) traversal."""
160
+ yield from traverse(
161
+ root, "postorder", recursive=recursive, yield_nodes=yield_nodes
162
+ )
163
+
164
+
165
+ def levelorder(
166
+ root: _Node, *, yield_nodes: bool = False
167
+ ) -> Generator[_Yield, None, None]:
168
+ """Breadth-first (level-order) traversal."""
169
+ yield from traverse(root, "levelorder", yield_nodes=yield_nodes)
170
+
171
+
172
+ def reverse_inorder(
173
+ root: _Node, *, recursive: bool = False, yield_nodes: bool = False
174
+ ) -> Generator[_Yield, None, None]:
175
+ """Reverse-inorder (Right → Root → Left) traversal."""
176
+ yield from traverse(
177
+ root, "reverse_inorder", recursive=recursive, yield_nodes=yield_nodes
178
+ )
179
+
180
+
181
+ def zigzag(root: _Node, *, yield_nodes: bool = False) -> Generator[_Yield, None, None]:
182
+ """Zigzag level-order traversal."""
183
+ yield from traverse(root, "zigzag", yield_nodes=yield_nodes)
184
+
185
+
186
+ def boundary(root: _Node, *, yield_nodes: bool = False) -> Generator[_Yield, None, None]:
187
+ """Boundary traversal (anti-clockwise)."""
188
+ yield from traverse(root, "boundary", yield_nodes=yield_nodes)
189
+
190
+
191
+ def vertical(root: _Node, *, yield_nodes: bool = False) -> Generator[_Yield, None, None]:
192
+ """Vertical-order traversal."""
193
+ yield from traverse(root, "vertical", yield_nodes=yield_nodes)
194
+
195
+
196
+ # ---------------------------------------------------------------------------
197
+ # Internal: Inorder
198
+ # ---------------------------------------------------------------------------
199
+
200
+
201
+ def _inorder_rec(root: _Node) -> Generator[_Node, None, None]:
202
+ if root is None:
203
+ return
204
+ yield from _inorder_rec(root.left)
205
+ yield root
206
+ yield from _inorder_rec(root.right)
207
+
208
+
209
+ def _inorder_iter(root: _Node) -> Generator[_Node, None, None]:
210
+ stack: list[_Node] = []
211
+ current: _Node = root
212
+ while current or stack:
213
+ while current:
214
+ stack.append(current)
215
+ current = current.left
216
+ current = stack.pop()
217
+ yield current
218
+ current = current.right
219
+
220
+
221
+ # ---------------------------------------------------------------------------
222
+ # Internal: Preorder
223
+ # ---------------------------------------------------------------------------
224
+
225
+
226
+ def _preorder_rec(root: _Node) -> Generator[_Node, None, None]:
227
+ if root is None:
228
+ return
229
+ yield root
230
+ yield from _preorder_rec(root.left)
231
+ yield from _preorder_rec(root.right)
232
+
233
+
234
+ def _preorder_iter(root: _Node) -> Generator[_Node, None, None]:
235
+ if root is None:
236
+ return
237
+ stack: list[_Node] = [root]
238
+ while stack:
239
+ node = stack.pop()
240
+ yield node
241
+ if node.right:
242
+ stack.append(node.right)
243
+ if node.left:
244
+ stack.append(node.left)
245
+
246
+
247
+ # ---------------------------------------------------------------------------
248
+ # Internal: Postorder
249
+ # ---------------------------------------------------------------------------
250
+
251
+
252
+ def _postorder_rec(root: _Node) -> Generator[_Node, None, None]:
253
+ if root is None:
254
+ return
255
+ yield from _postorder_rec(root.left)
256
+ yield from _postorder_rec(root.right)
257
+ yield root
258
+
259
+
260
+ def _postorder_iter(root: _Node) -> Generator[_Node, None, None]:
261
+ if root is None:
262
+ return
263
+ stack: list[_Node] = [root]
264
+ result: list[_Node] = []
265
+ while stack:
266
+ node = stack.pop()
267
+ result.append(node)
268
+ if node.left:
269
+ stack.append(node.left)
270
+ if node.right:
271
+ stack.append(node.right)
272
+ yield from reversed(result)
273
+
274
+
275
+ # ---------------------------------------------------------------------------
276
+ # Internal: Level-order
277
+ # ---------------------------------------------------------------------------
278
+
279
+
280
+ def _levelorder_iter(root: _Node) -> Generator[_Node, None, None]:
281
+ if root is None:
282
+ return
283
+ queue: deque[_Node] = deque([root])
284
+ while queue:
285
+ node = queue.popleft()
286
+ yield node
287
+ if node.left:
288
+ queue.append(node.left)
289
+ if node.right:
290
+ queue.append(node.right)
291
+
292
+
293
+ # ---------------------------------------------------------------------------
294
+ # Internal: Reverse inorder
295
+ # ---------------------------------------------------------------------------
296
+
297
+
298
+ def _reverse_inorder_rec(root: _Node) -> Generator[_Node, None, None]:
299
+ if root is None:
300
+ return
301
+ yield from _reverse_inorder_rec(root.right)
302
+ yield root
303
+ yield from _reverse_inorder_rec(root.left)
304
+
305
+
306
+ def _reverse_inorder_iter(root: _Node) -> Generator[_Node, None, None]:
307
+ stack: list[_Node] = []
308
+ current: _Node = root
309
+ while current or stack:
310
+ while current:
311
+ stack.append(current)
312
+ current = current.right
313
+ current = stack.pop()
314
+ yield current
315
+ current = current.left
316
+
317
+
318
+ # ---------------------------------------------------------------------------
319
+ # Internal: Zigzag
320
+ # ---------------------------------------------------------------------------
321
+
322
+
323
+ def _zigzag_iter(root: _Node) -> Generator[_Node, None, None]:
324
+ if root is None:
325
+ return
326
+ current_level: list[_Node] = [root]
327
+ left_to_right = True
328
+ while current_level:
329
+ next_level: list[_Node] = []
330
+ if left_to_right:
331
+ for node in current_level:
332
+ yield node
333
+ if node.left:
334
+ next_level.append(node.left)
335
+ if node.right:
336
+ next_level.append(node.right)
337
+ else:
338
+ for node in reversed(current_level):
339
+ yield node
340
+ # children still appended L→R for next reversal
341
+ for node in current_level:
342
+ if node.left:
343
+ next_level.append(node.left)
344
+ if node.right:
345
+ next_level.append(node.right)
346
+ current_level = next_level
347
+ left_to_right = not left_to_right
348
+
349
+
350
+ # ---------------------------------------------------------------------------
351
+ # Internal: Boundary
352
+ # ---------------------------------------------------------------------------
353
+
354
+
355
+ def _boundary_iter(root: _Node) -> Generator[_Node, None, None]:
356
+ """Anti-clockwise boundary: root → left boundary → leaves → right boundary."""
357
+ if root is None:
358
+ return
359
+ yield root
360
+ if root.left is None and root.right is None:
361
+ return
362
+
363
+ # Left boundary (top-down, excluding leaf)
364
+ node: _Node = root.left
365
+ while node:
366
+ if not (node.left is None and node.right is None):
367
+ yield node
368
+ node = node.left if node.left else node.right
369
+
370
+ # Leaves (left to right)
371
+ yield from _leaves_iter(root.left)
372
+ yield from _leaves_iter(root.right)
373
+
374
+ # Right boundary (bottom-up, excluding leaf)
375
+ right_boundary: list[_Node] = []
376
+ node = root.right
377
+ while node:
378
+ if not (node.left is None and node.right is None):
379
+ right_boundary.append(node)
380
+ node = node.right if node.right else node.left
381
+ yield from reversed(right_boundary)
382
+
383
+
384
+ def _leaves_iter(root: _Node) -> Generator[_Node, None, None]:
385
+ if root is None:
386
+ return
387
+ if root.left is None and root.right is None:
388
+ yield root
389
+ return
390
+ yield from _leaves_iter(root.left)
391
+ yield from _leaves_iter(root.right)
392
+
393
+
394
+ # ---------------------------------------------------------------------------
395
+ # Internal: Vertical
396
+ # ---------------------------------------------------------------------------
397
+
398
+
399
+ def _vertical_iter(root: _Node) -> Generator[_Node, None, None]:
400
+ """
401
+ Yield nodes grouped by horizontal distance (HD) from the root.
402
+
403
+ Nodes at the same HD are ordered top-down, left-before-right within
404
+ the same level.
405
+ """
406
+ if root is None:
407
+ return
408
+ # BFS tracking horizontal distance
409
+ hd_map: dict[int, list[_Node]] = defaultdict(list)
410
+ queue: deque[tuple[_Node, int]] = deque([(root, 0)])
411
+ while queue:
412
+ node, hd = queue.popleft()
413
+ hd_map[hd].append(node)
414
+ if node.left:
415
+ queue.append((node.left, hd - 1))
416
+ if node.right:
417
+ queue.append((node.right, hd + 1))
418
+ for hd in sorted(hd_map):
419
+ yield from hd_map[hd]
420
+
421
+
422
+ # ---------------------------------------------------------------------------
423
+ # Iterator helpers (public)
424
+ # ---------------------------------------------------------------------------
425
+
426
+
427
+ def iter_levels(
428
+ root: _Node,
429
+ ) -> Generator[list[tuple[Any, Any]], None, None]:
430
+ """
431
+ Yield one list of ``(key, value)`` pairs per level (BFS).
432
+
433
+ Useful for pretty-printing or level-wise analysis.
434
+
435
+ Parameters
436
+ ----------
437
+ root:
438
+ Root of the tree.
439
+
440
+ Yields
441
+ ------
442
+ list of (key, value)
443
+ One list per level, left to right.
444
+ """
445
+ if root is None:
446
+ return
447
+ current_level: list[_Node] = [root]
448
+ while current_level:
449
+ yield [(n.key, n.value) for n in current_level]
450
+ next_level: list[_Node] = []
451
+ for node in current_level:
452
+ if node.left:
453
+ next_level.append(node.left)
454
+ if node.right:
455
+ next_level.append(node.right)
456
+ current_level = next_level