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,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
|