Skip to content

Commit

Permalink
C++ backend for AVL Trees (#564)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kishan-Ved authored Jul 8, 2024
1 parent 4c6c935 commit d1bc67c
Show file tree
Hide file tree
Showing 11 changed files with 488 additions and 54 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ jobs:
- name: Run tests
run: |
python -c "import pydatastructs; pydatastructs.test(include_benchmarks=True)"
python -c "import pydatastructs; pydatastructs.test(only_benchmarks=True)"
- name: Build Documentation
run: |
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ You can use the examples given in the following book as tests for your code:

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

### Light weighted testing (without benchmarks)

Make sure you have activated the conda environment: `pyds-env` and your working directory is `../pydatastructs`.

In the terminal, run: `python -c "from pydatastructs.utils.testing_util import test; test()"`.

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.

Why do we use Python?
------------------

Expand Down
21 changes: 14 additions & 7 deletions pydatastructs/linear_data_structures/tests/test_arrays.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pydatastructs.utils.misc_util import Backend
from pydatastructs.utils.raises_util import raises
from pydatastructs.utils import TreeNode
from pydatastructs.utils._backend.cpp import _nodes

def test_OneDimensionalArray():
ODA = OneDimensionalArray
Expand Down Expand Up @@ -135,13 +136,19 @@ def test_DynamicOneDimensionalArray2():
assert str(A[0]) == "(None, 1, 100, None)"

def _test_ArrayForTrees(backend):
AFT = ArrayForTrees
root = TreeNode(1, 100)
A = AFT(TreeNode, [root], backend=backend)
assert str(A) == "['(None, 1, 100, None)']"
node = TreeNode(2, 200, backend=backend)
A.append(node)
assert str(A) == "['(None, 1, 100, None)', '(None, 2, 200, None)']"
AFT = ArrayForTrees
root = TreeNode(1, 100,backend=backend)
if backend==Backend.PYTHON:
A = AFT(TreeNode, [root], backend=backend)
B = AFT(TreeNode, 0, backend=backend)
else:
A = AFT(_nodes.TreeNode, [root], backend=backend)
B = AFT(_nodes.TreeNode, 0, backend=backend)
assert str(A) == "['(None, 1, 100, None)']"
node = TreeNode(2, 200, backend=backend)
A.append(node)
assert str(A) == "['(None, 1, 100, None)', '(None, 2, 200, None)']"
assert str(B) == "[]"

def test_ArrayForTrees():
_test_ArrayForTrees(Backend.PYTHON)
Expand Down
370 changes: 370 additions & 0 deletions pydatastructs/trees/_backend/cpp/AVLTree.hpp

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion pydatastructs/trees/_backend/cpp/BinarySearchTree.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ static PyObject* BinarySearchTree_search(BinarySearchTree* self, PyObject* args,
return NULL;
}
BinaryTree* bt = self->binary_tree;
Py_INCREF(Py_None);
PyObject* parent = Py_None;
PyObject* walk = PyLong_FromLong(PyLong_AsLong(bt->root_idx));
PyObject* walk = bt->root_idx;

if (reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])->key == Py_None) {
Py_RETURN_NONE;
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 @@ -16,6 +16,7 @@
#include "SelfBalancingBinaryTree.hpp"
#include "RedBlackTree.hpp"
#include "SplayTree.hpp"
#include "AVLTree.hpp"

typedef struct {
PyObject_HEAD
Expand Down Expand Up @@ -44,10 +45,16 @@ static PyObject* BinaryTreeTraversal___new__(PyTypeObject* type, PyObject *args,
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.
return NULL;
}
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 (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 *)&RedBlackTreeType)) {
self->tree = reinterpret_cast<RedBlackTree*>(tree)->sbbt->bst->binary_tree;
}
Expand Down
9 changes: 0 additions & 9 deletions pydatastructs/trees/_backend/cpp/SplayTree.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,16 +267,7 @@ static PyObject* SplayTree_split(SplayTree *self, PyObject* args) {
}
SplayTree* other = reinterpret_cast<SplayTree*>(SplayTree___new__(self->type, Py_BuildValue("(OOOO)", Py_None, Py_None, bt->comparator, PyZero), PyDict_New()));

// SplayTree* other = reinterpret_cast<SplayTree*>(PyObject_GetItem(args, PyOne));
if (reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(bt->root_idx)])->right != Py_None) {
// 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.
// return NULL;
// }
// BinaryTreeTraversal* traverse = reinterpret_cast<BinaryTreeTraversal*>(BinaryTreeTraversal___new__(&BinaryTreeTraversalType, Py_BuildValue("(O)", self), PyDict_New()));
// PyObject* kwd_dict = PyDict_New();
// PyDict_SetItemString(kwd_dict, "node", reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(bt->root_idx)])->right);
// PyDict_SetItemString(kwd_dict, "order", PyUnicode_FromString("pre_order"));
// PyObject* elements = BinaryTreeTraversal_depth_first_search(traverse, Py_BuildValue("()"), kwd_dict);
PyObject* elements = SplayTree__pre_order(self, Py_BuildValue("(O)", reinterpret_cast<TreeNode*>(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(bt->root_idx)])->right));
for (int i=0; i<PyList_Size(elements); i++) {
SelfBalancingBinaryTree_insert(other->sbbt, Py_BuildValue("(OO)", reinterpret_cast<TreeNode*>( PyList_GetItem(elements, i))->key, reinterpret_cast<TreeNode*>( PyList_GetItem(elements, i))->data));
Expand Down
7 changes: 7 additions & 0 deletions pydatastructs/trees/_backend/cpp/trees.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "RedBlackTree.hpp"
#include "BinaryIndexedTree.hpp"
#include "SplayTree.hpp"
#include "AVLTree.hpp"

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

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

return trees;
}
14 changes: 13 additions & 1 deletion pydatastructs/trees/binary_trees.py
Original file line number Diff line number Diff line change
Expand Up @@ -921,9 +921,18 @@ class AVLTree(SelfBalancingBinaryTree):
pydatastructs.trees.binary_trees.BinaryTree
"""

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.AVLTree(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 ['insert', 'delete']
return ['__new__', 'set_tree', 'insert', 'delete']

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

def set_tree(self, arr):
self.tree = arr

def _right_rotate(self, j, k):
super(AVLTree, self)._right_rotate(j, k)
self.tree[j].height = max(self.left_height(self.tree[j]),
Expand Down
89 changes: 59 additions & 30 deletions pydatastructs/trees/tests/test_binary_trees.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from copy import deepcopy
from pydatastructs.utils.misc_util import Backend
import random
from pydatastructs.utils._backend.cpp import _nodes

def _test_BinarySearchTree(backend):
BST = BinarySearchTree
Expand Down Expand Up @@ -159,8 +160,8 @@ def test_BinaryTreeTraversal():
def test_cpp_BinaryTreeTraversal():
_test_BinaryTreeTraversal(Backend.CPP)

def test_AVLTree():
a = AVLTree('M', 'M')
def _test_AVLTree(backend):
a = AVLTree('M', 'M', backend=backend)
a.insert('N', 'N')
a.insert('O', 'O')
a.insert('L', 'L')
Expand All @@ -171,70 +172,71 @@ def test_AVLTree():
a.insert('I', 'I')
a.insert('A', 'A')

trav = BinaryTreeTraversal(a)
trav = BinaryTreeTraversal(a, 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] == ['A', 'H', 'I', 'K', 'L', 'M', 'N', 'O', 'P', 'Q']
assert [node.key for node in pre_order] == ['N', 'I', 'H', 'A', 'L', 'K', 'M', 'P', 'O', 'Q']

assert [a.balance_factor(n) for n in a.tree if n is not None] == \
assert [a.balance_factor(a.tree[i]) for i in range(a.tree.size) if a.tree[i] is not None] == \
[0, -1, 0, 0, 0, 0, 0, -1, 0, 0]
a1 = AVLTree(1, 1)
a1 = AVLTree(1, 1, backend=backend)
a1.insert(2, 2)
a1.insert(3, 3)
a1.insert(4, 4)
a1.insert(5, 5)

trav = BinaryTreeTraversal(a1)
trav = BinaryTreeTraversal(a1, 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] == [1, 2, 3, 4, 5]
assert [node.key for node in pre_order] == [2, 1, 4, 3, 5]

a3 = AVLTree(-1, 1)
a3 = AVLTree(-1, 1, backend=backend)
a3.insert(-2, 2)
a3.insert(-3, 3)
a3.insert(-4, 4)
a3.insert(-5, 5)

trav = BinaryTreeTraversal(a3)
trav = BinaryTreeTraversal(a3, 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] == [-5, -4, -3, -2, -1]
assert [node.key for node in pre_order] == [-2, -4, -5, -3, -1]

a2 = AVLTree()
a2 = AVLTree(backend=backend)
a2.insert(1, 1)
a2.insert(1, 1)

trav = BinaryTreeTraversal(a2)
trav = BinaryTreeTraversal(a2, 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] == [1]
assert [node.key for node in pre_order] == [1]

a3 = AVLTree()
a3.tree = ArrayForTrees(TreeNode, 0)
for i in range(7):
a3.tree.append(TreeNode(i, i))
a3 = AVLTree(backend=backend)
a3.set_tree( ArrayForTrees(TreeNode, 0, backend=backend) )
for i in range(0,7):
a3.tree.append(TreeNode(i, i, backend=backend))
a3.tree[0].left = 1
a3.tree[0].right = 6
a3.tree[1].left = 5
a3.tree[1].right = 2
a3.tree[2].left = 3
a3.tree[2].right = 4
a3._left_right_rotate(0, 1)
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)]"

trav = BinaryTreeTraversal(a3)
trav = BinaryTreeTraversal(a3, 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] == [5, 1, 3, 2, 4, 0, 6]
assert [node.key for node in pre_order] == [2, 1, 5, 3, 0, 4, 6]

a4 = AVLTree()
a4.tree = ArrayForTrees(TreeNode, 0)
for i in range(7):
a4.tree.append(TreeNode(i, i))
a4 = AVLTree(backend=backend)
a4.set_tree( ArrayForTrees(TreeNode, 0, backend=backend) )
for i in range(0,7):
a4.tree.append(TreeNode(i, i,backend=backend))
a4.tree[0].left = 1
a4.tree[0].right = 2
a4.tree[2].left = 3
Expand All @@ -243,14 +245,15 @@ def test_AVLTree():
a4.tree[3].right = 6
a4._right_left_rotate(0, 2)

trav = BinaryTreeTraversal(a4)
trav = BinaryTreeTraversal(a4, 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] == [1, 0, 5, 3, 6, 2, 4]
assert [node.key for node in pre_order] == [3,0,1,5,2,6,4]

a5 = AVLTree(is_order_statistic=True)
a5.tree = ArrayForTrees(TreeNode, [
a5 = AVLTree(is_order_statistic=True,backend=backend)
if backend==Backend.PYTHON:
a5.set_tree( ArrayForTrees(TreeNode, [
TreeNode(10, 10),
TreeNode(5, 5),
TreeNode(17, 17),
Expand All @@ -265,7 +268,24 @@ def test_AVLTree():
TreeNode(30, 30),
TreeNode(13, 13),
TreeNode(33, 33)
])
]) )
else:
a5.set_tree( ArrayForTrees(_nodes.TreeNode, [
TreeNode(10, 10,backend=backend),
TreeNode(5, 5,backend=backend),
TreeNode(17, 17,backend=backend),
TreeNode(2, 2,backend=backend),
TreeNode(9, 9,backend=backend),
TreeNode(12, 12,backend=backend),
TreeNode(20, 20,backend=backend),
TreeNode(3, 3,backend=backend),
TreeNode(11, 11,backend=backend),
TreeNode(15, 15,backend=backend),
TreeNode(18, 18,backend=backend),
TreeNode(30, 30,backend=backend),
TreeNode(13, 13,backend=backend),
TreeNode(33, 33,backend=backend)
],backend=backend) )

a5.tree[0].left, a5.tree[0].right, a5.tree[0].parent, a5.tree[0].height = \
1, 2, None, 4
Expand Down Expand Up @@ -311,16 +331,18 @@ def test_AVLTree():
a5.tree[11].size = 2
a5.tree[12].size = 1
a5.tree[13].size = 1
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)]"

assert raises(ValueError, lambda: a5.select(0))
assert raises(ValueError, lambda: a5.select(15))

assert a5.rank(-1) is None
def test_select_rank(expected_output):
output = []
for i in range(len(expected_output)):
output.append(a5.select(i + 1).key)
assert output == expected_output

if backend==Backend.PYTHON:
output = []
for i in range(len(expected_output)):
output.append(a5.select(i + 1).key)
assert output == expected_output
output = []
expected_ranks = [i + 1 for i in range(len(expected_output))]
for i in range(len(expected_output)):
Expand All @@ -331,8 +353,9 @@ def test_select_rank(expected_output):
a5.delete(9)
a5.delete(13)
a5.delete(20)
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)]"

trav = BinaryTreeTraversal(a5)
trav = BinaryTreeTraversal(a5, 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] == [2, 3, 5, 10, 11, 12, 15, 17, 18, 30, 33]
Expand All @@ -341,6 +364,7 @@ def test_select_rank(expected_output):
test_select_rank([2, 3, 5, 10, 11, 12, 15, 17, 18, 30, 33])
a5.delete(10)
a5.delete(17)
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)]"
test_select_rank([2, 3, 5, 11, 12, 15, 18, 30, 33])
a5.delete(11)
a5.delete(30)
Expand All @@ -359,8 +383,13 @@ def test_select_rank(expected_output):
test_select_rank([2])
a5.delete(2)
test_select_rank([])
assert str(a5) == "[(None, None, None, None)]"


def test_AVLTree():
_test_AVLTree(backend=Backend.PYTHON)
def test_cpp_AVLTree():
_test_AVLTree(backend=Backend.CPP)
test_cpp_AVLTree()
def _test_BinaryIndexedTree(backend):

FT = BinaryIndexedTree
Expand Down
Loading

0 comments on commit d1bc67c

Please sign in to comment.