From 6f688cd87e65e95071bb0021c15fd895091a9843 Mon Sep 17 00:00:00 2001 From: FamALouiz Date: Fri, 14 Mar 2025 20:35:44 +0100 Subject: [PATCH 1/8] Added parent pointer tree node for python backend --- pydatastructs/utils/__init__.py | 1 + pydatastructs/utils/misc_util.py | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/pydatastructs/utils/__init__.py b/pydatastructs/utils/__init__.py index 20a8c750c..7ea5689dd 100644 --- a/pydatastructs/utils/__init__.py +++ b/pydatastructs/utils/__init__.py @@ -13,6 +13,7 @@ BinomialTreeNode, AdjacencyListGraphNode, AdjacencyMatrixGraphNode, + ParentPointerTreeNode, GraphEdge, Set, CartesianTreeNode, diff --git a/pydatastructs/utils/misc_util.py b/pydatastructs/utils/misc_util.py index 4c7ca7faa..1cc43811b 100644 --- a/pydatastructs/utils/misc_util.py +++ b/pydatastructs/utils/misc_util.py @@ -13,6 +13,7 @@ 'Set', 'CartesianTreeNode', 'RedBlackTreeNode', + 'ParentPointerTreeNode', 'TrieNode', 'SkipNode', 'minimum', @@ -275,6 +276,39 @@ def add_children(self, *children): def __str__(self): return str((self.key, self.data)) +class ParentPointerTreeNode(TreeNode): + """ + Represents node in trees with parent pointers. + + Parameters + ========== + + key + Required for comparison operations. + data + Any valid data to be stored in the node. + parent + Reference to the parent node. + backend: pydatastructs.Backend + The backend to be used. + Optional, by default, the best available + backend is used. + """ + __slots__ = ['key', 'data', 'parent'] + + @classmethod + def methods(cls): + return ['__new__', '__str__'] + + def __new__(cls, key, data=None, parent=None, **kwargs): + raise_if_backend_is_not_python( + cls, kwargs.get('backend', Backend.PYTHON)) + obj = Node.__new__(cls) + obj.data, obj.key, obj.parent = data, key, parent + return obj + + def __str__(self): + return str((self.key, self.data)) class LinkedListNode(Node): """ From 9563a44280d399880b559bd3da23eb4d6717cdf7 Mon Sep 17 00:00:00 2001 From: FamALouiz Date: Wed, 19 Mar 2025 12:25:01 +0100 Subject: [PATCH 2/8] Added basic skeleton for parent pointer tree --- pydatastructs/trees/m_ary_trees.py | 154 ++++++++++++++++++++++++++++- pydatastructs/utils/misc_util.py | 2 +- 2 files changed, 154 insertions(+), 2 deletions(-) diff --git a/pydatastructs/trees/m_ary_trees.py b/pydatastructs/trees/m_ary_trees.py index a06fda9ee..4382282af 100644 --- a/pydatastructs/trees/m_ary_trees.py +++ b/pydatastructs/trees/m_ary_trees.py @@ -1,4 +1,4 @@ -from pydatastructs.utils import MAryTreeNode +from pydatastructs.utils import MAryTreeNode, ParentPointerTreeNode from pydatastructs.linear_data_structures.arrays import ArrayForTrees from pydatastructs.utils.misc_util import ( Backend, raise_if_backend_is_not_python) @@ -170,3 +170,155 @@ def __str__(self): if j is not None: to_be_printed[i].append(j) return str(to_be_printed) + +class ParentPointerTree(object): + """ + Implements a tree with parent pointers. + + Parameters + ========== + + key + Required if tree is to be instantiated with + root otherwise not needed. + root_data + Optional, the root node of the tree. + If not of type TreeNode, it will consider + root as data and a new root node will + be created. + comp: lambda + Optional, A lambda function which will be used + for comparison of keys. Should return a + bool value. By default it implements less + than operator. + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Tree_(data_structure)#Parent_pointer_tree + """ + + __slots__ = ['root_idx', 'comparator', 'tree', 'size'] + + def __new__(cls, key=None, root_data=None, comp=None, **kwargs): + raise_if_backend_is_not_python( + cls, kwargs.get('backend', Backend.PYTHON)) + obj = object.__new__(cls) + if key is None and root_data is not None: + raise ValueError('Key required.') + key = None if root_data is None else key + root = ParentPointerTreeNode(key, root_data) + root.is_root = True + obj.root_idx = 0 + obj.tree, obj.size = ArrayForTrees(ParentPointerTreeNode, [root]), 1 + obj.comparator = lambda key1, key2: key1 < key2 \ + if comp is None else comp + + return obj + + @classmethod + def methods(cls): + return ['__new__', '__str__'] + + def insert(self, parent_key, key, data=None): + """ + Inserts data by the passed key using iterative + algorithm. + + Parameters + ========== + + key + The key for comparison. + data + The data to be inserted. + + Returns + ======= + + None + """ + if parent_key is None: + raise ValueError("Parent key is required.") + if key is None: + raise ValueError("Key is required.") + if self.search(key) is not None: + raise ValueError("Key already exists.") + + parent_node = self.search(parent_key, parent=True) + new_node = ParentPointerTreeNode(key, data, parent_node) + + self.tree.append(new_node) + self.size += 1 + + def delete(self, key): + """ + Deletes the data with the passed key + using iterative algorithm. + + Parameters + ========== + + key + The key of the node which is + to be deleted. + + Returns + ======= + + True + If the node is deleted successfully. + + None + If the node to be deleted doesn't exists. + + Note + ==== + + The node is deleted means that the connection to that + node are removed but the it is still in tree. + """ + for idx in range(self.size): + if self.tree[idx].key == key: + self.tree.delete(idx) + return True + + return None + + def search(self, key, **kwargs): + """ + Searches for the data in the tree + using iterative algorithm. + + Parameters + ========== + + key + The key for searching. + parent: bool + If true then returns index of the + parent of the node with the passed + key. + By default, False + + Returns + ======= + + int + If the node with the passed key is + in the tree. + tuple + The index of the searched node and + the index of the parent of that node. + None + In all other cases. + """ + + + def __str__(self): + 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] is not None: + node = self.tree[i] + to_be_printed[i] = (node.key, node.data, node.parent) + return str(to_be_printed) diff --git a/pydatastructs/utils/misc_util.py b/pydatastructs/utils/misc_util.py index 1cc43811b..81aae1d3c 100644 --- a/pydatastructs/utils/misc_util.py +++ b/pydatastructs/utils/misc_util.py @@ -300,7 +300,7 @@ class ParentPointerTreeNode(TreeNode): def methods(cls): return ['__new__', '__str__'] - def __new__(cls, key, data=None, parent=None, **kwargs): + def __new__(cls, key, data=None, parent: 'ParentPointerTreeNode' = None, **kwargs): raise_if_backend_is_not_python( cls, kwargs.get('backend', Backend.PYTHON)) obj = Node.__new__(cls) From 7c731fd8e6bb61cb4661ca072bb2f7a2cf90d4a4 Mon Sep 17 00:00:00 2001 From: FamALouiz Date: Wed, 19 Mar 2025 15:37:04 +0100 Subject: [PATCH 3/8] Immplemented search and least common ancestor of parent pointer tree --- pydatastructs/trees/m_ary_trees.py | 65 ++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/pydatastructs/trees/m_ary_trees.py b/pydatastructs/trees/m_ary_trees.py index 4382282af..de0b92c4e 100644 --- a/pydatastructs/trees/m_ary_trees.py +++ b/pydatastructs/trees/m_ary_trees.py @@ -171,7 +171,7 @@ def __str__(self): to_be_printed[i].append(j) return str(to_be_printed) -class ParentPointerTree(object): +class ParentPointerTree(MAryTree): """ Implements a tree with parent pointers. @@ -296,7 +296,7 @@ def search(self, key, **kwargs): key The key for searching. parent: bool - If true then returns index of the + If true then returns node of the parent of the node with the passed key. By default, False @@ -304,17 +304,68 @@ def search(self, key, **kwargs): Returns ======= - int - If the node with the passed key is + ParentPointerTreeNode + The tree node if it was found in the tree. - tuple - The index of the searched node and - the index of the parent of that node. None In all other cases. """ + parent = kwargs.get('parent', False) + + for idx in range(self.size): + node = self.tree[idx] + if node is not None and node.key == key: + if parent: + return node.parent + return node + + return None + def least_common_ancestor(self, first_child_key, second_child_key): + """ + Finds the least common ancestor of two nodes in + the tree. + + Parameters + ========== + + first_child_key + The key of the first child node. + second_child_key + The key of the second child node. + + Returns + ======= + + ParentPointerTreeNode + The least common ancestor node. + None + If either of the nodes doesn't exist in the tree. + """ + first_node_idx = self.search(first_child_key) + second_node_idx = self.search(second_child_key) + + # One or both nodes do not exist + if first_node_idx is None or second_node_idx is None: + return None + + first_node = self.tree[first_node_idx] + second_node = self.tree[second_node_idx] + + first_ancestors = set() + + while first_node is not None: + first_ancestors.add(first_node) + first_node = first_node.parent + + while second_node is not None: + if second_node in first_ancestors: + return second_node # Found the least common ancestor + second_node = second_node.parent + + return None # No common ancestor found + def __str__(self): to_be_printed = ['' for i in range(self.tree._last_pos_filled + 1)] for i in range(self.tree._last_pos_filled + 1): From baaa06da58b75f0d9a6b1b13b08fe7c4100f38a3 Mon Sep 17 00:00:00 2001 From: FamALouiz Date: Wed, 19 Mar 2025 15:40:15 +0100 Subject: [PATCH 4/8] Added parent pointer tree skeleton tests --- pydatastructs/trees/tests/test_m_ary_trees.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pydatastructs/trees/tests/test_m_ary_trees.py b/pydatastructs/trees/tests/test_m_ary_trees.py index 6cbc84ace..b289a7ead 100644 --- a/pydatastructs/trees/tests/test_m_ary_trees.py +++ b/pydatastructs/trees/tests/test_m_ary_trees.py @@ -1,5 +1,9 @@ from pydatastructs import MAryTree +from pydatastructs.trees.m_ary_trees import ParentPointerTree def test_MAryTree(): m = MAryTree(1, 1) assert str(m) == '[(1, 1)]' + +def test_ParentPointerTree(): + pass \ No newline at end of file From 77b7a362b00a75c42e9247c4798866544ebee2e9 Mon Sep 17 00:00:00 2001 From: FamALouiz Date: Wed, 19 Mar 2025 19:19:02 +0100 Subject: [PATCH 5/8] Added parent pointer tree tests --- pydatastructs/trees/tests/test_m_ary_trees.py | 68 +++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/pydatastructs/trees/tests/test_m_ary_trees.py b/pydatastructs/trees/tests/test_m_ary_trees.py index b289a7ead..a85f45f26 100644 --- a/pydatastructs/trees/tests/test_m_ary_trees.py +++ b/pydatastructs/trees/tests/test_m_ary_trees.py @@ -1,9 +1,69 @@ -from pydatastructs import MAryTree -from pydatastructs.trees.m_ary_trees import ParentPointerTree +from pydatastructs.utils.misc_util import Backend +from pydatastructs.trees.m_ary_trees import MAryTree, ParentPointerTree + def test_MAryTree(): m = MAryTree(1, 1) assert str(m) == '[(1, 1)]' -def test_ParentPointerTree(): - pass \ No newline at end of file + +def _test_ParentPointerTree(backend): + PPT = ParentPointerTree + + tree = PPT(1, "root", backend=backend) + assert tree.root_idx == 0 + assert tree.tree[0].key == 1 + assert tree.tree[0].data == "root" + assert tree.tree[0].parent is None + + tree.insert(1, 2, "child_1") + tree.insert(1, 3, "child_2") + tree.insert(1, 4, "child_3") + assert tree.size == 4 + assert tree.tree[1].key == 2 + assert tree.tree[1].data == "child_1" + assert tree.tree[1].parent == tree.tree[0] + assert tree.tree[2].key == 3 + assert tree.tree[2].data == "child_2" + assert tree.tree[2].parent == tree.tree[0] + assert tree.tree[3].key == 4 + assert tree.tree[3].data == "child_3" + assert tree.tree[3].parent == tree.tree[0] + + assert tree.search(2).data == "child_1" + assert tree.search(3).data == "child_2" + assert tree.search(4).data == "child_3" + assert tree.search(5) is None + assert tree.search(2, parent=True) == tree.tree[0] + + tree.insert(2, 5, "child_4") + tree.insert(2, 6, "child_5") + assert tree.least_common_ancestor(5, 6) == tree.tree[1] + assert tree.least_common_ancestor(5, 3) == tree.tree[0] + assert tree.least_common_ancestor(2, 4) == tree.tree[0] + assert tree.least_common_ancestor(5, 7) is None + + assert tree.delete(5) is True + assert tree.search(5) is None + assert tree.size == 5 + assert tree.delete(6) is True + assert tree.search(6) is None + assert tree.size == 4 + assert tree.delete(10) is None + + expected = '''[(1, 'root', 'None'), (2, 'child_1', "(1, 'root')"), (3, 'child_2', "(1, 'root')"), (4, 'child_3', "(1, 'root')")]''' + assert str(tree) == expected + + empty_tree = PPT(backend=backend) + assert empty_tree.size == 0 + assert empty_tree.search(1) is None + assert empty_tree.delete(1) is None + assert empty_tree.least_common_ancestor(1, 2) is None + + empty_tree.insert(None, 7, "child_6") + + expected = '''[(7, 'child_6', 'None')]''' + assert str(empty_tree) == expected + +def test_ParentPointerTree(): + _test_ParentPointerTree(Backend.PYTHON) From 1bb35f02936c923efdc40475d5214251a7243724 Mon Sep 17 00:00:00 2001 From: FamALouiz Date: Wed, 19 Mar 2025 19:19:26 +0100 Subject: [PATCH 6/8] Fixed bugs with parent pointer tree insert and search functions --- pydatastructs/trees/m_ary_trees.py | 65 +++++++++++++++++++----------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/pydatastructs/trees/m_ary_trees.py b/pydatastructs/trees/m_ary_trees.py index de0b92c4e..b9ac3b20e 100644 --- a/pydatastructs/trees/m_ary_trees.py +++ b/pydatastructs/trees/m_ary_trees.py @@ -204,9 +204,15 @@ def __new__(cls, key=None, root_data=None, comp=None, **kwargs): raise_if_backend_is_not_python( cls, kwargs.get('backend', Backend.PYTHON)) obj = object.__new__(cls) - if key is None and root_data is not None: - raise ValueError('Key required.') - key = None if root_data is None else key + + # Empty tree + if key is None: + obj.root_idx = None + obj.tree, obj.size = ArrayForTrees(ParentPointerTreeNode, []), 0 + obj.comparator = lambda key1, key2: key1 < key2 \ + if comp is None else comp + return obj + root = ParentPointerTreeNode(key, root_data) root.is_root = True obj.root_idx = 0 @@ -238,16 +244,29 @@ def insert(self, parent_key, key, data=None): None """ - if parent_key is None: - raise ValueError("Parent key is required.") if key is None: raise ValueError("Key is required.") + + # Empty tree + if self.size == 0: + if parent_key is not None: + raise ValueError("Parent key should be None.") + + root = ParentPointerTreeNode(key, data) + root.is_root = True + self.tree.append(root) + self.size += 1 + return + + if parent_key is None: + raise ValueError("Parent key is required.") + if self.search(key) is not None: raise ValueError("Key already exists.") - - parent_node = self.search(parent_key, parent=True) + + parent_node = self.search(parent_key) new_node = ParentPointerTreeNode(key, data, parent_node) - + self.tree.append(new_node) self.size += 1 @@ -278,11 +297,12 @@ def delete(self, key): The node is deleted means that the connection to that node are removed but the it is still in tree. """ - for idx in range(self.size): - if self.tree[idx].key == key: + for idx in range(self.tree._last_pos_filled + 1): + if self.tree[idx] and self.tree[idx].key == key: self.tree.delete(idx) + self.size -= 1 return True - + return None def search(self, key, **kwargs): @@ -295,7 +315,7 @@ def search(self, key, **kwargs): key The key for searching. - parent: bool + get_parent: bool If true then returns node of the parent of the node with the passed key. @@ -312,7 +332,7 @@ def search(self, key, **kwargs): """ parent = kwargs.get('parent', False) - for idx in range(self.size): + for idx in range(self.tree._last_pos_filled + 1): node = self.tree[idx] if node is not None and node.key == key: if parent: @@ -320,11 +340,11 @@ def search(self, key, **kwargs): return node return None - + def least_common_ancestor(self, first_child_key, second_child_key): """ - Finds the least common ancestor of two nodes in + Finds the least common ancestor of two nodes in the tree. Parameters @@ -343,16 +363,13 @@ def least_common_ancestor(self, first_child_key, second_child_key): None If either of the nodes doesn't exist in the tree. """ - first_node_idx = self.search(first_child_key) - second_node_idx = self.search(second_child_key) + first_node = self.search(first_child_key) + second_node = self.search(second_child_key) # One or both nodes do not exist - if first_node_idx is None or second_node_idx is None: - return None + if first_node is None or second_node is None: + return None - first_node = self.tree[first_node_idx] - second_node = self.tree[second_node_idx] - first_ancestors = set() while first_node is not None: @@ -367,9 +384,9 @@ def least_common_ancestor(self, first_child_key, second_child_key): return None # No common ancestor found def __str__(self): - to_be_printed = ['' for i in range(self.tree._last_pos_filled + 1)] + to_be_printed = [] for i in range(self.tree._last_pos_filled + 1): if self.tree[i] is not None: node = self.tree[i] - to_be_printed[i] = (node.key, node.data, node.parent) + to_be_printed.append((node.key, node.data, str(node.parent))) return str(to_be_printed) From 19c09202bc09ba9fea6d720f5020790f4e37dbcc Mon Sep 17 00:00:00 2001 From: FamALouiz Date: Thu, 20 Mar 2025 11:33:59 +0100 Subject: [PATCH 7/8] Added parent pointer tree to trees subpackage --- pydatastructs/trees/__init__.py | 4 +++- pydatastructs/trees/m_ary_trees.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pydatastructs/trees/__init__.py b/pydatastructs/trees/__init__.py index 1c99cca25..2eaf1100d 100644 --- a/pydatastructs/trees/__init__.py +++ b/pydatastructs/trees/__init__.py @@ -22,7 +22,9 @@ __all__.extend(binary_trees.__all__) from .m_ary_trees import ( - MAryTreeNode, MAryTree + MAryTreeNode, + MAryTree, + ParentPointerTree ) __all__.extend(m_ary_trees.__all__) diff --git a/pydatastructs/trees/m_ary_trees.py b/pydatastructs/trees/m_ary_trees.py index b9ac3b20e..17ee71005 100644 --- a/pydatastructs/trees/m_ary_trees.py +++ b/pydatastructs/trees/m_ary_trees.py @@ -4,7 +4,8 @@ Backend, raise_if_backend_is_not_python) __all__ = [ - 'MAryTree' + 'MAryTree', + 'ParentPointerTree' ] class MAryTree(object): From 8f2c68d399fea320fcbdb8ffc41f873a87d5f611 Mon Sep 17 00:00:00 2001 From: FamALouiz Date: Thu, 20 Mar 2025 11:34:06 +0100 Subject: [PATCH 8/8] Updated trees docs --- docs/source/pydatastructs/trees/m_ary_trees.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/pydatastructs/trees/m_ary_trees.rst b/docs/source/pydatastructs/trees/m_ary_trees.rst index a191643f3..ada8bf794 100644 --- a/docs/source/pydatastructs/trees/m_ary_trees.rst +++ b/docs/source/pydatastructs/trees/m_ary_trees.rst @@ -1,3 +1,4 @@ M-ary Trees =========== +.. autoclass:: pydatastructs.ParentPointerTree