Skip to content

Commit 4e068f2

Browse files
committed
Feature Request: Implement C++ Backend for Breadth-First Search (BFS) codezonediitj#674
Open
1 parent eb237c1 commit 4e068f2

File tree

3 files changed

+197
-17
lines changed

3 files changed

+197
-17
lines changed

pydatastructs/graphs/_backend/cpp/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#include <Python.h>
2+
#include <iostream>
3+
#include <unordered_map>
4+
#include <vector>
5+
#include <queue>
6+
#include <thread>
7+
#include <functional>
8+
#include <mutex>
9+
10+
// Graph class to represent the graph
11+
class Graph {
12+
public:
13+
// Add a node to the graph
14+
void addNode(const std::string& nodeName) {
15+
adjacencyList[nodeName] = std::vector<std::string>();
16+
}
17+
18+
// Add an edge between two nodes
19+
void addEdge(const std::string& node1, const std::string& node2) {
20+
adjacencyList[node1].push_back(node2);
21+
adjacencyList[node2].push_back(node1); // Assuming undirected graph
22+
}
23+
24+
// Get neighbors of a node
25+
const std::vector<std::string>& getNeighbors(const std::string& node) const {
26+
return adjacencyList.at(node);
27+
}
28+
29+
// Check if node exists
30+
bool hasNode(const std::string& node) const {
31+
return adjacencyList.find(node) != adjacencyList.end();
32+
}
33+
34+
private:
35+
std::unordered_map<std::string, std::vector<std::string>> adjacencyList;
36+
};
37+
38+
// Python Graph Object Definition
39+
typedef struct {
40+
PyObject_HEAD
41+
Graph graph;
42+
} PyGraphObject;
43+
44+
static PyTypeObject PyGraphType;
45+
46+
// Serial BFS Implementation
47+
static PyObject* breadth_first_search(PyObject* self, PyObject* args) {
48+
PyGraphObject* pyGraph;
49+
const char* sourceNode;
50+
PyObject* pyOperation;
51+
52+
if (!PyArg_ParseTuple(args, "OsO", &pyGraph, &sourceNode, &pyOperation)) {
53+
return NULL;
54+
}
55+
56+
auto operation = [pyOperation](const std::string& currNode, const std::string& nextNode) -> bool {
57+
PyObject* result = PyObject_CallFunction(pyOperation, "ss", currNode.c_str(), nextNode.c_str());
58+
if (result == NULL) {
59+
return false;
60+
}
61+
bool status = PyObject_IsTrue(result);
62+
Py_XDECREF(result);
63+
return status;
64+
};
65+
66+
std::unordered_map<std::string, bool> visited;
67+
std::queue<std::string> bfsQueue;
68+
bfsQueue.push(sourceNode);
69+
visited[sourceNode] = true;
70+
71+
while (!bfsQueue.empty()) {
72+
std::string currNode = bfsQueue.front();
73+
bfsQueue.pop();
74+
75+
for (const std::string& nextNode : pyGraph->graph.getNeighbors(currNode)) {
76+
if (!visited[nextNode]) {
77+
if (!operation(currNode, nextNode)) {
78+
Py_RETURN_NONE;
79+
}
80+
bfsQueue.push(nextNode);
81+
visited[nextNode] = true;
82+
}
83+
}
84+
}
85+
86+
Py_RETURN_NONE;
87+
}
88+
89+
// Parallel BFS Implementation
90+
static PyObject* breadth_first_search_parallel(PyObject* self, PyObject* args) {
91+
PyGraphObject* pyGraph;
92+
const char* sourceNode;
93+
int numThreads;
94+
PyObject* pyOperation;
95+
96+
if (!PyArg_ParseTuple(args, "OsIO", &pyGraph, &sourceNode, &numThreads, &pyOperation)) {
97+
return NULL;
98+
}
99+
100+
auto operation = [pyOperation](const std::string& currNode, const std::string& nextNode) -> bool {
101+
PyObject* result = PyObject_CallFunction(pyOperation, "ss", currNode.c_str(), nextNode.c_str());
102+
if (result == NULL) {
103+
return false;
104+
}
105+
bool status = PyObject_IsTrue(result);
106+
Py_XDECREF(result);
107+
return status;
108+
};
109+
110+
std::unordered_map<std::string, bool> visited;
111+
std::queue<std::string> bfsQueue;
112+
std::mutex queueMutex;
113+
114+
bfsQueue.push(sourceNode);
115+
visited[sourceNode] = true;
116+
117+
auto bfsWorker = [&](int threadId) {
118+
while (true) {
119+
std::string currNode;
120+
{
121+
std::lock_guard<std::mutex> lock(queueMutex);
122+
if (bfsQueue.empty()) return;
123+
currNode = bfsQueue.front();
124+
bfsQueue.pop();
125+
}
126+
127+
for (const std::string& nextNode : pyGraph->graph.getNeighbors(currNode)) {
128+
if (!visited[nextNode]) {
129+
if (!operation(currNode, nextNode)) {
130+
return;
131+
}
132+
std::lock_guard<std::mutex> lock(queueMutex);
133+
bfsQueue.push(nextNode);
134+
visited[nextNode] = true;
135+
}
136+
}
137+
}
138+
};
139+
140+
std::vector<std::thread> threads;
141+
for (int i = 0; i < numThreads; ++i) {
142+
threads.push_back(std::thread(bfsWorker, i));
143+
}
144+
145+
for (auto& t : threads) {
146+
t.join();
147+
}
148+
149+
Py_RETURN_NONE;
150+
}
151+
152+
// Module Method Definitions
153+
static PyMethodDef module_methods[] = {
154+
{"breadth_first_search", breadth_first_search, METH_VARARGS, "Serial Breadth First Search."},
155+
{"breadth_first_search_parallel", breadth_first_search_parallel, METH_VARARGS, "Parallel Breadth First Search."},
156+
{NULL, NULL, 0, NULL}
157+
};
158+
159+
// Python Module Definition
160+
static struct PyModuleDef graphmodule = {
161+
PyModuleDef_HEAD_INIT,
162+
"_graph_algorithms",
163+
"Graph Algorithms C++ Backend",
164+
-1,
165+
module_methods
166+
};
167+
168+
// Module Initialization
169+
PyMODINIT_FUNC PyInit__graph_algorithms(void) {
170+
PyObject* m;
171+
172+
// Initialize Graph Type
173+
PyGraphType.tp_name = "Graph";
174+
PyGraphType.tp_basicsize = sizeof(PyGraphObject);
175+
PyGraphType.tp_flags = Py_TPFLAGS_DEFAULT;
176+
PyGraphType.tp_doc = "Graph object in C++.";
177+
178+
if (PyType_Ready(&PyGraphType) < 0) {
179+
return NULL;
180+
}
181+
182+
m = PyModule_Create(&graphmodule);
183+
if (m == NULL) {
184+
return NULL;
185+
}
186+
187+
// Add Graph Type to module
188+
Py_INCREF(&PyGraphType);
189+
PyModule_AddObject(m, "Graph", (PyObject*)&PyGraphType);
190+
191+
return m;
192+
}

pydatastructs/graphs/algorithms.py

+5-17
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.alogorithms import algorithms
1415

1516
__all__ = [
1617
'breadth_first_search',
@@ -83,15 +84,8 @@ def breadth_first_search(
8384
"""
8485
raise_if_backend_is_not_python(
8586
breadth_first_search, kwargs.get('backend', Backend.PYTHON))
86-
import pydatastructs.graphs.algorithms as algorithms
87-
func = "_breadth_first_search_" + graph._impl
88-
if not hasattr(algorithms, func):
89-
raise NotImplementedError(
90-
"Currently breadth first search isn't implemented for "
91-
"%s graphs."%(graph._impl))
92-
return getattr(algorithms, func)(
93-
graph, source_node, operation, *args, **kwargs)
94-
87+
return algorithms.breadth_first_search(graph, source_node, operation)
88+
9589
def _breadth_first_search_adjacency_list(
9690
graph, source_node, operation, *args, **kwargs):
9791
bfs_queue = Queue()
@@ -171,14 +165,8 @@ def breadth_first_search_parallel(
171165
"""
172166
raise_if_backend_is_not_python(
173167
breadth_first_search_parallel, kwargs.get('backend', Backend.PYTHON))
174-
import pydatastructs.graphs.algorithms as algorithms
175-
func = "_breadth_first_search_parallel_" + graph._impl
176-
if not hasattr(algorithms, func):
177-
raise NotImplementedError(
178-
"Currently breadth first search isn't implemented for "
179-
"%s graphs."%(graph._impl))
180-
return getattr(algorithms, func)(
181-
graph, source_node, num_threads, operation, *args, **kwargs)
168+
return algorithms.breadth_first_search_parallel(graph, source_node, num_threads, operation)
169+
182170

183171
def _generate_layer(**kwargs):
184172
_args, _kwargs = kwargs.get('args'), kwargs.get('kwargs')

0 commit comments

Comments
 (0)