22
33import heapq
44import random
5+ from dataclasses import dataclass
56from typing import Any , Dict , Iterator , List , Optional , Tuple , Union
67
78from graphviz import Digraph , nohtml
2526VALUE_FIELD = "value"
2627
2728NodeValue = Union [float , int ]
28- NodeProperty = Union [float , int , bool ]
29+
30+
31+ @dataclass
32+ class NodeProperties :
33+ height : int
34+ size : int
35+ is_max_heap : bool
36+ is_min_heap : bool
37+ is_perfect : bool
38+ is_strict : bool
39+ is_complete : bool
40+ leaf_count : int
41+ min_node_value : NodeValue
42+ max_node_value : NodeValue
43+ min_leaf_depth : int
44+ max_leaf_depth : int
2945
3046
3147class Node :
@@ -39,13 +55,12 @@ class Node:
3955 :param value: Node value (must be a number).
4056 :type value: int | float
4157 :param left: Left child node (default: None).
42- :type left: binarytree.Node
58+ :type left: binarytree.Node | None
4359 :param right: Right child node (default: None).
44- :type right: binarytree.Node
60+ :type right: binarytree.Node | None
4561 :raise binarytree.exceptions.NodeTypeError: If left or right child node is
4662 not an instance of :class:`binarytree.Node`.
47- :raise binarytree.exceptions.NodeValueError: If node value is not a number
48- (e.g. int, float).
63+ :raise binarytree.exceptions.NodeValueError: If node value is not an int or float.
4964 """
5065
5166 def __init__ (
@@ -180,7 +195,7 @@ def __iter__(self) -> Iterator["Node"]:
180195 https://en.wikipedia.org/wiki/Tree_traversal#Breadth-first_search
181196
182197 :return: Node iterator.
183- :rtype: ( binarytree.Node)
198+ :rtype: Iterator[ binarytree.Node]
184199
185200 **Example**:
186201
@@ -239,7 +254,7 @@ def __len__(self) -> int:
239254 .. note::
240255 This method is equivalent to :attr:`binarytree.Node.size`.
241256 """
242- return self . properties [ "size" ]
257+ return sum ( 1 for _ in iter ( self ))
243258
244259 def __getitem__ (self , index : int ) -> "Node" :
245260 """Return the node (or subtree) at the given level-order_ index.
@@ -449,7 +464,7 @@ def _repr_svg_(self) -> str:
449464 .. _Jupyter notebooks: https://jupyter.org
450465 """
451466 # noinspection PyProtectedMember
452- return self .graphviz ()._repr_svg_ ()
467+ return str ( self .graphviz ()._repr_svg_ () )
453468
454469 def graphviz (self , * args : Any , ** kwargs : Any ) -> Digraph :
455470 """Return a graphviz.Digraph_ object representing the binary tree.
@@ -795,7 +810,7 @@ def height(self) -> int:
795810 .. note::
796811 A binary tree with only a root node has a height of 0.
797812 """
798- return _get_tree_properties (self )[ " height" ]
813+ return _get_tree_properties (self ). height
799814
800815 @property
801816 def size (self ) -> int :
@@ -821,7 +836,7 @@ def size(self) -> int:
821836 .. note::
822837 This method is equivalent to :func:`binarytree.Node.__len__`.
823838 """
824- return _get_tree_properties ( self )[ "size" ]
839+ return self . __len__ ()
825840
826841 @property
827842 def leaf_count (self ) -> int :
@@ -846,7 +861,7 @@ def leaf_count(self) -> int:
846861 >>> root.leaf_count
847862 2
848863 """
849- return _get_tree_properties (self )[ " leaf_count" ]
864+ return _get_tree_properties (self ). leaf_count
850865
851866 @property
852867 def is_balanced (self ) -> bool :
@@ -982,7 +997,7 @@ def is_max_heap(self) -> bool:
982997 >>> root.is_max_heap
983998 True
984999 """
985- return _get_tree_properties (self )[ " is_max_heap" ]
1000+ return _get_tree_properties (self ). is_max_heap
9861001
9871002 @property
9881003 def is_min_heap (self ) -> bool :
@@ -1012,7 +1027,7 @@ def is_min_heap(self) -> bool:
10121027 >>> root.is_min_heap
10131028 True
10141029 """
1015- return _get_tree_properties (self )[ " is_min_heap" ]
1030+ return _get_tree_properties (self ). is_min_heap
10161031
10171032 @property
10181033 def is_perfect (self ) -> bool :
@@ -1049,7 +1064,7 @@ def is_perfect(self) -> bool:
10491064 >>> root.is_perfect
10501065 True
10511066 """
1052- return _get_tree_properties (self )[ " is_perfect" ]
1067+ return _get_tree_properties (self ). is_perfect
10531068
10541069 @property
10551070 def is_strict (self ) -> bool :
@@ -1084,7 +1099,7 @@ def is_strict(self) -> bool:
10841099 >>> root.is_strict
10851100 True
10861101 """
1087- return _get_tree_properties (self )[ " is_strict" ]
1102+ return _get_tree_properties (self ). is_strict
10881103
10891104 @property
10901105 def is_complete (self ) -> bool :
@@ -1121,7 +1136,7 @@ def is_complete(self) -> bool:
11211136 >>> root.is_complete
11221137 True
11231138 """
1124- return _get_tree_properties (self )[ " is_complete" ]
1139+ return _get_tree_properties (self ). is_complete
11251140
11261141 @property
11271142 def min_node_value (self ) -> NodeValue :
@@ -1143,7 +1158,7 @@ def min_node_value(self) -> NodeValue:
11431158 >>> root.min_node_value
11441159 1
11451160 """
1146- return _get_tree_properties (self )[ " min_node_value" ]
1161+ return _get_tree_properties (self ). min_node_value
11471162
11481163 @property
11491164 def max_node_value (self ) -> NodeValue :
@@ -1165,7 +1180,7 @@ def max_node_value(self) -> NodeValue:
11651180 >>> root.max_node_value
11661181 3
11671182 """
1168- return _get_tree_properties (self )[ " max_node_value" ]
1183+ return _get_tree_properties (self ). max_node_value
11691184
11701185 @property
11711186 def max_leaf_depth (self ) -> int :
@@ -1199,7 +1214,7 @@ def max_leaf_depth(self) -> int:
11991214 >>> root.max_leaf_depth
12001215 3
12011216 """
1202- return _get_tree_properties (self )[ " max_leaf_depth" ]
1217+ return _get_tree_properties (self ). max_leaf_depth
12031218
12041219 @property
12051220 def min_leaf_depth (self ) -> int :
@@ -1233,7 +1248,7 @@ def min_leaf_depth(self) -> int:
12331248 >>> root.min_leaf_depth
12341249 1
12351250 """
1236- return _get_tree_properties (self )[ " min_leaf_depth" ]
1251+ return _get_tree_properties (self ). min_leaf_depth
12371252
12381253 @property
12391254 def properties (self ) -> Dict [str , Any ]:
@@ -1286,14 +1301,10 @@ def properties(self) -> Dict[str, Any]:
12861301 >>> props['is_strict'] # equivalent to root.is_strict
12871302 True
12881303 """
1289- properties = _get_tree_properties (self )
1290- properties .update (
1291- {
1292- "is_bst" : _is_bst (self ),
1293- "is_balanced" : _is_balanced (self ) >= 0 ,
1294- "is_symmetric" : _is_symmetric (self ),
1295- }
1296- )
1304+ properties = _get_tree_properties (self ).__dict__ .copy ()
1305+ properties ["is_balanced" ] = _is_balanced (self ) >= 0
1306+ properties ["is_bst" ] = _is_bst (self )
1307+ properties ["is_symmetric" ] = _is_symmetric (self )
12971308 return properties
12981309
12991310 @property
@@ -1492,7 +1503,7 @@ def _is_balanced(root: Optional[Node]) -> int:
14921503 """Return the tree height + 1 if balanced, -1 otherwise.
14931504
14941505 :param root: Root node of the binary tree.
1495- :type root: binarytree.Node
1506+ :type root: binarytree.Node | None
14961507 :return: Height if the binary tree is balanced, -1 otherwise.
14971508 :rtype: int
14981509 """
@@ -1511,7 +1522,7 @@ def _is_bst(root: Optional[Node]) -> bool:
15111522 """Check if the binary tree is a BST (binary search tree).
15121523
15131524 :param root: Root node of the binary tree.
1514- :type root: binarytree.Node
1525+ :type root: binarytree.Node | None
15151526 :return: True if the binary tree is a BST, False otherwise.
15161527 :rtype: bool
15171528 """
@@ -1536,26 +1547,26 @@ def _is_symmetric(root: Optional[Node]) -> bool:
15361547 """Check if the binary tree is symmetric.
15371548
15381549 :param root: Root node of the binary tree.
1539- :type root: binarytree.Node
1550+ :type root: binarytree.Node | None
15401551 :return: True if the binary tree is symmetric, False otherwise.
15411552 :rtype: bool
15421553 """
15431554
1544- def symmetric_helper (left_subtree , right_subtree ) :
1545- if left_subtree is None and right_subtree is None :
1555+ def symmetric_helper (left : Optional [ Node ], right : Optional [ Node ]) -> bool :
1556+ if left is None and right is None :
15461557 return True
1547- if left_subtree is None or right_subtree is None :
1558+ if left is None or right is None :
15481559 return False
15491560 return (
1550- left_subtree .val == right_subtree .val
1551- and symmetric_helper (left_subtree .left , right_subtree .right )
1552- and symmetric_helper (left_subtree .right , right_subtree .left )
1561+ left .val == right .val
1562+ and symmetric_helper (left .left , right .right )
1563+ and symmetric_helper (left .right , right .left )
15531564 )
15541565
15551566 return symmetric_helper (root , root )
15561567
15571568
1558- def _validate_tree_height (height : int ):
1569+ def _validate_tree_height (height : int ) -> None :
15591570 """Check if the height of the binary tree is valid.
15601571
15611572 :param height: Height of the binary tree (must be 0 - 9 inclusive).
@@ -1640,7 +1651,7 @@ def _build_tree_string(
16401651 call then combines its left and right sub-boxes to build a larger box etc.
16411652
16421653 :param root: Root node of the binary tree.
1643- :type root: binarytree.Node
1654+ :type root: binarytree.Node | None
16441655 :param curr_index: Level-order_ index of the current node (root node is 0).
16451656 :type curr_index: int
16461657 :param index: If set to True, include the level-order_ node indexes using
@@ -1717,13 +1728,13 @@ def _build_tree_string(
17171728 return new_box , len (new_box [0 ]), new_root_start , new_root_end
17181729
17191730
1720- def _get_tree_properties (root : Node ) -> Dict [ str , Any ] :
1731+ def _get_tree_properties (root : Node ) -> NodeProperties :
17211732 """Inspect the binary tree and return its properties (e.g. height).
17221733
17231734 :param root: Root node of the binary tree.
17241735 :type root: binarytree.Node
17251736 :return: Binary tree properties.
1726- :rtype: dict
1737+ :rtype: binarytree.NodeProperties
17271738 """
17281739 is_descending = True
17291740 is_ascending = True
@@ -1779,20 +1790,20 @@ def _get_tree_properties(root: Node) -> Dict[str, Any]:
17791790
17801791 current_level = next_level
17811792
1782- return {
1783- " height" : max_leaf_depth ,
1784- " size" : size ,
1785- " is_max_heap" : is_complete and is_descending ,
1786- " is_min_heap" : is_complete and is_ascending ,
1787- " is_perfect" : leaf_count == 2 ** max_leaf_depth ,
1788- " is_strict" : is_strict ,
1789- " is_complete" : is_complete ,
1790- " leaf_count" : leaf_count ,
1791- " min_node_value" : min_node_value ,
1792- " max_node_value" : max_node_value ,
1793- " min_leaf_depth" : min_leaf_depth ,
1794- " max_leaf_depth" : max_leaf_depth ,
1795- }
1793+ return NodeProperties (
1794+ height = max_leaf_depth ,
1795+ size = size ,
1796+ is_max_heap = is_complete and is_descending ,
1797+ is_min_heap = is_complete and is_ascending ,
1798+ is_perfect = leaf_count == 2 ** max_leaf_depth ,
1799+ is_strict = is_strict ,
1800+ is_complete = is_complete ,
1801+ leaf_count = leaf_count ,
1802+ min_node_value = min_node_value ,
1803+ max_node_value = max_node_value ,
1804+ min_leaf_depth = min_leaf_depth ,
1805+ max_leaf_depth = max_leaf_depth ,
1806+ )
17961807
17971808
17981809def get_parent (root : Node , child : Node ) -> Optional [Node ]:
@@ -1847,7 +1858,7 @@ def get_parent(root: Node, child: Node) -> Optional[Node]:
18471858 return None
18481859
18491860
1850- def build (values : List ) -> Optional [Node ]:
1861+ def build (values : List [ int ] ) -> Optional [Node ]:
18511862 """Build a tree from `list representation`_ and return its root node.
18521863
18531864 .. _list representation:
0 commit comments