From 70bbc875ab355cb75238437b7393bbfaacfb2ac9 Mon Sep 17 00:00:00 2001 From: arvinder004 Date: Tue, 8 Apr 2025 22:15:43 +0530 Subject: [PATCH 1/5] added backend for brick sort and brick sort parallel --- .../_backend/cpp/algorithms/algorithms.cpp | 6 + .../cpp/algorithms/quadratic_time_sort.hpp | 153 ++++++++++++++++++ .../linear_data_structures/algorithms.py | 8 + 3 files changed, 167 insertions(+) diff --git a/pydatastructs/linear_data_structures/_backend/cpp/algorithms/algorithms.cpp b/pydatastructs/linear_data_structures/_backend/cpp/algorithms/algorithms.cpp index 33df78323..1a0b356d8 100644 --- a/pydatastructs/linear_data_structures/_backend/cpp/algorithms/algorithms.cpp +++ b/pydatastructs/linear_data_structures/_backend/cpp/algorithms/algorithms.cpp @@ -4,6 +4,12 @@ #include "misc_algorithms.hpp" static PyMethodDef algorithms_PyMethodDef[] = { + {"brick_sort_parallel_impl", (PyCFunction) brick_sort_parallel_impl, + METH_VARARGS | METH_KEYWORDS, ""}, + {"brick_sort_impl", (PyCFunction) brick_sort_impl, + METH_VARARGS | METH_KEYWORDS, ""}, + {"brick_sort", (PyCFunction) brick_sort, + METH_VARARGS | METH_KEYWORDS, ""}, {"quick_sort", (PyCFunction) quick_sort, METH_VARARGS | METH_KEYWORDS, ""}, {"bubble_sort", (PyCFunction) bubble_sort, diff --git a/pydatastructs/linear_data_structures/_backend/cpp/algorithms/quadratic_time_sort.hpp b/pydatastructs/linear_data_structures/_backend/cpp/algorithms/quadratic_time_sort.hpp index 45e2d6dee..a197f6b60 100644 --- a/pydatastructs/linear_data_structures/_backend/cpp/algorithms/quadratic_time_sort.hpp +++ b/pydatastructs/linear_data_structures/_backend/cpp/algorithms/quadratic_time_sort.hpp @@ -185,4 +185,157 @@ static PyObject* insertion_sort(PyObject* self, PyObject* args, PyObject* kwds) } +static PyObject* brick_sort_impl(PyObject* array, size_t lower, size_t upper, PyObject* comp){ + bool is_sorted = false; + + while(!is_sorted){ + is_sorted = true; + + for (size_t i = lower + 1; i < upper; i += 2) { + PyObject* i_PyObject = PyLong_FromSize_t(i); + PyObject* i1_PyObject = PyLong_FromSize_t(i + 1); + if (_comp(PyObject_GetItem(array, i_PyObject), PyObject_GetItem(array, i1_PyObject), comp) != 1) { + PyObject* tmp = PyObject_GetItem(array, i1_PyObject); + PyObject_SetItem(array, i1_PyObject, PyObject_GetItem(array, i_PyObject)); + PyObject_SetItem(array, i_PyObject, tmp); + is_sorted = false; + } + } + + for (size_t i = lower; i < upper; i += 2) { + PyObject* i_PyObject = PyLong_FromSize_t(i); + PyObject* i1_PyObject = PyLong_FromSize_t(i + 1); + if (_comp(PyObject_GetItem(array, i_PyObject), PyObject_GetItem(array, i1_PyObject), comp) != 1) { + PyObject* tmp = PyObject_GetItem(array, i1_PyObject); + PyObject_SetItem(array, i1_PyObject, PyObject_GetItem(array, i_PyObject)); + PyObject_SetItem(array, i_PyObject, tmp); + is_sorted = false; + } + } + } + + return array; +} + +static PyObject* brick_sort(PyObject* self, PyObject* args, PyObject* kwds){ + PyObject *args0 = NULL, *start = NULL, *end = NULL; + PyObject *comp = NULL; + size_t lower, upper; + + args0 = PyObject_GetItem(args, PyZero); + int is_DynamicOneDimensionalArray = _check_type(args0, &DynamicOneDimensionalArrayType); + int is_OneDimensionalArray = _check_type(args0, &OneDimensionalArrayType); + if (!is_DynamicOneDimensionalArray && !is_OneDimensionalArray) { + raise_exception_if_not_array(args0); + return NULL; + } + + comp = PyObject_GetItem(kwds, PyUnicode_FromString("comp")); + if (comp == NULL) { + PyErr_Clear(); + } + + start = PyObject_GetItem(kwds, PyUnicode_FromString("start")); + if (start == NULL) { + PyErr_Clear(); + lower = 0; + } else { + lower = PyLong_AsSize_t(start); + } + + end = PyObject_GetItem(kwds, PyUnicode_FromString("end")); + if (end == NULL) { + PyErr_Clear(); + upper = PyObject_Length(args0) - 1; + } else { + upper = PyLong_AsSize_t(end); + } + + args0 = brick_sort_impl(args0, lower, upper, comp); + if (is_DynamicOneDimensionalArray) { + PyObject_CallMethod(args0, "_modify", "O", Py_True); + } + + Py_INCREF(args0); + return args0; +} + + +static PyObject* brick_sort_parallel_impl(PyObject* array, size_t lower, size_t upper, PyObject* comp) { + bool is_sorted = false; + + while (!is_sorted) { + is_sorted = true; + + #pragma omp parallel for + for (size_t i = lower + 1; i < upper; i += 2) { + PyObject* i_PyObject = PyLong_FromSize_t(i); + PyObject* i1_PyObject = PyLong_FromSize_t(i + 1); + if (_comp(PyObject_GetItem(array, i_PyObject), PyObject_GetItem(array, i1_PyObject), comp) != 1) { + PyObject* tmp = PyObject_GetItem(array, i1_PyObject); + PyObject_SetItem(array, i1_PyObject, PyObject_GetItem(array, i_PyObject)); + PyObject_SetItem(array, i_PyObject, tmp); + is_sorted = false; + } + } + + #pragma omp parallel for + for (size_t i = lower; i < upper; i += 2) { + PyObject* i_PyObject = PyLong_FromSize_t(i); + PyObject* i1_PyObject = PyLong_FromSize_t(i + 1); + if (_comp(PyObject_GetItem(array, i_PyObject), PyObject_GetItem(array, i1_PyObject), comp) != 1) { + PyObject* tmp = PyObject_GetItem(array, i1_PyObject); + PyObject_SetItem(array, i1_PyObject, PyObject_GetItem(array, i_PyObject)); + PyObject_SetItem(array, i_PyObject, tmp); + is_sorted = false; + } + } + } + + return array; +} + +static PyObject* brick_sort_parallel(PyObject* self, PyObject* args, PyObject* kwds) { + PyObject *args0 = NULL, *start = NULL, *end = NULL; + PyObject *comp = NULL; + size_t lower, upper; + + args0 = PyObject_GetItem(args, PyZero); + int is_DynamicOneDimensionalArray = _check_type(args0, &DynamicOneDimensionalArrayType); + int is_OneDimensionalArray = _check_type(args0, &OneDimensionalArrayType); + if (!is_DynamicOneDimensionalArray && !is_OneDimensionalArray) { + raise_exception_if_not_array(args0); + return NULL; + } + + comp = PyObject_GetItem(kwds, PyUnicode_FromString("comp")); + if (comp == NULL) { + PyErr_Clear(); + } + + start = PyObject_GetItem(kwds, PyUnicode_FromString("start")); + if (start == NULL) { + PyErr_Clear(); + lower = 0; + } else { + lower = PyLong_AsSize_t(start); + } + + end = PyObject_GetItem(kwds, PyUnicode_FromString("end")); + if (end == NULL) { + PyErr_Clear(); + upper = PyObject_Length(args0) - 1; + } else { + upper = PyLong_AsSize_t(end); + } + + args0 = brick_sort_parallel_impl(args0, lower, upper, comp); + if (is_DynamicOneDimensionalArray) { + PyObject_CallMethod(args0, "_modify", "O", Py_True); + } + + Py_INCREF(args0); + return args0; +} + #endif diff --git a/pydatastructs/linear_data_structures/algorithms.py b/pydatastructs/linear_data_structures/algorithms.py index 8b7295833..de32dfdea 100644 --- a/pydatastructs/linear_data_structures/algorithms.py +++ b/pydatastructs/linear_data_structures/algorithms.py @@ -181,6 +181,10 @@ def brick_sort(array, **kwargs): ========== .. [1] https://www.geeksforgeeks.org/odd-even-sort-brick-sort/ """ + backend = kwargs.pop("backend", Backend.PYTHON) + if backend == Backend.CPP: + return _algorithms.brick_sort(array, **kwargs) + raise_if_backend_is_not_python( brick_sort, kwargs.get('backend', Backend.PYTHON)) start = kwargs.get('start', 0) @@ -257,6 +261,10 @@ def brick_sort_parallel(array, num_threads, **kwargs): .. [1] https://en.wikipedia.org/wiki/Odd%E2%80%93even_sort """ + backend = kwargs.pop("backend", Backend.PYTHON) + if backend == Backend.CPP: + return _algorithms.brick_sort_parallel(array, num_threads, **kwargs) + raise_if_backend_is_not_python( brick_sort_parallel, kwargs.get('backend', Backend.PYTHON)) start = kwargs.get('start', 0) From 3777aa6056f17b6544ed3230cdee52ddb1d4d473 Mon Sep 17 00:00:00 2001 From: arvinder004 Date: Wed, 9 Apr 2025 14:49:38 +0530 Subject: [PATCH 2/5] added tests for brick sort and brick sort parallel for c++ backend --- pydatastructs/linear_data_structures/tests/test_algorithms.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pydatastructs/linear_data_structures/tests/test_algorithms.py b/pydatastructs/linear_data_structures/tests/test_algorithms.py index dc6336b48..66dbe2d64 100644 --- a/pydatastructs/linear_data_structures/tests/test_algorithms.py +++ b/pydatastructs/linear_data_structures/tests/test_algorithms.py @@ -84,9 +84,11 @@ def test_merge_sort_parallel(): def test_brick_sort(): _test_common_sort(brick_sort) + _test_common_search(brick_sort, Backend = Backend.CPP) def test_brick_sort_parallel(): _test_common_sort(brick_sort_parallel, num_threads=3) + _test_common_search(brick_sort_parallel, num_threads=3, Backend = Backend.CPP) def test_heapsort(): _test_common_sort(heapsort) From d38e21f2dee69672637779299adc43cf8ce5b88f Mon Sep 17 00:00:00 2001 From: arvinder004 Date: Wed, 9 Apr 2025 14:49:57 +0530 Subject: [PATCH 3/5] added tests for brick sort and brick sort parallel for c++ backend --- pydatastructs/linear_data_structures/tests/test_algorithms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pydatastructs/linear_data_structures/tests/test_algorithms.py b/pydatastructs/linear_data_structures/tests/test_algorithms.py index 66dbe2d64..0dbd2cf5b 100644 --- a/pydatastructs/linear_data_structures/tests/test_algorithms.py +++ b/pydatastructs/linear_data_structures/tests/test_algorithms.py @@ -84,11 +84,11 @@ def test_merge_sort_parallel(): def test_brick_sort(): _test_common_sort(brick_sort) - _test_common_search(brick_sort, Backend = Backend.CPP) + _test_common_sort(brick_sort, Backend = Backend.CPP) def test_brick_sort_parallel(): _test_common_sort(brick_sort_parallel, num_threads=3) - _test_common_search(brick_sort_parallel, num_threads=3, Backend = Backend.CPP) + _test_common_sort(brick_sort_parallel, num_threads=3, Backend = Backend.CPP) def test_heapsort(): _test_common_sort(heapsort) From 9377c6b030c5b707ab9881c48d6132e084a62273 Mon Sep 17 00:00:00 2001 From: arvinder004 Date: Wed, 9 Apr 2025 16:51:05 +0530 Subject: [PATCH 4/5] added benchmark tests for brick sort and brick sort parallel --- .../tests/benchmarks/test_algorithms.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pydatastructs/linear_data_structures/tests/benchmarks/test_algorithms.py b/pydatastructs/linear_data_structures/tests/benchmarks/test_algorithms.py index da676c69c..dbc09cd83 100644 --- a/pydatastructs/linear_data_structures/tests/benchmarks/test_algorithms.py +++ b/pydatastructs/linear_data_structures/tests/benchmarks/test_algorithms.py @@ -1,7 +1,8 @@ import random, timeit, functools, os, pytest from pydatastructs import (OneDimensionalArray, Backend, DynamicOneDimensionalArray, quick_sort, bubble_sort, selection_sort, - insertion_sort, is_ordered, linear_search, binary_search, jump_search) + insertion_sort, is_ordered, linear_search, binary_search, jump_search, + brick_sort, brick_sort_parallel) def _test_common_sort(sort, **kwargs): cpp = Backend.CPP @@ -52,6 +53,16 @@ def test_insertion_sort(): _test_common_sort(insertion_sort, size=2000) +@pytest.mark.xfail +def test_brick_sort(): + _test_common_sort(brick_sort, size=2000) + + +@pytest.mark.xfail +def test_brick_sort_parallel(): + _test_common_sort(brick_sort_parallel, size=2000, num_threads=4) + + @pytest.mark.xfail def test_is_ordered(): cpp = Backend.CPP From 710a2de1287c3243d48c9050e0b07b8e55acb508 Mon Sep 17 00:00:00 2001 From: arvinder004 Date: Thu, 10 Apr 2025 08:50:33 +0530 Subject: [PATCH 5/5] reduced dataset size for benchmark tests for brick sort sequential and parallel --- .../tests/benchmarks/test_algorithms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pydatastructs/linear_data_structures/tests/benchmarks/test_algorithms.py b/pydatastructs/linear_data_structures/tests/benchmarks/test_algorithms.py index dbc09cd83..8577dbf18 100644 --- a/pydatastructs/linear_data_structures/tests/benchmarks/test_algorithms.py +++ b/pydatastructs/linear_data_structures/tests/benchmarks/test_algorithms.py @@ -55,12 +55,12 @@ def test_insertion_sort(): @pytest.mark.xfail def test_brick_sort(): - _test_common_sort(brick_sort, size=2000) + _test_common_sort(brick_sort, size=500) @pytest.mark.xfail def test_brick_sort_parallel(): - _test_common_sort(brick_sort_parallel, size=2000, num_threads=4) + _test_common_sort(brick_sort_parallel, size=500, num_threads=4) @pytest.mark.xfail