Skip to content

Commit

Permalink
C++ backend for Cartesian Trees (#567)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kishan-Ved authored Jul 13, 2024
1 parent d1bc67c commit 4b2272f
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 5 deletions.
8 changes: 7 additions & 1 deletion pydatastructs/trees/_backend/cpp/BinaryTree.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,13 @@ static PyObject* BinaryTree___str__(BinaryTree *self) {
OneDimensionalArray* oda = self->tree->_one_dimensional_array;
TreeNode* node = reinterpret_cast<TreeNode*>(oda->_data[i]);
if (reinterpret_cast<PyObject*>(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);
}
Expand Down
7 changes: 7 additions & 0 deletions pydatastructs/trees/_backend/cpp/BinaryTreeTraversal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "RedBlackTree.hpp"
#include "SplayTree.hpp"
#include "AVLTree.hpp"
#include "CartesianTree.hpp"

typedef struct {
PyObject_HEAD
Expand Down Expand Up @@ -48,13 +49,19 @@ 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<SplayTree*>(tree)->sbbt->bst->binary_tree;
}
else if (PyObject_IsInstance(tree, (PyObject *)&AVLTreeType)) {
self->tree = reinterpret_cast<AVLTree*>(tree)->sbbt->bst->binary_tree;
}
else if (PyObject_IsInstance(tree, (PyObject *)&CartesianTreeType)) {
self->tree = reinterpret_cast<CartesianTree*>(tree)->sbbt->bst->binary_tree;
}
else if (PyObject_IsInstance(tree, (PyObject *)&RedBlackTreeType)) {
self->tree = reinterpret_cast<RedBlackTree*>(tree)->sbbt->bst->binary_tree;
}
Expand Down
210 changes: 210 additions & 0 deletions pydatastructs/trees/_backend/cpp/CartesianTree.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
#ifndef TREES_CARTESIANTREE_HPP
#define TREES_CARTESIANTREE_HPP

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <structmember.h>
#include <cstdlib>
#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<PyObject*>(self));
}

static PyObject* CartesianTree___new__(PyTypeObject* type, PyObject *args, PyObject *kwds) {
CartesianTree *self;
self = reinterpret_cast<CartesianTree*>(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<SelfBalancingBinaryTree*>(p);
self->tree = reinterpret_cast<SelfBalancingBinaryTree*>(p)->bst->binary_tree->tree;

return reinterpret_cast<PyObject*>(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<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)]);
PyObject* parent_idx = reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->parent;
TreeNode* parent = reinterpret_cast<TreeNode*>(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<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)]);
parent_idx = reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->parent;
if (parent_idx != Py_None) {
parent = reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(parent_idx)]);
}
}
if (node->parent == Py_None) {
reinterpret_cast<TreeNode*>(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<TreeNode*>(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<TreeNode*>(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<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->left));
}
else if (reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node->left)])->priority < reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node->right)])->priority) {
SelfBalancingBinaryTree__right_rotate(self->sbbt, Py_BuildValue("OO", node_idx, reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->left));
}
else {
SelfBalancingBinaryTree__left_rotate(self->sbbt, Py_BuildValue("OO", node_idx, reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->right));
}
node = reinterpret_cast<TreeNode*>(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<TreeNode*>(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*>(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<PyObject*>(new_node);
if (node->is_root) {
reinterpret_cast<TreeNode*>(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
7 changes: 7 additions & 0 deletions pydatastructs/trees/_backend/cpp/trees.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "BinaryIndexedTree.hpp"
#include "SplayTree.hpp"
#include "AVLTree.hpp"
#include "CartesianTree.hpp"

static struct PyModuleDef trees_struct = {
PyModuleDef_HEAD_INIT,
Expand Down Expand Up @@ -68,5 +69,11 @@ PyMODINIT_FUNC PyInit__trees(void) {
Py_INCREF(&AVLTreeType);
PyModule_AddObject(trees, "AVLTree", reinterpret_cast<PyObject*>(&AVLTreeType));

if (PyType_Ready(&CartesianTreeType) < 0) {
return NULL;
}
Py_INCREF(&CartesianTreeType);
PyModule_AddObject(trees, "CartesianTree", reinterpret_cast<PyObject*>(&CartesianTreeType));

return trees;
}
11 changes: 10 additions & 1 deletion pydatastructs/trees/binary_trees.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
13 changes: 10 additions & 3 deletions pydatastructs/trees/tests/test_binary_trees.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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]
Expand All @@ -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
Expand All @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions pydatastructs/utils/_backend/cpp/TreeNode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ typedef struct {
PyObject* parent;
long size;
long color;
bool isCartesianTreeNode;
long priority;
} TreeNode;

static void TreeNode_dealloc(TreeNode *self) {
Expand All @@ -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<PyObject*>(self);
}
Expand Down

0 comments on commit 4b2272f

Please sign in to comment.