Skip to content
Snippets Groups Projects
Commit 5937c209 authored by Giuseppe Lipari's avatar Giuseppe Lipari
Browse files

first

parent 260a75d2
No related branches found
No related tags found
No related merge requests found
cmake_minimum_required(VERSION 3.11)
project(
Graph
VERSION 0.1
DESCRIPTION "COA TP4 - Graph class")
add_compile_options(-fsanitize=address,undefined)
add_link_options(-fsanitize=address,undefined)
enable_testing()
add_subdirectory(test)
# coa-tp4-graphs
TP4 - Graphes
---------------
## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin https://gitlab.univ-lille.fr/coa-2024/coa-tp4-graphs.git
git branch -M main
git push -uf origin main
```
## Integrate with your tools
- [ ] [Set up project integrations](https://gitlab.univ-lille.fr/coa-2024/coa-tp4-graphs/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
Le sujet est dans le répertoire sujet, en format [pdf](sujet/tp4.pdf)
et en format [md](sujet/tp4.md).
#ifndef __GRAPH_HPP__
#define __GRAPH_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;
}
};
class Graph {
struct Node {
int node_id;
std::string data;
};
struct Edge {
int edge_id;
std::string 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() { /*todo*/ }
Graph(const Graph &other) { /* todo */ }
inline int add_node(const std::string &m) { /* todo */ return -1; }
inline bool node_exist(int id) const { /* todo */ return false; }
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 int search_node(const std::string &m) const { /* todo */ return 0;}
inline std::string get_node_data(int node_id) const { /* todo */ return "TODO";}
inline std::string get_edge_data(int edge_id) const { /* todo */ return "TODO";}
inline int get_edge_source(int edge_id) const { /* todo */ return -1;}
inline int get_edge_dest(int edge_id) const { /* todo */ return -1;}
std::vector<int> get_successors(int node_id) const { /* todo */ return std::vector<int>();}
std::vector<int> get_predecessors(int node_id) const { /* todo */ return std::vector<int>();}
using Path=std::vector<int>;
std::vector<Path> all_paths(int from, int to) const
{ /* todo */ return std::vector<Path>();}
Path shortest_path(int from, int to) const
{ /* todo */ return Path{};}
};
#endif
#ifndef __GRAPH_HPP__
#define __GRAPH_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;
}
};
class Graph {
struct Node {
int node_id;
std::string data;
};
struct Edge {
int edge_id;
std::string 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() { /*todo*/ }
Graph(const Graph &other) { /* todo */ }
inline int add_node(const std::string &m) { /* todo */ }
inline bool node_exist(int id) const { /* todo */ }
inline int add_edge(const std::string &m, int source_id, int dest_id)
{ /* todo */ }
inline void remove_node(int node_id) { /* todo */ }
inline int search_node(const std::string &m) const { /* todo */ }
inline std::string get_node_data(int node_id) const { /* todo */ }
inline std::string get_edge_data(int edge_id) const { /* todo */ }
inline int get_edge_source(int edge_id) const { /* todo */ }
inline int get_edge_dest(int edge_id) const { /* todo */ }
std::vector<int> get_successors(int node_id) const { /* todo */ }
std::vector<int> get_predecessors(int node_id) const { /* todo */ }
using Path=std::vector<int>;
std::vector<Path> all_paths(int from, int to) const
{ /* todo */ }
Path shortest_path(int from, int to) const
{ /* todo */ }
};
#endif
sujet/graph-ex.png

33.7 KiB

# Graph library
Un *graphe orienté* est une [structure de données](https://fr.wikipedia.org/wiki/Graphe_(math%25C3%25A9matiques_discr%25C3%25A8tes)) utilisé en
mathématique et en informatique pour représenter des réseaux
d'objets. Le but de ce TP est de construire une *graph template
library* pour créer et manipuler de graphes.
## Définitions
Un *graphe orienté* est un ensemble de noeuds connectés par des
arêtes orientées (*edges*). Il est possible d'associer des objets
(étiquettes) aux noeuds et aux arêtes.
Par exemple, on veut représenter le réseau des routes dans le
Nord. Chaque noeud représentes une ville et les arêtes sont les
routes. Dans ce cas, à chaque noeud on associe un objet de type
`Ville` et à chaque arête on associe un objet de type `Route`.
![img](graph-ex.png)
Il y a plusieurs manières de [représenter un graphe](https://en.wikipedia.org/wiki/Graph_(abstract_data_type)#Common_Data_Structures_for_Graph_Representation).
Dans ce TP nous utiliserons une implémentation très simple.
- Chaque noeud est réprésenté par une structure de donné. Un noeud
possede un entier non négatif qui sert comme identifiant unique,
et l'objet associé au noeud (par exemple une string) ;
- La liste de noeuds est gardé dans une *map* qui associé à chaque
identifiants le noeud correspondant ;
- Une arête est une couple de noeuds, et elle est associé avec une
étiquette ; elle possede un identifiant unique.
- La liste des arêtes est gardée aussi dans une *map* qui associé à
chaque identifiant la structure representant l'arête.
- Finalement, on utilise une *map* qui associe à chaque noeuds la
liste des arêtes *sortantes* du noeud (c'est à dire : les arêtes
qui ont le noeud comme source).
Voici la squelette de code que nous utiliserons.
class Graph {
struct Node {
int node_id;
std::string data;
};
struct Edge {
int edge_id;
std::string 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) :
nodes(other.nodes), edges(other.edges), dests(other.dests),
id_counter(other.id_counter), edge_counter(other.edge_counter) {}
inline int add_node(const std::string &m) { /* todo */ }
inline bool node_exist(int id) const { /* todo */ }
inline int add_edge(const std::string &m, int source_id, int dest_id) {
/* todo */
}
inline void remove_node(int node_id) {
/* todo */
}
inline int search_node(const std::string &m) const {
/* todo */
}
inline std::string get_node_data(int node_id) const {
/* todo */
}
inline std::string get_edge_data(int edge_id) const {
/* todo */
}
inline int get_edge_source(int edge_id) const {
/* todo */
}
inline int get_edge_dest(int edge_id) const {
/* todo */
}
std::vector<int> get_successors(int node_id) const {
/* todo */
}
std::vector<int> get_predecessors(int node_id) const {
/* todo */
}
using Path=std::vector<int>;
std::vector<Path> all_paths(int from, int to) const {
/* todo */
}
Path shortest_path(int from, int to) const {
/* todo */
}
};
Le but est de faire une librairie. D'abord on développe une classe
non-template, ou les noeuds et les arêtes sont associés à des
strings.
## Question 1 : coder la classe
Les méthodes à implémenter:
- un *copy constructor* ;
- `add_node` ajoute un noeud dans le graph et retourne
l'identifiant unique du noeud (un entier).
- `add_edge` ajoute une arête entre deux noeuds à partir de leur
ids. Il retourne l'identifiant unique de l'arête (un entier).
- `get_node_data()` et `get_edge_data()` retournent les contenus à partir des ids.
- pour une arête, `get_source` et `get_dest` retournent les
identifiants des noeuds source et destination.
- pour un noeud, `get_successors` retourne un vecteur d'arêtes
sortants; `get_predecessor` retourne un vecteur d'arêtes entrants
- Un `Path` est juste un vecteur d'arêtes qui marque un chemin dans
le graphe.
- La fonction `all_paths` retourne la liste de tous les chemins
possibles d'un noeud `from` au noeud `to`. Si aucun chemin
existe, il retourne un vecteur vide. **Attention : pour simplifier
le codage, on assume que le graphe ne contient jamais de boucles !**
- Pour l'algorithme `shortest_path`, on utilisera [l'algorithme de Djikstra](https://en.wikipedia.org/wiki/Dijkstra%2527s_algorithm#Description).
Implémenter la classe. Écrire de tests pour vérifier qu'elle
fonctionne correctement.
## Question 2 : template
Généraliser la classe Graph pour associer aux noeuds et aux arêtes
des objets d'un type quelconque :
- La classe Graph devient une classe template:
template<class ND, class ED>
class Graph {
...
};
- Les fonctions `get_node_data(int node_id)` et `get_edge_data(int
edge_id)` doivent retourner les objets correspondants :
template<class ND, class ED>
class Graph {
...
public:
ND get_node_data(int node_id) const { /* todo */ }
ED get_edge_data(int edge_id) const { /* todo */ }
};
Vérifier que les tests sont encore correctes quand on spécifie des
strings pour cette classe.
Suggestion : codez la classe template dans un fichier différent,
par exemple dans `graph_t.hpp`. Il faut inclure l'un ou l'autre.
## Question 3: Décoration
On voudrait décorer les arêtes avec des informations
supplémentaires sans forcement modifier la classe associé aux
arêtes.
Par exemple, supposez que le graphe représente les routes dans le
département du Nord. On associé un `std::string` aux arêtes avec le
nom de la route. Plus tard, on voudrais ajouter l'information sur
la longueur en Km de la route.
Voici comment on fait:
- On prépare une classe template variadique `EdgeData` qui contient
des strings.
template <typename ...Tp>
class EdgeData : public Tp ... {
std::string str;
public:
void set_string(const std::string &s) { str = s; }
std::string get_string() const { return str; }
};
- On déclare une class `RouteLenght` :
class RouteLenght {
double l;
public:
void set_lenght(double len) { l = len; }
double get_lenght(double len) const { return l; }
};
- Maintenant, la classe `EdgeData<RouteLenght>` contient une
`string` et un `double`, et on peut l'utiliser comme dans le code
suivant :
EdgeData<RouteLenght> data;
data.set_string("Lille-Valenciennes");
data.set_lenght(44.14);
- On déclare une instance de `Graph` ayant comme paramètre la classe
`EdgeData<RouteLenght>` :
Graph<string, EdgeData<RouteLenght>> mygraph;
int lille = mygraph.add_node("Lille");
int valen = mygraph.add_node("Valenciennes");
mygraph.add_edge(data, lille, valen);
Tester le bon fonctionnement de cette technique. Ajouter aussi une
deuxième propriété `AverageTime` pour mémoriser le temps moyenne de
parcours d'une route, et vérifier que tout fonctionne correctement.
## Question 4: Généralisation de `shortest_path`
On voudrait spécialiser `shortest_path` pour prendre en compte une
propriété générique des edges. Par exemple, dans la cas d'un graphe
qui représente les routes du département, on voudrais calculer le
parcours plus court, ou le parcours avec le plus grande nombre de
stations d'essence, etc.
Pour faire ça, la méthode devient une méthode *template* qui prends
comme paramètre une fonction d'évaluation de la métrique
sur les arêtes.
Écrire la méthode template `shortest_path`, et tester avec la
classe `EdgeData<RouteLenght>` implémentée dans la
question précédente.
## Question 5: shared pointers
Dans les questions précédentes il n'y a pas manière de changer
les informations associés aux noeuds et aux arêtes. Pour permettre
ça, on va changer de strategie.
- Dans la struct `Node` et dans la struct `Edge` on memorise un
`shared_ptr` vers l'objet associé
template <typename N, typename E>
class Graph {
struct Node {
int node_id;
std::shared_ptr<N> data;
};
struct Edge {
int edge_id;
int source_id;
int dest_id;
std::shared_ptr<E> data;
};
/* etc. */
};
- Les fonctions `get_node_data_` et `get_edge_data` retournent un `shared_ptr<>`
vers l'objet associé qu'on peut modifier après:
std::shared_ptr<N> get_node(int node_id) {/*todo*/}
std::shared_ptr<E> get_edge(int edge_id) {/*todo*/}
Implémentez cette nouvelle version.
- Tester le scénario suivant :
1. Un utilisateur crée un graphe de distances entre des villes
dans le département du nord.
2. Il calcule le chemin minimale en utilisant la technique
implémenté à la question 4.
3. Il modifie un distance.
4. Il recalcule la chemin optimale et il obtient un parcours
différent.
- Tester qu'une référence obtenue avec `get_node_data()` est toujours
valide après avoir détruit le graphe.
## Question 6: copie profonde
Le copy constructor par défaut fait une copie *shallow* du graph,
et donc les objets pointés par le `shared_ptr` ne sont par copiés.
Ajouter une fonction pour faire la copie profonde du graph:
template <typename N, typename E>
class Graph {
/* ... */
public:
Graph() {}
Graph (const Graph &other) { /* shallow copy */ }
Graph deep_copy() const { /* deep copy */ }
/* ... */
};
La fonction vérifie si le type `N` est polymorphique: si oui, on
utilise la fonction `clone`, si non on utilise le copy
constructor. Même chose pour le type `E` (il faut utiliser la
technique `if constexpr` du C++17 vue en cours).
Tester que le copie sont effectué correctement: en particulier,
dans le cas d'une copie profonde, si on modifie l'objet original,
la copie n'est pas modifiée.
#+OPTIONS: toc:nil ^:nil num:nil
#+BEGIN_SRC emacs-lisp :exports none :results silent
(setq org-latex-minted-options
'(("frame" "lines")
;;("bgcolor" "mybg")
;;("fontsize" "\\scriptsize")
("mathescape" "")
("samepage" "")
("xrightmargin" "0.5cm")
("xleftmargin" "0.5cm")
;; ("escapeinside" "@@")
))
#+END_SRC
#+TITLE: Construction Objets Avancée : TP 4
* Graph library
Un /graphe orienté/ est une [[https://fr.wikipedia.org/wiki/Graphe_(math%25C3%25A9matiques_discr%25C3%25A8tes)][structure de données]] utilisé en
mathématique et en informatique pour représenter des réseaux
d'objets. Le but de ce TP est de construire une /graph template
library/ pour créer et manipuler de graphes.
** Définitions
Un /graphe orienté/ est un ensemble de noeuds connectés par des
arêtes orientées (/edges/). Il est possible d'associer des objets
(étiquettes) aux noeuds et aux arêtes.
Par exemple, on veut représenter le réseau des routes dans le
Nord. Chaque noeud représentes une ville et les arêtes sont les
routes. Dans ce cas, à chaque noeud on associe un objet de type
=Ville= et à chaque arête on associe un objet de type =Route=.
#+BEGIN_SRC dot :file graph-ex.png
digraph nord {
Lille -> Bethune;
Lille -> Lens;
Lille -> { Douai Valenciennes };
subgraph { rank = same; Douai; Valenciennes; };
Bethune -> Lens;
Lens -> Douai;
Douai -> Valenciennes;
Lens -> Arras;
Douai -> Arras;
}
#+END_SRC
#+attr_latex: :float t :width .4\textwidth
#+RESULTS:
[[file:graph-ex.png]]
Il y a plusieurs manières de [[https://en.wikipedia.org/wiki/Graph_(abstract_data_type)#Common_Data_Structures_for_Graph_Representation][représenter un graphe]].
Dans ce TP nous utiliserons une implémentation très simple.
- Chaque noeud est réprésenté par une structure de donné. Un noeud
possede un entier non négatif qui sert comme identifiant unique,
et l'objet associé au noeud (par exemple une string) ;
- La liste de noeuds est gardé dans une /map/ qui associé à chaque
identifiants le noeud correspondant ;
- Une arête est une couple de noeuds, et elle est associé avec une
étiquette ; elle possede un identifiant unique.
- La liste des arêtes est gardée aussi dans une /map/ qui associé à
chaque identifiant la structure representant l'arête.
- Finalement, on utilise une /map/ qui associe à chaque noeuds la
liste des arêtes /sortantes/ du noeud (c'est à dire : les arêtes
qui ont le noeud comme source).
Voici la squelette de code que nous utiliserons.
#+BEGIN_SRC c++
class Graph {
struct Node {
int node_id;
std::string data;
};
struct Edge {
int edge_id;
std::string 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) :
nodes(other.nodes), edges(other.edges), dests(other.dests),
id_counter(other.id_counter), edge_counter(other.edge_counter) {}
inline int add_node(const std::string &m) { /* todo */ }
inline bool node_exist(int id) const { /* todo */ }
inline int add_edge(const std::string &m, int source_id, int dest_id) {
/* todo */
}
inline void remove_node(int node_id) {
/* todo */
}
inline int search_node(const std::string &m) const {
/* todo */
}
inline std::string get_node_data(int node_id) const {
/* todo */
}
inline std::string get_edge_data(int edge_id) const {
/* todo */
}
inline int get_edge_source(int edge_id) const {
/* todo */
}
inline int get_edge_dest(int edge_id) const {
/* todo */
}
std::vector<int> get_successors(int node_id) const {
/* todo */
}
std::vector<int> get_predecessors(int node_id) const {
/* todo */
}
using Path=std::vector<int>;
std::vector<Path> all_paths(int from, int to) const {
/* todo */
}
Path shortest_path(int from, int to) const {
/* todo */
}
};
#+END_SRC
Le but est de faire une librairie. D'abord on développe une classe
non-template, ou les noeuds et les arêtes sont associés à des
strings.
** Question 1 : coder la classe
Les méthodes à implémenter:
- un /copy constructor/ ;
- =add_node= ajoute un noeud dans le graph et retourne
l'identifiant unique du noeud (un entier).
- =add_edge= ajoute une arête entre deux noeuds à partir de leur
ids. Il retourne l'identifiant unique de l'arête (un entier).
- =get_node_data()= et =get_edge_data()= retournent les contenus à partir des ids.
- pour une arête, =get_source= et =get_dest= retournent les
identifiants des noeuds source et destination.
- pour un noeud, =get_successors= retourne un vecteur d'arêtes
sortants; =get_predecessor= retourne un vecteur d'arêtes entrants
- Un =Path= est juste un vecteur d'arêtes qui marque un chemin dans
le graphe.
- La fonction =all_paths= retourne la liste de tous les chemins
possibles d'un noeud =from= au noeud =to=. Si aucun chemin
existe, il retourne un vecteur vide. *Attention : pour simplifier
le codage, on assume que le graphe ne contient jamais de boucles !*
- Pour l'algorithme =shortest_path=, on utilisera [[https://en.wikipedia.org/wiki/Dijkstra%2527s_algorithm#Description][l'algorithme de Djikstra]].
Implémenter la classe. Écrire de tests pour vérifier qu'elle
fonctionne correctement.
** Question 2 : template
Généraliser la classe Graph pour associer aux noeuds et aux arêtes
des objets d'un type quelconque :
- La classe Graph devient une classe template:
#+BEGIN_SRC c++
template<class ND, class ED>
class Graph {
...
};
#+END_SRC
- Les fonctions =get_node_data(int node_id)= et =get_edge_data(int
edge_id)= doivent retourner les objets correspondants :
#+BEGIN_SRC c++
template<class ND, class ED>
class Graph {
...
public:
ND get_node_data(int node_id) const { /* todo */ }
ED get_edge_data(int edge_id) const { /* todo */ }
};
#+END_SRC
Vérifier que les tests sont encore correctes quand on spécifie des
strings pour cette classe.
Suggestion : codez la classe template dans un fichier différent,
par exemple dans =graph_t.hpp=. Il faut inclure l'un ou l'autre.
** Question 3: Décoration
On voudrait décorer les arêtes avec des informations
supplémentaires sans forcement modifier la classe associé aux
arêtes.
Par exemple, supposez que le graphe représente les routes dans le
département du Nord. On associé un =std::string= aux arêtes avec le
nom de la route. Plus tard, on voudrais ajouter l'information sur
la longueur en Km de la route.
Voici comment on fait:
- On prépare une classe template variadique =EdgeData= qui contient
des strings.
#+BEGIN_SRC C++
template <typename ...Tp>
class EdgeData : public Tp ... {
std::string str;
public:
void set_string(const std::string &s) { str = s; }
std::string get_string() const { return str; }
};
#+END_SRC
- On déclare une class =RouteLenght= :
#+BEGIN_SRC C++
class RouteLenght {
double l;
public:
void set_lenght(double len) { l = len; }
double get_lenght(double len) const { return l; }
};
#+END_SRC
- Maintenant, la classe =EdgeData<RouteLenght>= contient une
=string= et un =double=, et on peut l'utiliser comme dans le code
suivant :
#+BEGIN_SRC c++
EdgeData<RouteLenght> data;
data.set_string("Lille-Valenciennes");
data.set_lenght(44.14);
#+END_SRC
- On déclare une instance de =Graph= ayant comme paramètre la classe
=EdgeData<RouteLenght>= :
#+BEGIN_SRC c++
Graph<string, EdgeData<RouteLenght>> mygraph;
int lille = mygraph.add_node("Lille");
int valen = mygraph.add_node("Valenciennes");
mygraph.add_edge(data, lille, valen);
#+END_SRC
Tester le bon fonctionnement de cette technique. Ajouter aussi une
deuxième propriété =AverageTime= pour mémoriser le temps moyenne de
parcours d'une route, et vérifier que tout fonctionne correctement.
** Question 4: Généralisation de =shortest_path=
On voudrait spécialiser =shortest_path= pour prendre en compte une
propriété générique des edges. Par exemple, dans la cas d'un graphe
qui représente les routes du département, on voudrais calculer le
parcours plus court, ou le parcours avec le plus grande nombre de
stations d'essence, etc.
Pour faire ça, la méthode devient une méthode /template/ qui prends
comme paramètre une fonction d'évaluation de la métrique
sur les arêtes.
Écrire la méthode template =shortest_path=, et tester avec la
classe =EdgeData<RouteLenght>= implémentée dans la
question précédente.
** Question 5: shared pointers
Dans les questions précédentes il n'y a pas manière de changer
les informations associés aux noeuds et aux arêtes. Pour permettre
ça, on va changer de strategie.
- Dans la struct =Node= et dans la struct =Edge= on memorise un
=shared_ptr= vers l'objet associé
#+BEGIN_SRC C++
template <typename N, typename E>
class Graph {
struct Node {
int node_id;
std::shared_ptr<N> data;
};
struct Edge {
int edge_id;
int source_id;
int dest_id;
std::shared_ptr<E> data;
};
/* etc. */
};
#+END_SRC
- Les fonctions =get_node_data_= et =get_edge_data= retournent un =shared_ptr<>=
vers l'objet associé qu'on peut modifier après:
#+BEGIN_SRC C++
std::shared_ptr<N> get_node(int node_id) {/*todo*/}
std::shared_ptr<E> get_edge(int edge_id) {/*todo*/}
#+END_SRC
Implémentez cette nouvelle version.
- Tester le scénario suivant :
1) Un utilisateur crée un graphe de distances entre des villes
dans le département du nord.
2) Il calcule le chemin minimale en utilisant la technique
implémenté à la question 4.
3) Il modifie un distance.
4) Il recalcule la chemin optimale et il obtient un parcours
différent.
- Tester qu'une référence obtenue avec =get_node_data()= est toujours
valide après avoir détruit le graphe.
** Question 6: copie profonde
Le copy constructor par défaut fait une copie /shallow/ du graph,
et donc les objets pointés par le =shared_ptr= ne sont par copiés.
Ajouter une fonction pour faire la copie profonde du graph:
#+BEGIN_SRC C++
template <typename N, typename E>
class Graph {
/* ... */
public:
Graph() {}
Graph (const Graph &other) { /* shallow copy */ }
Graph deep_copy() const { /* deep copy */ }
/* ... */
};
#+END_SRC
La fonction vérifie si le type =N= est polymorphique: si oui, on
utilise la fonction =clone=, si non on utilise le copy
constructor. Même chose pour le type =E= (il faut utiliser la
technique =if constexpr= du C++17 vue en cours).
Tester que le copie sont effectué correctement: en particulier,
dans le cas d'une copie profonde, si on modifie l'objet original,
la copie n'est pas modifiée.
File added
cmake_minimum_required(VERSION 3.11)
Include(FetchContent)
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.0.1)
FetchContent_MakeAvailable(Catch2)
add_executable(test_gnt test_gnt.cpp)
target_compile_features(test_gnt PRIVATE cxx_std_17)
if (WIN32)
target_compile_options(test_gnt PRIVATE /WX /W4)
else()
target_compile_options(test_gnt PRIVATE -Wall)
endif()
target_include_directories(test_gnt PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(test_gnt PRIVATE Catch2::Catch2WithMain)
add_test (NAME test_gnt COMMAND test_gnt)
cmake_minimum_required(VERSION 3.11)
Include(FetchContent)
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.0.1)
FetchContent_MakeAvailable(Catch2)
add_executable(test_gnt test_gnt.cpp)
target_compile_features(test_gnt PRIVATE cxx_std_17)
if (WIN32)
target_compile_options(test_gnt PRIVATE /WX /W4)
else()
target_compile_options(test_gnt PRIVATE -Wall)
endif()
target_include_directories(test_gnt PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(test_gnt PRIVATE Catch2::Catch2WithMain)
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
TEST_CASE("null test", "[nil]")
{
REQUIRE(true);
}
#include <catch2/catch_test_macros.hpp>
#include "graph_nt.hpp"
using namespace std;
SCENARIO("Graph properties", "[graph nt]")
{
GIVEN("A graph with some items") {
Graph 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 ab = g.add_edge("msg-ab", a,b);
int ac = g.add_edge("msg-ac", a,c);
int bd = g.add_edge("msg-bd", b,d);
int cd = g.add_edge("msg-cd", c,d);
REQUIRE(g.get_node_data(a) == "A");
REQUIRE(g.get_node_data(b) == "B");
REQUIRE(g.get_node_data(c) == "C");
REQUIRE(g.get_node_data(d) == "D");
REQUIRE(ab == 0);
REQUIRE(ac == 1);
REQUIRE(g.get_edge_data(ab) == "msg-ab");
REQUIRE(g.get_edge_data(ac) == "msg-ac");
REQUIRE(g.get_edge_data(cd) == "msg-cd");
REQUIRE(g.get_edge_data(bd) == "msg-bd");
vector<int> succ_a = {b, c};
REQUIRE(g.get_successors(a) == succ_a);
vector<int> succ_b = {d};
REQUIRE(g.get_successors(b) == succ_b);
vector<int> prec_d = {b, c};
REQUIRE(g.get_predecessors(d) == prec_d);
WHEN("Copying the graph") {
Graph g1(g);
THEN ("We obtain the same graph, with the same indexes") {
REQUIRE(g1.get_predecessors(d) == prec_d);
}
}
WHEN("Changing the copy") {
Graph g2(g);
g2.add_edge("msg-bc", b,c);
THEN ("The original is not changed") {
REQUIRE(g.get_successors(b).size() == 1);
REQUIRE(g2.get_successors(b).size() == 2);
}
}
}
}
SCENARIO("Computing paths", "[graph nt]")
{
GIVEN("A graph with some elements and no loops") {
Graph 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);
}
}
}
/** AJOUTER DES TESTS ... */
#include <catch2/catch.hpp>
#include "graph_nt.hpp"
using namespace std;
SCENARIO("Graph properties", "[graph nt]")
{
GIVEN("A graph with some items") {
Graph 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 ab = g.add_edge("msg-ab", a,b);
int ac = g.add_edge("msg-ac", a,c);
int bd = g.add_edge("msg-bd", b,d);
int cd = g.add_edge("msg-cd", c,d);
REQUIRE(g.get_node_data(a) == "A");
REQUIRE(g.get_node_data(b) == "B");
REQUIRE(g.get_node_data(c) == "C");
REQUIRE(g.get_node_data(d) == "D");
REQUIRE(ab == 0);
REQUIRE(ac == 1);
REQUIRE(g.get_edge_data(ab) == "msg-ab");
REQUIRE(g.get_edge_data(ac) == "msg-ac");
REQUIRE(g.get_edge_data(cd) == "msg-cd");
REQUIRE(g.get_edge_data(bd) == "msg-bd");
vector<int> succ_a = {b, c};
REQUIRE(g.get_successors(a) == succ_a);
vector<int> succ_b = {d};
REQUIRE(g.get_successors(b) == succ_b);
vector<int> prec_d = {b, c};
REQUIRE(g.get_predecessors(d) == prec_d);
WHEN("Copying the graph") {
Graph g1(g);
THEN ("We obtain the same graph, with the same indexes") {
REQUIRE(g1.get_predecessors(d) == prec_d);
}
}
WHEN("Changing the copy") {
Graph g2(g);
g2.add_edge("msg-bc", b,c);
THEN ("The original is not changed") {
REQUIRE(g.get_successors(b).size() == 1);
REQUIRE(g2.get_successors(b).size() == 2);
}
}
}
}
SCENARIO("Computing paths", "[graph nt]")
{
GIVEN("A graph with some elements and no loops") {
Graph 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);
}
}
}
/** AJOUTER DES TESTS ... */
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment