Skip to content

Commit 8c91a6a

Browse files
authored
Added C++ backend for pydatastructs.quick_sort (#482)
1 parent 5db2912 commit 8c91a6a

File tree

11 files changed

+318
-52
lines changed

11 files changed

+318
-52
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#include <Python.h>
2+
#include "quick_sort.hpp"
3+
4+
static PyMethodDef algorithms_PyMethodDef[] = {
5+
{"quick_sort", (PyCFunction) quick_sort,
6+
METH_VARARGS | METH_KEYWORDS, ""},
7+
{NULL, NULL, 0, NULL} /* Sentinel */
8+
};
9+
10+
static struct PyModuleDef algorithms_struct = {
11+
PyModuleDef_HEAD_INIT,
12+
"_algorithms",
13+
0,
14+
-1,
15+
algorithms_PyMethodDef
16+
};
17+
18+
PyMODINIT_FUNC PyInit__algorithms(void) {
19+
Py_Initialize();
20+
PyObject *algorithms = PyModule_Create(&algorithms_struct);
21+
return algorithms;
22+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#ifndef LINEAR_DATA_STRUCTURES_ALGORITHMS_QUICK_SORT_HPP
2+
#define LINEAR_DATA_STRUCTURES_ALGORITHMS_QUICK_SORT_HPP
3+
4+
#define PY_SSIZE_T_CLEAN
5+
#include <Python.h>
6+
#include "../arrays/OneDimensionalArray.hpp"
7+
#include "../arrays/DynamicOneDimensionalArray.hpp"
8+
#include "../../../../utils/_backend/cpp/utils.hpp"
9+
#include <stack>
10+
11+
static PyObject* call_pick_pivot_element(PyObject* pick_pivot_element,
12+
size_t low, size_t high, PyObject* array) {
13+
PyObject* high_PyObject = PyLong_FromSize_t(high);
14+
if( pick_pivot_element ) {
15+
return PyObject_CallFunctionObjArgs(pick_pivot_element,
16+
PyLong_FromSize_t(low),
17+
high_PyObject,
18+
array);
19+
}
20+
21+
return PyObject_GetItem(array, high_PyObject);
22+
}
23+
24+
static size_t quick_sort_partition(size_t low, size_t high,
25+
PyObject* pick_pivot_element, PyObject* comp, PyObject* array) {
26+
int64_t i = low - 1;
27+
PyObject* x = call_pick_pivot_element(pick_pivot_element, low, high, array);
28+
for( size_t j = low; j < high; j++ ) {
29+
PyObject* j_PyObject = PyLong_FromSize_t(j);
30+
if( _comp(PyObject_GetItem(array, j_PyObject), x, comp) == 1 ) {
31+
i = i + 1;
32+
PyObject* i_PyObject = PyLong_FromLongLong(i);
33+
PyObject* tmp = PyObject_GetItem(array, i_PyObject);
34+
PyObject_SetItem(array, i_PyObject,
35+
PyObject_GetItem(array, j_PyObject));
36+
PyObject_SetItem(array, j_PyObject, tmp);
37+
}
38+
}
39+
PyObject* i_PyObject = PyLong_FromLongLong(i + 1);
40+
PyObject* high_PyObject = PyLong_FromSize_t(high);
41+
PyObject* tmp = PyObject_GetItem(array, i_PyObject);
42+
PyObject_SetItem(array, i_PyObject,
43+
PyObject_GetItem(array, high_PyObject));
44+
PyObject_SetItem(array, high_PyObject, tmp);
45+
return i + 1;
46+
}
47+
48+
static PyObject* quick_sort_impl(PyObject* array, size_t lower, size_t upper,
49+
PyObject* comp, PyObject* pick_pivot_element) {
50+
51+
size_t low, high, p;
52+
std::stack<size_t> rstack;
53+
54+
rstack.push(lower);
55+
rstack.push(upper);
56+
57+
while( !rstack.empty() ) {
58+
high = rstack.top();
59+
rstack.pop();
60+
low = rstack.top();
61+
rstack.pop();
62+
p = quick_sort_partition(low, high, pick_pivot_element,
63+
comp, array);
64+
if( p - 1 > low ) {
65+
rstack.push(low);
66+
rstack.push(p - 1);
67+
}
68+
if( p + 1 < high ) {
69+
rstack.push(p + 1);
70+
rstack.push(high);
71+
}
72+
}
73+
74+
return array;
75+
}
76+
77+
static PyObject* quick_sort(PyObject* self, PyObject* args, PyObject* kwds) {
78+
PyObject *args0 = NULL, *start = NULL, *end = NULL;
79+
PyObject *comp = NULL, *pick_pivot_element = NULL;
80+
size_t lower, upper;
81+
args0 = PyObject_GetItem(args, PyZero);
82+
int is_DynamicOneDimensionalArray = _check_type(args0, &DynamicOneDimensionalArrayType);
83+
int is_OneDimensionalArray = _check_type(args0, &OneDimensionalArrayType);
84+
if( !is_DynamicOneDimensionalArray && !is_OneDimensionalArray ) {
85+
raise_exception_if_not_array(args0);
86+
return NULL;
87+
}
88+
comp = PyObject_GetItem(kwds, PyUnicode_FromString("comp"));
89+
if( comp == NULL ) {
90+
PyErr_Clear();
91+
}
92+
pick_pivot_element = PyObject_GetItem(kwds, PyUnicode_FromString("pick_pivot_element"));
93+
if( pick_pivot_element == NULL ) {
94+
PyErr_Clear();
95+
}
96+
start = PyObject_GetItem(kwds, PyUnicode_FromString("start"));
97+
if( start == NULL ) {
98+
PyErr_Clear();
99+
lower = 0;
100+
} else {
101+
lower = PyLong_AsSize_t(start);
102+
}
103+
end = PyObject_GetItem(kwds, PyUnicode_FromString("end"));
104+
if( end == NULL ) {
105+
PyErr_Clear();
106+
upper = PyObject_Length(args0) - 1;
107+
} else {
108+
upper = PyLong_AsSize_t(end);
109+
}
110+
111+
args0 = quick_sort_impl(args0, lower, upper, comp, pick_pivot_element);
112+
if( is_DynamicOneDimensionalArray ) {
113+
PyObject_CallMethod(args0, "_modify", "O", Py_True);
114+
}
115+
Py_INCREF(args0);
116+
return args0;
117+
}
118+
119+
#endif

pydatastructs/linear_data_structures/_backend/cpp/arrays/DynamicOneDimensionalArray.hpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,30 +90,30 @@ static PyObject* DynamicOneDimensionalArray__modify(DynamicOneDimensionalArray *
9090
force = Py_False;
9191
}
9292

93-
long i;
93+
long i, j;
9494
PyObject** _data = self->_one_dimensional_array->_data;
9595
long _size = (long) self->_one_dimensional_array->_size;
9696
if( force == Py_True ) {
9797
i = -1;
98-
while( _data[i] == Py_None) {
98+
j = _size - 1;
99+
while( _data[j] == Py_None) {
99100
i--;
101+
j--;
100102
}
101-
long _last_pos_filled = std::abs(i) % _size;
102-
if( i < 0 ) {
103-
_last_pos_filled += _size;
104-
}
105-
self->_last_pos_filled = _last_pos_filled;
103+
self->_last_pos_filled = i + _size;
106104
}
107105

108106
if( ((float) self->_num)/((float) _size) < self->_load_factor ) {
109107
long new_size = 2 * self->_num + 1;
110108
PyObject** arr_new = reinterpret_cast<PyObject**>(std::malloc(new_size * sizeof(PyObject*)));
111109
for( i = 0; i < new_size; i++ ) {
110+
Py_INCREF(Py_None);
112111
arr_new[i] = Py_None;
113112
}
114113
long j = 0;
115114
for( i = 0; i <= self->_last_pos_filled; i++ ) {
116115
if( _data[i] != Py_None ) {
116+
Py_INCREF(Py_None);
117117
arr_new[j] = _data[i];
118118
j += 1;
119119
}

pydatastructs/linear_data_structures/_extensions.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88

99
cpp = 'cpp'
1010

11-
submodule = 'arrays'
12-
13-
dummy_submodule = '_arrays.py'
14-
1511
arrays = '.'.join([project, module, backend, cpp, '_arrays'])
1612
arrays_sources = ['/'.join([project, module, backend, cpp,
17-
submodule, 'arrays.cpp'])]
13+
'arrays', 'arrays.cpp'])]
14+
15+
algorithms = '.'.join([project, module, backend, cpp, '_algorithms'])
16+
algorithms_sources = ['/'.join([project, module, backend, cpp,
17+
'algorithms', 'algorithms.cpp'])]
1818

1919
extensions = [
20-
Extension(arrays, sources=arrays_sources)
20+
Extension(arrays, sources=arrays_sources),
21+
Extension(algorithms, sources=algorithms_sources)
2122
]

pydatastructs/linear_data_structures/algorithms.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from pydatastructs.linear_data_structures.arrays import (
22
OneDimensionalArray, DynamicArray, DynamicOneDimensionalArray, Array)
3+
from pydatastructs.linear_data_structures._backend.cpp import _algorithms, _arrays
34
from pydatastructs.utils.misc_util import (
45
_check_type, _comp, Backend,
56
raise_if_backend_is_not_python)
@@ -129,8 +130,8 @@ def merge_sort_parallel(array, num_threads, **kwargs):
129130
end, comp).result()
130131
i = i + 2*pow_2
131132

132-
if _check_type(array, DynamicArray):
133-
array._modify(force=True)
133+
if _check_type(array, (DynamicArray, _arrays.DynamicOneDimensionalArray)):
134+
array._modify(True)
134135

135136
def brick_sort(array, **kwargs):
136137
"""
@@ -195,8 +196,8 @@ def brick_sort(array, **kwargs):
195196
array[i], array[i+1] = array[i+1], array[i]
196197
is_sorted = False
197198

198-
if _check_type(array, DynamicArray):
199-
array._modify(force=True)
199+
if _check_type(array, (DynamicArray, _arrays.DynamicOneDimensionalArray)):
200+
array._modify(True)
200201

201202
def _brick_sort_swap(array, i, j, comp, is_sorted):
202203
if _comp(array[j], array[i], comp):
@@ -269,8 +270,8 @@ def brick_sort_parallel(array, num_threads, **kwargs):
269270
for i in range(start, end, 2):
270271
Executor.submit(_brick_sort_swap, array, i, i + 1, comp, is_sorted).result()
271272

272-
if _check_type(array, DynamicArray):
273-
array._modify(force=True)
273+
if _check_type(array, (DynamicArray, _arrays.DynamicOneDimensionalArray)):
274+
array._modify(True)
274275

275276
def heapsort(array, **kwargs):
276277
"""
@@ -333,8 +334,8 @@ def heapsort(array, **kwargs):
333334
array[i] = h.extract().key
334335
i += 1
335336

336-
if _check_type(array, DynamicArray):
337-
array._modify(force=True)
337+
if _check_type(array, (DynamicArray, _arrays.DynamicOneDimensionalArray)):
338+
array._modify(True)
338339

339340
def counting_sort(array: Array, **kwargs) -> Array:
340341
"""
@@ -596,8 +597,8 @@ def bucket_sort(array: Array, **kwargs) -> Array:
596597
array[i] = None
597598
for i in range(start, end - number_of_null_values + 1):
598599
array[i] = sorted_list[i-start]
599-
if _check_type(array, DynamicArray):
600-
array._modify(force=True)
600+
if _check_type(array, (DynamicArray, _arrays.DynamicOneDimensionalArray)):
601+
array._modify(True)
601602
return array
602603

603604
def cocktail_shaker_sort(array: Array, **kwargs) -> Array:
@@ -679,8 +680,8 @@ def swap(i, j):
679680
swapping = False
680681
lower = lower + 1
681682

682-
if _check_type(array, DynamicArray):
683-
array._modify(force=True)
683+
if _check_type(array, (DynamicArray, _arrays.DynamicOneDimensionalArray)):
684+
array._modify(True)
684685

685686
return array
686687

@@ -747,8 +748,9 @@ def quick_sort(array: Array, **kwargs) -> Array:
747748
748749
.. [1] https://en.wikipedia.org/wiki/Quicksort
749750
"""
750-
raise_if_backend_is_not_python(
751-
quick_sort, kwargs.get('backend', Backend.PYTHON))
751+
backend = kwargs.pop("backend", Backend.PYTHON)
752+
if backend == Backend.CPP:
753+
return _algorithms.quick_sort(array, **kwargs)
752754
from pydatastructs import Stack
753755
comp = kwargs.get("comp", lambda u, v: u <= v)
754756
pick_pivot_element = kwargs.get("pick_pivot_element",
@@ -782,8 +784,8 @@ def partition(low, high, pick_pivot_element):
782784
stack.push(p + 1)
783785
stack.push(high)
784786

785-
if _check_type(array, DynamicArray):
786-
array._modify(force=True)
787+
if _check_type(array, (DynamicArray, _arrays.DynamicOneDimensionalArray)):
788+
array._modify(True)
787789

788790
return array
789791

@@ -1370,8 +1372,8 @@ def bubble_sort(array, **kwargs):
13701372
if not _comp(array[j], array[j + 1], comp):
13711373
array[j], array[j + 1] = array[j + 1], array[j]
13721374

1373-
if _check_type(array, DynamicArray):
1374-
array._modify(force=True)
1375+
if _check_type(array, (DynamicArray, _arrays.DynamicOneDimensionalArray)):
1376+
array._modify(True)
13751377

13761378
return array
13771379

@@ -1442,8 +1444,8 @@ def selection_sort(array, **kwargs):
14421444
if jMin != i:
14431445
array[i], array[jMin] = array[jMin], array[i]
14441446

1445-
if _check_type(array, DynamicArray):
1446-
array._modify(force=True)
1447+
if _check_type(array, (DynamicArray, _arrays.DynamicOneDimensionalArray)):
1448+
array._modify(True)
14471449

14481450
return array
14491451

@@ -1514,8 +1516,8 @@ def insertion_sort(array, **kwargs):
15141516
j -= 1
15151517
array[j] = temp
15161518

1517-
if _check_type(array, DynamicArray):
1518-
array._modify(force=True)
1519+
if _check_type(array, (DynamicArray, _arrays.DynamicOneDimensionalArray)):
1520+
array._modify(True)
15191521

15201522
return array
15211523

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import random, timeit, functools, os, pytest
2+
from pydatastructs import (OneDimensionalArray, Backend,
3+
DynamicOneDimensionalArray, quick_sort)
4+
5+
def _test_common_sort(sort, **kwargs):
6+
cpp = Backend.CPP
7+
repeat = 2
8+
number = 2
9+
10+
size = int(os.environ.get("PYDATASTRUCTS_BENCHMARK_SIZE", "1000"))
11+
size = kwargs.get("size", size)
12+
13+
def _common(array_type, dtype, *args, **kwargs):
14+
array = array_type(dtype, *args, **kwargs)
15+
16+
timer_python = timeit.Timer(functools.partial(sort, array))
17+
python_backend = min(timer_python.repeat(repeat, number))
18+
19+
backend_dict = {"backend": cpp}
20+
timer_cpp = timeit.Timer(functools.partial(sort, array, **backend_dict))
21+
cpp_backend = min(timer_cpp.repeat(repeat, number))
22+
23+
assert cpp_backend < python_backend
24+
25+
# Case 1: int
26+
data = [random.randint(0, 2 * size) for _ in range(size)]
27+
_common(OneDimensionalArray, int, data, backend=cpp)
28+
29+
# Case 3: float
30+
data = [random.random() * 2 * size for _ in range(size)]
31+
_common(OneDimensionalArray, float, data, backend=cpp)
32+
33+
34+
@pytest.mark.xfail
35+
def test_quick_sort():
36+
_test_common_sort(quick_sort, size=4000)

0 commit comments

Comments
 (0)