From 4b2272f5fb0611b80e22c31b71eef2f2f5610997 Mon Sep 17 00:00:00 2001 From: Kishan Ved Date: Sat, 13 Jul 2024 19:45:52 +0530 Subject: [PATCH] C++ backend for Cartesian Trees (#567) --- .../trees/_backend/cpp/BinaryTree.hpp | 8 +- .../_backend/cpp/BinaryTreeTraversal.hpp | 7 + .../trees/_backend/cpp/CartesianTree.hpp | 210 ++++++++++++++++++ pydatastructs/trees/_backend/cpp/trees.cpp | 7 + pydatastructs/trees/binary_trees.py | 11 +- .../trees/tests/test_binary_trees.py | 13 +- pydatastructs/utils/_backend/cpp/TreeNode.hpp | 3 + 7 files changed, 254 insertions(+), 5 deletions(-) create mode 100644 pydatastructs/trees/_backend/cpp/CartesianTree.hpp diff --git a/pydatastructs/trees/_backend/cpp/BinaryTree.hpp b/pydatastructs/trees/_backend/cpp/BinaryTree.hpp index 72b67090..55baa0d5 100644 --- a/pydatastructs/trees/_backend/cpp/BinaryTree.hpp +++ b/pydatastructs/trees/_backend/cpp/BinaryTree.hpp @@ -102,7 +102,13 @@ static PyObject* BinaryTree___str__(BinaryTree *self) { OneDimensionalArray* oda = self->tree->_one_dimensional_array; TreeNode* node = reinterpret_cast(oda->_data[i]); if (reinterpret_cast(node) != Py_None) { - PyObject* out = Py_BuildValue("(OOOO)", node->left, node->key, node->data, node->right); + PyObject* out; + if (node->isCartesianTreeNode == true) { + out = Py_BuildValue("(OOOOO)", node->left, node->key, PyLong_FromLong(node->priority), node->data, node->right); + } + else { + out = Py_BuildValue("(OOOO)", node->left, node->key, node->data, node->right); + } Py_INCREF(out); PyList_SET_ITEM(list, i, out); } diff --git a/pydatastructs/trees/_backend/cpp/BinaryTreeTraversal.hpp b/pydatastructs/trees/_backend/cpp/BinaryTreeTraversal.hpp index f7c08bdb..b430706f 100644 --- a/pydatastructs/trees/_backend/cpp/BinaryTreeTraversal.hpp +++ b/pydatastructs/trees/_backend/cpp/BinaryTreeTraversal.hpp @@ -17,6 +17,7 @@ #include "RedBlackTree.hpp" #include "SplayTree.hpp" #include "AVLTree.hpp" +#include "CartesianTree.hpp" typedef struct { PyObject_HEAD @@ -48,6 +49,9 @@ static PyObject* BinaryTreeTraversal___new__(PyTypeObject* type, PyObject *args, if (PyType_Ready(&AVLTreeType) < 0) { // This has to be present to finalize a type object. This should be called on all type objects to finish their initialization. return NULL; } + if (PyType_Ready(&CartesianTreeType) < 0) { // This has to be present to finalize a type object. This should be called on all type objects to finish their initialization. + return NULL; + } if (PyObject_IsInstance(tree, (PyObject *)&SplayTreeType)) { self->tree = reinterpret_cast(tree)->sbbt->bst->binary_tree; @@ -55,6 +59,9 @@ static PyObject* BinaryTreeTraversal___new__(PyTypeObject* type, PyObject *args, else if (PyObject_IsInstance(tree, (PyObject *)&AVLTreeType)) { self->tree = reinterpret_cast(tree)->sbbt->bst->binary_tree; } + else if (PyObject_IsInstance(tree, (PyObject *)&CartesianTreeType)) { + self->tree = reinterpret_cast(tree)->sbbt->bst->binary_tree; + } else if (PyObject_IsInstance(tree, (PyObject *)&RedBlackTreeType)) { self->tree = reinterpret_cast(tree)->sbbt->bst->binary_tree; } diff --git a/pydatastructs/trees/_backend/cpp/CartesianTree.hpp b/pydatastructs/trees/_backend/cpp/CartesianTree.hpp new file mode 100644 index 00000000..f21ed5a5 --- /dev/null +++ b/pydatastructs/trees/_backend/cpp/CartesianTree.hpp @@ -0,0 +1,210 @@ +#ifndef TREES_CARTESIANTREE_HPP +#define TREES_CARTESIANTREE_HPP + +#define PY_SSIZE_T_CLEAN +#include +#include +#include +#include "../../../utils/_backend/cpp/utils.hpp" +#include "../../../utils/_backend/cpp/TreeNode.hpp" +#include "../../../linear_data_structures/_backend/cpp/arrays/ArrayForTrees.hpp" +#include "../../../linear_data_structures/_backend/cpp/arrays/DynamicOneDimensionalArray.hpp" +#include "BinarySearchTree.hpp" +#include "SelfBalancingBinaryTree.hpp" + +typedef struct { + PyObject_HEAD + SelfBalancingBinaryTree* sbbt; + ArrayForTrees* tree; +} CartesianTree; + +static void CartesianTree_dealloc(CartesianTree *self) { + Py_TYPE(self)->tp_free(reinterpret_cast(self)); +} + +static PyObject* CartesianTree___new__(PyTypeObject* type, PyObject *args, PyObject *kwds) { + CartesianTree *self; + self = reinterpret_cast(type->tp_alloc(type, 0)); + + if (PyType_Ready(&SelfBalancingBinaryTreeType) < 0) { // This has to be present to finalize a type object. This should be called on all type objects to finish their initialization. + return NULL; + } + PyObject* p = SelfBalancingBinaryTree___new__(&SelfBalancingBinaryTreeType, args, kwds); + self->sbbt = reinterpret_cast(p); + self->tree = reinterpret_cast(p)->bst->binary_tree->tree; + + return reinterpret_cast(self); +} + +static PyObject* CartesianTree___str__(CartesianTree *self) { + return BinarySearchTree___str__(self->sbbt->bst); +} + +static PyObject* CartesianTree_search(CartesianTree* self, PyObject *args, PyObject *kwds) { + return BinarySearchTree_search(self->sbbt->bst, args, kwds); +} + +static PyObject* Cartesian_Tree__bubble_up(CartesianTree* self, PyObject *args) { + PyObject* node_idx = PyObject_GetItem(args, PyZero); + BinaryTree* bt = self->sbbt->bst->binary_tree; + TreeNode* node = reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)]); + PyObject* parent_idx = reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->parent; + TreeNode* parent = reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(parent_idx)]); + + while ((node->parent != Py_None) && (parent->priority > node->priority)) { + if (parent->right == node_idx) { + SelfBalancingBinaryTree__left_rotate(self->sbbt, Py_BuildValue("OO", parent_idx, node_idx)); + } + else { + SelfBalancingBinaryTree__right_rotate(self->sbbt, Py_BuildValue("OO", parent_idx, node_idx)); + } + node = reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)]); + parent_idx = reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->parent; + if (parent_idx != Py_None) { + parent = reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(parent_idx)]); + } + } + if (node->parent == Py_None) { + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->is_root = true; + } + Py_RETURN_NONE; +} + +static PyObject* Cartesian_Tree__trickle_down(CartesianTree* self, PyObject *args) { + PyObject* node_idx = PyObject_GetItem(args, PyZero); + BinaryTree* bt = self->sbbt->bst->binary_tree; + TreeNode* node = reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)]); + while (node->left != Py_None || node->right != Py_None) { + if (node->left == Py_None) { + SelfBalancingBinaryTree__left_rotate(self->sbbt, Py_BuildValue("OO", node_idx, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->right)); + } + else if (node->right == Py_None) { + SelfBalancingBinaryTree__right_rotate(self->sbbt, Py_BuildValue("OO", node_idx, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->left)); + } + else if (reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node->left)])->priority < reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node->right)])->priority) { + SelfBalancingBinaryTree__right_rotate(self->sbbt, Py_BuildValue("OO", node_idx, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->left)); + } + else { + SelfBalancingBinaryTree__left_rotate(self->sbbt, Py_BuildValue("OO", node_idx, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->right)); + } + node = reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)]); + } + Py_RETURN_NONE; +} + +static PyObject* CartesianTree_insert(CartesianTree *self, PyObject* args) { + Py_INCREF(Py_None); + PyObject* key = Py_None; + Py_INCREF(Py_None); + PyObject* priority = Py_None; + Py_INCREF(Py_None); + PyObject* data = Py_None; + if (!PyArg_ParseTuple(args, "OO|O", &key, &priority, &data)) { // data is optional + return NULL; + } + BinaryTree* bt = self->sbbt->bst->binary_tree; + + SelfBalancingBinaryTree_insert(self->sbbt, Py_BuildValue("(OO)", key, data)); + PyObject* node_idx = SelfBalancingBinaryTree_search(self->sbbt, Py_BuildValue("(O)", key), PyDict_New()); + TreeNode* node = reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)]); + if (PyType_Ready(&TreeNodeType) < 0) { // This has to be present to finalize a type object. This should be called on all type objects to finish their initialization. + return NULL; + } + TreeNode* new_node = reinterpret_cast(TreeNode___new__(&TreeNodeType, Py_BuildValue("(OO)", key, data), PyDict_New())); + new_node->isCartesianTreeNode = true; + new_node->priority = PyLong_AsLong(priority); + new_node->parent = node->parent; + new_node->left = node->left; + new_node->right = node->right; + bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)] = reinterpret_cast(new_node); + if (node->is_root) { + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->is_root = true; + } + else { + Cartesian_Tree__bubble_up(self, Py_BuildValue("(O)", node_idx)); + } + Py_RETURN_NONE; +} + +static PyObject* CartesianTree_delete(CartesianTree* self, PyObject *args, PyObject *kwds) { + Py_INCREF(Py_None); + PyObject* key = Py_None; + PyObject* balancing_info = PyZero; + static char* keywords[] = {"key","balancing_info", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O", keywords, &key, &balancing_info)) { + return NULL; + } + PyObject* node_idx = SelfBalancingBinaryTree_search(self->sbbt, Py_BuildValue("(O)", key), PyDict_New()); + if (node_idx != Py_None) { + Cartesian_Tree__trickle_down(self, Py_BuildValue("(O)", node_idx)); + PyObject* kwd_bal = PyDict_New(); + PyDict_SetItemString(kwd_bal, "balancing_info", balancing_info); + return SelfBalancingBinaryTree_delete(self->sbbt, Py_BuildValue("(O)", key), kwd_bal); + } + Py_RETURN_NONE; +} + +static PyObject* CartesianTree_root_idx(CartesianTree *self, void *closure) { + return self->sbbt->bst->binary_tree->root_idx; +} + + +static struct PyMethodDef CartesianTree_PyMethodDef[] = { + {"insert", (PyCFunction) CartesianTree_insert, METH_VARARGS, NULL}, + {"delete", (PyCFunction) CartesianTree_delete, METH_VARARGS | METH_KEYWORDS, NULL}, + {"search", (PyCFunction) CartesianTree_search, METH_VARARGS | METH_KEYWORDS, NULL}, + {NULL} /* Sentinel */ +}; + +static PyGetSetDef CartesianTree_GetterSetters[] = { + {"root_idx", (getter) CartesianTree_root_idx, NULL, "returns the index of the tree's root", NULL}, + {NULL} /* Sentinel */ +}; + +static PyMemberDef CartesianTree_PyMemberDef[] = { + {"tree", T_OBJECT_EX, offsetof(CartesianTree, tree), 0, "tree"}, + {NULL} /* Sentinel */ +}; + + +static PyTypeObject CartesianTreeType = { + /* tp_name */ PyVarObject_HEAD_INIT(NULL, 0) "CartesianTree", + /* tp_basicsize */ sizeof(CartesianTree), + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor) CartesianTree_dealloc, + /* tp_print */ 0, + /* tp_getattr */ 0, + /* tp_setattr */ 0, + /* tp_reserved */ 0, + /* tp_repr */ 0, + /* tp_as_number */ 0, + /* tp_as_sequence */ 0, + /* tp_as_mapping */ 0, + /* tp_hash */ 0, + /* tp_call */ 0, + /* tp_str */ (reprfunc) CartesianTree___str__, + /* tp_getattro */ 0, + /* tp_setattro */ 0, + /* tp_as_buffer */ 0, + /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + /* tp_doc */ 0, + /* tp_traverse */ 0, + /* tp_clear */ 0, + /* tp_richcompare */ 0, + /* tp_weaklistoffset */ 0, + /* tp_iter */ 0, + /* tp_iternext */ 0, + /* tp_methods */ CartesianTree_PyMethodDef, + /* tp_members */ CartesianTree_PyMemberDef, + /* tp_getset */ CartesianTree_GetterSetters, + /* tp_base */ &SelfBalancingBinaryTreeType, + /* tp_dict */ 0, + /* tp_descr_get */ 0, + /* tp_descr_set */ 0, + /* tp_dictoffset */ 0, + /* tp_init */ 0, + /* tp_alloc */ 0, + /* tp_new */ CartesianTree___new__, +}; + +#endif diff --git a/pydatastructs/trees/_backend/cpp/trees.cpp b/pydatastructs/trees/_backend/cpp/trees.cpp index 4747c71b..86ecabed 100644 --- a/pydatastructs/trees/_backend/cpp/trees.cpp +++ b/pydatastructs/trees/_backend/cpp/trees.cpp @@ -7,6 +7,7 @@ #include "BinaryIndexedTree.hpp" #include "SplayTree.hpp" #include "AVLTree.hpp" +#include "CartesianTree.hpp" static struct PyModuleDef trees_struct = { PyModuleDef_HEAD_INIT, @@ -68,5 +69,11 @@ PyMODINIT_FUNC PyInit__trees(void) { Py_INCREF(&AVLTreeType); PyModule_AddObject(trees, "AVLTree", reinterpret_cast(&AVLTreeType)); + if (PyType_Ready(&CartesianTreeType) < 0) { + return NULL; + } + Py_INCREF(&CartesianTreeType); + PyModule_AddObject(trees, "CartesianTree", reinterpret_cast(&CartesianTreeType)); + return trees; } diff --git a/pydatastructs/trees/binary_trees.py b/pydatastructs/trees/binary_trees.py index d4b5e54b..93c8e22f 100644 --- a/pydatastructs/trees/binary_trees.py +++ b/pydatastructs/trees/binary_trees.py @@ -804,9 +804,18 @@ class CartesianTree(SelfBalancingBinaryTree): pydatastructs.trees.binary_trees.SelfBalancingBinaryTree """ + def __new__(cls, key=None, root_data=None, comp=None, + is_order_statistic=False, **kwargs): + backend = kwargs.get('backend', Backend.PYTHON) + if backend == Backend.CPP: + if comp is None: + comp = lambda key1, key2: key1 < key2 + return _trees.CartesianTree(key, root_data, comp, is_order_statistic, **kwargs) # If any argument is not given, then it is passed as None, except for comp + return super().__new__(cls, key, root_data, comp, is_order_statistic, **kwargs) + @classmethod def methods(cls): - return ['__str__', 'insert', 'delete'] + return ['__new__', '__str__', 'insert', 'delete'] def _bubble_up(self, node_idx): node = self.tree[node_idx] diff --git a/pydatastructs/trees/tests/test_binary_trees.py b/pydatastructs/trees/tests/test_binary_trees.py index 3cd672f7..f2935e58 100644 --- a/pydatastructs/trees/tests/test_binary_trees.py +++ b/pydatastructs/trees/tests/test_binary_trees.py @@ -410,8 +410,8 @@ def test_BinaryIndexedTree(): def test_cpp_BinaryIndexedTree(): _test_BinaryIndexedTree(Backend.CPP) -def test_CartesianTree(): - tree = CartesianTree() +def _test_CartesianTree(backend): + tree = CartesianTree(backend=backend) tree.insert(3, 1, 3) tree.insert(1, 6, 1) tree.insert(0, 9, 0) @@ -430,7 +430,7 @@ def test_CartesianTree(): "(7, 7, 22, 7, 8), (None, 6, 42, 6, None), " "(None, 8, 49, 8, None), (None, 2, 99, 2, None)]") - trav = BinaryTreeTraversal(tree) + trav = BinaryTreeTraversal(tree, backend=backend) in_order = trav.depth_first_search(order='in_order') pre_order = trav.depth_first_search(order='pre_order') assert [node.key for node in in_order] == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] @@ -446,6 +446,7 @@ def test_CartesianTree(): k = tree.search(1.5) assert tree.tree[tree.tree[k].parent].key == 3 tree.delete(1.5) + assert tree.root_idx == 0 tree.tree[tree.tree[tree.root_idx].left].key == 1 tree.delete(8) assert tree.search(8) is None @@ -455,6 +456,12 @@ def test_CartesianTree(): assert tree.search(3) is None assert tree.delete(18) is None +def test_CartesianTree(): + _test_CartesianTree(backend=Backend.PYTHON) + +def test_cpp_CartesianTree(): + _test_CartesianTree(backend=Backend.CPP) + def test_Treap(): random.seed(0) diff --git a/pydatastructs/utils/_backend/cpp/TreeNode.hpp b/pydatastructs/utils/_backend/cpp/TreeNode.hpp index e7ed2841..259991f2 100644 --- a/pydatastructs/utils/_backend/cpp/TreeNode.hpp +++ b/pydatastructs/utils/_backend/cpp/TreeNode.hpp @@ -18,6 +18,8 @@ typedef struct { PyObject* parent; long size; long color; + bool isCartesianTreeNode; + long priority; } TreeNode; static void TreeNode_dealloc(TreeNode *self) { @@ -42,6 +44,7 @@ static PyObject* TreeNode___new__(PyTypeObject* type, PyObject *args, PyObject * self->size = 1; self->is_root = false; self->color = 1; + self->isCartesianTreeNode = false; return reinterpret_cast(self); }