From c0fdee9ccec05d6f3cb642f857a04112d825a2cf Mon Sep 17 00:00:00 2001 From: Gagandeep Singh Date: Wed, 30 Oct 2019 19:26:01 +0530 Subject: [PATCH] Added order statistics to BST (#31) * added ArrayForTrees * added order statistics to BSTs * removed tuple as input from arrays --- .../linear_data_structures/arrays.py | 59 ++++- .../tests/test_arrays.py | 7 +- pydatastructs/trees/binary_trees.py | 247 +++++++++++++----- .../trees/tests/test_binary_trees.py | 95 +++++-- pydatastructs/utils/misc_util.py | 5 +- 5 files changed, 322 insertions(+), 91 deletions(-) diff --git a/pydatastructs/linear_data_structures/arrays.py b/pydatastructs/linear_data_structures/arrays.py index cc92c9362..eb769cafd 100644 --- a/pydatastructs/linear_data_structures/arrays.py +++ b/pydatastructs/linear_data_structures/arrays.py @@ -23,7 +23,7 @@ class OneDimensionalArray(Array): A valid object type. size: int The number of elements in the array. - elements: list/tuple + elements: list The elements in the array, all should be of same type. init: a python type @@ -73,12 +73,18 @@ def __new__(cls, dtype=NoneType, *args, **kwargs): obj = object.__new__(cls) obj._dtype = dtype if len(args) == 2: - if _check_type(args[0], (list, tuple)) and \ + if _check_type(args[0], list) and \ _check_type(args[1], int): - size, data = args[1], [dtype(arg) for arg in args[0]] - elif _check_type(args[1], (list, tuple)) and \ + for i in range(len(args[0])): + if dtype != type(args[0][i]): + args[0][i] = dtype(args[0][i]) + size, data = args[1], [arg for arg in args[0]] + elif _check_type(args[1], list) and \ _check_type(args[0], int): - size, data = args[0], [dtype(arg) for arg in args[1]] + for i in range(len(args[1])): + if dtype != type(args[1][i]): + args[1][i] = dtype(args[1][i]) + size, data = args[0], [arg for arg in args[1]] else: raise TypeError("Expected type of size is int and " "expected type of data is list/tuple.") @@ -93,8 +99,11 @@ def __new__(cls, dtype=NoneType, *args, **kwargs): init = kwargs.get('init', None) obj._data = [init for i in range(args[0])] elif _check_type(args[0], (list, tuple)): + for i in range(len(args[0])): + if dtype != type(args[0][i]): + args[0][i] = dtype(args[0][i]) obj._size, obj._data = len(args[0]), \ - [dtype(arg) for arg in args[0]] + [arg for arg in args[0]] else: raise TypeError("Expected type of size is int and " "expected type of data is list/tuple.") @@ -110,7 +119,9 @@ def __setitem__(self, idx, elem): if elem is None: self._data[idx] = None else: - self._data[idx] = self._dtype(elem) + if type(elem) != self._dtype: + elem = self._dtype(elem) + self._data[idx] = elem def fill(self, elem): elem = self._dtype(elem) @@ -237,8 +248,40 @@ def delete(self, idx): self[idx] != None: self[idx] = None self._num -= 1 - self._modify() + return self._modify() @property def size(self): return self._size + +class ArrayForTrees(DynamicOneDimensionalArray): + """ + Utility dynamic array for storing nodes of a tree. + + See Also + ======== + + pydatastructs.linear_data_structures.arrays.DynamicOneDimensionalArray + """ + def _modify(self): + if self._num/self._size < self._load_factor: + new_indices = dict() + arr_new = OneDimensionalArray(self._dtype, 2*self._num + 1) + j = 0 + for i in range(self._last_pos_filled + 1): + if self[i] != None: + arr_new[j] = self[i] + new_indices[self[i].key] = j + j += 1 + for i in range(j): + if arr_new[i].left != None: + arr_new[i].left = new_indices[self[arr_new[i].left].key] + if arr_new[i].right != None: + arr_new[i].right = new_indices[self[arr_new[i].right].key] + if arr_new[i].parent != None: + arr_new[i].parent = new_indices[self[arr_new[i].parent].key] + self._last_pos_filled = j - 1 + self._data = arr_new._data + self._size = arr_new._size + return new_indices + return None diff --git a/pydatastructs/linear_data_structures/tests/test_arrays.py b/pydatastructs/linear_data_structures/tests/test_arrays.py index 420882665..0114ef72a 100644 --- a/pydatastructs/linear_data_structures/tests/test_arrays.py +++ b/pydatastructs/linear_data_structures/tests/test_arrays.py @@ -5,11 +5,12 @@ def test_OneDimensionalArray(): ODA = OneDimensionalArray - A = ODA(int, 5, [1, 2, 3, 4, 5], init=6) + A = ODA(int, 5, [1.0, 2, 3, 4, 5], init=6) + A[1] = 2.0 assert A - assert ODA(int, (1, 2, 3, 4, 5), 5) + assert ODA(int, [1.0, 2, 3, 4, 5], 5) assert ODA(int, 5) - assert ODA(int, [1, 2, 3]) + assert ODA(int, [1.0, 2, 3]) raises(IndexError, lambda: A[7]) raises(IndexError, lambda: A[-1]) raises(ValueError, lambda: ODA()) diff --git a/pydatastructs/trees/binary_trees.py b/pydatastructs/trees/binary_trees.py index e8fb8206a..e680c263e 100644 --- a/pydatastructs/trees/binary_trees.py +++ b/pydatastructs/trees/binary_trees.py @@ -1,7 +1,9 @@ from __future__ import print_function, division from pydatastructs.utils import Node from pydatastructs.miscellaneous_data_structures import Stack -from pydatastructs.linear_data_structures import OneDimensionalArray +from pydatastructs.linear_data_structures import ( + OneDimensionalArray, DynamicOneDimensionalArray) +from pydatastructs.linear_data_structures.arrays import ArrayForTrees # TODO: REPLACE COLLECTIONS QUEUE WITH PYDATASTRUCTS QUEUE from collections import deque as Queue @@ -32,6 +34,9 @@ class BinaryTree(object): for comparison of keys. Should return a bool value. By default it implements less than operator. + is_order_statistic: bool + Set it to True, if you want to use the + order statistic features of the tree. References ========== @@ -39,9 +44,11 @@ class BinaryTree(object): .. [1] https://en.wikipedia.org/wiki/Binary_tree """ - __slots__ = ['root_idx', 'comparator', 'tree', 'size'] + __slots__ = ['root_idx', 'comparator', 'tree', 'size', + 'is_order_statistic'] - def __new__(cls, key=None, root_data=None, comp=None): + def __new__(cls, key=None, root_data=None, comp=None, + is_order_statistic=False): obj = object.__new__(cls) if key == None and root_data != None: raise ValueError('Key required.') @@ -49,9 +56,10 @@ def __new__(cls, key=None, root_data=None, comp=None): root = Node(key, root_data) root.is_root = True obj.root_idx = 0 - obj.tree, obj.size = [root], 1 + obj.tree, obj.size = ArrayForTrees(Node, [root]), 1 obj.comparator = lambda key1, key2: key1 < key2 \ if comp == None else comp + obj.is_order_statistic = is_order_statistic return obj def insert(self, key, data): @@ -141,9 +149,12 @@ def search(self, key, **kwargs): def __str__(self): - return str([(node.left, node.key, node.data, node.right) - for node in self.tree]) - + to_be_printed = ['' for i in range(self.tree._last_pos_filled + 1)] + for i in range(self.tree._last_pos_filled + 1): + if self.tree[i] != None: + node = self.tree[i] + to_be_printed[i] = (node.left, node.key, node.data, node.right) + return str(to_be_printed) class BinarySearchTree(BinaryTree): """ @@ -182,32 +193,49 @@ class BinarySearchTree(BinaryTree): pydatastructs.trees.binary_tree.BinaryTree """ + left_size = lambda self, node: self.tree[node.left].size \ + if node.left != None else 0 + right_size = lambda self, node: self.tree[node.right].size \ + if node.right != None else 0 + + def _update_size(self, start_idx): + if self.is_order_statistic: + walk = start_idx + while walk != None: + self.tree[walk].size = ( + self.left_size(self.tree[walk]) + + self.right_size(self.tree[walk]) + 1) + walk = self.tree[walk].parent def insert(self, key, data): + res = self.search(key) + if res != None: + self.tree[res].data = data + return None walk = self.root_idx if self.tree[walk].key == None: self.tree[walk].key = key self.tree[walk].data = data return None - new_node = Node(key, data) - while True: - if self.tree[walk].key == key: - self.tree[walk].data = data - return None + new_node, prev_node, flag = Node(key, data), 0, True + while flag: if not self.comparator(key, self.tree[walk].key): if self.tree[walk].right == None: + new_node.parent = prev_node self.tree.append(new_node) self.tree[walk].right = self.size self.size += 1 - return None - walk = self.tree[walk].right + flag = False + prev_node = walk = self.tree[walk].right else: if self.tree[walk].left == None: + new_node.parent = prev_node self.tree.append(new_node) self.tree[walk].left = self.size self.size += 1 - return None - walk = self.tree[walk].left + flag = False + prev_node = walk = self.tree[walk].left + self._update_size(walk) def search(self, key, **kwargs): ret_parent = kwargs.get('parent', False) @@ -241,20 +269,40 @@ def delete(self, key, **kwargs): else: self.tree[parent].right = None a = parent + par_key, root_key = (self.tree[parent].key, + self.tree[self.root_idx].key) + new_indices = self.tree.delete(walk) + if new_indices != None: + a = new_indices[par_key] + self.root_idx = new_indices[root_key] + self._update_size(a) elif self.tree[walk].left != None and \ self.tree[walk].right != None: twalk = self.tree[walk].right par = walk + flag = False while self.tree[twalk].left != None: + flag = True par = twalk twalk = self.tree[twalk].left self.tree[walk].data = self.tree[twalk].data self.tree[walk].key = self.tree[twalk].key - self.tree[par].left = self.tree[twalk].right + if flag: + self.tree[par].left = self.tree[twalk].right + else: + self.tree[par].right = self.tree[twalk].right if self.tree[twalk].right != None: - self.tree[self.tree[twalk].right].parent = twalk - a = par + self.tree[self.tree[twalk].right].parent = par + if twalk != None: + a = par + par_key, root_key = (self.tree[par].key, + self.tree[self.root_idx].key) + new_indices = self.tree.delete(twalk) + if new_indices != None: + a = new_indices[par_key] + self.root_idx = new_indices[root_key] + self._update_size(a) else: if self.tree[walk].left != None: @@ -266,8 +314,11 @@ def delete(self, key, **kwargs): self.tree[self.root_idx].right = self.tree[child].right self.tree[self.root_idx].data = self.tree[child].data self.tree[self.root_idx].key = self.tree[child].key - self.tree[child].left = None - self.tree[child].right = None + self.tree[self.root_idx].parent = None + root_key = self.tree[self.root_idx].key + new_indices = self.tree.delete(child) + if new_indices != None: + self.root_idx = new_indices[root_key] else: if self.tree[parent].left == walk: self.tree[parent].left = child @@ -275,11 +326,96 @@ def delete(self, key, **kwargs): self.tree[parent].right = child self.tree[child].parent = parent a = parent + par_key, root_key = (self.tree[parent].key, + self.tree[self.root_idx].key) + new_indices = self.tree.delete(walk) + if new_indices != None: + parent = new_indices[par_key] + self.tree[child].parent = new_indices[par_key] + a = new_indices[par_key] + self.root_idx = new_indices[root_key] + self._update_size(a) if kwargs.get("balancing_info", False) is not False: return a return True + def select(self, i): + """ + Finds the i-th smallest node in the tree. + + Parameters + ========== + + i: int + A positive integer + + Returns + ======= + + n: Node + The node with the i-th smallest key + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Order_statistic_tree + """ + i -= 1 # The algorithm is based on zero indexing + if i < 0: + raise ValueError("Expected a positive integer, got %d"%(i + 1)) + if i >= self.tree._num: + raise ValueError("%d is greater than the size of the " + "tree which is, %d"%(i + 1, self.tree._num)) + walk = self.root_idx + while walk != None: + l = self.left_size(self.tree[walk]) + if i == l: + return self.tree[walk] + left_walk = self.tree[walk].left + right_walk = self.tree[walk].right + if left_walk == None and right_walk == None: + raise IndexError("The traversal is terminated " + "due to no child nodes ahead.") + if i < l: + if left_walk != None and \ + self.comparator(self.tree[left_walk].key, + self.tree[walk].key): + walk = left_walk + else: + walk = right_walk + else: + if right_walk != None and \ + not self.comparator(self.tree[right_walk].key, + self.tree[walk].key): + walk = right_walk + else: + walk = left_walk + i -= (l + 1) + + def rank(self, x): + """ + Finds the rank of the given node, i.e. + its index in the sorted list of nodes + of the tree. + + Parameter + ========= + + x: key + The key of the node whose rank is to be found out. + """ + walk = self.search(x) + if walk == None: + return None + r = self.left_size(self.tree[walk]) + 1 + while self.tree[walk].key != self.tree[self.root_idx].key: + p = self.tree[walk].parent + if walk == self.tree[p].right: + r += self.left_size(self.tree[p]) + 1 + walk = p + return r + class AVLTree(BinarySearchTree): """ Represents AVL trees. @@ -315,14 +451,12 @@ def _right_rotate(self, j, k): self.tree[k].right = j self.tree[j].height = max(self.left_height(self.tree[j]), self.right_height(self.tree[j])) + 1 - self.tree[k].height = max(self.left_height(self.tree[k]), - self.right_height(self.tree[k])) + 1 kp = self.tree[k].parent - if kp != None: - self.tree[kp].height = max(self.left_height(self.tree[kp]), - self.right_height(self.tree[kp])) + 1 - else: + if kp == None: self.root_idx = k + if self.is_order_statistic: + self.tree[j].size = (self.left_size(self.tree[j]) + + self.right_size(self.tree[j]) + 1) def _left_right_rotate(self, j, k): i = self.tree[k].right @@ -345,10 +479,13 @@ def _left_right_rotate(self, j, k): self.tree[ip].left = i else: self.tree[ip].right = i - self.tree[ip].height = max(self.left_height(self.tree[ip]), - self.right_height(self.tree[ip])) + 1 else: self.root_idx = i + if self.is_order_statistic: + self.tree[j].size = (self.left_size(self.tree[j]) + + self.right_size(self.tree[j]) + 1) + self.tree[k].size = (self.left_size(self.tree[k]) + + self.right_size(self.tree[k]) + 1) def _right_left_rotate(self, j, k): i = self.tree[k].left @@ -371,10 +508,13 @@ def _right_left_rotate(self, j, k): self.tree[ip].left = i else: self.tree[ip].right = i - self.tree[ip].height = max(self.left_height(self.tree[ip]), - self.right_height(self.tree[ip])) + 1 else: self.root_idx = i + if self.is_order_statistic: + self.tree[j].size = (self.left_size(self.tree[j]) + + self.right_size(self.tree[j]) + 1) + self.tree[k].size = (self.left_size(self.tree[k]) + + self.right_size(self.tree[k]) + 1) def _left_rotate(self, j, k): y = self.tree[k].left @@ -391,11 +531,11 @@ def _left_rotate(self, j, k): self.tree[k].height = max(self.left_height(self.tree[k]), self.right_height(self.tree[k])) + 1 kp = self.tree[k].parent - if kp != None: - self.tree[kp].height = max(self.left_height(self.tree[kp]), - self.right_height(self.tree[kp])) + 1 - else: + if kp == None: self.root_idx = k + if self.is_order_statistic: + self.tree[j].size = (self.left_size(self.tree[j]) + + self.right_size(self.tree[j]) + 1) def _balance_insertion(self, curr, last): walk = last @@ -404,6 +544,9 @@ def _balance_insertion(self, curr, last): while walk != None: self.tree[walk].height = max(self.left_height(self.tree[walk]), self.right_height(self.tree[walk])) + 1 + if self.is_order_statistic: + self.tree[walk].size = (self.left_size(self.tree[walk]) + + self.right_size(self.tree[walk]) + 1) last = path.popleft() last2last = path.popleft() if self.balance_factor(self.tree[walk]) not in (1, 0, -1): @@ -421,39 +564,17 @@ def _balance_insertion(self, curr, last): walk = self.tree[walk].parent def insert(self, key, data): - walk = self.root_idx - if self.tree[walk].key == None: - self.tree[walk].key = key - self.tree[walk].data = data - return None - new_node, prev_node, flag = Node(key, data), 0, True - while flag: - if self.tree[walk].key == key: - self.tree[walk].data = data - flag = False - else: - if not self.comparator(key, self.tree[walk].key): - if self.tree[walk].right == None: - new_node.parent = prev_node - self.tree.append(new_node) - self.tree[walk].right = self.size - self.size += 1 - flag = False - prev_node = walk = self.tree[walk].right - else: - if self.tree[walk].left == None: - new_node.parent = prev_node - self.tree.append(new_node) - self.tree[walk].left = self.size - self.size += 1 - flag = False - prev_node = walk = self.tree[walk].left - + super(AVLTree, self).insert(key, data) self._balance_insertion(self.size - 1, self.tree[self.size-1].parent) def _balance_deletion(self, start_idx, key): walk = start_idx while walk != None: + self.tree[walk].height = max(self.left_height(self.tree[walk]), + self.right_height(self.tree[walk])) + 1 + if self.is_order_statistic: + self.tree[walk].size = (self.left_size(self.tree[walk]) + + self.right_size(self.tree[walk]) + 1) if self.balance_factor(self.tree[walk]) not in (1, 0, -1): if self.balance_factor(self.tree[walk]) < 0: b = self.tree[walk].left diff --git a/pydatastructs/trees/tests/test_binary_trees.py b/pydatastructs/trees/tests/test_binary_trees.py index 6b2aafc58..c3713196b 100644 --- a/pydatastructs/trees/tests/test_binary_trees.py +++ b/pydatastructs/trees/tests/test_binary_trees.py @@ -1,11 +1,15 @@ from pydatastructs.trees.binary_trees import ( - BinarySearchTree, BinaryTreeTraversal, AVLTree) + BinarySearchTree, BinaryTreeTraversal, AVLTree, + ArrayForTrees) from pydatastructs.utils.raises_util import raises from pydatastructs.utils.misc_util import Node +from copy import deepcopy def test_BinarySearchTree(): BST = BinarySearchTree b = BST(8, 8) + b.delete(8) + b.insert(8, 8) b.insert(3, 3) b.insert(10, 10) b.insert(1, 1) @@ -28,9 +32,13 @@ def test_BinarySearchTree(): assert b.search(3) == None assert b.delete(13) == None assert str(b) == \ - ("[(1, 8, 8, 7), (3, 4, 4, 4), (None, 10, 10, 7), (None, 1, 1, None), " - "(None, 6, 6, 6), (None, 4, 4, None), (None, 7, 7, None), (None, 14, 14, None), " - "(None, 13, 13, None)]") + ("[(1, 8, 8, 7), (3, 4, 4, 4), '', (None, 1, 1, None), " + "(None, 6, 6, 6), '', (None, 7, 7, None), (None, 14, 14, None), '']") + b.delete(7) + b.delete(6) + b.delete(1) + b.delete(4) + assert str(b) == "[(None, 8, 8, 2), '', (None, 14, 14, None)]" bc = BST(1, 1) assert bc.insert(1, 2) == None b = BST(-8, 8) @@ -108,7 +116,8 @@ def test_AVLTree(): "(None, 'K', 'K', None), (None, 'Q', 'Q', None), " "(2, 'P', 'P', 5), (9, 'H', 'H', None), " "(7, 'I', 'I', 3), (None, 'A', 'A', None)]") - assert [a.balance_factor(n) for n in a.tree] == [0, -1, 0, 0, 0, 0, 0, -1, 0, 0] + assert [a.balance_factor(n) for n in a.tree if n is not None] == \ + [0, -1, 0, 0, 0, 0, 0, -1, 0, 0] a1 = AVLTree(1, 1) a1.insert(2, 2) a1.insert(3, 3) @@ -129,7 +138,7 @@ def test_AVLTree(): a2.insert(1, 1) assert str(a2) == "[(None, 1, 1, None)]" a3 = AVLTree() - a3.tree = [] + a3.tree = ArrayForTrees(Node, 0) for i in range(7): a3.tree.append(Node(i, i)) a3.tree[0].left = 1 @@ -143,7 +152,7 @@ def test_AVLTree(): "(None, 3, 3, None), (None, 4, 4, None), " "(None, 5, 5, None), (None, 6, 6, None)]") a4 = AVLTree() - a4.tree = [] + a4.tree = ArrayForTrees(Node, 0) for i in range(7): a4.tree.append(Node(i, i)) a4.tree[0].left = 1 @@ -157,8 +166,8 @@ def test_AVLTree(): "(0, 3, 3, 2), (None, 4, 4, None), (None, 5, 5, None), " "(None, 6, 6, None)]") - a5 = AVLTree() - a5.tree = [ + a5 = AVLTree(is_order_statistic=True) + a5.tree = ArrayForTrees(Node, [ Node(10, 10), Node(5, 5), Node(17, 17), @@ -173,7 +182,7 @@ def test_AVLTree(): Node(30, 30), Node(13, 13), Node(33, 33) - ] + ]) a5.tree[0].left, a5.tree[0].right, a5.tree[0].parent, a5.tree[0].height = \ 1, 2, None, 4 @@ -203,9 +212,65 @@ def test_AVLTree(): None, None, 9, 0 a5.tree[13].left, a5.tree[13].right, a5.tree[13].parent, a5.tree[13].height = \ None, None, 11, 0 + + # testing order statistics + a5.tree[0].size = 14 + a5.tree[1].size = 4 + a5.tree[2].size = 9 + a5.tree[3].size = 2 + a5.tree[4].size = 1 + a5.tree[5].size = 4 + a5.tree[6].size = 4 + a5.tree[7].size = 1 + a5.tree[8].size = 1 + a5.tree[9].size = 2 + a5.tree[10].size = 1 + a5.tree[11].size = 2 + a5.tree[12].size = 1 + a5.tree[13].size = 1 + + raises(ValueError, lambda: a5.select(0)) + raises(ValueError, lambda: a5.select(15)) + assert a5.rank(-1) == None + def test_select_rank(expected_output): + output = [] + for i in range(len(expected_output)): + output.append(a5.select(i + 1).key) + assert output == expected_output + + output = [] + expected_ranks = [i + 1 for i in range(len(expected_output))] + for i in range(len(expected_output)): + output.append(a5.rank(expected_output[i])) + assert output == expected_ranks + + test_select_rank([2, 3, 5, 9, 10, 11, 12, 13, 15, 17, 18, 20, 30, 33]) a5.delete(9) - assert str(a5) == ("[(7, 10, 10, 5), (None, 5, 5, None), (0, 17, 17, 6), " - "(None, 2, 2, None), (None, 9, 9, None), (8, 12, 12, 9), " - "(10, 20, 20, 11), (3, 3, 3, 1), (None, 11, 11, None), " - "(12, 15, 15, None), (None, 18, 18, None), (None, 30, 30, 13), " - "(None, 13, 13, None), (None, 33, 33, None)]") + a5.delete(13) + a5.delete(20) + assert str(a5) == ("[(7, 10, 10, 5), (None, 5, 5, None), " + "(0, 17, 17, 6), (None, 2, 2, None), '', " + "(8, 12, 12, 9), (10, 30, 30, 13), (3, 3, 3, 1), " + "(None, 11, 11, None), (None, 15, 15, None), " + "(None, 18, 18, None), '', '', (None, 33, 33, None)]") + test_select_rank([2, 3, 5, 10, 11, 12, 15, 17, 18, 30, 33]) + a5.delete(10) + a5.delete(17) + test_select_rank([2, 3, 5, 11, 12, 15, 18, 30, 33]) + a5.delete(11) + a5.delete(30) + test_select_rank([2, 3, 5, 12, 15, 18, 33]) + a5.delete(12) + test_select_rank([2, 3, 5, 15, 18, 33]) + a5.delete(15) + test_select_rank([2, 3, 5, 18, 33]) + a5.delete(18) + test_select_rank([2, 3, 5, 33]) + a5.delete(33) + test_select_rank([2, 3, 5]) + a5.delete(5) + test_select_rank([2, 3]) + a5.delete(3) + test_select_rank([2]) + a5.delete(2) + test_select_rank([]) diff --git a/pydatastructs/utils/misc_util.py b/pydatastructs/utils/misc_util.py index a12be5cbe..35a56c8d8 100644 --- a/pydatastructs/utils/misc_util.py +++ b/pydatastructs/utils/misc_util.py @@ -25,12 +25,13 @@ class Node(object): """ __slots__ = ['key', 'data', 'left', 'right', 'is_root', - 'height', 'parent'] + 'height', 'parent', 'size'] def __new__(cls, key, data): obj = object.__new__(cls) obj.data, obj.key = data, key - obj.left, obj.right, obj.parent, obj.height = None, None, None, 0 + obj.left, obj.right, obj.parent, obj.height, obj.size = \ + None, None, None, 0, 1 obj.is_root = False return obj