Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C++ backend for AVL Trees #564

Merged
merged 32 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3cef009
C++ backend for AVL trees, till insert()
Kishan-Ved Jun 29, 2024
a8ea072
added tests
Kishan-Ved Jun 29, 2024
103fa8e
Updates, removed errors, tests passing
Kishan-Ved Jun 30, 2024
8a05f02
code quality
Kishan-Ved Jun 30, 2024
51e21de
Fixed ArrayForTrees test
Kishan-Ved Jun 30, 2024
26992d0
Fixed ArrayForTrees test
Kishan-Ved Jun 30, 2024
9bbfd1c
removed unnecessary code
Kishan-Ved Jun 30, 2024
538c1d2
select(), rank(), and set_tree()
Kishan-Ved Jul 1, 2024
12959c9
SEGFAULT Fixed
Kishan-Ved Jul 1, 2024
8e442e0
code quality
Kishan-Ved Jul 1, 2024
59358a5
delete() for AVL Trees
Kishan-Ved Jul 2, 2024
5a17b97
CI check
Kishan-Ved Jul 2, 2024
521cf55
CI check
Kishan-Ved Jul 2, 2024
83bd81e
check
Kishan-Ved Jul 2, 2024
4e73189
check
Kishan-Ved Jul 2, 2024
9167da5
check
Kishan-Ved Jul 2, 2024
6fa0287
check
Kishan-Ved Jul 2, 2024
e28a5ce
check
Kishan-Ved Jul 2, 2024
faf92ca
check
Kishan-Ved Jul 2, 2024
990948f
check
Kishan-Ved Jul 2, 2024
aa8387c
check
Kishan-Ved Jul 2, 2024
cd7320a
check
Kishan-Ved Jul 2, 2024
0628176
check
Kishan-Ved Jul 2, 2024
55cc2b8
check
Kishan-Ved Jul 2, 2024
a41046b
test_select_rank() in Python backend
Kishan-Ved Jul 5, 2024
d9ce6a8
revert test_select_rank to original
Kishan-Ved Jul 5, 2024
45853fc
only benchmarks for first CI check
Kishan-Ved Jul 5, 2024
2bad5e6
test rank()
Kishan-Ved Jul 7, 2024
7c57c34
test select()
Kishan-Ved Jul 7, 2024
2a4a3cb
test select() only for PYTHON
Kishan-Ved Jul 7, 2024
92b75e9
test select() only for PYTHON
Kishan-Ved Jul 7, 2024
ba5e875
update readme for non benchmark tests
Kishan-Ved Jul 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading