Skip to content

Commit 4b2272f

Browse files
authored
C++ backend for Cartesian Trees (#567)
1 parent d1bc67c commit 4b2272f

File tree

7 files changed

+254
-5
lines changed

7 files changed

+254
-5
lines changed

pydatastructs/trees/_backend/cpp/BinaryTree.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,13 @@ static PyObject* BinaryTree___str__(BinaryTree *self) {
102102
OneDimensionalArray* oda = self->tree->_one_dimensional_array;
103103
TreeNode* node = reinterpret_cast<TreeNode*>(oda->_data[i]);
104104
if (reinterpret_cast<PyObject*>(node) != Py_None) {
105-
PyObject* out = Py_BuildValue("(OOOO)", node->left, node->key, node->data, node->right);
105+
PyObject* out;
106+
if (node->isCartesianTreeNode == true) {
107+
out = Py_BuildValue("(OOOOO)", node->left, node->key, PyLong_FromLong(node->priority), node->data, node->right);
108+
}
109+
else {
110+
out = Py_BuildValue("(OOOO)", node->left, node->key, node->data, node->right);
111+
}
106112
Py_INCREF(out);
107113
PyList_SET_ITEM(list, i, out);
108114
}

pydatastructs/trees/_backend/cpp/BinaryTreeTraversal.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "RedBlackTree.hpp"
1818
#include "SplayTree.hpp"
1919
#include "AVLTree.hpp"
20+
#include "CartesianTree.hpp"
2021

2122
typedef struct {
2223
PyObject_HEAD
@@ -48,13 +49,19 @@ static PyObject* BinaryTreeTraversal___new__(PyTypeObject* type, PyObject *args,
4849
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.
4950
return NULL;
5051
}
52+
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.
53+
return NULL;
54+
}
5155

5256
if (PyObject_IsInstance(tree, (PyObject *)&SplayTreeType)) {
5357
self->tree = reinterpret_cast<SplayTree*>(tree)->sbbt->bst->binary_tree;
5458
}
5559
else if (PyObject_IsInstance(tree, (PyObject *)&AVLTreeType)) {
5660
self->tree = reinterpret_cast<AVLTree*>(tree)->sbbt->bst->binary_tree;
5761
}
62+
else if (PyObject_IsInstance(tree, (PyObject *)&CartesianTreeType)) {
63+
self->tree = reinterpret_cast<CartesianTree*>(tree)->sbbt->bst->binary_tree;
64+
}
5865
else if (PyObject_IsInstance(tree, (PyObject *)&RedBlackTreeType)) {
5966
self->tree = reinterpret_cast<RedBlackTree*>(tree)->sbbt->bst->binary_tree;
6067
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
#ifndef TREES_CARTESIANTREE_HPP
2+
#define TREES_CARTESIANTREE_HPP
3+
4+
#define PY_SSIZE_T_CLEAN
5+
#include <Python.h>
6+
#include <structmember.h>
7+
#include <cstdlib>
8+
#include "../../../utils/_backend/cpp/utils.hpp"
9+
#include "../../../utils/_backend/cpp/TreeNode.hpp"
10+
#include "../../../linear_data_structures/_backend/cpp/arrays/ArrayForTrees.hpp"
11+
#include "../../../linear_data_structures/_backend/cpp/arrays/DynamicOneDimensionalArray.hpp"
12+
#include "BinarySearchTree.hpp"
13+
#include "SelfBalancingBinaryTree.hpp"
14+
15+
typedef struct {
16+
PyObject_HEAD
17+
SelfBalancingBinaryTree* sbbt;
18+
ArrayForTrees* tree;
19+
} CartesianTree;
20+
21+
static void CartesianTree_dealloc(CartesianTree *self) {
22+
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
23+
}
24+
25+
static PyObject* CartesianTree___new__(PyTypeObject* type, PyObject *args, PyObject *kwds) {
26+
CartesianTree *self;
27+
self = reinterpret_cast<CartesianTree*>(type->tp_alloc(type, 0));
28+
29+
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.
30+
return NULL;
31+
}
32+
PyObject* p = SelfBalancingBinaryTree___new__(&SelfBalancingBinaryTreeType, args, kwds);
33+
self->sbbt = reinterpret_cast<SelfBalancingBinaryTree*>(p);
34+
self->tree = reinterpret_cast<SelfBalancingBinaryTree*>(p)->bst->binary_tree->tree;
35+
36+
return reinterpret_cast<PyObject*>(self);
37+
}
38+
39+
static PyObject* CartesianTree___str__(CartesianTree *self) {
40+
return BinarySearchTree___str__(self->sbbt->bst);
41+
}
42+
43+
static PyObject* CartesianTree_search(CartesianTree* self, PyObject *args, PyObject *kwds) {
44+
return BinarySearchTree_search(self->sbbt->bst, args, kwds);
45+
}
46+
47+
static PyObject* Cartesian_Tree__bubble_up(CartesianTree* self, PyObject *args) {
48+
PyObject* node_idx = PyObject_GetItem(args, PyZero);
49+
BinaryTree* bt = self->sbbt->bst->binary_tree;
50+
TreeNode* node = reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)]);
51+
PyObject* parent_idx = reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->parent;
52+
TreeNode* parent = reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(parent_idx)]);
53+
54+
while ((node->parent != Py_None) && (parent->priority > node->priority)) {
55+
if (parent->right == node_idx) {
56+
SelfBalancingBinaryTree__left_rotate(self->sbbt, Py_BuildValue("OO", parent_idx, node_idx));
57+
}
58+
else {
59+
SelfBalancingBinaryTree__right_rotate(self->sbbt, Py_BuildValue("OO", parent_idx, node_idx));
60+
}
61+
node = reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)]);
62+
parent_idx = reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->parent;
63+
if (parent_idx != Py_None) {
64+
parent = reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(parent_idx)]);
65+
}
66+
}
67+
if (node->parent == Py_None) {
68+
reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->is_root = true;
69+
}
70+
Py_RETURN_NONE;
71+
}
72+
73+
static PyObject* Cartesian_Tree__trickle_down(CartesianTree* self, PyObject *args) {
74+
PyObject* node_idx = PyObject_GetItem(args, PyZero);
75+
BinaryTree* bt = self->sbbt->bst->binary_tree;
76+
TreeNode* node = reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)]);
77+
while (node->left != Py_None || node->right != Py_None) {
78+
if (node->left == Py_None) {
79+
SelfBalancingBinaryTree__left_rotate(self->sbbt, Py_BuildValue("OO", node_idx, reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->right));
80+
}
81+
else if (node->right == Py_None) {
82+
SelfBalancingBinaryTree__right_rotate(self->sbbt, Py_BuildValue("OO", node_idx, reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->left));
83+
}
84+
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) {
85+
SelfBalancingBinaryTree__right_rotate(self->sbbt, Py_BuildValue("OO", node_idx, reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->left));
86+
}
87+
else {
88+
SelfBalancingBinaryTree__left_rotate(self->sbbt, Py_BuildValue("OO", node_idx, reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->right));
89+
}
90+
node = reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)]);
91+
}
92+
Py_RETURN_NONE;
93+
}
94+
95+
static PyObject* CartesianTree_insert(CartesianTree *self, PyObject* args) {
96+
Py_INCREF(Py_None);
97+
PyObject* key = Py_None;
98+
Py_INCREF(Py_None);
99+
PyObject* priority = Py_None;
100+
Py_INCREF(Py_None);
101+
PyObject* data = Py_None;
102+
if (!PyArg_ParseTuple(args, "OO|O", &key, &priority, &data)) { // data is optional
103+
return NULL;
104+
}
105+
BinaryTree* bt = self->sbbt->bst->binary_tree;
106+
107+
SelfBalancingBinaryTree_insert(self->sbbt, Py_BuildValue("(OO)", key, data));
108+
PyObject* node_idx = SelfBalancingBinaryTree_search(self->sbbt, Py_BuildValue("(O)", key), PyDict_New());
109+
TreeNode* node = reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)]);
110+
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.
111+
return NULL;
112+
}
113+
TreeNode* new_node = reinterpret_cast<TreeNode*>(TreeNode___new__(&TreeNodeType, Py_BuildValue("(OO)", key, data), PyDict_New()));
114+
new_node->isCartesianTreeNode = true;
115+
new_node->priority = PyLong_AsLong(priority);
116+
new_node->parent = node->parent;
117+
new_node->left = node->left;
118+
new_node->right = node->right;
119+
bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)] = reinterpret_cast<PyObject*>(new_node);
120+
if (node->is_root) {
121+
reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node_idx)])->is_root = true;
122+
}
123+
else {
124+
Cartesian_Tree__bubble_up(self, Py_BuildValue("(O)", node_idx));
125+
}
126+
Py_RETURN_NONE;
127+
}
128+
129+
static PyObject* CartesianTree_delete(CartesianTree* self, PyObject *args, PyObject *kwds) {
130+
Py_INCREF(Py_None);
131+
PyObject* key = Py_None;
132+
PyObject* balancing_info = PyZero;
133+
static char* keywords[] = {"key","balancing_info", NULL};
134+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O", keywords, &key, &balancing_info)) {
135+
return NULL;
136+
}
137+
PyObject* node_idx = SelfBalancingBinaryTree_search(self->sbbt, Py_BuildValue("(O)", key), PyDict_New());
138+
if (node_idx != Py_None) {
139+
Cartesian_Tree__trickle_down(self, Py_BuildValue("(O)", node_idx));
140+
PyObject* kwd_bal = PyDict_New();
141+
PyDict_SetItemString(kwd_bal, "balancing_info", balancing_info);
142+
return SelfBalancingBinaryTree_delete(self->sbbt, Py_BuildValue("(O)", key), kwd_bal);
143+
}
144+
Py_RETURN_NONE;
145+
}
146+
147+
static PyObject* CartesianTree_root_idx(CartesianTree *self, void *closure) {
148+
return self->sbbt->bst->binary_tree->root_idx;
149+
}
150+
151+
152+
static struct PyMethodDef CartesianTree_PyMethodDef[] = {
153+
{"insert", (PyCFunction) CartesianTree_insert, METH_VARARGS, NULL},
154+
{"delete", (PyCFunction) CartesianTree_delete, METH_VARARGS | METH_KEYWORDS, NULL},
155+
{"search", (PyCFunction) CartesianTree_search, METH_VARARGS | METH_KEYWORDS, NULL},
156+
{NULL} /* Sentinel */
157+
};
158+
159+
static PyGetSetDef CartesianTree_GetterSetters[] = {
160+
{"root_idx", (getter) CartesianTree_root_idx, NULL, "returns the index of the tree's root", NULL},
161+
{NULL} /* Sentinel */
162+
};
163+
164+
static PyMemberDef CartesianTree_PyMemberDef[] = {
165+
{"tree", T_OBJECT_EX, offsetof(CartesianTree, tree), 0, "tree"},
166+
{NULL} /* Sentinel */
167+
};
168+
169+
170+
static PyTypeObject CartesianTreeType = {
171+
/* tp_name */ PyVarObject_HEAD_INIT(NULL, 0) "CartesianTree",
172+
/* tp_basicsize */ sizeof(CartesianTree),
173+
/* tp_itemsize */ 0,
174+
/* tp_dealloc */ (destructor) CartesianTree_dealloc,
175+
/* tp_print */ 0,
176+
/* tp_getattr */ 0,
177+
/* tp_setattr */ 0,
178+
/* tp_reserved */ 0,
179+
/* tp_repr */ 0,
180+
/* tp_as_number */ 0,
181+
/* tp_as_sequence */ 0,
182+
/* tp_as_mapping */ 0,
183+
/* tp_hash */ 0,
184+
/* tp_call */ 0,
185+
/* tp_str */ (reprfunc) CartesianTree___str__,
186+
/* tp_getattro */ 0,
187+
/* tp_setattro */ 0,
188+
/* tp_as_buffer */ 0,
189+
/* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
190+
/* tp_doc */ 0,
191+
/* tp_traverse */ 0,
192+
/* tp_clear */ 0,
193+
/* tp_richcompare */ 0,
194+
/* tp_weaklistoffset */ 0,
195+
/* tp_iter */ 0,
196+
/* tp_iternext */ 0,
197+
/* tp_methods */ CartesianTree_PyMethodDef,
198+
/* tp_members */ CartesianTree_PyMemberDef,
199+
/* tp_getset */ CartesianTree_GetterSetters,
200+
/* tp_base */ &SelfBalancingBinaryTreeType,
201+
/* tp_dict */ 0,
202+
/* tp_descr_get */ 0,
203+
/* tp_descr_set */ 0,
204+
/* tp_dictoffset */ 0,
205+
/* tp_init */ 0,
206+
/* tp_alloc */ 0,
207+
/* tp_new */ CartesianTree___new__,
208+
};
209+
210+
#endif

pydatastructs/trees/_backend/cpp/trees.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "BinaryIndexedTree.hpp"
88
#include "SplayTree.hpp"
99
#include "AVLTree.hpp"
10+
#include "CartesianTree.hpp"
1011

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

72+
if (PyType_Ready(&CartesianTreeType) < 0) {
73+
return NULL;
74+
}
75+
Py_INCREF(&CartesianTreeType);
76+
PyModule_AddObject(trees, "CartesianTree", reinterpret_cast<PyObject*>(&CartesianTreeType));
77+
7178
return trees;
7279
}

pydatastructs/trees/binary_trees.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,9 +804,18 @@ class CartesianTree(SelfBalancingBinaryTree):
804804
805805
pydatastructs.trees.binary_trees.SelfBalancingBinaryTree
806806
"""
807+
def __new__(cls, key=None, root_data=None, comp=None,
808+
is_order_statistic=False, **kwargs):
809+
backend = kwargs.get('backend', Backend.PYTHON)
810+
if backend == Backend.CPP:
811+
if comp is None:
812+
comp = lambda key1, key2: key1 < key2
813+
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
814+
return super().__new__(cls, key, root_data, comp, is_order_statistic, **kwargs)
815+
807816
@classmethod
808817
def methods(cls):
809-
return ['__str__', 'insert', 'delete']
818+
return ['__new__', '__str__', 'insert', 'delete']
810819

811820
def _bubble_up(self, node_idx):
812821
node = self.tree[node_idx]

pydatastructs/trees/tests/test_binary_trees.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,8 @@ def test_BinaryIndexedTree():
410410
def test_cpp_BinaryIndexedTree():
411411
_test_BinaryIndexedTree(Backend.CPP)
412412

413-
def test_CartesianTree():
414-
tree = CartesianTree()
413+
def _test_CartesianTree(backend):
414+
tree = CartesianTree(backend=backend)
415415
tree.insert(3, 1, 3)
416416
tree.insert(1, 6, 1)
417417
tree.insert(0, 9, 0)
@@ -430,7 +430,7 @@ def test_CartesianTree():
430430
"(7, 7, 22, 7, 8), (None, 6, 42, 6, None), "
431431
"(None, 8, 49, 8, None), (None, 2, 99, 2, None)]")
432432

433-
trav = BinaryTreeTraversal(tree)
433+
trav = BinaryTreeTraversal(tree, backend=backend)
434434
in_order = trav.depth_first_search(order='in_order')
435435
pre_order = trav.depth_first_search(order='pre_order')
436436
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():
446446
k = tree.search(1.5)
447447
assert tree.tree[tree.tree[k].parent].key == 3
448448
tree.delete(1.5)
449+
assert tree.root_idx == 0
449450
tree.tree[tree.tree[tree.root_idx].left].key == 1
450451
tree.delete(8)
451452
assert tree.search(8) is None
@@ -455,6 +456,12 @@ def test_CartesianTree():
455456
assert tree.search(3) is None
456457
assert tree.delete(18) is None
457458

459+
def test_CartesianTree():
460+
_test_CartesianTree(backend=Backend.PYTHON)
461+
462+
def test_cpp_CartesianTree():
463+
_test_CartesianTree(backend=Backend.CPP)
464+
458465
def test_Treap():
459466

460467
random.seed(0)

pydatastructs/utils/_backend/cpp/TreeNode.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ typedef struct {
1818
PyObject* parent;
1919
long size;
2020
long color;
21+
bool isCartesianTreeNode;
22+
long priority;
2123
} TreeNode;
2224

2325
static void TreeNode_dealloc(TreeNode *self) {
@@ -42,6 +44,7 @@ static PyObject* TreeNode___new__(PyTypeObject* type, PyObject *args, PyObject *
4244
self->size = 1;
4345
self->is_root = false;
4446
self->color = 1;
47+
self->isCartesianTreeNode = false;
4548

4649
return reinterpret_cast<PyObject*>(self);
4750
}

0 commit comments

Comments
 (0)