Skip to content

Commit d1bc67c

Browse files
authored
C++ backend for AVL Trees (#564)
1 parent 4c6c935 commit d1bc67c

File tree

11 files changed

+488
-54
lines changed

11 files changed

+488
-54
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ jobs:
9393
9494
- name: Run tests
9595
run: |
96-
python -c "import pydatastructs; pydatastructs.test(include_benchmarks=True)"
96+
python -c "import pydatastructs; pydatastructs.test(only_benchmarks=True)"
9797
9898
- name: Build Documentation
9999
run: |

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ You can use the examples given in the following book as tests for your code:
6060

6161
- [https://opendatastructures.org/ods-python.pdf](https://opendatastructures.org/ods-python.pdf)
6262

63+
### Light weighted testing (without benchmarks)
64+
65+
Make sure you have activated the conda environment: `pyds-env` and your working directory is `../pydatastructs`.
66+
67+
In the terminal, run: `python -c "from pydatastructs.utils.testing_util import test; test()"`.
68+
69+
This will run all the test files, except benchmark tests. This should be used if benchmark tests are computationally too heavy to be run on your local machine.
70+
6371
Why do we use Python?
6472
------------------
6573

pydatastructs/linear_data_structures/tests/test_arrays.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pydatastructs.utils.misc_util import Backend
55
from pydatastructs.utils.raises_util import raises
66
from pydatastructs.utils import TreeNode
7+
from pydatastructs.utils._backend.cpp import _nodes
78

89
def test_OneDimensionalArray():
910
ODA = OneDimensionalArray
@@ -135,13 +136,19 @@ def test_DynamicOneDimensionalArray2():
135136
assert str(A[0]) == "(None, 1, 100, None)"
136137

137138
def _test_ArrayForTrees(backend):
138-
AFT = ArrayForTrees
139-
root = TreeNode(1, 100)
140-
A = AFT(TreeNode, [root], backend=backend)
141-
assert str(A) == "['(None, 1, 100, None)']"
142-
node = TreeNode(2, 200, backend=backend)
143-
A.append(node)
144-
assert str(A) == "['(None, 1, 100, None)', '(None, 2, 200, None)']"
139+
AFT = ArrayForTrees
140+
root = TreeNode(1, 100,backend=backend)
141+
if backend==Backend.PYTHON:
142+
A = AFT(TreeNode, [root], backend=backend)
143+
B = AFT(TreeNode, 0, backend=backend)
144+
else:
145+
A = AFT(_nodes.TreeNode, [root], backend=backend)
146+
B = AFT(_nodes.TreeNode, 0, backend=backend)
147+
assert str(A) == "['(None, 1, 100, None)']"
148+
node = TreeNode(2, 200, backend=backend)
149+
A.append(node)
150+
assert str(A) == "['(None, 1, 100, None)', '(None, 2, 200, None)']"
151+
assert str(B) == "[]"
145152

146153
def test_ArrayForTrees():
147154
_test_ArrayForTrees(Backend.PYTHON)

pydatastructs/trees/_backend/cpp/AVLTree.hpp

Lines changed: 370 additions & 0 deletions
Large diffs are not rendered by default.

pydatastructs/trees/_backend/cpp/BinarySearchTree.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,9 @@ static PyObject* BinarySearchTree_search(BinarySearchTree* self, PyObject* args,
8484
return NULL;
8585
}
8686
BinaryTree* bt = self->binary_tree;
87+
Py_INCREF(Py_None);
8788
PyObject* parent = Py_None;
88-
PyObject* walk = PyLong_FromLong(PyLong_AsLong(bt->root_idx));
89+
PyObject* walk = bt->root_idx;
8990

9091
if (reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])->key == Py_None) {
9192
Py_RETURN_NONE;

pydatastructs/trees/_backend/cpp/BinaryTreeTraversal.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "SelfBalancingBinaryTree.hpp"
1717
#include "RedBlackTree.hpp"
1818
#include "SplayTree.hpp"
19+
#include "AVLTree.hpp"
1920

2021
typedef struct {
2122
PyObject_HEAD
@@ -44,10 +45,16 @@ static PyObject* BinaryTreeTraversal___new__(PyTypeObject* type, PyObject *args,
4445
if (PyType_Ready(&SplayTreeType) < 0) { // This has to be present to finalize a type object. This should be called on all type objects to finish their initialization.
4546
return NULL;
4647
}
48+
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.
49+
return NULL;
50+
}
4751

4852
if (PyObject_IsInstance(tree, (PyObject *)&SplayTreeType)) {
4953
self->tree = reinterpret_cast<SplayTree*>(tree)->sbbt->bst->binary_tree;
5054
}
55+
else if (PyObject_IsInstance(tree, (PyObject *)&AVLTreeType)) {
56+
self->tree = reinterpret_cast<AVLTree*>(tree)->sbbt->bst->binary_tree;
57+
}
5158
else if (PyObject_IsInstance(tree, (PyObject *)&RedBlackTreeType)) {
5259
self->tree = reinterpret_cast<RedBlackTree*>(tree)->sbbt->bst->binary_tree;
5360
}

pydatastructs/trees/_backend/cpp/SplayTree.hpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -267,16 +267,7 @@ static PyObject* SplayTree_split(SplayTree *self, PyObject* args) {
267267
}
268268
SplayTree* other = reinterpret_cast<SplayTree*>(SplayTree___new__(self->type, Py_BuildValue("(OOOO)", Py_None, Py_None, bt->comparator, PyZero), PyDict_New()));
269269

270-
// SplayTree* other = reinterpret_cast<SplayTree*>(PyObject_GetItem(args, PyOne));
271270
if (reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(bt->root_idx)])->right != Py_None) {
272-
// if (PyType_Ready(&BinaryTreeTraversalType) < 0) { // This has to be present to finalize a type object. This should be called on all type objects to finish their initialization.
273-
// return NULL;
274-
// }
275-
// BinaryTreeTraversal* traverse = reinterpret_cast<BinaryTreeTraversal*>(BinaryTreeTraversal___new__(&BinaryTreeTraversalType, Py_BuildValue("(O)", self), PyDict_New()));
276-
// PyObject* kwd_dict = PyDict_New();
277-
// PyDict_SetItemString(kwd_dict, "node", reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(bt->root_idx)])->right);
278-
// PyDict_SetItemString(kwd_dict, "order", PyUnicode_FromString("pre_order"));
279-
// PyObject* elements = BinaryTreeTraversal_depth_first_search(traverse, Py_BuildValue("()"), kwd_dict);
280271
PyObject* elements = SplayTree__pre_order(self, Py_BuildValue("(O)", reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(bt->root_idx)])->right));
281272
for (int i=0; i<PyList_Size(elements); i++) {
282273
SelfBalancingBinaryTree_insert(other->sbbt, Py_BuildValue("(OO)", reinterpret_cast<TreeNode*>( PyList_GetItem(elements, i))->key, reinterpret_cast<TreeNode*>( PyList_GetItem(elements, i))->data));

pydatastructs/trees/_backend/cpp/trees.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "RedBlackTree.hpp"
77
#include "BinaryIndexedTree.hpp"
88
#include "SplayTree.hpp"
9+
#include "AVLTree.hpp"
910

1011
static struct PyModuleDef trees_struct = {
1112
PyModuleDef_HEAD_INIT,
@@ -61,5 +62,11 @@ PyMODINIT_FUNC PyInit__trees(void) {
6162
Py_INCREF(&SplayTreeType);
6263
PyModule_AddObject(trees, "SplayTree", reinterpret_cast<PyObject*>(&SplayTreeType));
6364

65+
if (PyType_Ready(&AVLTreeType) < 0) {
66+
return NULL;
67+
}
68+
Py_INCREF(&AVLTreeType);
69+
PyModule_AddObject(trees, "AVLTree", reinterpret_cast<PyObject*>(&AVLTreeType));
70+
6471
return trees;
6572
}

pydatastructs/trees/binary_trees.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -921,9 +921,18 @@ class AVLTree(SelfBalancingBinaryTree):
921921
pydatastructs.trees.binary_trees.BinaryTree
922922
"""
923923

924+
def __new__(cls, key=None, root_data=None, comp=None,
925+
is_order_statistic=False, **kwargs):
926+
backend = kwargs.get('backend', Backend.PYTHON)
927+
if backend == Backend.CPP:
928+
if comp is None:
929+
comp = lambda key1, key2: key1 < key2
930+
return _trees.AVLTree(key, root_data, comp, is_order_statistic, **kwargs) # If any argument is not given, then it is passed as None, except for comp
931+
return super().__new__(cls, key, root_data, comp, is_order_statistic, **kwargs)
932+
924933
@classmethod
925934
def methods(cls):
926-
return ['insert', 'delete']
935+
return ['__new__', 'set_tree', 'insert', 'delete']
927936

928937
left_height = lambda self, node: self.tree[node.left].height \
929938
if node.left is not None else -1
@@ -932,6 +941,9 @@ def methods(cls):
932941
balance_factor = lambda self, node: self.right_height(node) - \
933942
self.left_height(node)
934943

944+
def set_tree(self, arr):
945+
self.tree = arr
946+
935947
def _right_rotate(self, j, k):
936948
super(AVLTree, self)._right_rotate(j, k)
937949
self.tree[j].height = max(self.left_height(self.tree[j]),

pydatastructs/trees/tests/test_binary_trees.py

Lines changed: 59 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from copy import deepcopy
77
from pydatastructs.utils.misc_util import Backend
88
import random
9+
from pydatastructs.utils._backend.cpp import _nodes
910

1011
def _test_BinarySearchTree(backend):
1112
BST = BinarySearchTree
@@ -159,8 +160,8 @@ def test_BinaryTreeTraversal():
159160
def test_cpp_BinaryTreeTraversal():
160161
_test_BinaryTreeTraversal(Backend.CPP)
161162

162-
def test_AVLTree():
163-
a = AVLTree('M', 'M')
163+
def _test_AVLTree(backend):
164+
a = AVLTree('M', 'M', backend=backend)
164165
a.insert('N', 'N')
165166
a.insert('O', 'O')
166167
a.insert('L', 'L')
@@ -171,70 +172,71 @@ def test_AVLTree():
171172
a.insert('I', 'I')
172173
a.insert('A', 'A')
173174

174-
trav = BinaryTreeTraversal(a)
175+
trav = BinaryTreeTraversal(a, backend=backend)
175176
in_order = trav.depth_first_search(order='in_order')
176177
pre_order = trav.depth_first_search(order='pre_order')
177178
assert [node.key for node in in_order] == ['A', 'H', 'I', 'K', 'L', 'M', 'N', 'O', 'P', 'Q']
178179
assert [node.key for node in pre_order] == ['N', 'I', 'H', 'A', 'L', 'K', 'M', 'P', 'O', 'Q']
179180

180-
assert [a.balance_factor(n) for n in a.tree if n is not None] == \
181+
assert [a.balance_factor(a.tree[i]) for i in range(a.tree.size) if a.tree[i] is not None] == \
181182
[0, -1, 0, 0, 0, 0, 0, -1, 0, 0]
182-
a1 = AVLTree(1, 1)
183+
a1 = AVLTree(1, 1, backend=backend)
183184
a1.insert(2, 2)
184185
a1.insert(3, 3)
185186
a1.insert(4, 4)
186187
a1.insert(5, 5)
187188

188-
trav = BinaryTreeTraversal(a1)
189+
trav = BinaryTreeTraversal(a1, backend=backend)
189190
in_order = trav.depth_first_search(order='in_order')
190191
pre_order = trav.depth_first_search(order='pre_order')
191192
assert [node.key for node in in_order] == [1, 2, 3, 4, 5]
192193
assert [node.key for node in pre_order] == [2, 1, 4, 3, 5]
193194

194-
a3 = AVLTree(-1, 1)
195+
a3 = AVLTree(-1, 1, backend=backend)
195196
a3.insert(-2, 2)
196197
a3.insert(-3, 3)
197198
a3.insert(-4, 4)
198199
a3.insert(-5, 5)
199200

200-
trav = BinaryTreeTraversal(a3)
201+
trav = BinaryTreeTraversal(a3, backend=backend)
201202
in_order = trav.depth_first_search(order='in_order')
202203
pre_order = trav.depth_first_search(order='pre_order')
203204
assert [node.key for node in in_order] == [-5, -4, -3, -2, -1]
204205
assert [node.key for node in pre_order] == [-2, -4, -5, -3, -1]
205206

206-
a2 = AVLTree()
207+
a2 = AVLTree(backend=backend)
207208
a2.insert(1, 1)
208209
a2.insert(1, 1)
209210

210-
trav = BinaryTreeTraversal(a2)
211+
trav = BinaryTreeTraversal(a2, backend=backend)
211212
in_order = trav.depth_first_search(order='in_order')
212213
pre_order = trav.depth_first_search(order='pre_order')
213214
assert [node.key for node in in_order] == [1]
214215
assert [node.key for node in pre_order] == [1]
215216

216-
a3 = AVLTree()
217-
a3.tree = ArrayForTrees(TreeNode, 0)
218-
for i in range(7):
219-
a3.tree.append(TreeNode(i, i))
217+
a3 = AVLTree(backend=backend)
218+
a3.set_tree( ArrayForTrees(TreeNode, 0, backend=backend) )
219+
for i in range(0,7):
220+
a3.tree.append(TreeNode(i, i, backend=backend))
220221
a3.tree[0].left = 1
221222
a3.tree[0].right = 6
222223
a3.tree[1].left = 5
223224
a3.tree[1].right = 2
224225
a3.tree[2].left = 3
225226
a3.tree[2].right = 4
226227
a3._left_right_rotate(0, 1)
228+
assert str(a3) == "[(4, 0, 0, 6), (5, 1, 1, 3), (1, 2, 2, 0), (None, 3, 3, None), (None, 4, 4, None), (None, 5, 5, None), (None, 6, 6, None)]"
227229

228-
trav = BinaryTreeTraversal(a3)
230+
trav = BinaryTreeTraversal(a3, backend=backend)
229231
in_order = trav.depth_first_search(order='in_order')
230232
pre_order = trav.depth_first_search(order='pre_order')
231233
assert [node.key for node in in_order] == [5, 1, 3, 2, 4, 0, 6]
232234
assert [node.key for node in pre_order] == [2, 1, 5, 3, 0, 4, 6]
233235

234-
a4 = AVLTree()
235-
a4.tree = ArrayForTrees(TreeNode, 0)
236-
for i in range(7):
237-
a4.tree.append(TreeNode(i, i))
236+
a4 = AVLTree(backend=backend)
237+
a4.set_tree( ArrayForTrees(TreeNode, 0, backend=backend) )
238+
for i in range(0,7):
239+
a4.tree.append(TreeNode(i, i,backend=backend))
238240
a4.tree[0].left = 1
239241
a4.tree[0].right = 2
240242
a4.tree[2].left = 3
@@ -243,14 +245,15 @@ def test_AVLTree():
243245
a4.tree[3].right = 6
244246
a4._right_left_rotate(0, 2)
245247

246-
trav = BinaryTreeTraversal(a4)
248+
trav = BinaryTreeTraversal(a4, backend=backend)
247249
in_order = trav.depth_first_search(order='in_order')
248250
pre_order = trav.depth_first_search(order='pre_order')
249251
assert [node.key for node in in_order] == [1, 0, 5, 3, 6, 2, 4]
250252
assert [node.key for node in pre_order] == [3,0,1,5,2,6,4]
251253

252-
a5 = AVLTree(is_order_statistic=True)
253-
a5.tree = ArrayForTrees(TreeNode, [
254+
a5 = AVLTree(is_order_statistic=True,backend=backend)
255+
if backend==Backend.PYTHON:
256+
a5.set_tree( ArrayForTrees(TreeNode, [
254257
TreeNode(10, 10),
255258
TreeNode(5, 5),
256259
TreeNode(17, 17),
@@ -265,7 +268,24 @@ def test_AVLTree():
265268
TreeNode(30, 30),
266269
TreeNode(13, 13),
267270
TreeNode(33, 33)
268-
])
271+
]) )
272+
else:
273+
a5.set_tree( ArrayForTrees(_nodes.TreeNode, [
274+
TreeNode(10, 10,backend=backend),
275+
TreeNode(5, 5,backend=backend),
276+
TreeNode(17, 17,backend=backend),
277+
TreeNode(2, 2,backend=backend),
278+
TreeNode(9, 9,backend=backend),
279+
TreeNode(12, 12,backend=backend),
280+
TreeNode(20, 20,backend=backend),
281+
TreeNode(3, 3,backend=backend),
282+
TreeNode(11, 11,backend=backend),
283+
TreeNode(15, 15,backend=backend),
284+
TreeNode(18, 18,backend=backend),
285+
TreeNode(30, 30,backend=backend),
286+
TreeNode(13, 13,backend=backend),
287+
TreeNode(33, 33,backend=backend)
288+
],backend=backend) )
269289

270290
a5.tree[0].left, a5.tree[0].right, a5.tree[0].parent, a5.tree[0].height = \
271291
1, 2, None, 4
@@ -311,16 +331,18 @@ def test_AVLTree():
311331
a5.tree[11].size = 2
312332
a5.tree[12].size = 1
313333
a5.tree[13].size = 1
334+
assert str(a5) == "[(1, 10, 10, 2), (3, 5, 5, 4), (5, 17, 17, 6), (None, 2, 2, 7), (None, 9, 9, None), (8, 12, 12, 9), (10, 20, 20, 11), (None, 3, 3, None), (None, 11, 11, None), (12, 15, 15, None), (None, 18, 18, None), (None, 30, 30, 13), (None, 13, 13, None), (None, 33, 33, None)]"
314335

315336
assert raises(ValueError, lambda: a5.select(0))
316337
assert raises(ValueError, lambda: a5.select(15))
338+
317339
assert a5.rank(-1) is None
318340
def test_select_rank(expected_output):
319-
output = []
320-
for i in range(len(expected_output)):
321-
output.append(a5.select(i + 1).key)
322-
assert output == expected_output
323-
341+
if backend==Backend.PYTHON:
342+
output = []
343+
for i in range(len(expected_output)):
344+
output.append(a5.select(i + 1).key)
345+
assert output == expected_output
324346
output = []
325347
expected_ranks = [i + 1 for i in range(len(expected_output))]
326348
for i in range(len(expected_output)):
@@ -331,8 +353,9 @@ def test_select_rank(expected_output):
331353
a5.delete(9)
332354
a5.delete(13)
333355
a5.delete(20)
356+
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)]"
334357

335-
trav = BinaryTreeTraversal(a5)
358+
trav = BinaryTreeTraversal(a5, backend=backend)
336359
in_order = trav.depth_first_search(order='in_order')
337360
pre_order = trav.depth_first_search(order='pre_order')
338361
assert [node.key for node in in_order] == [2, 3, 5, 10, 11, 12, 15, 17, 18, 30, 33]
@@ -341,6 +364,7 @@ def test_select_rank(expected_output):
341364
test_select_rank([2, 3, 5, 10, 11, 12, 15, 17, 18, 30, 33])
342365
a5.delete(10)
343366
a5.delete(17)
367+
assert str(a5) == "[(7, 11, 11, 5), (None, 5, 5, None), (0, 18, 18, 6), (None, 2, 2, None), '', (None, 12, 12, 9), (None, 30, 30, 13), (3, 3, 3, 1), '', (None, 15, 15, None), '', '', '', (None, 33, 33, None)]"
344368
test_select_rank([2, 3, 5, 11, 12, 15, 18, 30, 33])
345369
a5.delete(11)
346370
a5.delete(30)
@@ -359,8 +383,13 @@ def test_select_rank(expected_output):
359383
test_select_rank([2])
360384
a5.delete(2)
361385
test_select_rank([])
386+
assert str(a5) == "[(None, None, None, None)]"
362387

363-
388+
def test_AVLTree():
389+
_test_AVLTree(backend=Backend.PYTHON)
390+
def test_cpp_AVLTree():
391+
_test_AVLTree(backend=Backend.CPP)
392+
test_cpp_AVLTree()
364393
def _test_BinaryIndexedTree(backend):
365394

366395
FT = BinaryIndexedTree

0 commit comments

Comments
 (0)