diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 664a7758e..27343bb92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -142,9 +142,10 @@ jobs: python -m pip install -r docs/requirements.txt - name: Build package + env: + MACOSX_DEPLOYMENT_TARGET: 11.0 run: | CXXFLAGS="-std=c++17" python scripts/build/install.py - - name: Run tests run: | python -c "import pydatastructs; pydatastructs.test()" diff --git a/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp b/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp index 5a1c3260b..9bcb4ee9f 100644 --- a/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp +++ b/pydatastructs/graphs/_backend/cpp/AdjacencyList.hpp @@ -349,7 +349,7 @@ static PyMethodDef AdjacencyListGraph_methods[] = { }; -PyTypeObject AdjacencyListGraphType = { +inline PyTypeObject AdjacencyListGraphType = { PyVarObject_HEAD_INIT(NULL, 0) // ob_base "_graph.AdjacencyListGraph", // tp_name sizeof(AdjacencyListGraph), // tp_basicsize diff --git a/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp b/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp index 0222f0b88..4850e5975 100644 --- a/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp +++ b/pydatastructs/graphs/_backend/cpp/AdjacencyMatrix.hpp @@ -236,7 +236,7 @@ static PyMethodDef AdjacencyMatrixGraph_methods[] = { {NULL} }; -PyTypeObject AdjacencyMatrixGraphType = { +inline PyTypeObject AdjacencyMatrixGraphType = { PyVarObject_HEAD_INIT(NULL, 0) // ob_base "_graph.AdjacencyMatrixGraph", // tp_name sizeof(AdjacencyMatrixGraph), // tp_basicsize diff --git a/pydatastructs/graphs/_backend/cpp/Algorithms.hpp b/pydatastructs/graphs/_backend/cpp/Algorithms.hpp new file mode 100644 index 000000000..a427e744e --- /dev/null +++ b/pydatastructs/graphs/_backend/cpp/Algorithms.hpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include "AdjacencyList.hpp" +#include "AdjacencyMatrix.hpp" + + +static PyObject* breadth_first_search_adjacency_list(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* graph_obj; + const char* source_name; + PyObject* operation; + PyObject* varargs = nullptr; + PyObject* kwargs_dict = nullptr; + + static const char* kwlist[] = {"graph", "source_node", "operation", "args", "kwargs", nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!sO|OO", const_cast(kwlist), + &AdjacencyListGraphType, &graph_obj, + &source_name, &operation, + &varargs, &kwargs_dict)) { + return nullptr; + } + + AdjacencyListGraph* cpp_graph = reinterpret_cast(graph_obj); + + auto it = cpp_graph->node_map.find(source_name); + AdjacencyListGraphNode* start_node = it->second; + + std::unordered_set visited; + std::queue q; + + q.push(start_node); + visited.insert(start_node->name); + + while (!q.empty()) { + AdjacencyListGraphNode* node = q.front(); + q.pop(); + + for (const auto& [adj_name, adj_obj] : node->adjacent) { + if (visited.count(adj_name)) continue; + if (!PyObject_IsInstance(adj_obj, (PyObject*)&AdjacencyListGraphNodeType)) continue; + + AdjacencyListGraphNode* adj_node = reinterpret_cast(adj_obj); + + PyObject* base_args = PyTuple_Pack(2, + reinterpret_cast(node), + reinterpret_cast(adj_node)); + if (!base_args) + return nullptr; + + PyObject* final_args; + if (varargs && PyTuple_Check(varargs)) { + final_args = PySequence_Concat(base_args, varargs); + Py_DECREF(base_args); + if (!final_args) + return nullptr; + } else { + final_args = base_args; + } + + PyObject* result = PyObject_Call(operation, final_args, kwargs_dict); + Py_DECREF(final_args); + + if (!result) + return nullptr; + + Py_DECREF(result); + + visited.insert(adj_name); + q.push(adj_node); + } + } + if (PyErr_Occurred()) { + return nullptr; + } + + Py_RETURN_NONE; +} + +static PyObject* breadth_first_search_adjacency_matrix(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* graph_obj; + const char* source_name; + PyObject* operation; + PyObject* varargs = nullptr; + PyObject* kwargs_dict = nullptr; + + static const char* kwlist[] = {"graph", "source_node", "operation", "args", "kwargs", nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!sO|OO", const_cast(kwlist), + &AdjacencyMatrixGraphType, &graph_obj, + &source_name, &operation, + &varargs, &kwargs_dict)) { + return nullptr; + } + + AdjacencyMatrixGraph* cpp_graph = reinterpret_cast(graph_obj); + + auto it = cpp_graph->node_map.find(source_name); + if (it == cpp_graph->node_map.end()) { + PyErr_SetString(PyExc_KeyError, "Source node not found in graph"); + return nullptr; + } + AdjacencyMatrixGraphNode* start_node = it->second; + + std::unordered_set visited; + std::queue q; + + q.push(start_node); + visited.insert(source_name); + + while (!q.empty()) { + AdjacencyMatrixGraphNode* node = q.front(); + q.pop(); + + std::string node_name = reinterpret_cast(node)->name; + auto& neighbors = cpp_graph->matrix[node_name]; + + for (const auto& [adj_name, connected] : neighbors) { + if (!connected || visited.count(adj_name)) continue; + + auto adj_it = cpp_graph->node_map.find(adj_name); + if (adj_it == cpp_graph->node_map.end()) continue; + + AdjacencyMatrixGraphNode* adj_node = adj_it->second; + + PyObject* base_args = PyTuple_Pack(2, + reinterpret_cast(node), + reinterpret_cast(adj_node)); + if (!base_args) return nullptr; + + PyObject* final_args; + if (varargs && PyTuple_Check(varargs)) { + final_args = PySequence_Concat(base_args, varargs); + Py_DECREF(base_args); + if (!final_args) return nullptr; + } else { + final_args = base_args; + } + + PyObject* result = PyObject_Call(operation, final_args, kwargs_dict); + Py_DECREF(final_args); + if (!result) return nullptr; + Py_DECREF(result); + + visited.insert(adj_name); + q.push(adj_node); + } + } + + if (PyErr_Occurred()) { + return nullptr; + } + + Py_RETURN_NONE; +} diff --git a/pydatastructs/graphs/_backend/cpp/algorithms.cpp b/pydatastructs/graphs/_backend/cpp/algorithms.cpp new file mode 100644 index 000000000..9f3f01505 --- /dev/null +++ b/pydatastructs/graphs/_backend/cpp/algorithms.cpp @@ -0,0 +1,19 @@ +#include +#include "Algorithms.hpp" +#include "AdjacencyList.hpp" +#include "AdjacencyMatrix.hpp" + +static PyMethodDef AlgorithmsMethods[] = { + {"bfs_adjacency_list", (PyCFunction)breadth_first_search_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Run BFS on adjacency list with callback"}, + {"bfs_adjacency_matrix", (PyCFunction)breadth_first_search_adjacency_matrix, METH_VARARGS | METH_KEYWORDS, "Run BFS on adjacency matrix with callback"}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef algorithms_module = { + PyModuleDef_HEAD_INIT, + "_algorithms", NULL, -1, AlgorithmsMethods +}; + +PyMODINIT_FUNC PyInit__algorithms(void) { + return PyModule_Create(&algorithms_module); +} diff --git a/pydatastructs/graphs/_backend/cpp/graph.cpp b/pydatastructs/graphs/_backend/cpp/graph.cpp index 5d04b1d15..d41ca60f4 100644 --- a/pydatastructs/graphs/_backend/cpp/graph.cpp +++ b/pydatastructs/graphs/_backend/cpp/graph.cpp @@ -6,7 +6,15 @@ #include "AdjacencyMatrixGraphNode.hpp" #include "graph_bindings.hpp" +#ifdef __cplusplus +extern "C" { +#endif +PyMODINIT_FUNC PyInit__graph(void); + +#ifdef __cplusplus +} +#endif static struct PyModuleDef graph_module = { PyModuleDef_HEAD_INIT, diff --git a/pydatastructs/graphs/_extensions.py b/pydatastructs/graphs/_extensions.py index b3ddcd6f1..f550d5254 100644 --- a/pydatastructs/graphs/_extensions.py +++ b/pydatastructs/graphs/_extensions.py @@ -12,7 +12,12 @@ graph = '.'.join([project, module, backend, cpp, '_graph']) graph_sources = ['/'.join([project, module, backend, cpp, 'graph.cpp']),"pydatastructs/utils/_backend/cpp/graph_utils.cpp"] +algorithms = '.'.join([project, module, backend, cpp, '_algorithms']) +algorithms_sources = ['/'.join([project, module, backend, cpp, + 'algorithms.cpp']),"pydatastructs/utils/_backend/cpp/graph_utils.cpp"] include_dir = os.path.abspath(os.path.join(project, 'utils', '_backend', 'cpp')) -extensions = [Extension(graph, sources=graph_sources,include_dirs=[include_dir])] +extensions = [Extension(graph, sources=graph_sources,include_dirs=[include_dir], language="c++", extra_compile_args=["-std=c++17"]), + Extension(algorithms, sources=algorithms_sources,include_dirs=[include_dir], language="c++", extra_compile_args=["-std=c++17"]), + ] diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 334f522c5..393eb25b9 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -81,16 +81,24 @@ def breadth_first_search( >>> G.add_edge(V2.name, V3.name) >>> breadth_first_search(G, V1.name, f, V3.name) """ - raise_if_backend_is_not_python( - breadth_first_search, kwargs.get('backend', Backend.PYTHON)) - import pydatastructs.graphs.algorithms as algorithms - func = "_breadth_first_search_" + graph._impl - if not hasattr(algorithms, func): - raise NotImplementedError( - "Currently breadth first search isn't implemented for " - "%s graphs."%(graph._impl)) - return getattr(algorithms, func)( - graph, source_node, operation, *args, **kwargs) + backend = kwargs.get('backend', Backend.PYTHON) + if backend == Backend.PYTHON: + import pydatastructs.graphs.algorithms as algorithms + func = "_breadth_first_search_" + graph._impl + if not hasattr(algorithms, func): + raise NotImplementedError( + "Currently breadth first search isn't implemented for " + "%s graphs."%(graph._impl)) + return getattr(algorithms, func)( + graph, source_node, operation, *args, **kwargs) + else: + from pydatastructs.graphs._backend.cpp._algorithms import bfs_adjacency_list, bfs_adjacency_matrix + if (graph._impl == "adjacency_list"): + extra_args = args if args else () + return bfs_adjacency_list(graph, source_node, operation, extra_args) + if (graph._impl == "adjacency_matrix"): + extra_args = args if args else () + return bfs_adjacency_matrix(graph, source_node, operation, extra_args) def _breadth_first_search_adjacency_list( graph, source_node, operation, *args, **kwargs): diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index 553377f34..4cfab7e58 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -4,6 +4,10 @@ depth_first_search, shortest_paths,all_pair_shortest_paths, topological_sort, topological_sort_parallel, max_flow, find_bridges) from pydatastructs.utils.raises_util import raises +from pydatastructs.utils.misc_util import AdjacencyListGraphNode, AdjacencyMatrixGraphNode +from pydatastructs.graphs._backend.cpp import _graph +from pydatastructs.graphs._backend.cpp import _algorithms +from pydatastructs.utils.misc_util import Backend def test_breadth_first_search(): @@ -40,6 +44,32 @@ def bfs_tree(curr_node, next_node, parent): assert (parent[V3.name] == V1.name and parent[V2.name] == V1.name) or \ (parent[V3.name] == V2.name and parent[V2.name] == V1.name) + if (ds=='List'): + parent2 = {} + V9 = AdjacencyListGraphNode("9",0,backend = Backend.CPP) + V10 = AdjacencyListGraphNode("10",0,backend = Backend.CPP) + V11 = AdjacencyListGraphNode("11",0,backend = Backend.CPP) + G2 = Graph(V9, V10, V11,implementation = 'adjacency_list', backend = Backend.CPP) + assert G2.num_vertices()==3 + G2.add_edge("9", "10") + G2.add_edge("10", "11") + breadth_first_search(G2, "9", bfs_tree, parent2, backend = Backend.CPP) + assert parent2[V10] == V9 + assert parent2[V11] == V10 + + if (ds == 'Matrix'): + parent3 = {} + V12 = AdjacencyMatrixGraphNode("12", 0, backend = Backend.CPP) + V13 = AdjacencyMatrixGraphNode("13", 0, backend = Backend.CPP) + V14 = AdjacencyMatrixGraphNode("14", 0, backend = Backend.CPP) + G3 = Graph(V12, V13, V14, implementation = 'adjacency_matrix', backend = Backend.CPP) + assert G3.num_vertices() == 3 + G3.add_edge("12", "13") + G3.add_edge("13", "14") + breadth_first_search(G3, "12", bfs_tree, parent3, backend = Backend.CPP) + assert parent3[V13] == V12 + assert parent3[V14] == V13 + V4 = GraphNode(0) V5 = GraphNode(1) V6 = GraphNode(2) diff --git a/pydatastructs/utils/_backend/cpp/AdjacencyListGraphNode.hpp b/pydatastructs/utils/_backend/cpp/AdjacencyListGraphNode.hpp index 65e54e4fb..8a48566d8 100644 --- a/pydatastructs/utils/_backend/cpp/AdjacencyListGraphNode.hpp +++ b/pydatastructs/utils/_backend/cpp/AdjacencyListGraphNode.hpp @@ -6,23 +6,28 @@ #include #include #include "GraphNode.hpp" +#include extern PyTypeObject AdjacencyListGraphNodeType; typedef struct { PyObject_HEAD std::string name; - PyObject* data; + std::variant data; + DataType data_type; std::unordered_map adjacent; } AdjacencyListGraphNode; static void AdjacencyListGraphNode_dealloc(AdjacencyListGraphNode* self) { - Py_XDECREF(self->data); + if (self->data_type == DataType::PyObject) { + Py_XDECREF(std::get(self->data)); + } + for (auto& pair : self->adjacent) { Py_XDECREF(pair.second); } self->adjacent.clear(); - Py_TYPE(self)->tp_free(reinterpret_cast(self)); + Py_TYPE(self)->tp_free(reinterpret_cast(self)); } static PyObject* AdjacencyListGraphNode_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { @@ -30,6 +35,9 @@ static PyObject* AdjacencyListGraphNode_new(PyTypeObject* type, PyObject* args, if (!self) return NULL; new (&self->adjacent) std::unordered_map(); new (&self->name) std::string(); + new (&self->data) std::variant(); + self->data_type = DataType::None; + self->data = std::monostate{}; static char* kwlist[] = { "name", "data", "adjacency_list", NULL }; const char* name; @@ -42,15 +50,31 @@ static PyObject* AdjacencyListGraphNode_new(PyTypeObject* type, PyObject* args, } self->name = std::string(name); - Py_INCREF(data); - self->data = data; + + if (data == Py_None) { + self->data_type = DataType::None; + self->data = std::monostate{}; + } else if (PyLong_Check(data)) { + self->data_type = DataType::Int; + self->data = static_cast(PyLong_AsLongLong(data)); + } else if (PyFloat_Check(data)) { + self->data_type = DataType::Double; + self->data = PyFloat_AsDouble(data); + } else if (PyUnicode_Check(data)) { + const char* str = PyUnicode_AsUTF8(data); + self->data_type = DataType::String; + self->data = std::string(str); + } else { + self->data_type = DataType::PyObject; + Py_INCREF(data); + self->data = data; + } if (PyList_Check(adjacency_list)) { Py_ssize_t size = PyList_Size(adjacency_list); for (Py_ssize_t i = 0; i < size; i++) { PyObject* node = PyList_GetItem(adjacency_list, i); - if (PyType_Ready(&AdjacencyListGraphNodeType) < 0) { PyErr_SetString(PyExc_RuntimeError, "Failed to initialize AdjacencyListGraphNodeType"); return NULL; @@ -65,7 +89,7 @@ static PyObject* AdjacencyListGraphNode_new(PyTypeObject* type, PyObject* args, std::string str = std::string(adj_name); Py_INCREF(node); self->adjacent[str] = node; - } + } } return reinterpret_cast(self); @@ -127,14 +151,49 @@ static int AdjacencyListGraphNode_set_name(AdjacencyListGraphNode* self, PyObjec } static PyObject* AdjacencyListGraphNode_get_data(AdjacencyListGraphNode* self, void* closure) { - Py_INCREF(self->data); - return self->data; + switch (self->data_type) { + case DataType::Int: + return PyLong_FromLongLong(std::get(self->data)); + case DataType::Double: + return PyFloat_FromDouble(std::get(self->data)); + case DataType::String: + return PyUnicode_FromString(std::get(self->data).c_str()); + case DataType::PyObject: + Py_INCREF(std::get(self->data)); + return std::get(self->data); + case DataType::None: + default: + Py_RETURN_NONE; + } } static int AdjacencyListGraphNode_set_data(AdjacencyListGraphNode* self, PyObject* value, void* closure) { - Py_XDECREF(self->data); - Py_INCREF(value); - self->data = value; + if (self->data_type == DataType::PyObject) { + Py_XDECREF(std::get(self->data)); + } + + if (value == Py_None) { + self->data_type = DataType::None; + self->data = std::monostate{}; + } else if (PyLong_Check(value)) { + self->data_type = DataType::Int; + self->data = static_cast(PyLong_AsLongLong(value)); + } else if (PyFloat_Check(value)) { + self->data_type = DataType::Double; + self->data = PyFloat_AsDouble(value); + } else if (PyUnicode_Check(value)) { + const char* str = PyUnicode_AsUTF8(value); + if (!str) { + PyErr_SetString(PyExc_ValueError, "Invalid UTF-8 string."); + return -1; + } + self->data_type = DataType::String; + self->data = std::string(str); + } else { + self->data_type = DataType::PyObject; + Py_INCREF(value); + self->data = value; + } return 0; } @@ -180,7 +239,6 @@ static PyGetSetDef AdjacencyListGraphNode_getsetters[] = { {NULL} }; - static PyMethodDef AdjacencyListGraphNode_methods[] = { {"add_adjacent_node", (PyCFunction)AdjacencyListGraphNode_add_adjacent_node, METH_VARARGS, "Add adjacent node"}, {"remove_adjacent_node", (PyCFunction)AdjacencyListGraphNode_remove_adjacent_node, METH_VARARGS, "Remove adjacent node"}, diff --git a/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp b/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp index adf8573c3..03ade8e7c 100644 --- a/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp +++ b/pydatastructs/utils/_backend/cpp/AdjacencyMatrixGraphNode.hpp @@ -13,7 +13,6 @@ typedef struct { } AdjacencyMatrixGraphNode; static void AdjacencyMatrixGraphNode_dealloc(AdjacencyMatrixGraphNode* self){ - Py_XDECREF(self->super.data); Py_TYPE(self)->tp_free(reinterpret_cast(self)); } diff --git a/pydatastructs/utils/_backend/cpp/GraphNode.hpp b/pydatastructs/utils/_backend/cpp/GraphNode.hpp index 0df90e6f3..8921dc763 100644 --- a/pydatastructs/utils/_backend/cpp/GraphNode.hpp +++ b/pydatastructs/utils/_backend/cpp/GraphNode.hpp @@ -4,24 +4,38 @@ #define PY_SSIZE_T_CLEAN #include #include +#include + +enum class DataType { + None, + Int, + Double, + String, + PyObject +}; typedef struct { PyObject_HEAD std::string name; - PyObject* data; + std::variant data; + DataType data_type; } GraphNode; -static void GraphNode_dealloc(GraphNode* self){ - Py_XDECREF(self->data); - Py_TYPE(self)->tp_free(reinterpret_cast(self)); +static void GraphNode_dealloc(GraphNode* self) { + if (self->data_type == DataType::PyObject) { + Py_XDECREF(std::get(self->data)); + } + Py_TYPE(self)->tp_free(reinterpret_cast(self)); } -static PyObject* GraphNode_new(PyTypeObject* type, PyObject* args, PyObject* kwds){ - GraphNode* self; - self = reinterpret_cast(type->tp_alloc(type,0)); - new (&self->name) std::string(); +static PyObject* GraphNode_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { + GraphNode* self = reinterpret_cast(type->tp_alloc(type, 0)); if (!self) return NULL; + new (&self->name) std::string(); + new (&self->data) std::variant(); + self->data_type = DataType::None; + static char* kwlist[] = { "name", "data", NULL }; const char* name; PyObject* data = Py_None; @@ -32,47 +46,120 @@ static PyObject* GraphNode_new(PyTypeObject* type, PyObject* args, PyObject* kwd } self->name = std::string(name); - Py_INCREF(data); - self->data = data; + + if (data == Py_None) { + self->data = std::monostate{}; + self->data_type = DataType::None; + } else if (PyLong_Check(data)) { + self->data = static_cast(PyLong_AsLongLong(data)); + self->data_type = DataType::Int; + } else if (PyFloat_Check(data)) { + self->data = PyFloat_AsDouble(data); + self->data_type = DataType::Double; + } else if (PyUnicode_Check(data)) { + const char* s = PyUnicode_AsUTF8(data); + self->data = std::string(s); + self->data_type = DataType::String; + } else { + self->data = data; + self->data_type = DataType::PyObject; + Py_INCREF(data); + } return reinterpret_cast(self); } static PyObject* GraphNode_str(GraphNode* self) { - return PyUnicode_FromString(("('" + self->name + "', " + PyUnicode_AsUTF8(PyObject_Str(self->data)) + ")").c_str()); + std::string repr = "('" + self->name + "', "; + + switch (self->data_type) { + case DataType::None: + repr += "None"; + break; + case DataType::Int: + repr += std::to_string(std::get(self->data)); + break; + case DataType::Double: + repr += std::to_string(std::get(self->data)); + break; + case DataType::String: + repr += "'" + std::get(self->data) + "'"; + break; + case DataType::PyObject: { + PyObject* repr_obj = PyObject_Repr(std::get(self->data)); + if (repr_obj) { + const char* repr_cstr = PyUnicode_AsUTF8(repr_obj); + repr += repr_cstr ? repr_cstr : ""; + Py_DECREF(repr_obj); + } else { + repr += ""; + } + break; + } + } + + repr += ")"; + return PyUnicode_FromString(repr.c_str()); } static PyObject* GraphNode_get(GraphNode* self, void *closure) { - if (closure == (void*)"name") { + const char* attr = reinterpret_cast(closure); + if (strcmp(attr, "name") == 0) { return PyUnicode_FromString(self->name.c_str()); - } - if (closure == (void*)"data") { - Py_INCREF(self->data); - return self->data; + } else if (strcmp(attr, "data") == 0) { + switch (self->data_type) { + case DataType::None: + Py_RETURN_NONE; + case DataType::Int: + return PyLong_FromLongLong(std::get(self->data)); + case DataType::Double: + return PyFloat_FromDouble(std::get(self->data)); + case DataType::String: + return PyUnicode_FromString(std::get(self->data).c_str()); + case DataType::PyObject: + Py_INCREF(std::get(self->data)); + return std::get(self->data); + } } Py_RETURN_NONE; } static int GraphNode_set(GraphNode* self, PyObject *value, void *closure) { - if (value == NULL) { - PyErr_SetString(PyExc_ValueError, "value is NULL"); + const char* attr = reinterpret_cast(closure); + if (!value) { + PyErr_SetString(PyExc_ValueError, "Cannot delete attributes"); return -1; } - if (closure == (void*)"name") { + if (strcmp(attr, "name") == 0) { if (!PyUnicode_Check(value)) { - PyErr_SetString(PyExc_TypeError, "value to be set must be a string"); + PyErr_SetString(PyExc_TypeError, "name must be a string"); return -1; } self->name = PyUnicode_AsUTF8(value); - } - else if (closure == (void*)"data") { - PyObject *tmp = self->data; - Py_INCREF(value); - self->data = value; - Py_DECREF(tmp); - } - else { + } else if (strcmp(attr, "data") == 0) { + if (self->data_type == DataType::PyObject) { + Py_XDECREF(std::get(self->data)); + } + + if (value == Py_None) { + self->data = std::monostate{}; + self->data_type = DataType::None; + } else if (PyLong_Check(value)) { + self->data = static_cast(PyLong_AsLongLong(value)); + self->data_type = DataType::Int; + } else if (PyFloat_Check(value)) { + self->data = PyFloat_AsDouble(value); + self->data_type = DataType::Double; + } else if (PyUnicode_Check(value)) { + self->data = std::string(PyUnicode_AsUTF8(value)); + self->data_type = DataType::String; + } else { + Py_INCREF(value); + self->data = value; + self->data_type = DataType::PyObject; + } + } else { PyErr_SetString(PyExc_AttributeError, "Unknown attribute"); return -1; } @@ -80,6 +167,24 @@ static int GraphNode_set(GraphNode* self, PyObject *value, void *closure) { return 0; } +static PyGetSetDef GraphNode_getsetters[] = { + { + const_cast("name"), + reinterpret_cast(GraphNode_get), + reinterpret_cast(GraphNode_set), + const_cast("name"), + reinterpret_cast(const_cast("name")) + }, + { + const_cast("data"), + reinterpret_cast(GraphNode_get), + reinterpret_cast(GraphNode_set), + const_cast("data"), + reinterpret_cast(const_cast("data")) + }, + {nullptr} +}; + static PyTypeObject GraphNodeType = { /* tp_name */ PyVarObject_HEAD_INIT(NULL, 0) "GraphNode", /* tp_basicsize */ sizeof(GraphNode), @@ -109,7 +214,7 @@ static PyTypeObject GraphNodeType = { /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ 0, - /* tp_getset */ 0, + /* tp_getset */ GraphNode_getsetters, /* tp_base */ &PyBaseObject_Type, /* tp_dict */ 0, /* tp_descr_get */ 0, diff --git a/pydatastructs/utils/_extensions.py b/pydatastructs/utils/_extensions.py index f8b71aff0..7aae04ef3 100644 --- a/pydatastructs/utils/_extensions.py +++ b/pydatastructs/utils/_extensions.py @@ -1,21 +1,26 @@ from setuptools import Extension +import os +import sys project = 'pydatastructs' - module = 'utils' - backend = '_backend' - cpp = 'cpp' nodes = '.'.join([project, module, backend, cpp, '_nodes']) -nodes_sources = ['/'.join([project, module, backend, cpp, - 'nodes.cpp'])] +nodes_sources = [os.path.join(project, module, backend, cpp, 'nodes.cpp')] + graph_utils = '.'.join([project, module, backend, cpp, '_graph_utils']) -graph_utils_sources = ['/'.join([project, module, backend, cpp, - 'graph_utils.cpp'])] +graph_utils_sources = [os.path.join(project, module, backend, cpp, 'graph_utils.cpp')] + +extra_compile_args = ["-std=c++17"] + +if sys.platform == "darwin": + extra_compile_args.append("-mmacosx-version-min=10.13") +elif sys.platform == "win32": + extra_compile_args = ["/std:c++17"] extensions = [ - Extension(nodes, sources=nodes_sources), - Extension(graph_utils, sources = graph_utils_sources) + Extension(nodes, sources=nodes_sources, language="c++", extra_compile_args=extra_compile_args), + Extension(graph_utils, sources=graph_utils_sources, language="c++", extra_compile_args=extra_compile_args), ] diff --git a/pydatastructs/utils/tests/test_misc_util.py b/pydatastructs/utils/tests/test_misc_util.py index d779731c9..2f8810dcd 100644 --- a/pydatastructs/utils/tests/test_misc_util.py +++ b/pydatastructs/utils/tests/test_misc_util.py @@ -36,12 +36,16 @@ def test_AdjacencyListGraphNode(): h.add_adjacent_node('h_1', 4) assert h.adjacent['h_1'] == 4 assert str(h) == "('h', 0)" + h_5 = AdjacencyListGraphNode('h_5', h_1, backend = Backend.CPP) + assert h_5.data == h_1 def test_AdjacencyMatrixGraphNode(): g = AdjacencyMatrixGraphNode("1", 3) g2 = AdjacencyMatrixGraphNode("1", 3, backend = Backend.CPP) assert str(g) == "('1', 3)" assert str(g2) == "('1', 3)" + g3 = AdjacencyListGraphNode("3", g2, backend = Backend.CPP) + assert g3.data == g2 def test_GraphEdge():