Skip to content

Commit 4df2097

Browse files
Prerak SinghPrerak Singh
authored andcommitted
bfs complete
1 parent c1a2879 commit 4df2097

File tree

7 files changed

+152
-155
lines changed

7 files changed

+152
-155
lines changed

pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ static PyMethodDef AdjacencyListGraph_methods[] = {
349349
};
350350

351351

352-
PyTypeObject AdjacencyListGraphType = {
352+
inline PyTypeObject AdjacencyListGraphType = {
353353
PyVarObject_HEAD_INIT(NULL, 0) // ob_base
354354
"_graph.AdjacencyListGraph", // tp_name
355355
sizeof(AdjacencyListGraph), // tp_basicsize

pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ static PyMethodDef AdjacencyMatrixGraph_methods[] = {
236236
{NULL}
237237
};
238238

239-
PyTypeObject AdjacencyMatrixGraphType = {
239+
inline PyTypeObject AdjacencyMatrixGraphType = {
240240
PyVarObject_HEAD_INIT(NULL, 0) // ob_base
241241
"_graph.AdjacencyMatrixGraph", // tp_name
242242
sizeof(AdjacencyMatrixGraph), // tp_basicsize

pydatastructs/graphs/_backend/cpp/Algorithms.hpp

Lines changed: 115 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -2,153 +2,154 @@
22
#include <unordered_map>
33
#include <queue>
44
#include <string>
5+
#include <unordered_set>
56
#include "AdjacencyList.hpp"
7+
#include "AdjacencyMatrix.hpp"
8+
9+
10+
static PyObject* breadth_first_search_adjacency_list(PyObject* self, PyObject* args, PyObject* kwargs) {
11+
PyObject* graph_obj;
12+
const char* source_name;
13+
PyObject* operation;
14+
PyObject* varargs = nullptr;
15+
PyObject* kwargs_dict = nullptr;
16+
17+
static const char* kwlist[] = {"graph", "source_node", "operation", "args", "kwargs", nullptr};
18+
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!sO|OO", const_cast<char**>(kwlist),
19+
&AdjacencyListGraphType, &graph_obj,
20+
&source_name, &operation,
21+
&varargs, &kwargs_dict)) {
22+
return nullptr;
23+
}
624

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-
}
25+
AdjacencyListGraph* cpp_graph = reinterpret_cast<AdjacencyListGraph*>(graph_obj);
1126

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};
27+
auto it = cpp_graph->node_map.find(source_name);
28+
AdjacencyListGraphNode* start_node = it->second;
1429

15-
PyObject* py_graph = nullptr;
16-
const char* source_node = nullptr;
17-
PyObject* operation = nullptr;
18-
PyObject* extra_arg = nullptr;
30+
std::unordered_set<std::string> visited;
31+
std::queue<AdjacencyListGraphNode*> q;
1932

20-
fprintf(stderr, "[bfs] Parsing arguments...\n");
33+
q.push(start_node);
34+
visited.insert(start_node->name);
2135

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-
}
36+
while (!q.empty()) {
37+
AdjacencyListGraphNode* node = q.front();
38+
q.pop();
39+
40+
for (const auto& [adj_name, adj_obj] : node->adjacent) {
41+
if (visited.count(adj_name)) continue;
42+
if (!PyObject_IsInstance(adj_obj, (PyObject*)&AdjacencyListGraphNodeType)) continue;
43+
44+
AdjacencyListGraphNode* adj_node = reinterpret_cast<AdjacencyListGraphNode*>(adj_obj);
45+
46+
PyObject* base_args = PyTuple_Pack(2,
47+
reinterpret_cast<PyObject*>(node),
48+
reinterpret_cast<PyObject*>(adj_node));
49+
if (!base_args)
50+
return nullptr;
51+
52+
PyObject* final_args;
53+
if (varargs && PyTuple_Check(varargs)) {
54+
final_args = PySequence_Concat(base_args, varargs);
55+
Py_DECREF(base_args);
56+
if (!final_args)
57+
return nullptr;
58+
} else {
59+
final_args = base_args;
60+
}
2761

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-
}
62+
PyObject* result = PyObject_Call(operation, final_args, kwargs_dict);
63+
Py_DECREF(final_args);
4464

45-
PyObject* expected_type = PyObject_GetAttrString(graph_module, "AdjacencyListGraph");
46-
Py_DECREF(graph_module);
65+
if (!result)
66+
return nullptr;
4767

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-
}
68+
Py_DECREF(result);
5369

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;
70+
visited.insert(adj_name);
71+
q.push(adj_node);
5872
}
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;
73+
}
74+
if (PyErr_Occurred()) {
75+
return nullptr;
6476
}
6577

66-
AdjacencyListGraph* graph = (AdjacencyListGraph*)py_graph;
78+
Py_RETURN_NONE;
79+
}
6780

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;
81+
static PyObject* breadth_first_search_adjacency_matrix(PyObject* self, PyObject* args, PyObject* kwargs) {
82+
PyObject* graph_obj;
83+
const char* source_name;
84+
PyObject* operation;
85+
PyObject* varargs = nullptr;
86+
PyObject* kwargs_dict = nullptr;
87+
88+
static const char* kwlist[] = {"graph", "source_node", "operation", "args", "kwargs", nullptr};
89+
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!sO|OO", const_cast<char**>(kwlist),
90+
&AdjacencyMatrixGraphType, &graph_obj,
91+
&source_name, &operation,
92+
&varargs, &kwargs_dict)) {
93+
return nullptr;
7294
}
7395

74-
fprintf(stderr, "[bfs] Starting BFS from node: %s\n", source_node);
96+
AdjacencyMatrixGraph* cpp_graph = reinterpret_cast<AdjacencyMatrixGraph*>(graph_obj);
97+
98+
auto it = cpp_graph->node_map.find(source_name);
99+
if (it == cpp_graph->node_map.end()) {
100+
PyErr_SetString(PyExc_KeyError, "Source node not found in graph");
101+
return nullptr;
102+
}
103+
AdjacencyMatrixGraphNode* start_node = it->second;
75104

76-
std::unordered_map<std::string, bool> visited;
77-
std::queue<std::string> q;
105+
std::unordered_set<std::string> visited;
106+
std::queue<AdjacencyMatrixGraphNode*> q;
78107

79-
q.push(source_node);
80-
visited[source_node] = true;
108+
q.push(start_node);
109+
visited.insert(source_name);
81110

82111
while (!q.empty()) {
83-
std::string curr = q.front();
112+
AdjacencyMatrixGraphNode* node = q.front();
84113
q.pop();
85114

86-
fprintf(stderr, "[bfs] Visiting node: %s\n", curr.c_str());
115+
std::string node_name = reinterpret_cast<GraphNode*>(node)->name;
116+
auto& neighbors = cpp_graph->matrix[node_name];
87117

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;
118+
for (const auto& [adj_name, connected] : neighbors) {
119+
if (!connected || visited.count(adj_name)) continue;
102120

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());
121+
auto adj_it = cpp_graph->node_map.find(adj_name);
122+
if (adj_it == cpp_graph->node_map.end()) continue;
107123

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-
}
124+
AdjacencyMatrixGraphNode* adj_node = adj_it->second;
113125

114-
int keep_going = PyObject_IsTrue(result);
115-
Py_DECREF(result);
126+
PyObject* base_args = PyTuple_Pack(2,
127+
reinterpret_cast<PyObject*>(node),
128+
reinterpret_cast<PyObject*>(adj_node));
129+
if (!base_args) return nullptr;
116130

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-
}
131+
PyObject* final_args;
132+
if (varargs && PyTuple_Check(varargs)) {
133+
final_args = PySequence_Concat(base_args, varargs);
134+
Py_DECREF(base_args);
135+
if (!final_args) return nullptr;
136+
} else {
137+
final_args = base_args;
125138
}
126-
} else {
127-
fprintf(stderr, "[bfs] Leaf node reached: %s\n", curr.c_str());
128-
129-
PyObject* result = nullptr;
130139

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);
140+
PyObject* result = PyObject_Call(operation, final_args, kwargs_dict);
141+
Py_DECREF(final_args);
142+
if (!result) return nullptr;
143143
Py_DECREF(result);
144144

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-
}
145+
visited.insert(adj_name);
146+
q.push(adj_node);
149147
}
150148
}
151149

152-
fprintf(stderr, "[bfs] BFS traversal complete\n");
150+
if (PyErr_Occurred()) {
151+
return nullptr;
152+
}
153+
153154
Py_RETURN_NONE;
154155
}
Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,11 @@
11
#include <Python.h>
22
#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();
3+
#include "AdjacencyList.hpp"
4+
#include "AdjacencyMatrix.hpp"
295

306
static PyMethodDef AlgorithmsMethods[] = {
31-
{"bfs_adjacency_list", (PyCFunction)bfs_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Run BFS with callback"},
7+
{"bfs_adjacency_list", (PyCFunction)breadth_first_search_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Run BFS on adjacency list with callback"},
8+
{"bfs_adjacency_matrix", (PyCFunction)breadth_first_search_adjacency_matrix, METH_VARARGS | METH_KEYWORDS, "Run BFS on adjacency matrix with callback"},
329
{NULL, NULL, 0, NULL}
3310
};
3411

@@ -38,9 +15,5 @@ static struct PyModuleDef algorithms_module = {
3815
};
3916

4017
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-
4518
return PyModule_Create(&algorithms_module);
4619
}

pydatastructs/graphs/_backend/cpp/graph.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,15 @@
66
#include "AdjacencyMatrixGraphNode.hpp"
77
#include "graph_bindings.hpp"
88

9+
#ifdef __cplusplus
10+
extern "C" {
11+
#endif
912

13+
PyMODINIT_FUNC PyInit__graph(void);
14+
15+
#ifdef __cplusplus
16+
}
17+
#endif
1018

1119
static struct PyModuleDef graph_module = {
1220
PyModuleDef_HEAD_INIT,

pydatastructs/graphs/algorithms.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,11 @@ def breadth_first_search(
9494
graph, source_node, operation, *args, **kwargs)
9595
else:
9696
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)
97+
extra_args = args if args else ()
98+
return _algorithms.bfs_adjacency_list(graph, source_node, operation, extra_args)
99+
if (graph._impl == "adjacency_matrix"):
100+
extra_args = args if args else ()
101+
return _algorithms.bfs_adjacency_matrix(graph, source_node, operation, extra_args)
99102

100103
def _breadth_first_search_adjacency_list(
101104
graph, source_node, operation, *args, **kwargs):

0 commit comments

Comments
 (0)