diff --git a/.gitignore b/.gitignore index 40c560ef4d128872c96236e0bd6be3933b457645..d033570bd6117961e43109d831f212130ab83d50 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ test/CMakeFiles/ test/CTestTestfile.cmake test/Makefile test/cmake_install.cmake +Testing/ diff --git a/include/graph_nt.hpp b/include/graph_nt.hpp index 3b13ceec7c3d4ca8615649495e8aecc4a0653911..7ae9da37437ea1ce61ab08791053102deeb18abe 100644 --- a/include/graph_nt.hpp +++ b/include/graph_nt.hpp @@ -1,5 +1,5 @@ -#ifndef __GRAPH_HPP__ -#define __GRAPH_HPP__ +#ifndef __GRAPH_NT_HPP__ +#define __GRAPH_NT_HPP__ #include <exception> #include <map> @@ -59,39 +59,174 @@ class Graph { public: - Graph() { /*todo*/ } - Graph(const Graph &other) { /* todo */ } + Graph() { + id_counter = 0; + edge_counter = 0; + } - inline int add_node(const std::string &m) { /* todo */ return -1; } + Graph(const Graph &other) { + id_counter = other.id_counter; + edge_counter = other.edge_counter; + for (auto node : other.nodes) { + nodes[node.first] = node.second; + } + for (auto edge : other.edges) { + edges[edge.first] = edge.second; + } + for (auto dest : other.dests) { + dests[dest.first] = std::vector<int>(); + for (auto d : dest.second) { + dests[dest.first].push_back(d); + } + } + } - inline bool node_exist(int id) const { /* todo */ return false; } + inline int add_node(const std::string &m) { + Node node; + node.node_id = id_counter; + node.data = m; + nodes[id_counter] = node; + dests[id_counter] = std::vector<int>(); + return id_counter++; + } - inline int add_edge(const std::string &m, int source_id, int dest_id) - { /* todo */ return -1;} - - inline void remove_node(int node_id) { /* todo */ } + inline bool node_exist(int id) const { + if (nodes.find(id) == nodes.end()) { + return false; + } + return true; + } + + inline int add_edge(const std::string &m, int source_id, int dest_id) { + if (!node_exist(source_id)) { + throw NodeNotFound(source_id); + } + if (!node_exist(dest_id)) { + throw NodeNotFound(dest_id); + } + + Edge edge; + edge.edge_id = edge_counter; + edge.data = m; + edge.source_id = source_id; + edge.dest_id = dest_id; + edges[edge_counter] = edge; + dests[source_id].push_back(dest_id); + return edge_counter++; + } + + inline void remove_node(int node_id) { + if (!node_exist(node_id)) { + throw NodeNotFound(node_id); + } + for (auto edge_id : dests[node_id]) { + edges.erase(edge_id); + } + dests.erase(node_id); + nodes.erase(node_id); + } - inline int search_node(const std::string &m) const { /* todo */ return 0;} + inline int search_node(const std::string &m) const { + for (auto node : nodes) { + if (node.second.data == m) { + return node.first; + } + } + return -1; + } - inline std::string get_node_data(int node_id) const { /* todo */ return "TODO";} + inline std::string get_node_data(int node_id) const { + if (!node_exist(node_id)) { + throw NodeNotFound(node_id); + } + return nodes.at(node_id).data; + } - inline std::string get_edge_data(int edge_id) const { /* todo */ return "TODO";} + inline std::string get_edge_data(int edge_id) const { + if (edges.find(edge_id) == edges.end()) { + throw EdgeNotFound(edge_id); + } + return edges.at(edge_id).data; + } - inline int get_edge_source(int edge_id) const { /* todo */ return -1;} + inline int get_edge_source(int edge_id) const { + if (edges.find(edge_id) == edges.end()) { + throw EdgeNotFound(edge_id); + } + return edges.at(edge_id).source_id; + } - inline int get_edge_dest(int edge_id) const { /* todo */ return -1;} + inline int get_edge_dest(int edge_id) const { + if (edges.find(edge_id) == edges.end()) { + throw EdgeNotFound(edge_id); + } + return edges.at(edge_id).dest_id; + } - std::vector<int> get_successors(int node_id) const { /* todo */ return std::vector<int>();} + std::vector<int> get_successors(int node_id) const { + if (!node_exist(node_id)) { + throw NodeNotFound(node_id); + } + return dests.at(node_id); + } - std::vector<int> get_predecessors(int node_id) const { /* todo */ return std::vector<int>();} + std::vector<int> get_predecessors(int node_id) const { + if (!node_exist(node_id)) { + throw NodeNotFound(node_id); + } + std::vector<int> preds; + // Visit all edges to find predecessors + for (auto edge : edges) { + if (edge.second.dest_id == node_id) { + preds.push_back(edge.second.source_id); + } + } + return preds; + } using Path=std::vector<int>; std::vector<Path> all_paths(int from, int to) const - { /* todo */ return std::vector<Path>();} + { + std::vector<Path> paths; + std::vector<int> visited; + Path path; + all_paths(from, to, visited, path, paths); + return paths; + } + + void all_paths(int from, int to, std::vector<int> &visited, Path &path, std::vector<Path> &paths) const + { + visited.push_back(from); + path.push_back(from); + if (from == to) { + paths.push_back(path); + } else { + for (auto dest : get_successors(from)) { + if (std::find(visited.begin(), visited.end(), dest) == visited.end()) { + all_paths(dest, to, visited, path, paths); + } + } + } + visited.pop_back(); + path.pop_back(); + } Path shortest_path(int from, int to) const - { /* todo */ return Path{};} + { + + std::vector<Path> paths = all_paths(from, to); + if (paths.size() == 0) { + throw std::runtime_error("No path found"); + } + Path shortest = paths[0]; + for (auto path : paths) { + if (path.size() < shortest.size()) { + shortest = path; + } + } + return shortest; + } }; diff --git a/include/graph_t.hpp b/include/graph_t.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7ef14dfd02e7e6dfbb45a807e4b3b819cfc72c5b --- /dev/null +++ b/include/graph_t.hpp @@ -0,0 +1,235 @@ +#ifndef __GRAPH_T_HPP__ +#define __GRAPH_T_HPP__ + +#include <exception> +#include <map> +#include <vector> +#include <string> +#include <algorithm> + +// Searches an element in the container, if it exists, it is removed +template<class C, class E> +void find_remove(C &cont, E elem) +{ + auto pos = cont.find(elem); + if (pos != end(cont)) + cont.erase(pos); +} + +// Exception : wrong node identifier +class NodeNotFound { + const int id; +public: + NodeNotFound(int node_id) : id(node_id) {} + std::string msg() const { + std::string m = "Node " + std::to_string(id) + " not found"; + return m; + } +}; + +// Exception : wrong node identifier +class EdgeNotFound { + const int id; +public: + EdgeNotFound(int edge_id) : id(edge_id) {} + std::string msg() const { + std::string m = "Edge " + std::to_string(id) + " not found"; + return m; + } +}; + +template<class ND, class ED> +class Graph { + struct Node { + int node_id; + ND data; + }; + struct Edge { + int edge_id; + ED data; + int source_id; + int dest_id; + }; + + /* data structures */ + std::map<int, Node> nodes; + std::map<int, Edge> edges; + std::map<int, std::vector<int>> dests; + int id_counter; + int edge_counter; + +public: + + Graph() { + id_counter = 0; + edge_counter = 0; + } + + Graph(const Graph &other) { + id_counter = other.id_counter; + edge_counter = other.edge_counter; + for (auto node : other.nodes) { + nodes[node.first] = node.second; + } + for (auto edge : other.edges) { + edges[edge.first] = edge.second; + } + for (auto dest : other.dests) { + dests[dest.first] = std::vector<int>(); + for (auto d : dest.second) { + dests[dest.first].push_back(d); + } + } + } + + inline int add_node(const ND &m) { + Node node; + node.node_id = id_counter; + node.data = m; + nodes[id_counter] = node; + dests[id_counter] = std::vector<int>(); + return id_counter++; + } + + inline bool node_exist(int id) const { + if (nodes.find(id) == nodes.end()) { + return false; + } + return true; + } + + inline int add_edge(const ED &m, int source_id, int dest_id) { + if (!node_exist(source_id)) { + throw NodeNotFound(source_id); + } + if (!node_exist(dest_id)) { + throw NodeNotFound(dest_id); + } + + Edge edge; + edge.edge_id = edge_counter; + edge.data = m; + edge.source_id = source_id; + edge.dest_id = dest_id; + edges[edge_counter] = edge; + dests[source_id].push_back(dest_id); + return edge_counter++; + } + + inline void remove_node(int node_id) { + if (!node_exist(node_id)) { + throw NodeNotFound(node_id); + } + for (auto edge_id : dests[node_id]) { + edges.erase(edge_id); + } + dests.erase(node_id); + nodes.erase(node_id); + } + + inline int search_node(const ND &m) const { + for (auto node : nodes) { + if (node.second.data == m) { + return node.first; + } + } + return -1; + } + + inline ND get_node_data(int node_id) const { + if (!node_exist(node_id)) { + throw NodeNotFound(node_id); + } + return nodes.at(node_id).data; + } + + inline ED get_edge_data(int edge_id) const { + if (edges.find(edge_id) == edges.end()) { + throw EdgeNotFound(edge_id); + } + return edges.at(edge_id).data; + } + + inline int get_edge_source(int edge_id) const { + if (edges.find(edge_id) == edges.end()) { + throw EdgeNotFound(edge_id); + } + return edges.at(edge_id).source_id; + } + + inline int get_edge_dest(int edge_id) const { + if (edges.find(edge_id) == edges.end()) { + throw EdgeNotFound(edge_id); + } + return edges.at(edge_id).dest_id; + } + + std::vector<int> get_successors(int node_id) const { + if (!node_exist(node_id)) { + throw NodeNotFound(node_id); + } + return dests.at(node_id); + } + + std::vector<int> get_predecessors(int node_id) const { + if (!node_exist(node_id)) { + throw NodeNotFound(node_id); + } + std::vector<int> preds; + // Visit all edges to find predecessors + for (auto edge : edges) { + if (edge.second.dest_id == node_id) { + preds.push_back(edge.second.source_id); + } + } + return preds; + } + + using Path=std::vector<int>; + + std::vector<Path> all_paths(int from, int to) const + { + std::vector<Path> paths; + std::vector<int> visited; + Path path; + all_paths(from, to, visited, path, paths); + return paths; + } + + void all_paths(int from, int to, std::vector<int> &visited, Path &path, std::vector<Path> &paths) const + { + visited.push_back(from); + path.push_back(from); + if (from == to) { + paths.push_back(path); + } else { + for (auto dest : get_successors(from)) { + if (std::find(visited.begin(), visited.end(), dest) == visited.end()) { + all_paths(dest, to, visited, path, paths); + } + } + } + visited.pop_back(); + path.pop_back(); + } + + Path shortest_path(int from, int to) const + { + + std::vector<Path> paths = all_paths(from, to); + if (paths.size() == 0) { + throw std::runtime_error("No path found"); + } + Path shortest = paths[0]; + for (auto path : paths) { + if (path.size() < shortest.size()) { + shortest = path; + } + } + return shortest; + } +}; + + +#endif + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3cfed2a1ea97174776714221d0c379c658d627dd..2723260115b830d30a4952d4d0856d0191292837 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -5,7 +5,7 @@ Include(FetchContent) FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v3.0.1) + GIT_TAG v3.5.3) FetchContent_MakeAvailable(Catch2) diff --git a/test/test_gnt b/test/test_gnt new file mode 100755 index 0000000000000000000000000000000000000000..0d453e077b6a19f3275478b01f7baf167ba6c56b Binary files /dev/null and b/test/test_gnt differ diff --git a/test/test_gnt.cpp b/test/test_gnt.cpp index fd14267880d826a3c870f192812db9c9d1b75d08..3dc00d10622a6bd1d47cf96438a8a9d725f316b8 100644 --- a/test/test_gnt.cpp +++ b/test/test_gnt.cpp @@ -1,12 +1,13 @@ #include <catch2/catch_test_macros.hpp> -#include "graph_nt.hpp" +#include "graph_t.hpp" +#include <iostream> using namespace std; SCENARIO("Graph properties", "[graph nt]") { GIVEN("A graph with some items") { - Graph g; + Graph<string, string> g; int a = g.add_node("A"); int b = g.add_node("B"); int c = g.add_node("C"); @@ -57,7 +58,7 @@ SCENARIO("Graph properties", "[graph nt]") SCENARIO("Computing paths", "[graph nt]") { GIVEN("A graph with some elements and no loops") { - Graph g; + Graph<string, string> g; int a = g.add_node("A"); int b = g.add_node("B"); int c = g.add_node("C"); @@ -79,4 +80,30 @@ SCENARIO("Computing paths", "[graph nt]") } } -/** AJOUTER DES TESTS ... */ +SCENARIO("Shortest paths", "[graph nt]") +{ + GIVEN("A graph with some elements and no loops") { + Graph<string, string> g; + int a = g.add_node("A"); + int b = g.add_node("B"); + int c = g.add_node("C"); + int d = g.add_node("D"); + int e = g.add_node("E"); + + g.add_edge("ab", a, b); + g.add_edge("ac", a, c); + g.add_edge("bc", b, c); + g.add_edge("bd", b, d); + g.add_edge("cd", c, d); + g.add_edge("de", d, e); + g.add_edge("ce", c, e); + + WHEN ("Computing all paths") { + auto paths = g.all_paths(a, e); + REQUIRE(paths.size() == 5); + REQUIRE(g.shortest_path(a,b).size() == 2); + REQUIRE(g.shortest_path(a,d).size() == 3); + REQUIRE(g.shortest_path(a,e).size() == 3); + } + } +}