From d1bc67c60376378a3fe894a9eb100020d6f4519a Mon Sep 17 00:00:00 2001 From: Kishan Ved Date: Tue, 9 Jul 2024 00:36:46 +0530 Subject: [PATCH] C++ backend for AVL Trees (#564) --- .github/workflows/ci.yml | 2 +- README.md | 8 + .../tests/test_arrays.py | 21 +- pydatastructs/trees/_backend/cpp/AVLTree.hpp | 370 ++++++++++++++++++ .../trees/_backend/cpp/BinarySearchTree.hpp | 3 +- .../_backend/cpp/BinaryTreeTraversal.hpp | 7 + .../trees/_backend/cpp/SplayTree.hpp | 9 - pydatastructs/trees/_backend/cpp/trees.cpp | 7 + pydatastructs/trees/binary_trees.py | 14 +- .../trees/tests/test_binary_trees.py | 89 +++-- pydatastructs/utils/testing_util.py | 12 +- 11 files changed, 488 insertions(+), 54 deletions(-) create mode 100644 pydatastructs/trees/_backend/cpp/AVLTree.hpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc75a85da..b416eb02d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: | diff --git a/README.md b/README.md index 14ca9a35f..85cf84db5 100644 --- a/README.md +++ b/README.md @@ -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? ------------------ diff --git a/pydatastructs/linear_data_structures/tests/test_arrays.py b/pydatastructs/linear_data_structures/tests/test_arrays.py index 496978991..886510113 100644 --- a/pydatastructs/linear_data_structures/tests/test_arrays.py +++ b/pydatastructs/linear_data_structures/tests/test_arrays.py @@ -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 @@ -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) diff --git a/pydatastructs/trees/_backend/cpp/AVLTree.hpp b/pydatastructs/trees/_backend/cpp/AVLTree.hpp new file mode 100644 index 000000000..2183a63b2 --- /dev/null +++ b/pydatastructs/trees/_backend/cpp/AVLTree.hpp @@ -0,0 +1,370 @@ +#ifndef TREES_AVLTREE_HPP +#define TREES_AVLTREE_HPP + +#define PY_SSIZE_T_CLEAN +#include +#include +#include +#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; +} AVLTree; + +static void AVLTree_dealloc(AVLTree *self) { + Py_TYPE(self)->tp_free(reinterpret_cast(self)); +} + +static PyObject* AVLTree___new__(PyTypeObject* type, PyObject *args, PyObject *kwds) { + AVLTree *self; + self = reinterpret_cast(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(p); + self->tree = self->sbbt->bst->binary_tree->tree; + + return reinterpret_cast(self); +} + +static PyObject* AVLTree___str__(AVLTree *self) { + return BinarySearchTree___str__(self->sbbt->bst); +} + +static PyObject* AVLTree_set_tree(AVLTree* self, PyObject *args) { + ArrayForTrees* arr = reinterpret_cast(PyObject_GetItem(args, PyZero)); + self->sbbt->bst->binary_tree->tree = arr; + self->tree = self->sbbt->bst->binary_tree->tree; + Py_RETURN_NONE; +} + +static PyObject* AVLTree_search(AVLTree* self, PyObject *args, PyObject *kwds) { + return BinarySearchTree_search(self->sbbt->bst, args, kwds); +} + +static long AVLTree_left_height(AVLTree* self, PyObject *args) { + TreeNode* node = reinterpret_cast(PyObject_GetItem(args, PyZero)); + if (node->left != Py_None) { + BinaryTree* bt = self->sbbt->bst->binary_tree; + return reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node->left)])->height; + } + else{ + return (-1); + } +} + +static long AVLTree_right_height(AVLTree* self, PyObject *args) { + TreeNode* node = reinterpret_cast(PyObject_GetItem(args, PyZero)); + if (node->right != Py_None) { + BinaryTree* bt = self->sbbt->bst->binary_tree; + return reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(node->right)])->height; + } + else{ + return -1; + } +} + +static PyObject* AVLTree_balance_factor(AVLTree* self, PyObject *args) { + TreeNode* node = reinterpret_cast(PyObject_GetItem(args, PyZero)); + return PyLong_FromLong(AVLTree_right_height(self, Py_BuildValue("(O)", node)) - AVLTree_left_height(self, Py_BuildValue("(O)", node))); +} + +static PyObject* AVLTree__right_rotate(AVLTree* self, PyObject *args) { + PyObject* j = PyObject_GetItem(args, PyZero); + PyObject* k = PyObject_GetItem(args, PyOne); + BinaryTree* bt = self->sbbt->bst->binary_tree; + SelfBalancingBinaryTree__right_rotate(self->sbbt, Py_BuildValue("(OO)", j, k)); + + long lh = AVLTree_left_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + long rh = AVLTree_right_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])->height = std::max(lh, rh) + 1; + + if (bt->is_order_statistic == true) { + long ls = BinarySearchTree_left_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + long rs = BinarySearchTree_right_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])->size = ls + rs + 1; + } + + Py_RETURN_NONE; +} + +static PyObject* AVLTree__left_right_rotate(AVLTree* self, PyObject *args) { + PyObject* j = PyObject_GetItem(args, PyZero); + PyObject* k = PyObject_GetItem(args, PyOne); + BinaryTree* bt = self->sbbt->bst->binary_tree; + SelfBalancingBinaryTree__left_right_rotate(self->sbbt, Py_BuildValue("(OO)", j, k)); + + long lh = AVLTree_left_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + long rh = AVLTree_right_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])->height = std::max(lh, rh) + 1; + + lh = AVLTree_left_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(k)])); + rh = AVLTree_right_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(k)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(k)])->height = std::max(lh, rh) + 1; + + if (bt->is_order_statistic == true) { + long ls = BinarySearchTree_left_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + long rs = BinarySearchTree_right_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])->size = ls + rs + 1; + + ls = BinarySearchTree_left_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(k)])); + rs = BinarySearchTree_right_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(k)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(k)])->size = ls + rs + 1; + } + + Py_RETURN_NONE; +} + +static PyObject* AVLTree__right_left_rotate(AVLTree* self, PyObject *args) { + PyObject* j = PyObject_GetItem(args, PyZero); + PyObject* k = PyObject_GetItem(args, PyOne); + BinaryTree* bt = self->sbbt->bst->binary_tree; + SelfBalancingBinaryTree__right_left_rotate(self->sbbt, Py_BuildValue("(OO)", j, k)); + + long lh = AVLTree_left_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + long rh = AVLTree_right_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])->height = std::max(lh, rh) + 1; + + lh = AVLTree_left_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(k)])); + rh = AVLTree_right_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(k)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(k)])->height = std::max(lh, rh) + 1; + + if (bt->is_order_statistic == true) { + long ls = BinarySearchTree_left_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + long rs = BinarySearchTree_right_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])->size = ls + rs + 1; + + ls = BinarySearchTree_left_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(k)])); + rs = BinarySearchTree_right_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(k)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(k)])->size = ls + rs + 1; + } + + Py_RETURN_NONE; +} + +static PyObject* AVLTree__left_rotate(AVLTree* self, PyObject *args) { + PyObject* j = PyObject_GetItem(args, PyZero); + PyObject* k = PyObject_GetItem(args, PyOne); + BinaryTree* bt = self->sbbt->bst->binary_tree; + SelfBalancingBinaryTree__left_rotate(self->sbbt, Py_BuildValue("(OO)", j, k)); + + long lh = AVLTree_left_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + long rh = AVLTree_right_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])->height = std::max(lh, rh) + 1; + + lh = AVLTree_left_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(k)])); + rh = AVLTree_right_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(k)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(k)])->height = std::max(lh, rh) + 1; + + if (bt->is_order_statistic == true) { + long ls = BinarySearchTree_left_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + long rs = BinarySearchTree_right_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(j)])->size = ls + rs + 1; + } + + Py_RETURN_NONE; +} + +static PyObject* AVLTree__balance_insertion(AVLTree* self, PyObject *args) { + PyObject* curr = PyObject_GetItem(args, PyZero); + PyObject* last = PyObject_GetItem(args, PyOne); + BinaryTree* bt = self->sbbt->bst->binary_tree; + + PyObject* walk = last; + std::queue path; + path.push(curr); + path.push(last); + + while (walk != Py_None) { + long lh = AVLTree_left_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])); + long rh = AVLTree_right_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])->height = std::max(lh, rh) + 1; + + if (bt->is_order_statistic == true) { + long ls = BinarySearchTree_left_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])); + long rs = BinarySearchTree_right_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])->size = ls + rs + 1; + } + + last = path.front(); + path.pop(); + PyObject* last2last = path.front(); + path.pop(); + long bf = PyLong_AsLong(AVLTree_balance_factor(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)]))); + if (bf != 1 && bf != 0 && bf != -1) { + PyObject* l = reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])->left; + if (l != Py_None && l == last && reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(l)])->left == last2last) { + AVLTree__right_rotate(self, Py_BuildValue("(OO)", walk, last)); + } + + PyObject* r = reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])->right; + if (r != Py_None && r == last && reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(r)])->right == last2last) { + AVLTree__left_rotate(self, Py_BuildValue("(OO)", walk, last)); + } + + if (l != Py_None && l == last && reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(l)])->right == last2last) { + AVLTree__left_right_rotate(self, Py_BuildValue("(OO)", walk, last)); + } + + if (r != Py_None && r == last && reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(r)])->left == last2last) { + AVLTree__right_left_rotate(self, Py_BuildValue("(OO)", walk, last)); + } + } + path.push(walk); + path.push(last); + walk = reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])->parent; + } + + Py_RETURN_NONE; +} + +static PyObject* AVLTree_insert(AVLTree* self, PyObject *args) { + Py_INCREF(Py_None); + PyObject* key = Py_None; + Py_INCREF(Py_None); + PyObject* data = Py_None; + if (!PyArg_ParseTuple(args, "O|O", &key, &data)) { // data is optional + return NULL; + } + SelfBalancingBinaryTree_insert(self->sbbt, Py_BuildValue("(OO)", key, data)); + BinaryTree* bt = self->sbbt->bst->binary_tree; + long s = bt->size - 1; + AVLTree__balance_insertion(self, Py_BuildValue("(OO)", PyLong_FromLong(s), reinterpret_cast(bt->tree->_one_dimensional_array->_data[s])->parent)); + + Py_RETURN_NONE; +} + +static PyObject* AVLTree_rank(AVLTree* self, PyObject *args) { + return BinarySearchTree_rank(self->sbbt->bst, args); +} + +static PyObject* AVLTree_select(AVLTree* self, PyObject *args) { + return BinarySearchTree_select(self->sbbt->bst, args); +} + +static PyObject* AVLTree__balance_delete(AVLTree* self, PyObject *args) { + PyObject* start_idx = PyObject_GetItem(args, PyZero); + PyObject* key = PyObject_GetItem(args, PyOne); + BinaryTree* bt = self->sbbt->bst->binary_tree; + + PyObject* walk = start_idx; + while (walk != Py_None) { + long lh = AVLTree_left_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])); + long rh = AVLTree_right_height(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])->height = std::max(lh, rh) + 1; + + if (bt->is_order_statistic == true) { + long ls = BinarySearchTree_left_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])); + long rs = BinarySearchTree_right_size(self->sbbt->bst, reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])); + reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])->size = ls + rs + 1; + } + + long bf = PyLong_AsLong(AVLTree_balance_factor(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)]))); + if (bf != 1 && bf != 0 && bf != -1) { + if (bf < 0) { + PyObject* b = reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])->left; + if (PyLong_AsLong(AVLTree_balance_factor(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(b)]))) <= 0) { + AVLTree__right_rotate(self, Py_BuildValue("(OO)", walk, b)); + } + else { + AVLTree__left_right_rotate(self, Py_BuildValue("(OO)", walk, b)); + } + } + else { + PyObject* b = reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])->right; + if (PyLong_AsLong(AVLTree_balance_factor(self, Py_BuildValue("(O)", bt->tree->_one_dimensional_array->_data[PyLong_AsLong(b)]))) >= 0) { + AVLTree__left_rotate(self, Py_BuildValue("(OO)", walk, b)); + } + else { + AVLTree__right_left_rotate(self, Py_BuildValue("(OO)", walk, b)); + } + } + } + walk = reinterpret_cast(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])->parent; + } + + Py_RETURN_NONE; +} + +static PyObject* AVLTree_delete(AVLTree* self, PyObject *args, PyObject *kwds) { + PyObject* key = PyObject_GetItem(args, PyZero); + + PyObject* kwd_bal = PyDict_New(); + PyDict_SetItemString(kwd_bal, "balancing_info", PyLong_FromLong(1)); + PyObject* a = SelfBalancingBinaryTree_delete(self->sbbt, Py_BuildValue("(O)", key), kwd_bal); + AVLTree__balance_delete(self, Py_BuildValue("(OO)", a, key)); + + Py_RETURN_TRUE; +} + +static struct PyMethodDef AVLTree_PyMethodDef[] = { + {"search", (PyCFunction) AVLTree_search, METH_VARARGS | METH_KEYWORDS, NULL}, + {"insert", (PyCFunction) AVLTree_insert, METH_VARARGS, NULL}, + {"delete", (PyCFunction) AVLTree_delete, METH_VARARGS, NULL}, + {"set_tree", (PyCFunction) AVLTree_set_tree, METH_VARARGS, NULL}, + {"balance_factor", (PyCFunction) AVLTree_balance_factor, METH_VARARGS, NULL}, + {"rank", (PyCFunction) AVLTree_rank, METH_VARARGS, NULL}, + {"select", (PyCFunction) AVLTree_select, METH_VARARGS, NULL}, + {"_left_right_rotate", (PyCFunction) AVLTree__left_right_rotate, METH_VARARGS, NULL}, + {"_right_left_rotate", (PyCFunction) AVLTree__right_left_rotate, METH_VARARGS, NULL}, + {"_left_rotate", (PyCFunction) AVLTree__left_rotate, METH_VARARGS, NULL}, + {"_right_rotate", (PyCFunction) AVLTree__right_rotate, METH_VARARGS, NULL}, + {NULL} +}; + +static PyMemberDef AVLTree_PyMemberDef[] = { + {"tree", T_OBJECT_EX, offsetof(AVLTree, tree), 0, "tree"}, + {NULL} /* Sentinel */ +}; + + +static PyTypeObject AVLTreeType = { + /* tp_name */ PyVarObject_HEAD_INIT(NULL, 0) "AVLTree", + /* tp_basicsize */ sizeof(AVLTree), + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor) AVLTree_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) AVLTree___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 */ AVLTree_PyMethodDef, + /* tp_members */ AVLTree_PyMemberDef, + /* tp_getset */ 0, + /* 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 */ AVLTree___new__, +}; + +#endif diff --git a/pydatastructs/trees/_backend/cpp/BinarySearchTree.hpp b/pydatastructs/trees/_backend/cpp/BinarySearchTree.hpp index 01bd82262..352d91ce6 100644 --- a/pydatastructs/trees/_backend/cpp/BinarySearchTree.hpp +++ b/pydatastructs/trees/_backend/cpp/BinarySearchTree.hpp @@ -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(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(walk)])->key == Py_None) { Py_RETURN_NONE; diff --git a/pydatastructs/trees/_backend/cpp/BinaryTreeTraversal.hpp b/pydatastructs/trees/_backend/cpp/BinaryTreeTraversal.hpp index 8971597a6..f7c08bdbd 100644 --- a/pydatastructs/trees/_backend/cpp/BinaryTreeTraversal.hpp +++ b/pydatastructs/trees/_backend/cpp/BinaryTreeTraversal.hpp @@ -16,6 +16,7 @@ #include "SelfBalancingBinaryTree.hpp" #include "RedBlackTree.hpp" #include "SplayTree.hpp" +#include "AVLTree.hpp" typedef struct { PyObject_HEAD @@ -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(tree)->sbbt->bst->binary_tree; } + else if (PyObject_IsInstance(tree, (PyObject *)&AVLTreeType)) { + self->tree = reinterpret_cast(tree)->sbbt->bst->binary_tree; + } else if (PyObject_IsInstance(tree, (PyObject *)&RedBlackTreeType)) { self->tree = reinterpret_cast(tree)->sbbt->bst->binary_tree; } diff --git a/pydatastructs/trees/_backend/cpp/SplayTree.hpp b/pydatastructs/trees/_backend/cpp/SplayTree.hpp index b26041d93..37cf0e9db 100644 --- a/pydatastructs/trees/_backend/cpp/SplayTree.hpp +++ b/pydatastructs/trees/_backend/cpp/SplayTree.hpp @@ -267,16 +267,7 @@ static PyObject* SplayTree_split(SplayTree *self, PyObject* args) { } SplayTree* other = reinterpret_cast(SplayTree___new__(self->type, Py_BuildValue("(OOOO)", Py_None, Py_None, bt->comparator, PyZero), PyDict_New())); - // SplayTree* other = reinterpret_cast(PyObject_GetItem(args, PyOne)); if (reinterpret_cast(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___new__(&BinaryTreeTraversalType, Py_BuildValue("(O)", self), PyDict_New())); - // PyObject* kwd_dict = PyDict_New(); - // PyDict_SetItemString(kwd_dict, "node", reinterpret_cast(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(bt->tree->_one_dimensional_array->_data[PyLong_AsLong(bt->root_idx)])->right)); for (int i=0; isbbt, Py_BuildValue("(OO)", reinterpret_cast( PyList_GetItem(elements, i))->key, reinterpret_cast( PyList_GetItem(elements, i))->data)); diff --git a/pydatastructs/trees/_backend/cpp/trees.cpp b/pydatastructs/trees/_backend/cpp/trees.cpp index 00b7316d3..4747c71b9 100644 --- a/pydatastructs/trees/_backend/cpp/trees.cpp +++ b/pydatastructs/trees/_backend/cpp/trees.cpp @@ -6,6 +6,7 @@ #include "RedBlackTree.hpp" #include "BinaryIndexedTree.hpp" #include "SplayTree.hpp" +#include "AVLTree.hpp" static struct PyModuleDef trees_struct = { PyModuleDef_HEAD_INIT, @@ -61,5 +62,11 @@ PyMODINIT_FUNC PyInit__trees(void) { Py_INCREF(&SplayTreeType); PyModule_AddObject(trees, "SplayTree", reinterpret_cast(&SplayTreeType)); + if (PyType_Ready(&AVLTreeType) < 0) { + return NULL; + } + Py_INCREF(&AVLTreeType); + PyModule_AddObject(trees, "AVLTree", reinterpret_cast(&AVLTreeType)); + return trees; } diff --git a/pydatastructs/trees/binary_trees.py b/pydatastructs/trees/binary_trees.py index d914b8ab3..d4b5e54be 100644 --- a/pydatastructs/trees/binary_trees.py +++ b/pydatastructs/trees/binary_trees.py @@ -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 @@ -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]), diff --git a/pydatastructs/trees/tests/test_binary_trees.py b/pydatastructs/trees/tests/test_binary_trees.py index c13f38514..3cd672f73 100644 --- a/pydatastructs/trees/tests/test_binary_trees.py +++ b/pydatastructs/trees/tests/test_binary_trees.py @@ -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 @@ -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') @@ -171,52 +172,52 @@ 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 @@ -224,17 +225,18 @@ def test_AVLTree(): 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 @@ -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), @@ -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 @@ -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)): @@ -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] @@ -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) @@ -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 diff --git a/pydatastructs/utils/testing_util.py b/pydatastructs/utils/testing_util.py index 284052d89..6024912ba 100644 --- a/pydatastructs/utils/testing_util.py +++ b/pydatastructs/utils/testing_util.py @@ -18,7 +18,7 @@ SKIP_FILES = ['testing_util.py'] -def test(submodules=None, include_benchmarks=False, +def test(submodules=None, only_benchmarks=False, benchmarks_size=1000, **kwargs): """ Runs the library tests using pytest @@ -52,11 +52,12 @@ def test(submodules=None, include_benchmarks=False, else: raise Exception("Submodule should be of type: str or module") if sub in path: - if not include_benchmarks: + if not only_benchmarks: if not 'benchmarks' in path: test_files.append(path) else: - test_files.append(path) + if 'benchmarks' in path: + test_files.append(path) break else: for path in glob.glob(f'{ROOT_DIR}/**/test_*.py', recursive=True): @@ -67,11 +68,12 @@ def test(submodules=None, include_benchmarks=False, break if skip_test: continue - if not include_benchmarks: + if not only_benchmarks: if not 'benchmarks' in path: test_files.append(path) else: - test_files.append(path) + if 'benchmarks' in path: + test_files.append(path) extra_args = [] if not kwargs.get("n", False) is False: