Skip to content

Commit c17fe9c

Browse files
authored
C++ backend for Red Black Trees (#560)
1 parent 0bc3eb2 commit c17fe9c

File tree

9 files changed

+748
-18
lines changed

9 files changed

+748
-18
lines changed

pydatastructs/trees/_backend/cpp/BinarySearchTree.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ static PyObject* BinarySearchTree_insert(BinarySearchTree* self, PyObject* args)
135135
PyObject* key = Py_None;
136136
Py_INCREF(Py_None);
137137
PyObject* data = Py_None;
138-
if (!PyArg_ParseTuple(args, "O|O", &key, &data)) { // ret_parent is optional
138+
if (!PyArg_ParseTuple(args, "O|O", &key, &data)) { // data is optional
139139
return NULL;
140140
}
141141

pydatastructs/trees/_backend/cpp/BinaryTreeTraversal.hpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "BinaryTree.hpp"
1515
#include "BinarySearchTree.hpp"
1616
#include "SelfBalancingBinaryTree.hpp"
17+
#include "RedBlackTree.hpp"
1718

1819
typedef struct {
1920
PyObject_HEAD
@@ -36,7 +37,14 @@ static PyObject* BinaryTreeTraversal___new__(PyTypeObject* type, PyObject *args,
3637
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.
3738
return NULL;
3839
}
39-
if (PyObject_IsInstance(tree, (PyObject *)&SelfBalancingBinaryTreeType)) {
40+
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.
41+
return NULL;
42+
}
43+
44+
if (PyObject_IsInstance(tree, (PyObject *)&RedBlackTreeType)) {
45+
self->tree = reinterpret_cast<RedBlackTree*>(tree)->sbbt->bst->binary_tree;
46+
}
47+
else if (PyObject_IsInstance(tree, (PyObject *)&SelfBalancingBinaryTreeType)) {
4048
self->tree = reinterpret_cast<SelfBalancingBinaryTree*>(tree)->bst->binary_tree;
4149
}
4250
else if (PyObject_IsInstance(tree, (PyObject *)&BinarySearchTreeType)) {

pydatastructs/trees/_backend/cpp/RedBlackTree.hpp

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

pydatastructs/trees/_backend/cpp/SelfBalancingBinaryTree.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
#ifndef TREES_SELFBALANCINGSelfBalancingBinaryTree_HPP
2-
#define TREES_SELFBALANCINGSelfBalancingBinaryTree_HPP
1+
#ifndef TREES_SELFBALANCINGBINARYTREE_HPP
2+
#define TREES_SELFBALANCINGBINARYTREE_HPP
33

44
#define PY_SSIZE_T_CLEAN
55
#include <Python.h>

pydatastructs/trees/_backend/cpp/trees.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "BinarySearchTree.hpp"
44
#include "BinaryTreeTraversal.hpp"
55
#include "SelfBalancingBinaryTree.hpp"
6+
#include "RedBlackTree.hpp"
67

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

44+
if (PyType_Ready(&RedBlackTreeType) < 0) {
45+
return NULL;
46+
}
47+
Py_INCREF(&RedBlackTreeType);
48+
PyModule_AddObject(trees, "RedBlackTree", reinterpret_cast<PyObject*>(&RedBlackTreeType));
49+
4350
return trees;
4451
}

pydatastructs/trees/binary_trees.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1199,9 +1199,18 @@ class RedBlackTree(SelfBalancingBinaryTree):
11991199
pydatastructs.trees.binary_trees.SelfBalancingBinaryTree
12001200
"""
12011201

1202+
def __new__(cls, key=None, root_data=None, comp=None,
1203+
is_order_statistic=False, **kwargs):
1204+
backend = kwargs.get('backend', Backend.PYTHON)
1205+
if backend == Backend.CPP:
1206+
if comp is None:
1207+
comp = lambda key1, key2: key1 < key2
1208+
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
1209+
return super().__new__(cls, key, root_data, comp, is_order_statistic, **kwargs)
1210+
12021211
@classmethod
12031212
def methods(cls):
1204-
return ['insert', 'delete']
1213+
return ['__new__', 'insert', 'delete']
12051214

12061215
def _get_parent(self, node_idx):
12071216
return self.tree[node_idx].parent

pydatastructs/trees/tests/benchmarks/test_binary_trees.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import timeit, functools, os, pytest
2-
from pydatastructs.trees.binary_trees import (BinarySearchTree)
2+
from pydatastructs.trees.binary_trees import (BinarySearchTree, RedBlackTree)
33
from pydatastructs.utils.misc_util import Backend
44

55
@pytest.mark.xfail
@@ -22,7 +22,55 @@ def g(backend, tree):
2222
for node in range(-1000, 1000):
2323
tree.search(node)
2424
def h(backend, tree):
25-
for node in range(2000):
25+
for node in range(-1000, 1000):
26+
tree.delete(node)
27+
28+
kwds_dict_PY = {"backend": Backend.PYTHON, "tree":b1}
29+
kwds_dict_CPP = {"backend": Backend.CPP, "tree":b2}
30+
31+
timer_python = timeit.Timer(functools.partial(f, **kwds_dict_PY))
32+
python_insert = min(timer_python.repeat(repeat, number))
33+
34+
timer_cpp = timeit.Timer(functools.partial(f, **kwds_dict_CPP))
35+
cpp_insert = min(timer_cpp.repeat(repeat, number))
36+
assert cpp_insert < python_insert
37+
38+
timer_python = timeit.Timer(functools.partial(g, **kwds_dict_PY))
39+
python_search = min(timer_python.repeat(repeat, number))
40+
41+
timer_cpp = timeit.Timer(functools.partial(g, **kwds_dict_CPP))
42+
cpp_search = min(timer_cpp.repeat(repeat, number))
43+
assert cpp_search < python_search
44+
45+
timer_python = timeit.Timer(functools.partial(h, **kwds_dict_PY))
46+
python_delete = min(timer_python.repeat(repeat, number))
47+
48+
timer_cpp = timeit.Timer(functools.partial(h, **kwds_dict_CPP))
49+
cpp_delete = min(timer_cpp.repeat(repeat, number))
50+
assert cpp_delete < python_delete
51+
52+
@pytest.mark.xfail
53+
def test_RedBlackTree(**kwargs):
54+
cpp = Backend.CPP
55+
repeat = 1
56+
number = 1
57+
58+
size = int(os.environ.get("PYDATASTRUCTS_BENCHMARK_SIZE", "1000"))
59+
size = kwargs.get("size", size)
60+
61+
RBT = RedBlackTree
62+
b1 = RBT(backend=Backend.PYTHON)
63+
b2 = RBT(backend=Backend.CPP)
64+
65+
def f(backend, tree):
66+
for node in range(-1000,1000):
67+
tree.insert(node, node)
68+
69+
def g(backend, tree):
70+
for node in range(-1000, 1000):
71+
tree.search(node)
72+
def h(backend, tree):
73+
for node in range(-1000, 1000):
2674
tree.delete(node)
2775

2876
kwds_dict_PY = {"backend": Backend.PYTHON, "tree":b1}

pydatastructs/trees/tests/test_binary_trees.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -542,8 +542,8 @@ def test_SplayTree():
542542
assert [node.key for node in in_order] == [20, 30, 50, 55, 100, 200]
543543
assert [node.key for node in pre_order] == [200, 55, 50, 30, 20, 100]
544544

545-
def test_RedBlackTree():
546-
tree = RedBlackTree()
545+
def _test_RedBlackTree(backend):
546+
tree = RedBlackTree(backend=backend)
547547
tree.insert(10, 10)
548548
tree.insert(18, 18)
549549
tree.insert(7, 7)
@@ -556,8 +556,9 @@ def test_RedBlackTree():
556556
tree.insert(2, 2)
557557
tree.insert(17, 17)
558558
tree.insert(6, 6)
559+
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)]"
559560

560-
trav = BinaryTreeTraversal(tree)
561+
trav = BinaryTreeTraversal(tree, backend=backend)
561562
in_order = trav.depth_first_search(order='in_order')
562563
pre_order = trav.depth_first_search(order='pre_order')
563564
assert [node.key for node in in_order] == [2, 6, 7, 10, 15, 16, 17, 18, 25, 30, 40, 60]
@@ -583,7 +584,7 @@ def test_RedBlackTree():
583584
assert tree.upper_bound(60) is None
584585
assert tree.upper_bound(61) is None
585586

586-
tree = RedBlackTree()
587+
tree = RedBlackTree(backend=backend)
587588

588589
assert tree.lower_bound(1) is None
589590
assert tree.upper_bound(0) is None
@@ -606,10 +607,11 @@ def test_RedBlackTree():
606607
tree.insert(160)
607608
tree.insert(170)
608609
tree.insert(180)
610+
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)]"
609611

610612
assert tree._get_sibling(7) is None
611613

612-
trav = BinaryTreeTraversal(tree)
614+
trav = BinaryTreeTraversal(tree, backend=backend)
613615
in_order = trav.depth_first_search(order='in_order')
614616
pre_order = trav.depth_first_search(order='pre_order')
615617
assert [node.key for node in in_order] == [10, 20, 30, 40, 50, 60, 70, 80, 90,
@@ -684,36 +686,42 @@ def test_RedBlackTree():
684686
assert [node.key for node in in_order if node.key is not None] == []
685687
assert [node.key for node in pre_order if node.key is not None] == []
686688

687-
tree = RedBlackTree()
689+
tree = RedBlackTree(backend=backend)
688690
tree.insert(50)
689691
tree.insert(40)
690692
tree.insert(30)
691693
tree.insert(20)
692694
tree.insert(10)
693695
tree.insert(5)
694696

695-
trav = BinaryTreeTraversal(tree)
697+
trav = BinaryTreeTraversal(tree, backend=backend)
696698
in_order = trav.depth_first_search(order='in_order')
697699
pre_order = trav.depth_first_search(order='pre_order')
698700
assert [node.key for node in in_order] == [5, 10, 20, 30, 40, 50]
699701
assert [node.key for node in pre_order] == [40, 20, 10, 5, 30, 50]
700702

703+
assert tree.search(50) == 0
704+
assert tree.search(20) == 3
705+
assert tree.search(30) == 2
701706
tree.delete(50)
702707
tree.delete(20)
703708
tree.delete(30)
709+
assert tree.search(50) is None
710+
assert tree.search(20) is None
711+
assert tree.search(30) is None
704712

705713
in_order = trav.depth_first_search(order='in_order')
706714
pre_order = trav.depth_first_search(order='pre_order')
707715
assert [node.key for node in in_order] == [5, 10, 40]
708716
assert [node.key for node in pre_order] == [10, 5, 40]
709717

710-
tree = RedBlackTree()
718+
tree = RedBlackTree(backend=backend)
711719
tree.insert(10)
712720
tree.insert(5)
713721
tree.insert(20)
714722
tree.insert(15)
715723

716-
trav = BinaryTreeTraversal(tree)
724+
trav = BinaryTreeTraversal(tree, backend=backend)
717725
in_order = trav.depth_first_search(order='in_order')
718726
pre_order = trav.depth_first_search(order='pre_order')
719727
assert [node.key for node in in_order] == [5, 10, 15, 20]
@@ -726,15 +734,15 @@ def test_RedBlackTree():
726734
assert [node.key for node in in_order] == [10, 15, 20]
727735
assert [node.key for node in pre_order] == [15, 10, 20]
728736

729-
tree = RedBlackTree()
737+
tree = RedBlackTree(backend=backend)
730738
tree.insert(10)
731739
tree.insert(5)
732740
tree.insert(20)
733741
tree.insert(15)
734742
tree.insert(2)
735743
tree.insert(6)
736744

737-
trav = BinaryTreeTraversal(tree)
745+
trav = BinaryTreeTraversal(tree,backend=backend)
738746
in_order = trav.depth_first_search(order='in_order')
739747
pre_order = trav.depth_first_search(order='pre_order')
740748
assert [node.key for node in in_order] == [2, 5, 6, 10, 15, 20]
@@ -746,3 +754,9 @@ def test_RedBlackTree():
746754
pre_order = trav.depth_first_search(order='pre_order')
747755
assert [node.key for node in in_order] == [2, 5, 6, 15, 20]
748756
assert [node.key for node in pre_order] == [6, 5, 2, 20, 15]
757+
758+
def test_RedBlackTree():
759+
_test_RedBlackTree(Backend.PYTHON)
760+
761+
def test_cpp_RedBlackTree():
762+
_test_RedBlackTree(Backend.CPP)

pydatastructs/utils/_backend/cpp/TreeNode.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ typedef struct {
1717
long height;
1818
PyObject* parent;
1919
long size;
20+
long color;
2021
} TreeNode;
2122

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

4446
return reinterpret_cast<PyObject*>(self);
4547
}
@@ -59,6 +61,7 @@ static struct PyMemberDef TreeNode_PyMemberDef[] = {
5961
{"left", T_OBJECT, offsetof(TreeNode, left), 0, "TreeNode left"},
6062
{"right", T_OBJECT, offsetof(TreeNode, right), 0, "TreeNode right"},
6163
{"parent", T_OBJECT, offsetof(TreeNode, parent), 0, "TreeNode parent"},
64+
{"color", T_LONG, offsetof(TreeNode, size), 0, "RedBlackTreeNode color"},
6265
{NULL},
6366
};
6467

0 commit comments

Comments
 (0)