diff --git a/docs/source/pydatastructs/graphs/algorithms.rst b/docs/source/pydatastructs/graphs/algorithms.rst index c508421a..180eb5be 100644 --- a/docs/source/pydatastructs/graphs/algorithms.rst +++ b/docs/source/pydatastructs/graphs/algorithms.rst @@ -21,4 +21,6 @@ Algorithms .. autofunction:: pydatastructs.topological_sort_parallel -.. autofunction:: pydatastructs.find_bridges \ No newline at end of file +.. autofunction:: pydatastructs.find_bridges + +.. autofunction:: pydatastructs.bfs \ No newline at end of file diff --git a/pydatastructs/graphs/__init__.py b/pydatastructs/graphs/__init__.py index 21e0a5f3..7cb74df8 100644 --- a/pydatastructs/graphs/__init__.py +++ b/pydatastructs/graphs/__init__.py @@ -1,6 +1,11 @@ -__all__ = [] +__all__ = [ + 'bfs' + ] -from . import graph +from . import ( + graph, + _extensions +) from .graph import ( Graph ) @@ -22,7 +27,8 @@ topological_sort, topological_sort_parallel, max_flow, - find_bridges + find_bridges, + bfs ) __all__.extend(algorithms.__all__) diff --git a/pydatastructs/graphs/_backend/cpp/algorithms.cpp b/pydatastructs/graphs/_backend/cpp/algorithms.cpp new file mode 100644 index 00000000..d3d9a0aa --- /dev/null +++ b/pydatastructs/graphs/_backend/cpp/algorithms.cpp @@ -0,0 +1,21 @@ +#include +#include "bfs.hpp" + +static PyMethodDef bfs_PyMethodDef[] = { + {"bfs", (PyCFunction)bfs, METH_VARARGS | METH_KEYWORDS, "Breadth-First Search"}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef bfs_module = { + PyModuleDef_HEAD_INIT, + "_bfs", + "BFS algorithms module", + -1, + bfs_PyMethodDef +}; + +PyMODINIT_FUNC PyInit__bfs(void) { + PyObject *module = PyModule_Create(&bfs_module); + if (module == NULL) return NULL; + return module; +} diff --git a/pydatastructs/graphs/_backend/cpp/bfs.hpp b/pydatastructs/graphs/_backend/cpp/bfs.hpp new file mode 100644 index 00000000..5cc92bde --- /dev/null +++ b/pydatastructs/graphs/_backend/cpp/bfs.hpp @@ -0,0 +1,64 @@ +#ifndef BFS_HPP +#define BFS_HPP + +#define PY_SSIZE_T_CLEAN +#include +#include +#include + +struct Graph { + PyObject* adj_list; +}; + +static PyObject* bfs_impl(PyObject* graph, PyObject* start_vertex, PyObject* visited = NULL) { + if (!PyDict_Check(graph)) { + PyErr_SetString(PyExc_TypeError, "Graph must be a dictionary"); + return NULL; + } + + std::queue q; + PyObject* visited_dict = visited ? visited : PyDict_New(); + + q.push(start_vertex); + PyDict_SetItem(visited_dict, start_vertex, Py_True); + + PyObject* result = PyList_New(0); + + while (!q.empty()) { + PyObject* vertex = q.front(); + q.pop(); + + PyList_Append(result, vertex); + + PyObject* neighbors = PyDict_GetItem(graph, vertex); + if (neighbors && PyList_Check(neighbors)) { + Py_ssize_t size = PyList_Size(neighbors); + for (Py_ssize_t i = 0; i < size; i++) { + PyObject* neighbor = PyList_GetItem(neighbors, i); + if (!PyDict_Contains(visited_dict, neighbor)) { + q.push(neighbor); + PyDict_SetItem(visited_dict, neighbor, Py_True); + } + } + } + } + + if (!visited) Py_DECREF(visited_dict); + return result; +} + +static PyObject* bfs(PyObject* self, PyObject* args, PyObject* kwds) { + PyObject *graph = NULL, *start_vertex = NULL; + static char *kwlist[] = {"graph", "start_vertex", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, &graph, &start_vertex)) { + return NULL; + } + + PyObject* result = bfs_impl(graph, start_vertex); + if (result == NULL) return NULL; + Py_INCREF(result); + return result; +} + +#endif diff --git a/pydatastructs/graphs/_extensions.py b/pydatastructs/graphs/_extensions.py new file mode 100644 index 00000000..6c87aa16 --- /dev/null +++ b/pydatastructs/graphs/_extensions.py @@ -0,0 +1,17 @@ +from setuptools import Extension +import sysconfig + +project = 'pydatastructs' + +module = 'graphs' + +backend = "_backend" + +cpp = 'cpp' + +bfs = '.'.join([project, module, backend, cpp, '_bfs']) +bfs_sources = ['/'.join([project, module, backend, cpp, 'algorithms.cpp'])] + +extensions = [ + Extension(bfs, sources=bfs_sources) +] diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 334f522c..7f48d37c 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -11,6 +11,7 @@ from pydatastructs.graphs.graph import Graph from pydatastructs.linear_data_structures.algorithms import merge_sort_parallel from pydatastructs import PriorityQueue +from pydatastructs.graphs._backend.cpp._bfs import bfs as _bfs_cpp __all__ = [ 'breadth_first_search', @@ -24,7 +25,8 @@ 'topological_sort', 'topological_sort_parallel', 'max_flow', - 'find_bridges' + 'find_bridges', + 'bfs' ] Stack = Queue = deque @@ -1368,3 +1370,18 @@ def dfs(u): bridges.append((b, a)) bridges.sort() return bridges + +def bfs(graph, start_vertex, backend=Backend.PYTHON): + if backend == Backend.CPP: + return _bfs_cpp(graph, start_vertex) + from collections import deque + visited = set() + q = deque([start_vertex]) + result = [] + while q: + vertex = q.popleft() + if vertex not in visited: + visited.add(vertex) + result.append(vertex) + q.extend(graph.get(vertex, [])) + return result diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index 553377f3..d12baad8 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -2,7 +2,7 @@ breadth_first_search_parallel, minimum_spanning_tree, minimum_spanning_tree_parallel, strongly_connected_components, depth_first_search, shortest_paths,all_pair_shortest_paths, topological_sort, -topological_sort_parallel, max_flow, find_bridges) +topological_sort_parallel, max_flow, find_bridges, bfs, Backend) from pydatastructs.utils.raises_util import raises def test_breadth_first_search(): @@ -504,3 +504,20 @@ def _test_find_bridges(ds): _test_find_bridges("List") _test_find_bridges("Matrix") + +def test_bfs(): + graph = { + 0: [1, 2], + 1: [0, 3], + 2: [0], + 3: [1] + } + start_vertex = 0 + expected = [0, 1, 2, 3] + + result_python = bfs(graph, start_vertex, backend=Backend.PYTHON) + assert result_python == expected + + result_cpp = bfs(graph, start_vertex, backend=Backend.CPP) + result_cpp_list = [x for x in result_cpp] + assert result_cpp_list == expected diff --git a/setup.py b/setup.py index 60c4ec36..bbe3faef 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ from pydatastructs import linear_data_structures from pydatastructs import miscellaneous_data_structures from pydatastructs import trees +from pydatastructs import graphs with open("README.md", "r") as fh: long_description = fh.read() @@ -13,6 +14,7 @@ extensions.extend(linear_data_structures._extensions.extensions) extensions.extend(miscellaneous_data_structures._extensions.extensions) extensions.extend(trees._extensions.extensions) +extensions.extend(graphs._extensions.extensions) setuptools.setup( name="cz-pydatastructs",