Skip to content

Commit c1a2879

Browse files
Prerak SinghPrerak Singh
authored andcommitted
bfs
1 parent 2ac1662 commit c1a2879

File tree

5 files changed

+237
-11
lines changed

5 files changed

+237
-11
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#include <Python.h>
2+
#include <unordered_map>
3+
#include <queue>
4+
#include <string>
5+
#include "AdjacencyList.hpp"
6+
7+
static inline AdjacencyListGraphNode* get_node(AdjacencyListGraph* graph, const std::string& name) {
8+
auto it = graph->node_map.find(name);
9+
return (it != graph->node_map.end()) ? it->second : nullptr;
10+
}
11+
12+
static PyObject* bfs_adjacency_list(PyObject* self, PyObject* args, PyObject* kwds) {
13+
static char* kwlist[] = {(char*)"graph", (char*)"source_node", (char*)"operation", (char*)"extra_arg", NULL};
14+
15+
PyObject* py_graph = nullptr;
16+
const char* source_node = nullptr;
17+
PyObject* operation = nullptr;
18+
PyObject* extra_arg = nullptr;
19+
20+
fprintf(stderr, "[bfs] Parsing arguments...\n");
21+
22+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OsO|O", kwlist,
23+
&py_graph, &source_node, &operation, &extra_arg)) {
24+
fprintf(stderr, "[bfs] Failed to parse arguments\n");
25+
return NULL;
26+
}
27+
28+
fprintf(stderr, "[bfs] Arguments parsed:\n");
29+
fprintf(stderr, " - source_node: %s\n", source_node);
30+
fprintf(stderr, " - extra_arg: %s\n", (extra_arg ? Py_TYPE(extra_arg)->tp_name : "NULL"));
31+
fprintf(stderr, "[bfs] Checking type of py_graph...\n");
32+
fprintf(stderr, " - Expected: %s\n", AdjacencyListGraphType.tp_name);
33+
fprintf(stderr, " - Actual: %s\n", Py_TYPE(py_graph)->tp_name);
34+
fprintf(stderr, " - Expected address: %p\n", &AdjacencyListGraphType);
35+
fprintf(stderr, " - Actual type addr: %p\n", (void*)Py_TYPE(py_graph));
36+
37+
fprintf(stderr, "[bfs] Attempting to import _graph...\n");
38+
PyObject* graph_module = PyImport_ImportModule("_graph");
39+
if (!graph_module) {
40+
PyErr_Print();
41+
PyErr_SetString(PyExc_ImportError, "Could not import _graph module");
42+
return NULL;
43+
}
44+
45+
PyObject* expected_type = PyObject_GetAttrString(graph_module, "AdjacencyListGraph");
46+
Py_DECREF(graph_module);
47+
48+
if (!expected_type || !PyType_Check(expected_type)) {
49+
Py_XDECREF(expected_type);
50+
PyErr_SetString(PyExc_TypeError, "Could not retrieve AdjacencyListGraph type");
51+
return NULL;
52+
}
53+
54+
if (!PyObject_IsInstance(py_graph, expected_type)) {
55+
Py_DECREF(expected_type);
56+
PyErr_SetString(PyExc_TypeError, "Expected an AdjacencyListGraph instance");
57+
return NULL;
58+
}
59+
60+
if (!PyCallable_Check(operation)) {
61+
PyErr_SetString(PyExc_TypeError, "Expected a callable for operation");
62+
fprintf(stderr, "[bfs] operation is not callable\n");
63+
return NULL;
64+
}
65+
66+
AdjacencyListGraph* graph = (AdjacencyListGraph*)py_graph;
67+
68+
if (!get_node(graph, source_node)) {
69+
PyErr_SetString(PyExc_ValueError, "Source node does not exist in the graph");
70+
fprintf(stderr, "[bfs] source_node not found in graph\n");
71+
return NULL;
72+
}
73+
74+
fprintf(stderr, "[bfs] Starting BFS from node: %s\n", source_node);
75+
76+
std::unordered_map<std::string, bool> visited;
77+
std::queue<std::string> q;
78+
79+
q.push(source_node);
80+
visited[source_node] = true;
81+
82+
while (!q.empty()) {
83+
std::string curr = q.front();
84+
q.pop();
85+
86+
fprintf(stderr, "[bfs] Visiting node: %s\n", curr.c_str());
87+
88+
auto* curr_node = get_node(graph, curr);
89+
if (!curr_node) {
90+
fprintf(stderr, "[bfs] Warning: node %s not found in node_map\n", curr.c_str());
91+
continue;
92+
}
93+
94+
const auto& neighbors = curr_node->adjacent;
95+
96+
if (!neighbors.empty()) {
97+
for (const auto& [next_name, _] : neighbors) {
98+
if (!visited[next_name]) {
99+
fprintf(stderr, "[bfs] Considering neighbor: %s\n", next_name.c_str());
100+
101+
PyObject* result = nullptr;
102+
103+
if (extra_arg)
104+
result = PyObject_CallFunction(operation, "ssO", curr.c_str(), next_name.c_str(), extra_arg);
105+
else
106+
result = PyObject_CallFunction(operation, "ss", curr.c_str(), next_name.c_str());
107+
108+
if (!result) {
109+
fprintf(stderr, "[bfs] PyObject_CallFunction failed on (%s, %s)\n", curr.c_str(), next_name.c_str());
110+
PyErr_Print();
111+
return NULL;
112+
}
113+
114+
int keep_going = PyObject_IsTrue(result);
115+
Py_DECREF(result);
116+
117+
if (!keep_going) {
118+
fprintf(stderr, "[bfs] Operation requested to stop traversal at edge (%s -> %s)\n", curr.c_str(), next_name.c_str());
119+
Py_RETURN_NONE;
120+
}
121+
122+
visited[next_name] = true;
123+
q.push(next_name);
124+
}
125+
}
126+
} else {
127+
fprintf(stderr, "[bfs] Leaf node reached: %s\n", curr.c_str());
128+
129+
PyObject* result = nullptr;
130+
131+
if (extra_arg)
132+
result = PyObject_CallFunction(operation, "sO", curr.c_str(), extra_arg);
133+
else
134+
result = PyObject_CallFunction(operation, "s", curr.c_str());
135+
136+
if (!result) {
137+
fprintf(stderr, "[bfs] PyObject_CallFunction failed at leaf node (%s)\n", curr.c_str());
138+
PyErr_Print();
139+
return NULL;
140+
}
141+
142+
int keep_going = PyObject_IsTrue(result);
143+
Py_DECREF(result);
144+
145+
if (!keep_going) {
146+
fprintf(stderr, "[bfs] Operation requested to stop traversal at leaf node %s\n", curr.c_str());
147+
Py_RETURN_NONE;
148+
}
149+
}
150+
}
151+
152+
fprintf(stderr, "[bfs] BFS traversal complete\n");
153+
Py_RETURN_NONE;
154+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include <Python.h>
2+
#include "Algorithms.hpp"
3+
4+
static PyTypeObject* get_adjacency_list_graph_type() {
5+
static PyTypeObject* cached_type = nullptr;
6+
7+
if (cached_type != nullptr) return cached_type;
8+
9+
PyObject* graph_mod = PyImport_ImportModule("pydatastructs.graphs._backend.cpp._graph");
10+
if (!graph_mod) {
11+
PyErr_SetString(PyExc_ImportError, "[algorithms] Failed to import _graph module");
12+
return nullptr;
13+
}
14+
15+
PyObject* type_obj = PyObject_GetAttrString(graph_mod, "AdjacencyListGraph");
16+
Py_DECREF(graph_mod);
17+
18+
if (!type_obj || !PyType_Check(type_obj)) {
19+
Py_XDECREF(type_obj);
20+
PyErr_SetString(PyExc_TypeError, "[algorithms] AdjacencyListGraph is not a type object");
21+
return nullptr;
22+
}
23+
24+
cached_type = reinterpret_cast<PyTypeObject*>(type_obj);
25+
return cached_type;
26+
}
27+
28+
extern PyTypeObject* get_adjacency_list_graph_type();
29+
30+
static PyMethodDef AlgorithmsMethods[] = {
31+
{"bfs_adjacency_list", (PyCFunction)bfs_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Run BFS with callback"},
32+
{NULL, NULL, 0, NULL}
33+
};
34+
35+
static struct PyModuleDef algorithms_module = {
36+
PyModuleDef_HEAD_INIT,
37+
"_algorithms", NULL, -1, AlgorithmsMethods
38+
};
39+
40+
PyMODINIT_FUNC PyInit__algorithms(void) {
41+
PyObject* graph_mod = PyImport_ImportModule("pydatastructs.graphs._backend.cpp._graph");
42+
if (!graph_mod) return nullptr;
43+
Py_DECREF(graph_mod);
44+
45+
return PyModule_Create(&algorithms_module);
46+
}

pydatastructs/graphs/_extensions.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
graph = '.'.join([project, module, backend, cpp, '_graph'])
1313
graph_sources = ['/'.join([project, module, backend, cpp,
1414
'graph.cpp']),"pydatastructs/utils/_backend/cpp/graph_utils.cpp"]
15+
algorithms = '.'.join([project, module, backend, cpp, '_algorithms'])
16+
algorithms_sources = ['/'.join([project, module, backend, cpp,
17+
'algorithms.cpp']),"pydatastructs/utils/_backend/cpp/graph_utils.cpp"]
1518

1619
include_dir = os.path.abspath(os.path.join(project, 'utils', '_backend', 'cpp'))
1720

18-
extensions = [Extension(graph, sources=graph_sources,include_dirs=[include_dir], language="c++", extra_compile_args=["-std=c++17"])]
21+
extensions = [Extension(graph, sources=graph_sources,include_dirs=[include_dir], language="c++", extra_compile_args=["-std=c++17"]),
22+
Extension(algorithms, sources=algorithms_sources,include_dirs=[include_dir], language="c++", extra_compile_args=["-std=c++17"])]

pydatastructs/graphs/algorithms.py

Lines changed: 15 additions & 10 deletions
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 import _algorithms
1415

1516
__all__ = [
1617
'breadth_first_search',
@@ -81,16 +82,20 @@ def breadth_first_search(
8182
>>> G.add_edge(V2.name, V3.name)
8283
>>> breadth_first_search(G, V1.name, f, V3.name)
8384
"""
84-
raise_if_backend_is_not_python(
85-
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)
85+
backend = kwargs.get('backend', Backend.PYTHON)
86+
if backend == Backend.PYTHON:
87+
import pydatastructs.graphs.algorithms as algorithms
88+
func = "_breadth_first_search_" + graph._impl
89+
if not hasattr(algorithms, func):
90+
raise NotImplementedError(
91+
"Currently breadth first search isn't implemented for "
92+
"%s graphs."%(graph._impl))
93+
return getattr(algorithms, func)(
94+
graph, source_node, operation, *args, **kwargs)
95+
else:
96+
if (graph._impl == "adjacency_list"):
97+
extra_arg = args[0] if args else None
98+
return _algorithms.bfs_adjacency_list(graph,source_node, operation, extra_arg)
9499

95100
def _breadth_first_search_adjacency_list(
96101
graph, source_node, operation, *args, **kwargs):

pydatastructs/graphs/tests/test_algorithms.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
depth_first_search, shortest_paths,all_pair_shortest_paths, topological_sort,
55
topological_sort_parallel, max_flow, find_bridges)
66
from pydatastructs.utils.raises_util import raises
7+
from pydatastructs.utils.misc_util import AdjacencyListGraphNode, AdjacencyMatrixGraphNode
8+
from pydatastructs.graphs._backend.cpp import _graph
9+
from pydatastructs.utils.misc_util import Backend
710

811
def test_breadth_first_search():
912

@@ -39,6 +42,18 @@ def bfs_tree(curr_node, next_node, parent):
3942
breadth_first_search(G1, V1.name, bfs_tree, parent)
4043
assert (parent[V3.name] == V1.name and parent[V2.name] == V1.name) or \
4144
(parent[V3.name] == V2.name and parent[V2.name] == V1.name)
45+
46+
if (ds=='List'):
47+
V9 = AdjacencyListGraphNode("9",0,backend = Backend.CPP)
48+
V10 = AdjacencyListGraphNode("10",0,backend = Backend.CPP)
49+
V11 = AdjacencyListGraphNode("11",0,backend = Backend.CPP)
50+
G2 = Graph(V9, V10, V11,implementation = 'adjacency_list', backend = Backend.CPP)
51+
assert G2.num_vertices()==3
52+
G2.add_edge("9", "10")
53+
G2.add_edge("10", "11")
54+
breadth_first_search(G2, V1.name, bfs_tree, parent, backend = Backend.CPP)
55+
56+
4257

4358
V4 = GraphNode(0)
4459
V5 = GraphNode(1)
@@ -131,6 +146,8 @@ def bfs_tree(curr_node, next_node, parent):
131146
_test_breadth_first_search_parallel("List")
132147
_test_breadth_first_search_parallel("Matrix")
133148

149+
test_breadth_first_search()
150+
134151
def test_minimum_spanning_tree():
135152

136153
def _test_minimum_spanning_tree(func, ds, algorithm, *args):

0 commit comments

Comments
 (0)