Skip to content

Commit a8905a3

Browse files
authored
Add binary tree visualization in printBin.py
This script defines a binary tree structure and provides functions to visualize it in a 2D matrix or as a string. It includes methods for centering values, calculating tree depth, and registering nodes with their binary path codes.
1 parent 32c4f7b commit a8905a3

File tree

1 file changed

+206
-0
lines changed

1 file changed

+206
-0
lines changed

useful/printBin.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
from itertools import product
2+
from pprint import pprint
3+
4+
# Default configuration constants
5+
DEFAULT_UNIT_SIZE = 3
6+
DEFAULT_FILL_CHAR = " "
7+
8+
9+
class Node:
10+
"""
11+
Represents a node in a binary tree.
12+
13+
Attributes:
14+
val: The value stored in the node
15+
left: Reference to the left child node
16+
right: Reference to the right child node
17+
"""
18+
def __init__(self, val=0, left=None, right=None):
19+
self.val = val
20+
self.left = left
21+
self.right = right
22+
23+
def __str__(self):
24+
return str(self.val)
25+
26+
def __repr__(self):
27+
return str(self)
28+
29+
30+
def center(val, unitSize=None, fillChar=None):
31+
"""
32+
Centers a value within a fixed width string.
33+
34+
Args:
35+
val: The value to center
36+
unitSize: The total width of the output string (uses DEFAULT_UNIT_SIZE if None)
37+
fillChar: The character to use for padding (uses DEFAULT_FILL_CHAR if None)
38+
39+
Returns:
40+
A centered string representation of val
41+
"""
42+
if unitSize is None:
43+
unitSize = DEFAULT_UNIT_SIZE
44+
if fillChar is None:
45+
fillChar = DEFAULT_FILL_CHAR
46+
return str(val).center(unitSize, fillChar)
47+
48+
49+
def getDepth(node: Node):
50+
"""
51+
Calculates the depth (height) of a binary tree.
52+
53+
Args:
54+
node: The root node of the tree
55+
56+
Returns:
57+
The depth of the tree (number of levels from root to deepest leaf)
58+
"""
59+
if node == None:
60+
return 0
61+
return 1 + max(getDepth(node.left), getDepth(node.right))
62+
63+
64+
def register(node: Node, fillChar=None, unitSize=None, code="", mem=None):
65+
"""
66+
Recursively registers all nodes in a tree with their binary path codes.
67+
68+
Each node is assigned a binary code representing its position:
69+
- Empty string "" for root
70+
- "0" appended for left child
71+
- "1" appended for right child
72+
73+
Args:
74+
node: The current node being processed
75+
fillChar: Character used for padding (uses DEFAULT_FILL_CHAR if None)
76+
unitSize: Size for centering values (uses DEFAULT_UNIT_SIZE if None)
77+
code: The binary path code for the current node
78+
mem: Dictionary mapping binary codes to centered node values
79+
"""
80+
if mem is None:
81+
mem = {}
82+
if node:
83+
mem[code] = center(node.val, unitSize=unitSize, fillChar=fillChar)
84+
register(node.left, fillChar=fillChar, unitSize=unitSize, code=code + "0", mem=mem)
85+
register(node.right, fillChar=fillChar, unitSize=unitSize, code=code + "1", mem=mem)
86+
return mem
87+
88+
89+
def nodeToMat(node: Node, depth=-1, fillChar=None, unitSize=None, removeEmpty=True):
90+
"""
91+
Converts a binary tree into a 2D matrix representation for visualization.
92+
93+
The matrix includes:
94+
- Even rows (0, 2, 4...): Node values
95+
- Odd rows (1, 3, 5...): Connection lines (/ and \\)
96+
97+
Args:
98+
node: The root node of the tree to visualize
99+
depth: The depth of the tree (-1 for auto-calculation)
100+
fillChar: Character for padding (uses DEFAULT_FILL_CHAR if None)
101+
unitSize: Size for centering (uses DEFAULT_UNIT_SIZE if None)
102+
removeEmpty: Whether to remove empty leading columns
103+
104+
Returns:
105+
A 2D list (matrix) representing the tree structure
106+
"""
107+
if unitSize is None:
108+
unitSize = DEFAULT_UNIT_SIZE
109+
if fillChar is None:
110+
fillChar = DEFAULT_FILL_CHAR
111+
112+
if depth == -1:
113+
depth = getDepth(node)
114+
115+
# Register all nodes with their binary path codes
116+
tree = register(node, fillChar=fillChar, unitSize=unitSize, code="", mem={})
117+
118+
# Create matrix: (2*depth - 1) rows x (2^depth - 1) columns
119+
mat = [[center("", unitSize=unitSize, fillChar=fillChar) for _ in range(2 ** depth - 1)] for _ in range(2 * depth - 1)]
120+
121+
side = "left"
122+
leftBound, rightBound = 0, 2 ** depth - 2
123+
124+
# Start with all even column indices (where values can be placed)
125+
valueIndexes = [i for i in range(2 ** depth - 1) if i % 2 == 0]
126+
127+
# Build matrix from bottom to top
128+
for level in range(2 * (depth - 1), -1, -1):
129+
# Odd levels: place connection characters (/ and \)
130+
if level % 2 != 0:
131+
for i, index in enumerate(valueIndexes):
132+
mat[level][index] = [center("/", unitSize=unitSize, fillChar=fillChar), center("\\", unitSize=unitSize, fillChar=fillChar)][i % 2]
133+
134+
# Calculate parent positions (midpoints between child pairs)
135+
newIndexes = []
136+
for i in range(0, len(valueIndexes) - 1, 2):
137+
newIndexes.append((valueIndexes[i] + valueIndexes[i + 1]) // 2)
138+
valueIndexes = newIndexes
139+
continue
140+
141+
# Even levels: place node values
142+
# Generate all binary codes for current level
143+
codes = list(product(*["01" for _ in range(level // 2)]))
144+
codes = ["".join(code) for code in codes]
145+
146+
for i, index in enumerate(valueIndexes):
147+
mat[level][index] = tree.get(codes[i], center("", unitSize=unitSize, fillChar=fillChar))
148+
149+
# Remove empty leading columns if requested
150+
if removeEmpty:
151+
for i in range(2 ** depth - 1):
152+
remove = False
153+
if all(
154+
mat[j][i] in [
155+
center("", unitSize=unitSize, fillChar=fillChar),
156+
center("/", unitSize=unitSize, fillChar=fillChar),
157+
center("\\", unitSize=unitSize, fillChar=fillChar)
158+
] for j in range(2 * depth - 1)
159+
):
160+
remove = True
161+
if not remove:
162+
break
163+
for j in range(2 * depth - 1):
164+
mat[j][i] = ""
165+
166+
return mat
167+
168+
169+
def nodeToString(node: Node, depth=-1, fillChar=None, unitSize=None, removeEmpty=True):
170+
"""
171+
Converts a binary tree into a string representation for visualization.
172+
173+
Args:
174+
node: The root node of the tree to visualize
175+
depth: The depth of the tree (-1 for auto-calculation)
176+
fillChar: Character for padding (uses DEFAULT_FILL_CHAR if None)
177+
unitSize: Size for centering (uses DEFAULT_UNIT_SIZE if None)
178+
removeEmpty: Whether to remove empty leading columns
179+
180+
Returns:
181+
A string representation of the tree with each row on a new line
182+
"""
183+
mat = nodeToMat(node, depth=depth, fillChar=fillChar, unitSize=unitSize, removeEmpty=removeEmpty)
184+
return "\n".join("".join(row) for row in mat)
185+
186+
187+
# Example usage
188+
if __name__ == "__main__":
189+
myNode = Node(1)
190+
# myNode.left = Node(2)
191+
myNode.right = Node(3)
192+
# myNode.left.left = Node(4)
193+
# myNode.left.right = Node(5)
194+
myNode.right.left = Node(6)
195+
myNode.right.right = Node(7)
196+
197+
mat = nodeToMat(myNode)
198+
pprint(mat)
199+
for row in mat:
200+
print(*row)
201+
202+
print("\n--- Using nodeToString ---")
203+
print(nodeToString(myNode))
204+
205+
print("\n--- Custom unit size ---")
206+
print(nodeToString(myNode, unitSize=1))

0 commit comments

Comments
 (0)