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,220 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pkstruct.trees.visualization.ascii_renderer
|
|
3
|
+
============================================
|
|
4
|
+
Low-level ASCII rendering engine for binary trees.
|
|
5
|
+
|
|
6
|
+
Provides static methods that render a binary tree as a clean ASCII string.
|
|
7
|
+
Works with any node type that exposes ``.key``, ``.left``, and ``.right``
|
|
8
|
+
attributes (``TreeNode``, ``AVLNode``, ``RBNode``, ``IntervalNode``, etc.).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from collections import deque
|
|
14
|
+
from collections.abc import Callable
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AsciiRenderer:
|
|
19
|
+
"""Static methods for rendering binary trees as ASCII art strings."""
|
|
20
|
+
|
|
21
|
+
_SPACE: str = " "
|
|
22
|
+
_FORWARD_SLASH: str = "/"
|
|
23
|
+
_BACKSLASH: str = "\\"
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def render(
|
|
27
|
+
root: Any,
|
|
28
|
+
key_repr: Callable[[Any], str] = str,
|
|
29
|
+
) -> str:
|
|
30
|
+
"""
|
|
31
|
+
Render a binary tree as an ASCII art string.
|
|
32
|
+
|
|
33
|
+
The output uses slashes to connect parent nodes to their children::
|
|
34
|
+
|
|
35
|
+
10
|
|
36
|
+
/ \\
|
|
37
|
+
5 15
|
|
38
|
+
/ \\
|
|
39
|
+
3 7
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
root:
|
|
44
|
+
Root node of the tree. Must have ``.key``, ``.left``, and
|
|
45
|
+
``.right`` attributes.
|
|
46
|
+
key_repr:
|
|
47
|
+
Callable that converts a node's key to its string representation.
|
|
48
|
+
Defaults to ``str``.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
str
|
|
53
|
+
ASCII art representation, or ``"(empty)"`` if *root* is *None*.
|
|
54
|
+
"""
|
|
55
|
+
if root is None:
|
|
56
|
+
return "(empty)"
|
|
57
|
+
|
|
58
|
+
height = AsciiRenderer._height(root)
|
|
59
|
+
if height == 0:
|
|
60
|
+
return key_repr(root.key)
|
|
61
|
+
|
|
62
|
+
tree_lines: list[str] = []
|
|
63
|
+
level: list[Any | None] = [root]
|
|
64
|
+
level_num = 0
|
|
65
|
+
|
|
66
|
+
while level and level_num <= height:
|
|
67
|
+
next_level: list[Any | None] = []
|
|
68
|
+
for node in level:
|
|
69
|
+
if node is not None:
|
|
70
|
+
next_level.append(node.left)
|
|
71
|
+
next_level.append(node.right)
|
|
72
|
+
else:
|
|
73
|
+
next_level.append(None)
|
|
74
|
+
next_level.append(None)
|
|
75
|
+
|
|
76
|
+
indent = 2 ** (height - level_num) - 1
|
|
77
|
+
between = 2 ** (height - level_num + 1) - 1
|
|
78
|
+
|
|
79
|
+
line: list[str] = []
|
|
80
|
+
for node in level:
|
|
81
|
+
if node is None:
|
|
82
|
+
line.append(" " * (max(1, indent + 1)))
|
|
83
|
+
else:
|
|
84
|
+
rep = key_repr(node.key)
|
|
85
|
+
padding = max(0, indent - len(rep) + 1)
|
|
86
|
+
line.append(" " * padding + rep + " " * (indent - padding))
|
|
87
|
+
|
|
88
|
+
tree_lines.append("".join(line).rstrip())
|
|
89
|
+
|
|
90
|
+
if level_num < height:
|
|
91
|
+
connector_line: list[str] = []
|
|
92
|
+
for _, node in enumerate(level):
|
|
93
|
+
if node is None:
|
|
94
|
+
connector_line.append(" " * (indent + between // 2 + 1))
|
|
95
|
+
else:
|
|
96
|
+
left_char = AsciiRenderer._FORWARD_SLASH if node.left else " "
|
|
97
|
+
right_char = AsciiRenderer._BACKSLASH if node.right else " "
|
|
98
|
+
left_pad = indent - 1
|
|
99
|
+
mid_pad = between - 2
|
|
100
|
+
if left_pad < 0:
|
|
101
|
+
left_pad = 0
|
|
102
|
+
if mid_pad < 0:
|
|
103
|
+
mid_pad = 0
|
|
104
|
+
segment = (
|
|
105
|
+
" " * left_pad
|
|
106
|
+
+ left_char
|
|
107
|
+
+ " " * max(0, mid_pad)
|
|
108
|
+
+ right_char
|
|
109
|
+
+ " " * (indent)
|
|
110
|
+
)
|
|
111
|
+
connector_line.append(segment)
|
|
112
|
+
tree_lines.append("".join(connector_line).rstrip())
|
|
113
|
+
|
|
114
|
+
level = next_level
|
|
115
|
+
level_num += 1
|
|
116
|
+
|
|
117
|
+
return "\n".join(tree_lines)
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def render_compact(
|
|
121
|
+
root: Any,
|
|
122
|
+
key_repr: Callable[[Any], str] = str,
|
|
123
|
+
) -> str:
|
|
124
|
+
"""
|
|
125
|
+
Render a compact single-line representation of the tree.
|
|
126
|
+
|
|
127
|
+
Produces output like: ``10 (5 (3, 7), 15)``.
|
|
128
|
+
|
|
129
|
+
Parameters
|
|
130
|
+
----------
|
|
131
|
+
root:
|
|
132
|
+
Root node of the tree.
|
|
133
|
+
key_repr:
|
|
134
|
+
Callable that converts a node's key to a string.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
str
|
|
139
|
+
Compact representation, or ``"(empty)"`` if *root* is *None*.
|
|
140
|
+
"""
|
|
141
|
+
if root is None:
|
|
142
|
+
return "(empty)"
|
|
143
|
+
|
|
144
|
+
def _build(node: Any | None) -> str:
|
|
145
|
+
if node is None:
|
|
146
|
+
return ""
|
|
147
|
+
left_str = _build(node.left)
|
|
148
|
+
right_str = _build(node.right)
|
|
149
|
+
rep = key_repr(node.key)
|
|
150
|
+
if left_str or right_str:
|
|
151
|
+
parts = []
|
|
152
|
+
if left_str:
|
|
153
|
+
parts.append(left_str)
|
|
154
|
+
if right_str:
|
|
155
|
+
parts.append(right_str)
|
|
156
|
+
return f"{rep} ({', '.join(parts)})"
|
|
157
|
+
return rep
|
|
158
|
+
|
|
159
|
+
return _build(root)
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def render_levels(
|
|
163
|
+
root: Any,
|
|
164
|
+
key_repr: Callable[[Any], str] = str,
|
|
165
|
+
) -> str:
|
|
166
|
+
"""
|
|
167
|
+
Render the tree level by level, one line per level.
|
|
168
|
+
|
|
169
|
+
Produces output like::
|
|
170
|
+
|
|
171
|
+
Level 0: 10
|
|
172
|
+
Level 1: 5 15
|
|
173
|
+
Level 2: 3 7
|
|
174
|
+
|
|
175
|
+
Parameters
|
|
176
|
+
----------
|
|
177
|
+
root:
|
|
178
|
+
Root node of the tree.
|
|
179
|
+
key_repr:
|
|
180
|
+
Callable that converts a node's key to a string.
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
str
|
|
185
|
+
Level-by-level representation.
|
|
186
|
+
"""
|
|
187
|
+
if root is None:
|
|
188
|
+
return "(empty)"
|
|
189
|
+
|
|
190
|
+
lines: list[str] = []
|
|
191
|
+
queue: deque = deque([(root, 0)])
|
|
192
|
+
current_level = 0
|
|
193
|
+
level_nodes: list[str] = []
|
|
194
|
+
|
|
195
|
+
while queue:
|
|
196
|
+
node, level = queue.popleft()
|
|
197
|
+
if level != current_level:
|
|
198
|
+
lines.append(f"Level {current_level}: {' '.join(level_nodes)}")
|
|
199
|
+
level_nodes = []
|
|
200
|
+
current_level = level
|
|
201
|
+
level_nodes.append(key_repr(node.key))
|
|
202
|
+
if node.left:
|
|
203
|
+
queue.append((node.left, level + 1))
|
|
204
|
+
if node.right:
|
|
205
|
+
queue.append((node.right, level + 1))
|
|
206
|
+
|
|
207
|
+
if level_nodes:
|
|
208
|
+
lines.append(f"Level {current_level}: {' '.join(level_nodes)}")
|
|
209
|
+
|
|
210
|
+
return "\n".join(lines)
|
|
211
|
+
|
|
212
|
+
@staticmethod
|
|
213
|
+
def _height(node: Any | None) -> int:
|
|
214
|
+
"""Compute the height of the tree rooted at *node*."""
|
|
215
|
+
if node is None:
|
|
216
|
+
return -1
|
|
217
|
+
return 1 + max(
|
|
218
|
+
AsciiRenderer._height(node.left),
|
|
219
|
+
AsciiRenderer._height(node.right),
|
|
220
|
+
)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pkstruct.trees.visualization.tree_printer
|
|
3
|
+
==========================================
|
|
4
|
+
High-level tree visualizer that dispatches to AsciiRenderer based on tree type.
|
|
5
|
+
|
|
6
|
+
Provides a unified ``TreeVisualizer`` class with ``visualize()`` and
|
|
7
|
+
``debug_view()`` methods, mirroring the architecture of
|
|
8
|
+
``pkstruct.linear.visualization.linked_list_visualizer``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from .ascii_renderer import AsciiRenderer
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TreeVisualizer:
|
|
19
|
+
"""Provides ASCII visualization and debug views for any pkstruct tree."""
|
|
20
|
+
|
|
21
|
+
# Lazy imports to avoid circular dependencies at module load time.
|
|
22
|
+
@staticmethod
|
|
23
|
+
def _tree_types() -> tuple[type, ...]:
|
|
24
|
+
from pkstruct.trees.avl import AVLTree
|
|
25
|
+
from pkstruct.trees.bst import BinarySearchTree
|
|
26
|
+
from pkstruct.trees.interval_tree import IntervalTree
|
|
27
|
+
from pkstruct.trees.red_black import RedBlackTree
|
|
28
|
+
|
|
29
|
+
return (BinarySearchTree, AVLTree, RedBlackTree, IntervalTree)
|
|
30
|
+
|
|
31
|
+
def visualize(
|
|
32
|
+
self,
|
|
33
|
+
tree_instance: Any,
|
|
34
|
+
style: str = "ascii",
|
|
35
|
+
key_repr: Any = None,
|
|
36
|
+
) -> str:
|
|
37
|
+
"""
|
|
38
|
+
Return a string representation of *tree_instance*.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
tree_instance:
|
|
43
|
+
A supported tree instance (BST, AVL, RedBlack, IntervalTree).
|
|
44
|
+
style:
|
|
45
|
+
Rendering style. One of ``"ascii"``, ``"compact"``, or
|
|
46
|
+
``"levels"``. Defaults to ``"ascii"``.
|
|
47
|
+
key_repr:
|
|
48
|
+
Optional callable to convert a node's key to a string.
|
|
49
|
+
Defaults to ``str``.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
str
|
|
54
|
+
ASCII art string describing the tree.
|
|
55
|
+
|
|
56
|
+
Raises
|
|
57
|
+
------
|
|
58
|
+
TypeError
|
|
59
|
+
If *tree_instance* is not a recognised tree type.
|
|
60
|
+
ValueError
|
|
61
|
+
If *style* is not supported.
|
|
62
|
+
"""
|
|
63
|
+
if key_repr is None:
|
|
64
|
+
key_repr = str
|
|
65
|
+
|
|
66
|
+
styles = {"ascii", "compact", "levels"}
|
|
67
|
+
if style not in styles:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
f"Unsupported visualisation style {style!r}. "
|
|
70
|
+
f"Use one of: {sorted(styles)}."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
tree_types = self._tree_types()
|
|
74
|
+
if not isinstance(tree_instance, tree_types):
|
|
75
|
+
raise TypeError(
|
|
76
|
+
f"Unsupported tree type: {type(tree_instance).__name__}. "
|
|
77
|
+
f"Expected one of: {[t.__name__ for t in tree_types]}."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
root = getattr(tree_instance, "_root", None)
|
|
81
|
+
|
|
82
|
+
if style == "ascii":
|
|
83
|
+
return AsciiRenderer.render(root, key_repr=key_repr)
|
|
84
|
+
if style == "compact":
|
|
85
|
+
return AsciiRenderer.render_compact(root, key_repr=key_repr)
|
|
86
|
+
return AsciiRenderer.render_levels(root, key_repr=key_repr)
|
|
87
|
+
|
|
88
|
+
def debug_view(self, tree_instance: Any) -> dict[str, Any]:
|
|
89
|
+
"""
|
|
90
|
+
Return a diagnostic dictionary for *tree_instance*.
|
|
91
|
+
|
|
92
|
+
The returned dict always contains:
|
|
93
|
+
|
|
94
|
+
- ``type``: class name string
|
|
95
|
+
- ``size``: integer node count
|
|
96
|
+
- ``height``: integer tree height
|
|
97
|
+
- ``is_empty``: boolean
|
|
98
|
+
- ``is_valid``: boolean (calls the tree's ``validate()``)
|
|
99
|
+
- ``keys``: list of all keys in in-order
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
tree_instance:
|
|
104
|
+
Any supported tree instance.
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
dict
|
|
109
|
+
Dictionary with debugging information.
|
|
110
|
+
|
|
111
|
+
Raises
|
|
112
|
+
------
|
|
113
|
+
TypeError
|
|
114
|
+
If *tree_instance* is not a recognised tree type.
|
|
115
|
+
"""
|
|
116
|
+
tree_types = self._tree_types()
|
|
117
|
+
if not isinstance(tree_instance, tree_types):
|
|
118
|
+
raise TypeError(
|
|
119
|
+
f"Unsupported tree type: {type(tree_instance).__name__}."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
"type": type(tree_instance).__name__,
|
|
124
|
+
"size": tree_instance.size(),
|
|
125
|
+
"height": tree_instance.height(),
|
|
126
|
+
"is_empty": tree_instance.is_empty(),
|
|
127
|
+
"is_valid": tree_instance.validate(),
|
|
128
|
+
"keys": list(tree_instance),
|
|
129
|
+
}
|