Skip to content

Commit b2b04ec

Browse files
committed
added c++ backend for bfs algorithm
1 parent f7a6296 commit b2b04ec

File tree

8 files changed

+163
-4
lines changed

8 files changed

+163
-4
lines changed

pydatastructs/graphs/__init__.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
__all__ = []
22

3-
from . import graph
3+
from . import (
4+
graph,
5+
_extensions
6+
)
47
from .graph import (
58
Graph
69
)
@@ -22,7 +25,8 @@
2225
topological_sort,
2326
topological_sort_parallel,
2427
max_flow,
25-
find_bridges
28+
find_bridges,
29+
bfs
2630
)
2731

2832
__all__.extend(algorithms.__all__)
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from setuptools import setup, Extension
2+
import sysconfig
3+
4+
bfs_dfs_module = Extension(
5+
'_bfs_dfs', # Module name
6+
sources=['src/cpp/bfs_dfs.cpp'],
7+
include_dirs=[sysconfig.get_path('include')],
8+
extra_compile_args=['-std=c++11'],
9+
)
10+
11+
setup(
12+
name='my_pydatastructs',
13+
version='0.1',
14+
package_dir={'': 'src/python'},
15+
py_modules=['graph_algorithms'],
16+
ext_modules=[bfs_dfs_module],
17+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#include <Python.h>
2+
#include "bfs.hpp"
3+
4+
static PyMethodDef bfs_PyMethodDef[] = {
5+
{"bfs", (PyCFunction)bfs, METH_VARARGS | METH_KEYWORDS, "Breadth-First Search"},
6+
{NULL, NULL, 0, NULL}
7+
};
8+
9+
static struct PyModuleDef bfs_module = {
10+
PyModuleDef_HEAD_INIT,
11+
"bfs",
12+
"BFS algorithms module",
13+
-1,
14+
bfs_PyMethodDef
15+
};
16+
17+
PyMODINIT_FUNC PyInit_bfs(void) {
18+
PyObject *module = PyModule_Create(&bfs_module);
19+
if (module == NULL) return NULL;
20+
return module;
21+
}
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#ifndef BFS_HPP
2+
#define BFS_HPP
3+
4+
#define PY_SSIZE_T_CLEAN
5+
#include <Python.h>
6+
#include <queue>
7+
#include <unordered_map>
8+
9+
struct Graph {
10+
PyObject* adj_list;
11+
};
12+
13+
static PyObject* bfs_impl(PyObject* graph, PyObject* start_vertex, PyObject* visited = NULL) {
14+
if (!PyDict_Check(graph)) {
15+
PyErr_SetString(PyExc_TypeError, "Graph must be a dictionary");
16+
return NULL;
17+
}
18+
19+
std::queue<PyObject*> q;
20+
PyObject* visited_dict = visited ? visited : PyDict_New();
21+
22+
q.push(start_vertex);
23+
PyDict_SetItem(visited_dict, start_vertex, Py_True);
24+
25+
PyObject* result = PyList_New(0);
26+
27+
while (!q.empty()) {
28+
PyObject* vertex = q.front();
29+
q.pop();
30+
31+
PyList_Append(result, vertex);
32+
33+
PyObject* neighbors = PyDict_GetItem(graph, vertex);
34+
if (neighbors && PyList_Check(neighbors)) {
35+
Py_ssize_t size = PyList_Size(neighbors);
36+
for (Py_ssize_t i = 0; i < size; i++) {
37+
PyObject* neighbor = PyList_GetItem(neighbors, i);
38+
if (!PyDict_Contains(visited_dict, neighbor)) {
39+
q.push(neighbor);
40+
PyDict_SetItem(visited_dict, neighbor, Py_True);
41+
}
42+
}
43+
}
44+
}
45+
46+
if (!visited) Py_DECREF(visited_dict);
47+
return result;
48+
}
49+
50+
static PyObject* bfs(PyObject* self, PyObject* args, PyObject* kwds) {
51+
PyObject *graph = NULL, *start_vertex = NULL;
52+
static char *kwlist[] = {"graph", "start_vertex", NULL};
53+
54+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, &graph, &start_vertex)) {
55+
return NULL;
56+
}
57+
58+
PyObject* result = bfs_impl(graph, start_vertex);
59+
if (result == NULL) return NULL;
60+
Py_INCREF(result);
61+
return result;
62+
}
63+
64+
#endif

pydatastructs/graphs/_extensions.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from setuptools import Extension
2+
import sysconfig
3+
4+
project = 'pydatastructs'
5+
6+
module = 'graphs'
7+
8+
backend = "_backend"
9+
10+
cpp = 'cpp'
11+
12+
bfs = '.'.join([project, module, backend, cpp, '_bfs'])
13+
bfs_sources = ['/'.join([project, module, backend, cpp, 'algorithms.cpp'])]
14+
15+
extensions = [
16+
Extension(bfs, sources=bfs_sources)
17+
]

pydatastructs/graphs/algorithms.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pydatastructs.graphs.graph import Graph
1212
from pydatastructs.linear_data_structures.algorithms import merge_sort_parallel
1313
from pydatastructs import PriorityQueue
14+
from pydatastructs.graphs._backend.cpp._bfs import bfs as _bfs_cpp
1415

1516
__all__ = [
1617
'breadth_first_search',
@@ -24,7 +25,8 @@
2425
'topological_sort',
2526
'topological_sort_parallel',
2627
'max_flow',
27-
'find_bridges'
28+
'find_bridges',
29+
'bfs'
2830
]
2931

3032
Stack = Queue = deque
@@ -1368,3 +1370,18 @@ def dfs(u):
13681370
bridges.append((b, a))
13691371
bridges.sort()
13701372
return bridges
1373+
1374+
def bfs(graph, start_vertex, backend=Backend.PYTHON):
1375+
if backend == Backend.CPP:
1376+
return _bfs_cpp(graph, start_vertex)
1377+
from collections import deque
1378+
visited = set()
1379+
q = deque([start_vertex])
1380+
result = []
1381+
while q:
1382+
vertex = q.popleft()
1383+
if vertex not in visited:
1384+
visited.add(vertex)
1385+
result.append(vertex)
1386+
q.extend(graph.get(vertex, []))
1387+
return result

pydatastructs/graphs/tests/test_algorithms.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
breadth_first_search_parallel, minimum_spanning_tree,
33
minimum_spanning_tree_parallel, strongly_connected_components,
44
depth_first_search, shortest_paths,all_pair_shortest_paths, topological_sort,
5-
topological_sort_parallel, max_flow, find_bridges)
5+
topological_sort_parallel, max_flow, find_bridges, bfs, Backend)
66
from pydatastructs.utils.raises_util import raises
77

88
def test_breadth_first_search():
@@ -504,3 +504,20 @@ def _test_find_bridges(ds):
504504

505505
_test_find_bridges("List")
506506
_test_find_bridges("Matrix")
507+
508+
def test_bfs():
509+
graph = {
510+
0: [1, 2],
511+
1: [0, 3],
512+
2: [0],
513+
3: [1]
514+
}
515+
start_vertex = 0
516+
expected = [0, 1, 2, 3]
517+
518+
result_python = bfs(graph, start_vertex, backend=Backend.PYTHON)
519+
assert result_python == expected
520+
521+
result_cpp = bfs(graph, start_vertex, backend=Backend.CPP)
522+
result_cpp_list = [x for x in result_cpp]
523+
assert result_cpp_list == expected

setup.py

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from pydatastructs import linear_data_structures
44
from pydatastructs import miscellaneous_data_structures
55
from pydatastructs import trees
6+
from pydatastructs import graphs
67

78
with open("README.md", "r") as fh:
89
long_description = fh.read()
@@ -13,6 +14,7 @@
1314
extensions.extend(linear_data_structures._extensions.extensions)
1415
extensions.extend(miscellaneous_data_structures._extensions.extensions)
1516
extensions.extend(trees._extensions.extensions)
17+
extensions.extend(graphs._extensions.extensions)
1618

1719
setuptools.setup(
1820
name="cz-pydatastructs",

0 commit comments

Comments
 (0)