Licence Informatique 1
eannée Algorithmique et Programmation
Projet
1. Organisation
Le projet est à réaliser en binôme. En fin de semestre (date à préciser), les binômes présenteront leur travail aux encadrants de TP. Chaque binôme devra, au moment des soutenances, déposer sur Moodle le code du programme et les fichiers de données générés, ainsi qu’un rapport qui présentera les choix de programmation, les résultats obtenus (y compris les courbes), les problèmes rencontrés, les solutions apportées et tout élément permettant d'évaluer le travail réalisé.
2. Sujet
Le but du projet est de manipuler des données de taille importante pour tester les algorithmes de recherche et de tri. Les données sont les adresses en France, qu’on peut récupérer sur la page suivante : www.kaggle.com/openaddresses/openaddresses-europe?select=france.csv
Le fichier est aussi disponible via la page Web du module. Le fichier fait 2.2Go, il vaut mieux ne pas l’ouvrir dans un tableur ou un éditeur sous peine de faire planter le logiciel. Les données sont décrites par 11 valeurs, il y a donc 11 colonnes séparées par des virgules. La première ligne du fichier ne contient pas de données mais les noms des colonnes. En tout, il y a 27381996 lignes dans le fichier.
2.1 Chargement des données : écrire un programme qui charge à partir du fichier au moins les valeurs suivantes pour chaque adresse : numéro, rue, ville, code postal
Pour charger ces valeurs, il faut créer des enregistrements et des tableaux selon les besoins.
On peut commencer par charger 105 lignes seulement, puis 2*105, etc.
Il faut évaluer expérimentalement l’évolution du temps de chargement en fonction du nombre de lignes chargées. Ajoutez dans le programme du code pour mesurer le temps de chargement et pour stocker ces mesures dans un fichier CSV. Ici, chaque ligne contiendra le nombre de lignes chargées, et le temps de chargement, par exemple :
100000 , 281 , 200000 , 468 ,
…
À l’aide d’un tableur, tracez la courbe des temps en fonction du nombre de lignes chargées.
Expliquez comment évolue le temps de chargement. Vous pouvez également essayer de prédire le temps de chargement pour un nombre de lignes élevé (par exemple 1 ou 2 millions) et vérifier expérimentalement votre prédiction.
Aide technique :
● en C, les gros tableaux (de l’ordre d’un million ou plus de cases) ne peuvent être déclarés en variables locales, sous peine de faire déborder la pile d’exécution. Il faut donc les déclarer comme variables globales (hors fonction main), avec une taille suffisante.
● pour mesurer un temps de calcul, il faut utiliser la librairie time :
#include <time.h>
...
clock_t debut = clock(); // nombre de pas processeur par seconde ... // traitement dont on veut mesurer la durée
clock_t fin = clock();
int duree = fin-debut;
NB : pour avoir la durée en millisecondes, il faut diviser (fin-début) par le nombre de pas processeur par seconde, puis multiplier par 1000 : 1000*(fin-debut)/CLOCKS_PER_SEC. Mais si la durée est inférieure à une milliseconde, ce calcul donne toujours 0.
● pour découper une chaine en morceaux (ou token) selon un délimiteur (typiquement une virgule), il faut utiliser la fonction strsep de la librairie string :
#include <string.h>
...char* s = strdup(CHAINE); // on met dans s la CHAINE à découper ...
char* val = strsep(&s,","); // on récupère le premier token dans val while(val!=NULL){ // tant qu’il reste un token
... // traitement du token stocké dans val
val = strsep(&s,","); // récupération du token suivant }
NB : ceux qui utilisent un compilateur C autre que le GCC standard n’ont peu-être pas la fonction strsep dans leur librairie string.h (cette fonction a été rajoutée dans les dernières versions du C).
Dans ce cas, vous pouvez copier-coller le code de la fonction dans votre programme : char *strsep (char **stringp, const char *delim){
char *begin, *end;
begin = *stringp;
if (begin == NULL) return NULL;
end = begin + strcspn (begin, delim);
if (*end) {
*end++ = '\0';
*stringp = end;
}
else *stringp = NULL;
return begin;
}
● pour les chaînes, utilisez un type string de quelques dizaines de caractères (tableau de 50 caractères par exemple au lieu de 1024), car il y a beaucoup de chaînes à charger en mémoire, et vous risquez de la saturer.
2.2 Tri
Écrivez une fonction qui trie les données (par exemple par code postal), selon la méthode du tri breton donnée en annexe. Mesurez les temps de calcul du tri en fonction du nombre d’éléments triés, stockez ces temps dans un fichier CSV et utilisez un tableur pour tracer la courbe expérimentale de complexité du tri. Expliquez comment évolue le temps de tri.
Vous pouvez également, en bonus, écrire du code qui mélange les données, afin de pouvoir les re-trier sans avoir besoin de les recharger à partir du fichier. Mélanger les données dans un tableau peut se faire en parcourant le tableau et en échangeant le contenu de la case courante avec celui d’une autre case tirée au hasard parmi les cases non encore mélangées.
2.3 B onus (questions optionnelles qui rapportent néanmoins des points si elles sont traitées) On peut implémenter plusieurs tris parmi les tris vus en cours, et comparer les temps d’exécution des différents tris sur les données fournies, en générant des fichiers CSV.
On peut écrire une fonction qui cherche si les données contiennent un enregistrement pour une commune donnée. Cette recherche peut se faire de manière séquentielle ou par dichotomie, ou même par interpolation linéaire si on suppose que les numéros de commune sont répartis de façon à peu près linéaire. On peut lancer ces fonctions de recherche pour le cas au pire et sur un nombre d’éléments variables, et tracer les courbes expérimentales de complexité des méthodes de recherche.
Annexe : le tri breton
En Bretagne, les crêpes sont faites sur des biligs (sortes de poêles) sans bords, et il est donc difficile de faire des crêpes toutes de même taille. Mais une fois qu'une pile de crêpes de tailles différentes est prête, il existe une méthode simple pour trier les crêpes de la plus grande à la plus petite.
Pour placer la plus grande crêpe en dessous de la pile, on glisse la pelle sous la crêpe la plus grande et on retourne la pile des crêpes qui se trouvent au dessus de la pelle, ce qui place la crêpe la plus grande tout en haut de la pile. Puis on glisse la pelle sous la crêpe la plus basse et on retourne toute la pile de crêpes, ce qui place la crêpe la plus grande tout en bas de la pile. Pour trier toute la pile, il suffit de recommencer l'opération sur le reste de la pile.
Pour implémenter l’algorithme du tri breton afin de trier des données il faut écrire :
- une fonction qui prend en entrée un tableau des données et inverse l’ordre des éléments entre deux indices donnés. Par exemple, s’il s’agit d’inverser le tableau d’entiers t = [1,3,4,2,7,3]
entre les indices i = 1 et j = 4, une fois la fonction exécutée, on aura t = [1,7,2,4,3,3].
- une fonction qui prend en paramètres un tableau de données t et un entier i et renvoie l'indice du plus grand élément parmi ceux d'indices inférieurs ou égal à i. La notion de « plus grand élément » dépend évidemment du critère de tri. Par exemple si on trie les données d’inscription des étudiants selon le nombre d’étudiants inscrits dans la même formation, une case c1 du tableau est
« plus grande « qu’une case c2 si le champ nombre d’inscrits de c1 est plus grand que le champ nombre d’inscrits de c2.
- l’algorithme de tri breton qui utilise les deux fonctions précédentes.