Skip to content

Commit 1942891

Browse files
authored
Add CPP backend for search algorithms (#544)
1 parent 19e28e4 commit 1942891

File tree

5 files changed

+244
-13
lines changed

5 files changed

+244
-13
lines changed

pydatastructs/linear_data_structures/_backend/cpp/algorithms/algorithms.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ static PyMethodDef algorithms_PyMethodDef[] = {
1414
METH_VARARGS | METH_KEYWORDS, ""},
1515
{"is_ordered", (PyCFunction) is_ordered,
1616
METH_VARARGS | METH_KEYWORDS, ""},
17+
{"linear_search", (PyCFunction) linear_search,
18+
METH_VARARGS | METH_KEYWORDS, ""},
19+
{"binary_search", (PyCFunction) binary_search,
20+
METH_VARARGS | METH_KEYWORDS, ""},
21+
{"jump_search", (PyCFunction) jump_search,
22+
METH_VARARGS | METH_KEYWORDS, ""},
1723
{NULL, NULL, 0, NULL} /* Sentinel */
1824
};
1925

pydatastructs/linear_data_structures/_backend/cpp/algorithms/misc_algorithms.hpp

Lines changed: 192 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ static bool is_ordered_impl(PyObject* array, size_t lower, size_t upper,
1717
PyObject* i1_item = PyObject_GetItem(array, i1_PyObject);
1818
if (i_item == Py_None || i1_item == Py_None) continue;
1919
if( _comp(i_item, i1_item, comp) == 1 ) {
20-
printf("%d--\n", i);
2120
return false;
2221
}
2322
}
@@ -60,5 +59,197 @@ static PyObject* is_ordered(PyObject* self, PyObject* args, PyObject* kwds) {
6059
return res;
6160
}
6261

62+
static PyObject* linear_search(PyObject* self, PyObject* args, PyObject* kwds) {
63+
PyObject *args0 = NULL, *start = NULL, *end = NULL;
64+
PyObject *value = NULL, *res = NULL, *u = NULL;
65+
size_t lower, upper;
66+
args0 = PyObject_GetItem(args, PyZero);
67+
int is_DynamicOneDimensionalArray = _check_type(args0, &DynamicOneDimensionalArrayType);
68+
int is_OneDimensionalArray = _check_type(args0, &OneDimensionalArrayType);
69+
if( !is_DynamicOneDimensionalArray && !is_OneDimensionalArray ) {
70+
raise_exception_if_not_array(args0);
71+
return NULL;
72+
}
73+
start = PyObject_GetItem(kwds, PyUnicode_FromString("start"));
74+
if( start == NULL ) {
75+
PyErr_Clear();
76+
lower = 0;
77+
} else {
78+
lower = PyLong_AsSize_t(start);
79+
}
80+
end = PyObject_GetItem(kwds, PyUnicode_FromString("end"));
81+
if( end == NULL ) {
82+
PyErr_Clear();
83+
upper = PyObject_Length(args0) - 1;
84+
} else {
85+
upper = PyLong_AsSize_t(end);
86+
}
87+
value = PyObject_GetItem(args, PyLong_FromSize_t(1));
88+
if( value == NULL ) {
89+
PyErr_Format(PyExc_ValueError,
90+
"Expected Value to be not NULL");
91+
}
92+
for (size_t i = lower; i < upper + 1; i++) {
93+
u = PyObject_GetItem(args0, PyLong_FromSize_t(i));
94+
int result = PyObject_RichCompareBool(u, value, Py_EQ);
95+
if ( result == -1 ) {
96+
PyErr_Format(PyExc_ValueError,
97+
"Unable to compare %s object with %s object.",
98+
PyObject_AsString(PyObject_Repr(PyObject_Type(u))),
99+
PyObject_AsString(PyObject_Repr(PyObject_Type(value)))
100+
);
101+
} else if (result == 1) {
102+
if (i == 0) {
103+
res = PyZero;
104+
} else {
105+
res = PyLong_FromSize_t(i);
106+
}
107+
Py_INCREF(res);
108+
return res;
109+
}
110+
}
111+
res = Py_None;
112+
Py_INCREF(res);
113+
return res;
114+
}
115+
116+
static PyObject* binary_search(PyObject* self, PyObject* args, PyObject* kwds) {
117+
PyObject *args0 = NULL, *start = NULL, *end = NULL;
118+
PyObject *value = NULL, *res = NULL, *u = NULL, *comp = NULL;
119+
size_t lower, upper;
120+
args0 = PyObject_GetItem(args, PyZero);
121+
int is_DynamicOneDimensionalArray = _check_type(args0, &DynamicOneDimensionalArrayType);
122+
int is_OneDimensionalArray = _check_type(args0, &OneDimensionalArrayType);
123+
if( !is_DynamicOneDimensionalArray && !is_OneDimensionalArray ) {
124+
raise_exception_if_not_array(args0);
125+
return NULL;
126+
}
127+
comp = PyObject_GetItem(kwds, PyUnicode_FromString("comp"));
128+
if( comp == NULL ) {
129+
PyErr_Clear();
130+
}
131+
start = PyObject_GetItem(kwds, PyUnicode_FromString("start"));
132+
if( start == NULL ) {
133+
PyErr_Clear();
134+
lower = 0;
135+
} else {
136+
lower = PyLong_AsSize_t(start);
137+
}
138+
end = PyObject_GetItem(kwds, PyUnicode_FromString("end"));
139+
if( end == NULL ) {
140+
PyErr_Clear();
141+
upper = PyObject_Length(args0) - 1;
142+
} else {
143+
upper = PyLong_AsSize_t(end);
144+
}
145+
value = PyObject_GetItem(args, PyLong_FromSize_t(1));
146+
if( value == NULL ) {
147+
PyErr_Format(PyExc_ValueError,
148+
"Expected Value to be not NULL");
149+
}
150+
151+
int left = lower, right = upper;
152+
while (left <= right)
153+
{
154+
int middle = left/2 + right/2 + left % 2 * right % 2;
155+
u = PyObject_GetItem(args0, PyLong_FromSize_t(middle));
156+
int result = PyObject_RichCompareBool(u, value, Py_EQ);
157+
if ( result == -1 ) {
158+
PyErr_Format(PyExc_ValueError,
159+
"Unable to compare %s object with %s object.",
160+
PyObject_AsString(PyObject_Repr(PyObject_Type(u))),
161+
PyObject_AsString(PyObject_Repr(PyObject_Type(value)))
162+
);
163+
} else if (result == 1) {
164+
if (middle == 0) {
165+
res = PyZero;
166+
} else {
167+
res = PyLong_FromSize_t(middle);
168+
}
169+
Py_INCREF(res);
170+
return res;
171+
}
172+
if( _comp(u, value, comp) == 1 ) {
173+
left = middle + 1;
174+
} else {
175+
right = middle - 1;
176+
}
177+
}
178+
res = Py_None;
179+
Py_INCREF(res);
180+
return res;
181+
}
182+
183+
static PyObject* jump_search(PyObject* self, PyObject* args, PyObject* kwds) {
184+
PyObject *args0 = NULL, *start = NULL, *end = NULL;
185+
PyObject *value = NULL, *res = NULL, *u = NULL, *comp = NULL;
186+
size_t lower, upper;
187+
args0 = PyObject_GetItem(args, PyZero);
188+
int is_DynamicOneDimensionalArray = _check_type(args0, &DynamicOneDimensionalArrayType);
189+
int is_OneDimensionalArray = _check_type(args0, &OneDimensionalArrayType);
190+
if( !is_DynamicOneDimensionalArray && !is_OneDimensionalArray ) {
191+
raise_exception_if_not_array(args0);
192+
return NULL;
193+
}
194+
comp = PyObject_GetItem(kwds, PyUnicode_FromString("comp"));
195+
if( comp == NULL ) {
196+
PyErr_Clear();
197+
}
198+
start = PyObject_GetItem(kwds, PyUnicode_FromString("start"));
199+
if( start == NULL ) {
200+
PyErr_Clear();
201+
lower = 0;
202+
} else {
203+
lower = PyLong_AsSize_t(start);
204+
}
205+
end = PyObject_GetItem(kwds, PyUnicode_FromString("end"));
206+
if( end == NULL ) {
207+
PyErr_Clear();
208+
upper = PyObject_Length(args0) - 1;
209+
} else {
210+
upper = PyLong_AsSize_t(end);
211+
}
212+
value = PyObject_GetItem(args, PyLong_FromSize_t(1));
213+
if( value == NULL ) {
214+
PyErr_Format(PyExc_ValueError,
215+
"Expected Value to be not NULL");
216+
}
217+
int step = int(sqrt(double(upper - lower + 1)));
218+
int prev = lower;
219+
int element_pos = prev;
220+
u = PyObject_GetItem(args0, PyLong_FromSize_t(element_pos));
221+
while (element_pos <= upper && _comp(u, value, comp) == 1) {
222+
prev = element_pos;
223+
element_pos += step;
224+
if (element_pos > upper) {
225+
break;
226+
}
227+
u = PyObject_GetItem(args0, PyLong_FromSize_t(element_pos));
228+
}
229+
230+
while (prev <= upper) {
231+
u = PyObject_GetItem(args0, PyLong_FromSize_t(prev));
232+
int result = PyObject_RichCompareBool(u, value, Py_EQ);
233+
if ( result == -1 ) {
234+
PyErr_Format(PyExc_ValueError,
235+
"Unable to compare %s object with %s object.",
236+
PyObject_AsString(PyObject_Repr(PyObject_Type(u))),
237+
PyObject_AsString(PyObject_Repr(PyObject_Type(value)))
238+
);
239+
} else if (result == 1) {
240+
if (prev == 0) {
241+
res = PyZero;
242+
} else {
243+
res = PyLong_FromSize_t(prev);
244+
}
245+
Py_INCREF(res);
246+
return res;
247+
}
248+
prev += 1;
249+
}
250+
res = Py_None;
251+
Py_INCREF(res);
252+
return res;
253+
}
63254

64255
#endif

pydatastructs/linear_data_structures/algorithms.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,8 +1571,9 @@ def linear_search(array, value, **kwargs):
15711571
15721572
.. [1] https://en.wikipedia.org/wiki/Linear_search
15731573
"""
1574-
raise_if_backend_is_not_python(
1575-
linear_search, kwargs.get('backend', Backend.PYTHON))
1574+
backend = kwargs.pop("backend", Backend.PYTHON)
1575+
if backend == Backend.CPP:
1576+
return _algorithms.linear_search(array, value, **kwargs)
15761577
start = kwargs.get('start', 0)
15771578
end = kwargs.get('end', len(array) - 1)
15781579

@@ -1640,8 +1641,9 @@ def binary_search(array, value, **kwargs):
16401641
This algorithm assumes that the portion of the array
16411642
to be searched is already sorted.
16421643
"""
1643-
raise_if_backend_is_not_python(
1644-
binary_search, kwargs.get('backend', Backend.PYTHON))
1644+
backend = kwargs.pop("backend", Backend.PYTHON)
1645+
if backend == Backend.CPP:
1646+
return _algorithms.binary_search(array, value, **kwargs)
16451647
start = kwargs.get('start', 0)
16461648
end = kwargs.get('end', len(array) - 1)
16471649
comp = kwargs.get("comp", lambda u, v: u <= v)
@@ -1717,8 +1719,9 @@ def jump_search(array, value, **kwargs):
17171719
This algorithm assumes that the portion of the array
17181720
to be searched is already sorted.
17191721
"""
1720-
raise_if_backend_is_not_python(
1721-
jump_search, kwargs.get('backend', Backend.PYTHON))
1722+
backend = kwargs.pop("backend", Backend.PYTHON)
1723+
if backend == Backend.CPP:
1724+
return _algorithms.jump_search(array, value, **kwargs)
17221725
start = kwargs.get('start', 0)
17231726
end = kwargs.get('end', len(array) - 1)
17241727
comp = kwargs.get("comp", lambda u, v: u < v)

pydatastructs/linear_data_structures/tests/benchmarks/test_algorithms.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import random, timeit, functools, os, pytest
22
from pydatastructs import (OneDimensionalArray, Backend,
33
DynamicOneDimensionalArray, quick_sort, bubble_sort, selection_sort,
4-
insertion_sort, is_ordered)
4+
insertion_sort, is_ordered, linear_search, binary_search, jump_search)
55

66
def _test_common_sort(sort, **kwargs):
77
cpp = Backend.CPP
@@ -80,3 +80,31 @@ def _common(array_type, dtype, *args, **kwargs):
8080
# Case 3: float
8181
data = [random.random() * 2 * size for _ in range(size)]
8282
_common(OneDimensionalArray, float, data, backend=cpp)
83+
84+
85+
@pytest.mark.xfail
86+
def test_search():
87+
cpp = Backend.CPP
88+
repeat = 2
89+
number = 2
90+
91+
size = int(os.environ.get("PYDATASTRUCTS_BENCHMARK_SIZE", "4000"))
92+
93+
def _common(search_func, array_type, dtype, *args, **kwargs):
94+
array = array_type(dtype, *args, **kwargs)
95+
96+
timer_python = timeit.Timer(functools.partial(search_func, array, array[size-1]))
97+
python_backend = min(timer_python.repeat(repeat, number))
98+
99+
backend_dict = {"backend": cpp}
100+
timer_cpp = timeit.Timer(functools.partial(search_func, array, array[size-1],
101+
**backend_dict))
102+
cpp_backend = min(timer_cpp.repeat(repeat, number))
103+
104+
assert cpp_backend < python_backend
105+
106+
# Case 1: int
107+
data = [random.randint(0, 2 * size) for _ in range(size)]
108+
_common(linear_search, OneDimensionalArray, int, data, backend=cpp)
109+
_common(binary_search, OneDimensionalArray, int, data, backend=cpp)
110+
_common(jump_search, OneDimensionalArray, int, data, backend=cpp)

pydatastructs/linear_data_structures/tests/test_algorithms.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -373,16 +373,16 @@ def test_next_prev_permutation():
373373
_, orig_array = next_permutation(prev_array)
374374
assert str(orig_array) == str(array)
375375

376-
def _test_common_search(search_func, sort_array=True):
376+
def _test_common_search(search_func, sort_array=True, **kwargs):
377377
ODA = OneDimensionalArray
378378

379379
array = ODA(int, [1, 2, 5, 7, 10, 29, 40])
380380
for i in range(len(array)):
381-
assert i == search_func(array, array[i])
381+
assert i == search_func(array, array[i], **kwargs)
382382

383383
checker_array = [None, None, 2, 3, 4, 5, None]
384384
for i in range(len(array)):
385-
assert checker_array[i] == search_func(array, array[i], start=2, end=5)
385+
assert checker_array[i] == search_func(array, array[i], start=2, end=5, **kwargs)
386386

387387
random.seed(1000)
388388

@@ -395,16 +395,19 @@ def _test_common_search(search_func, sort_array=True):
395395
array = ODA(int, list(data))
396396

397397
for i in range(len(array)):
398-
assert search_func(array, array[i]) == i
398+
assert search_func(array, array[i], **kwargs) == i
399399

400400
for _ in range(50):
401-
assert search_func(array, random.randint(10001, 50000)) is None
401+
assert search_func(array, random.randint(10001, 50000), **kwargs) is None
402402

403403
def test_linear_search():
404404
_test_common_search(linear_search, sort_array=False)
405+
_test_common_search(linear_search, sort_array=False, backend=Backend.CPP)
405406

406407
def test_binary_search():
407408
_test_common_search(binary_search)
409+
_test_common_search(binary_search, backend=Backend.CPP)
408410

409411
def test_jump_search():
410412
_test_common_search(jump_search)
413+
_test_common_search(jump_search, backend=Backend.CPP)

0 commit comments

Comments
 (0)