Skip to content

Commit

Permalink
C++ backend for Red Black Trees (#560)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kishan-Ved authored Jun 12, 2024
1 parent 0bc3eb2 commit c17fe9c
Show file tree
Hide file tree
Showing 9 changed files with 748 additions and 18 deletions.
2 changes: 1 addition & 1 deletion pydatastructs/trees/_backend/cpp/BinarySearchTree.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ static PyObject* BinarySearchTree_insert(BinarySearchTree* self, PyObject* args)
PyObject* key = Py_None;
Py_INCREF(Py_None);
PyObject* data = Py_None;
if (!PyArg_ParseTuple(args, "O|O", &key, &data)) { // ret_parent is optional
if (!PyArg_ParseTuple(args, "O|O", &key, &data)) { // data is optional
return NULL;
}

Expand Down
10 changes: 9 additions & 1 deletion pydatastructs/trees/_backend/cpp/BinaryTreeTraversal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "BinaryTree.hpp"
#include "BinarySearchTree.hpp"
#include "SelfBalancingBinaryTree.hpp"
#include "RedBlackTree.hpp"

typedef struct {
PyObject_HEAD
Expand All @@ -36,7 +37,14 @@ static PyObject* BinaryTreeTraversal___new__(PyTypeObject* type, PyObject *args,
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;
}
if (PyObject_IsInstance(tree, (PyObject *)&SelfBalancingBinaryTreeType)) {
if (PyType_Ready(&RedBlackTreeType) < 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 *)&RedBlackTreeType)) {
self->tree = reinterpret_cast<RedBlackTree*>(tree)->sbbt->bst->binary_tree;
}
else if (PyObject_IsInstance(tree, (PyObject *)&SelfBalancingBinaryTreeType)) {
self->tree = reinterpret_cast<SelfBalancingBinaryTree*>(tree)->bst->binary_tree;
}
else if (PyObject_IsInstance(tree, (PyObject *)&BinarySearchTreeType)) {
Expand Down
641 changes: 641 additions & 0 deletions pydatastructs/trees/_backend/cpp/RedBlackTree.hpp

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pydatastructs/trees/_backend/cpp/SelfBalancingBinaryTree.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#ifndef TREES_SELFBALANCINGSelfBalancingBinaryTree_HPP
#define TREES_SELFBALANCINGSelfBalancingBinaryTree_HPP
#ifndef TREES_SELFBALANCINGBINARYTREE_HPP
#define TREES_SELFBALANCINGBINARYTREE_HPP

#define PY_SSIZE_T_CLEAN
#include <Python.h>
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 @@ -3,6 +3,7 @@
#include "BinarySearchTree.hpp"
#include "BinaryTreeTraversal.hpp"
#include "SelfBalancingBinaryTree.hpp"
#include "RedBlackTree.hpp"

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

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

return trees;
}
11 changes: 10 additions & 1 deletion pydatastructs/trees/binary_trees.py
Original file line number Diff line number Diff line change
Expand Up @@ -1199,9 +1199,18 @@ class RedBlackTree(SelfBalancingBinaryTree):
pydatastructs.trees.binary_trees.SelfBalancingBinaryTree
"""

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.RedBlackTree(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__', 'insert', 'delete']

def _get_parent(self, node_idx):
return self.tree[node_idx].parent
Expand Down
52 changes: 50 additions & 2 deletions pydatastructs/trees/tests/benchmarks/test_binary_trees.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import timeit, functools, os, pytest
from pydatastructs.trees.binary_trees import (BinarySearchTree)
from pydatastructs.trees.binary_trees import (BinarySearchTree, RedBlackTree)
from pydatastructs.utils.misc_util import Backend

@pytest.mark.xfail
Expand All @@ -22,7 +22,55 @@ def g(backend, tree):
for node in range(-1000, 1000):
tree.search(node)
def h(backend, tree):
for node in range(2000):
for node in range(-1000, 1000):
tree.delete(node)

kwds_dict_PY = {"backend": Backend.PYTHON, "tree":b1}
kwds_dict_CPP = {"backend": Backend.CPP, "tree":b2}

timer_python = timeit.Timer(functools.partial(f, **kwds_dict_PY))
python_insert = min(timer_python.repeat(repeat, number))

timer_cpp = timeit.Timer(functools.partial(f, **kwds_dict_CPP))
cpp_insert = min(timer_cpp.repeat(repeat, number))
assert cpp_insert < python_insert

timer_python = timeit.Timer(functools.partial(g, **kwds_dict_PY))
python_search = min(timer_python.repeat(repeat, number))

timer_cpp = timeit.Timer(functools.partial(g, **kwds_dict_CPP))
cpp_search = min(timer_cpp.repeat(repeat, number))
assert cpp_search < python_search

timer_python = timeit.Timer(functools.partial(h, **kwds_dict_PY))
python_delete = min(timer_python.repeat(repeat, number))

timer_cpp = timeit.Timer(functools.partial(h, **kwds_dict_CPP))
cpp_delete = min(timer_cpp.repeat(repeat, number))
assert cpp_delete < python_delete

@pytest.mark.xfail
def test_RedBlackTree(**kwargs):
cpp = Backend.CPP
repeat = 1
number = 1

size = int(os.environ.get("PYDATASTRUCTS_BENCHMARK_SIZE", "1000"))
size = kwargs.get("size", size)

RBT = RedBlackTree
b1 = RBT(backend=Backend.PYTHON)
b2 = RBT(backend=Backend.CPP)

def f(backend, tree):
for node in range(-1000,1000):
tree.insert(node, node)

def g(backend, tree):
for node in range(-1000, 1000):
tree.search(node)
def h(backend, tree):
for node in range(-1000, 1000):
tree.delete(node)

kwds_dict_PY = {"backend": Backend.PYTHON, "tree":b1}
Expand Down
36 changes: 25 additions & 11 deletions pydatastructs/trees/tests/test_binary_trees.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,8 +542,8 @@ def test_SplayTree():
assert [node.key for node in in_order] == [20, 30, 50, 55, 100, 200]
assert [node.key for node in pre_order] == [200, 55, 50, 30, 20, 100]

def test_RedBlackTree():
tree = RedBlackTree()
def _test_RedBlackTree(backend):
tree = RedBlackTree(backend=backend)
tree.insert(10, 10)
tree.insert(18, 18)
tree.insert(7, 7)
Expand All @@ -556,8 +556,9 @@ def test_RedBlackTree():
tree.insert(2, 2)
tree.insert(17, 17)
tree.insert(6, 6)
assert str(tree) == "[(11, 10, 10, 3), (10, 18, 18, None), (None, 7, 7, None), (None, 15, 15, None), (0, 16, 16, 6), (None, 30, 30, None), (1, 25, 25, 7), (5, 40, 40, 8), (None, 60, 60, None), (None, 2, 2, None), (None, 17, 17, None), (9, 6, 6, 2)]"

trav = BinaryTreeTraversal(tree)
trav = BinaryTreeTraversal(tree, 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, 6, 7, 10, 15, 16, 17, 18, 25, 30, 40, 60]
Expand All @@ -583,7 +584,7 @@ def test_RedBlackTree():
assert tree.upper_bound(60) is None
assert tree.upper_bound(61) is None

tree = RedBlackTree()
tree = RedBlackTree(backend=backend)

assert tree.lower_bound(1) is None
assert tree.upper_bound(0) is None
Expand All @@ -606,10 +607,11 @@ def test_RedBlackTree():
tree.insert(160)
tree.insert(170)
tree.insert(180)
assert str(tree) == "[(None, 10, None, None), (0, 20, None, 2), (None, 30, None, None), (1, 40, None, 5), (None, 50, None, None), (4, 60, None, 6), (None, 70, None, None), (3, 80, None, 11), (None, 90, None, None), (8, 100, None, 10), (None, 110, None, None), (9, 120, None, 13), (None, 130, None, None), (12, 140, None, 15), (None, 150, None, None), (14, 160, None, 16), (None, 170, None, 17), (None, 180, None, None)]"

assert tree._get_sibling(7) is None

trav = BinaryTreeTraversal(tree)
trav = BinaryTreeTraversal(tree, 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] == [10, 20, 30, 40, 50, 60, 70, 80, 90,
Expand Down Expand Up @@ -684,36 +686,42 @@ def test_RedBlackTree():
assert [node.key for node in in_order if node.key is not None] == []
assert [node.key for node in pre_order if node.key is not None] == []

tree = RedBlackTree()
tree = RedBlackTree(backend=backend)
tree.insert(50)
tree.insert(40)
tree.insert(30)
tree.insert(20)
tree.insert(10)
tree.insert(5)

trav = BinaryTreeTraversal(tree)
trav = BinaryTreeTraversal(tree, 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, 10, 20, 30, 40, 50]
assert [node.key for node in pre_order] == [40, 20, 10, 5, 30, 50]

assert tree.search(50) == 0
assert tree.search(20) == 3
assert tree.search(30) == 2
tree.delete(50)
tree.delete(20)
tree.delete(30)
assert tree.search(50) is None
assert tree.search(20) is None
assert tree.search(30) is None

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, 10, 40]
assert [node.key for node in pre_order] == [10, 5, 40]

tree = RedBlackTree()
tree = RedBlackTree(backend=backend)
tree.insert(10)
tree.insert(5)
tree.insert(20)
tree.insert(15)

trav = BinaryTreeTraversal(tree)
trav = BinaryTreeTraversal(tree, 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, 10, 15, 20]
Expand All @@ -726,15 +734,15 @@ def test_RedBlackTree():
assert [node.key for node in in_order] == [10, 15, 20]
assert [node.key for node in pre_order] == [15, 10, 20]

tree = RedBlackTree()
tree = RedBlackTree(backend=backend)
tree.insert(10)
tree.insert(5)
tree.insert(20)
tree.insert(15)
tree.insert(2)
tree.insert(6)

trav = BinaryTreeTraversal(tree)
trav = BinaryTreeTraversal(tree,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, 5, 6, 10, 15, 20]
Expand All @@ -746,3 +754,9 @@ def test_RedBlackTree():
pre_order = trav.depth_first_search(order='pre_order')
assert [node.key for node in in_order] == [2, 5, 6, 15, 20]
assert [node.key for node in pre_order] == [6, 5, 2, 20, 15]

def test_RedBlackTree():
_test_RedBlackTree(Backend.PYTHON)

def test_cpp_RedBlackTree():
_test_RedBlackTree(Backend.CPP)
3 changes: 3 additions & 0 deletions pydatastructs/utils/_backend/cpp/TreeNode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ typedef struct {
long height;
PyObject* parent;
long size;
long color;
} TreeNode;

static void TreeNode_dealloc(TreeNode *self) {
Expand All @@ -40,6 +41,7 @@ static PyObject* TreeNode___new__(PyTypeObject* type, PyObject *args, PyObject *
self->height = 0;
self->size = 1;
self->is_root = false;
self->color = 1;

return reinterpret_cast<PyObject*>(self);
}
Expand All @@ -59,6 +61,7 @@ static struct PyMemberDef TreeNode_PyMemberDef[] = {
{"left", T_OBJECT, offsetof(TreeNode, left), 0, "TreeNode left"},
{"right", T_OBJECT, offsetof(TreeNode, right), 0, "TreeNode right"},
{"parent", T_OBJECT, offsetof(TreeNode, parent), 0, "TreeNode parent"},
{"color", T_LONG, offsetof(TreeNode, size), 0, "RedBlackTreeNode color"},
{NULL},
};

Expand Down

0 comments on commit c17fe9c

Please sign in to comment.