Correction DS4 : structures, fichiers, tables associatives et tableaux bidimensionnels en OCaml et C - 3h
←
→
Transcription du contenu de la page
Si votre navigateur ne rend pas la page correctement, lisez s'il vous plaît le contenu de la page ci-dessous
Correction DS4 - 3h 02 février 2022 Correction DS4 : structures, fichiers, tables associatives et tableaux bidimensionnels en OCaml et C - 3h Lilian Besson 2 février 2022 La calculatrice et les appareils électroniques ne sont pas autorisés. Conseils méthodologiques : On veillera à présenter très clairement sa copie, on attachera un soin particulier à la rédaction et on encadrera les réponses. Il est très fortement conseillé d’utiliser de la couleur, par exemple pour les mots-clés des langages OCaml et C. Les commentaires des programmes doivent dans tous les cas être dans une autre couleur que le programme lui-même. Il est impératif de respecter l’indentation usuelle des fonctions OCaml et du code C. L’introduction et l’utilisation de fonction auxiliaires est autorisée, et même encouragée, d’autant plus si cela améliore la lisibilité et la compréhension. Les fonctions auxiliaires doivent être précédées de leur type et commentées. Toutes les parties sont indépendantes, mais il est conseillé de les traiter dans l’ordre, sans passer, toutefois, trop de temps sur chaque partie. Ex.0 : Questions de cours - OCaml et C Pour les questions vrai ou faux, évidemment vous justifierez rapidement vos réponses. — Q1. En OCaml, dans une expression conditionnelle (if expr1 then expr2 else expr3), quelles sont les contraintes de types sur expr1, expr2 et expr3 ? Quel est alors le type de cette expression et sa valeur ? C’était déjà présent dans le DS numéro 2 et 3 ! expr1 doit être un booléen de type bool, et expr2 et expr3 doivent avoir le même type ’a. L’expression vaut expr2 si expr1 s’évalue à true, expr3 sinon, et son type est ’a. On notera que la partie else expr3 est optionnelle si et seulement si expr2 est de type unit. — Q2. (vrai ou faux) En C, les variables allouées dynamiquement sont situées dans la pile du programme. Correction à travailler vous-même 1/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 Vrai, en opposition aux variables et tableaux et pointeurs alloués statiquement à l’aide de malloc qui eux sont situés sur le tas du programme. — Q3. (vrai ou faux) En C, le contexte des fonctions est situé dans la pile du programme. Vrai. — Q4. (vrai ou faux) En C, l’espace mémoire des variables allouées dynamiquement est automatiquement libéré lorsque l’on sort du bloc où elles sont déclarées. Vrai, contrairement à la question suivante. — Q5. (vrai ou faux) En C, l’espace mémoire des variables allouées statiquement (avec un malloc) est automatiquement libéré lorsque l’on sort du bloc où elles sont déclarées. Faux, c’est pour cela qu’il y a la fonction free qu’il faut penser à utiliser sur la mémoire allouée statiquement à l’aide de malloc. — Q6. (vrai ou faux) Un fichier binaire est un fichier texte qui ne contient que des entiers valant 0 ou 1. C’est faux, un fichier binaire n’est pas (nécessairement) un fichier texte mais est un fichier constitué d’octets quelconques, pas forcément affichable à l’écran. Un fichier texte est encodé selon un encodage précisé dans l’en-tête du fichier (ASCII, Windows 1252, ISO 8859-1, UTF-8 etc), et est constitué de caractère affichable à l’écran, représenté par des octets selon cet encodage. — Q7. (vrai ou faux) Le chemin /usr/bin/python3.6 est un adressage relatif du fichier exécutable python3.6. Si oui, écrire un exemple d’adressage absolu. Si non, écrire un exemple d’adressage relatif. C’est un adressage absolu, qui commence par un / représentant sous GNU/Linux et UNIX la racine du système de fichier. Un exemple d’adressage relatif serait ../bin/python3.6 depuis le dossier /usr/share/. — Q8. (vrai ou faux) En C ou en OCaml, lorsqu’un objet permettant de manipuler un fichier n’est plus utilisé, il vaut mieux le fermer à l’aide de l’instruction adéquate. Vrai il vaut mieux le fermer pour libérer de la ressource. Par défaut les flux de fichiers seront fermés lorsque le programme se terminera. — Q9. (vrai ou faux) En C, l’ouverture d’un fichier en mode "w" seul permet d’écrire dans un fichier initialement vide. Correction à travailler vous-même 2/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 Vrai. On peut aussi ouvrir en mode "a" (append) pour écrire depuis la fin d’un fichier initialement vide. — Q10. En C, que se passe-t-il si on ouvre en mode "w" un fichier non vide ? Son contenu est-il écrasé par les écritures suivantes ? Son contenu sera écrasé par les écritures suivantes, et pire que cela, dès l’ouver- ture du fichier il n’est plus garanti que son contenu n’ait pas été modifié. Son contenu peut être entièrement effacé par la simple opération de l’ouvrir en mode "w" (write). — Q11. Définir en C un type structure qui représente un vecteur de R3 à trois coordonnées flottantes x, y, z ; C’était déjà dans le DS numéro 3. Ce type vecteur3D se définit comme cela : 1 struct vecteur3D { double x; double y; double z; }; — Q12. Écrire en C une fonction bool est_premier(int n) permettant de tester si un entier n ≥ 0 donné est premier ou non. On n’utilisera pas la fonction sqrt du module math.h mais on implémentera √ l’algorithme efficace qui teste seulement les diviseurs jusqu’à diviseur_max = ⌊ n⌋. Correction : 1 #include 2 3 bool est_premier(int n) { 4 if (n < 2) { return false; } 5 if (n == 2) { return true; } 6 if (n % 2 == 0) { return false; } 7 for (int d = 3; d*d
Correction DS4 - 3h 02 février 2022 1 int fibonacci_rec(int n) { 2 if (n == 0) { return 0; } 3 if (n == 1) { return 1; } 4 return fibonacci_rec(n-1) + fibonacci_rec(n-2); 5 } 1 let fibonacci_rec n = 2 match n with 3 | 0 -> 0 4 | 1 -> 1 5 | n -> fibonacci_rec(n-1) + fibonacci_rec(n-2) 6 ;; — Q14. Écrire en C et en OCaml une fonction itérative qui calcule en temps au plus linéaire O(n) le n-ième terme de la suite de Fibonacci Fn . Correction en C et en OCaml : 1 int fibonacci_iter(int n) { 2 if (n == 0) { return 0; } 3 if (n == 1) { return 1; } 4 int fibo_i = 0; 5 int fibo_ip1 = 1; 6 int tmp; 7 for (int i = 0; i < n-1; i += 1) { 8 tmp = fibo_ip1; 9 fibo_ip1 = fibo_i + fibo_ip1; 10 fibo_i = tmp; 11 } 12 return fibo_ip1; 13 } Correction à travailler vous-même 4/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 1 let fibonacci_iter n = 2 match n with 3 | 0 -> 0 4 | 1 -> 1 5 | n -> 6 let fibo_i = ref 0 in 7 let fibo_ip1 = ref 1 in 8 let tmp = ref 0 in 9 for i = 0 to n-2 do 10 tmp := !fibo_ip1; 11 fibo_ip1 := !fibo_i + !fibo_ip1; 12 fibo_i := !tmp; 13 done; 14 !fibo_ip1 15 ;; — Q15. (plus long) Écrire en C et en OCaml une fonction qui affiche les k premiers entiers de Fibonacci qui sont aussi des nombres premiers. En OCaml on supposera avoir une fonction est_premier : int -> bool. Notez que l’on ne sait pas si la suite de Fibonacci contient une infinité de nombres premiers mais on supposera que pour les valeurs de n qui rentrent dans le type int et pour lesquels Fn rentre dans le type int, votre fonction (en C et en OCaml) devra réussir sans problème. Correction en C et en OCaml : 1 void premiers_fibonacci_premiers(int n) { 2 int nb_premiers = 0; 3 int fibo_i = 0; 4 int fibo_ip1 = 1; 5 int tmp; 6 int i = 0; 7 while(nb_premiers < n) { 8 tmp = fibo_ip1; 9 fibo_ip1 = fibo_i + fibo_ip1; 10 fibo_i = tmp; 11 i += 1; 12 if (est_premier(fibo_i)) { 13 nb_premiers += 1; 14 printf("%i le %i-ième nombre de Fibonacci est ֒→ premier.\n", fibo_i, i+1); 15 } 16 } 17 } Correction à travailler vous-même 5/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 2 le 4-ième nombre de Fibonacci est premier. 3 le 5-ième nombre de Fibonacci est premier. 5 le 6-ième nombre de Fibonacci est premier. 13 le 8-ième nombre de Fibonacci est premier. 89 le 12-ième nombre de Fibonacci est premier. 233 le 14-ième nombre de Fibonacci est premier. 1597 le 18-ième nombre de Fibonacci est premier. 28657 le 24-ième nombre de Fibonacci est premier. 514229 le 30-ième nombre de Fibonacci est premier. 433494437 le 44-ième nombre de Fibonacci est premier. 1 let fibonacci_iter n = 2 let fibo_i = ref 0 in 3 let fibo_ip1 = ref 1 in 4 let tmp = ref 0 in 5 let i = ref 0 in 6 for i = 0 to n-2 do 7 tmp := !fibo_ip1; 8 fibo_ip1 := !fibo_i + !fibo_ip1; 9 fibo_i := !tmp; 10 i := !i + 1; 11 if est_premier(!fibo_i) then 12 Printf.printf "%i le %i-ième nombre de Fibonacci est ֒→ premier.\n" fibo_i (i+1); 13 done; 14 ;; Ex.1 : Manipulation de structures en C — Soit les deux structures suivantes : 1 typedef struct { 2 int x; int y; 3 } point; 4 5 typedef struct { 6 int nbPoints; 7 point* points; 8 } figure; 9 — Q16. Écrire une fonction creer_figure qui prend en paramètre un nombre de points et qui alloue la mémoire nécessaire (sur le tas) pour une figure avec ce Correction à travailler vous-même 6/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 nombre de points. Le pointeur vers cette structure est renvoyé. Correction : 1 figure* creer_figure(int nbPoints) { 2 figure* une_figure = (figure*) malloc(sizeof(figure)); 3 une_figure->nbPoints = n; 4 une_figure->points = (point*) malloc(n * sizeof(point)); 5 return une_figure; 6 } — Q17. Écrire ensuite une fonction liberer_figure qui prend comme paramètre d’entrée un pointeur vers une figure, et qui libère toute la mémoire qui lui est allouée. Correction : 1 void liberer_figure(figure* une_figure) { 2 free(une_figure->points); 3 free(une_figure); 4 } — Q18. Écrire une fonction affiche_point qui prend une valeur instance de la struc- ture point et l’affiche sur la sortie standard au format "(x, y)" sans saut de ligne. Correction : 1 void affiche_point(point un_point_xy) { 2 printf("(%i, %i)", un_point_xy.x, un_point_xy.y); 3 } — Q19. Écrire une fonction affiche_figure qui prend une valeur instance de la structure figure et l’affiche sur la sortie standard au format "[(x1, y1); ...; (xn,yn)", sur une ligne à part. Pourquoi a-t-on besoin de stocker le nombre de points dans le champ nbPoints ? Correction : Correction à travailler vous-même 7/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 1 void affiche_figure(figure* une_figure) { 2 printf("["); 3 int nbPoints = une_figure->nbPoints; 4 if (nbPoints > 0) { 5 affiche_point(une_figure->points[0]) 6 } 7 for (int i = 1; i < nbPoints; i += 1) { 8 printf("; "); 9 affiche_point(une_figure->points[i]) 10 } 11 printf("]"); 12 } On a besoin de stocker le nombre de points dans un champ adéquat parce qu’en C un tableau ne connaît pas sa longueur. C’est un problème classique, qui se retrouve par exemple dans les arguments int argc et char* argv[] de chaque fonction main : argc compte le nombre de valeurs présentes dans le tableau de chaînes de caractère argv. — Soit la structure suivante qui reprend des éléments de la question précédente : 1 typedef struct { 2 int nbFigures; 3 figure** figures; 4 } dessin; 5 Elle représente un dessin composé de plusieurs figures, eux-même composés de plusieurs points en deux dimensions. Le champ figures est destiné à contenir un tableau de pointeurs sur des structures de type figure. Pour les questions suivantes, vous pouvez bien-sûr vous appuyez sur les fonctions de l’exercice précédent, même si vous ne l’avez pas effectué entièrement. — Q20. Écrire une fonction pour allouer la mémoire pour un nouveau dessin. Réflé- chissez bien aux paramètre d’entrée nécessaire à cette première fonction. Correction : Correction à travailler vous-même 8/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 1 dessin* creer_dessin(int nbFigures, int* nbPoints) { 2 dessin* un_dessin = (dessin*) malloc(sizeof(dessin)); 3 un_dessin->nbFigures = nbFigures; 4 un_dessin->figures = (figure**) malloc(nbFigures * ֒→ sizeof(figure*)); 5 for (int i = 0 ; i < nbFigures; i += 1) { 6 un_dessin->figures[i] = creer_figure(nbPoints[i]); 7 } 8 return un_dessin; 9 } — Q21. Écrire une fonction pour afficher un dessin. Correction : 1 void affiche_dessin(dessin* un_dessin) { 2 printf("{"); 3 int nbFigures = un_dessin->nbFigures; 4 if (nbFigures > 0) { 5 affiche_figure(un_dessin->figures[0]) 6 } 7 for (int i = 1; i < nbFigures; i += 1) { 8 printf(";\n"); 9 affiche_figure(un_dessin->figures[i]) 10 } 11 printf("}"); 12 } — Q22. Écrire une fonction pour libérer la mémoire pour un dessin. Correction : 1 void liberer_dessin(dessin* un_dessin) { 2 for (int i = 0; i < un_dessin->nbFigures; i += 1) { 3 liberer_figure(un_dessin->figures[i]) 4 } 5 free(un_dessin->figures); 6 free(un_dessin); 7 } Correction à travailler vous-même 9/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 Ex.2 : Manipulation de fichiers en C Rappels de syntaxe : — on ouvre un fichier avec FILE* fp = fopen(nom_fichier, mode) avec mode = "r" en lecture (read) ou mode = "w" en écriture (write). La fonction fopen renvoie un pointeur vers une structure de type FILE. En cas d’échec, le pointeur NULL est renvoyé. Il faut tester ce cas d’échec et les suivants, et arrêter vos programmes plus tôt avec un return EXIT_FAILURE. — on lit un caractère d’un fichier avec c = fgetc(fp), qui renvoie un char valide en cas de réussite, ou l’entier EOF en cas d’échec. Généralement, on définit char c avant et on lit les caractères du fichier jusqu’à la fin du fichier avec une boucle déclarée comme while( (c = fgetc(fp)) != EOF ) { ... }. — on peut écrire dans un fichier avec fprintf(fp, chaine_motif, arguments...) comme un printf normal qui écrit sur la sortie standard (le terminal). — on doit fermer un flux de fichier une fois qu’il n’est plus utilisé, avec fclose(fp). — on terminera les fonctions par un return EXIT_SUCCESS si tout s’est déroulé sans problème. Dans la fonction main, on peut choisir de s’arrêter dès que l’on rencontre un appel qui ne renvoie pas EXIT_SUCCESS et renvoyer comme code de retour au terminal le code de retour de la fonction appelée. — Q23. Écrire un programme C cat.c, séparé en une fonction intermédiaire int cat_sur_fichier(char* nom_fichier), qui affiche le contenu d’un fichier, caractère par caractère, et un int main(int argc, char* argv[]). Le programme fonction- nera comme la commande cat du terminal, et traitera avec cat_sur_fichier tous les arguments de la fonction main, sauf le premier (donc argv[1] jusqu’au dernier). Pour un exemple d’appel sur un fichier test.txt dont voici le contenu : ligne 1 ... ligne 30 1 $ gcc -o cat.exe cat.c # compilation 2 $ ./cat.exe test.txt # fait comme cat, mais avec votre programme 3 ligne 1 4 ... 5 ligne 30 Correction : Correction à travailler vous-même 10/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 1 #include 2 #include 3 4 int cat_sur_fichier(char* nom_fichier) { 5 FILE* fp = fopen(nom_fichier, "r"); 6 if (fp == NULL) { return EXIT_FAILURE; } 7 char c; 8 while ( (c = fgetc(fp)) != EOF ) { 9 printf("%c", c); 10 } 11 if (fclose(fp) == EOF) { 12 return EXIT_FAILURE; 13 } 14 return EXIT_SUCCESS; 15 } 16 17 int main(int argc, char* argv[]) { 18 for (int i = 1; i < argc; i += 1) { 19 cat_sur_fichier(argv[i]); 20 } 21 } — Q24. De même, écrire un programme C wc.c qui calcule et affiche le nombre de caractère de chaque fichier dont le nom est donné en argument en ligne de commande (sauf argv[0] qui est le nom du binaire), au format suivant : 1 $ gcc -o wc.exe wc.c # compilation 2 $ ./wc.exe DS_04_correction.tex 3 17102 DS_04_correction.tex Correction : Correction à travailler vous-même 11/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 1 #include 2 #include 3 4 int wc_sur_fichier(char* nom_fichier) { 5 FILE* fp = fopen(nom_fichier, "r"); 6 if (fp == NULL) { return EXIT_FAILURE; } 7 char c; 8 int count_char = 0; 9 while ( (c = fgetc(fp)) != EOF ) { 10 count_char += 1; 11 } 12 printf("%i %s\n", count_char, nom_fichier); 13 if (fclose(fp) == EOF) { 14 return EXIT_FAILURE; 15 } 16 return EXIT_SUCCESS; 17 } 18 19 int main(int argc, char* argv[]) { 20 for (int i = 1; i < argc; i += 1) { 21 wc_sur_fichier(argv[i]); 22 } 23 } — Q25. Écrire un programme C count_diff.c qui prend deux arguments en ligne de commande, fichier1 et fichier2 deux noms (ou chemins relatifs ou absolus) de fi- chiers, et calcule et affiche le nombre de caractères identiques situés à la même position dans les deux fichiers, au format suivant : $ ./count_diff.exe fichier1.txt fichier2.txt 1329 fichier1.txt fichier2.txt Correction : Correction à travailler vous-même 12/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 1 #include 2 #include 3 4 int count_diff(char* fichier1, char* fichier2) { 5 FILE* fp1 = fopen(fichier1, "r"); 6 if (fp1 == NULL) { return EXIT_FAILURE; } 7 FILE* fp2 = fopen(fichier2, "r"); 8 if (fp2 == NULL) { return EXIT_FAILURE; } 9 char c1; 10 char c2; 11 count_char = 0; 12 while ( (c1 = fgetc(fp1)) != EOF ) { 13 c2 = fgetc(fp2); 14 if (c2 == EOF) { 15 break; // fichier2 est fini plus tôt que fichier1 16 } 17 if (c1 == c2) { 18 count_char += 1; 19 } 20 } 21 printf("%i %s %s\n", count_char, fichier1, fichier2); 22 if (fclose(fp1) == EOF) { return EXIT_FAILURE; } 23 if (fclose(fp2) == EOF) { return EXIT_FAILURE; } 24 return EXIT_SUCCESS; 25 } 26 27 int main(int argc, char* argv[]) { 28 return count_diff(argv[1], argv[2]); 29 } — Q26. Écrire un programme en C cp.c qui prend deux arguments en ligne de commande, source et destination, et ouvre source en lecture et destination en écriture. Le programme copiera chaque caractère du fichier source vers le fichier destination pour en réaliser une copie, comme la commande cp du terminal. Correction : Correction à travailler vous-même 13/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 1 #include 2 #include 3 4 int cp_sur_fichiers(char* fichier1, char* fichier2) { 5 FILE* fp1 = fopen(fichier1, "r"); 6 if (fp1 == NULL) { return EXIT_FAILURE; } 7 FILE* fp2 = fopen(fichier2, "w"); 8 if (fp2 == NULL) { return EXIT_FAILURE; } 9 char c1; 10 while ( (c1 = fgetc(fp1)) != EOF ) { 11 fprintf(fp2, "%c", c1); 12 } 13 if (fclose(fp1) == EOF) { return EXIT_FAILURE; } 14 if (fclose(fp2) == EOF) { return EXIT_FAILURE; } 15 return EXIT_SUCCESS; 16 } 17 18 int main(int argc, char* argv[]) { 19 return cp_sur_fichiers(argv[1], argv[2]); 20 } Ex.3 : Manipulation de fichiers en OCaml Rappels de syntaxe : — on ouvre un fichier avec let fichier_in = open_in nom_fichier in ... en lecture (input) ou let fichier_out = open_out nom_fichier in ... en écri- ture (output). En cas d’échec une exception est renvoyée, on ne cherchera pas à la capturer. — on lit une ligne entière d’un fichier avec let ligne = input_line fichier_in, qui renvoie une string valide en cas de réussite, ou l’exception End_of_file en cas d’échec. On peut capturer cette exception avec un bloc try ... with End_of_file -> .... Généralement, on pense à fermer les flux de fichiers dans le corps du with. On peut écrire une chaîne de caractère dans un fichier avec la fonc- tion de formatage Printf.fprintf fichier_out chaine_motif arguments... qui fonctionne comme le fprintf du C (en particulier, les drapeaux %s etc ont la même signification). — après avoir manipulé un in_channel on pensera à le fermer avec la fonction close_in, et de même pour un out_channel avec close_out. Correction à travailler vous-même 14/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 — Q27. Écrire une fonction copie (nom_in:string) (nom_out:string) qui effectue une copie, ligne par ligne, du fichier d’entrée sur le fichier de sortie. Correction : 1 let copie (nom_in:string) (nom_out:string) = 2 let fichier_in = open_in nom_in in 3 let fichier_out = open_out nom_out in 4 try begin 5 let ligne = input_line fichier_in in 6 Printf.fprintf fichier_out "%s\n" ligne; 7 end with End_of_file -> begin 8 close_in fichier_in; 9 close_out fichier_out; 10 end; 11 ;; — Q28. Écrire une fonction catn (nom_in:string) qui affiche le contenu du fichier, ligne par ligne, avec un numéro de ligne écrit sur (au moins) six caractères (on pensera à utiliser le drapeau "%6i "). On écrira aussi une fonction main: unit -> unit, qui sera appelée en fin du programme, qui appelle catn sur chacun de ses arguments sauf le premier (qui représente le nom du binaire). Par exemple, cette fonction aura le même effet que la commande cat -n : 1 $ ./catn.exe fichier_court.txt # avec votre binaire 2 1 ligne1 3 2 ligne2 4 3 ligne3 5 $ cat -n fichier_court.txt # avec la commande cat du terminal 6 1 ligne1 7 2 ligne2 8 3 ligne3 Correction : Correction à travailler vous-même 15/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 1 let catn (nom_in:string) = 2 let fichier_in = open_in nom_in in 3 let count_ligne = ref 0 in 4 try begin 5 let ligne = input_line fichier_in in 6 count_ligne := !count_ligne + 1; 7 Printf.printf "%6i %s\n" !count_ligne ligne; 8 end with End_of_file -> begin 9 close_in fichier_in; 10 end; 11 ;; — Q29. (plus difficile) Écrire une fonction mini_sed (nom_in:string) (char_avant:char) (char_apres:char) qui affiche le contenu du fichier de nom (ou de chemin relatif ou absolu) nom_in mais en ayant modifié toutes les occurrences du caractère char_avant par char_apres. Par exemple sur le fichier suivant : Ceci est un exemple de fichier sur plusieurs lignes, qui termine ici. avec char_avant = ’e’ et char_apres = ’E’ le résultat sera le suivant : CEci Est un ExEmplE dE fichiEr sur plusiEurs lignEs, qui tErminE ici. Correction : Correction à travailler vous-même 16/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 1 let mini_sed (nom_in:string) (char_avant:char) (char_apres) = 2 let fichier_in = open_in nom_in in 3 try begin 4 while true do 5 let ligne = input_line fichier_in in 6 let taille_ligne = String.length ligne in 7 for i = 0 to taille_ligne-1 do 8 let char_to_print = ligne.[i] in 9 if char_to_print = char_avant then 10 print_char char_apres; 11 else 12 print_char char_to_print; 13 done; 14 print_newline(); 15 done; 16 end with End_of_file -> close_in fichier_in; 17 ;; — Q30. Écrire une fonction print_range (n:int) : unit qui affiche (sur la sortie stan- dard) la liste "[0; 1; ...; n−1]" pour un entier n donné en argument. De même, écrire une fonction ecrire_range (n:int) (nom_out:string) : unit qui écrit cette même liste dans le fichier de nom nom_out. Correction : Correction à travailler vous-même 17/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 1 let print_range (n:int) : unit = 2 print_string "["; 3 for i = 0 to n-1 do 4 if (i > 0) then print_string "; "; 5 print_int i; 6 done; 7 print_endline "]"; 8 ;; 9 10 let ecrire_range (n:int) (nom_out:string) : unit = 11 let fichier_out = open_out nom_out in 12 try begin 13 Printf.fprintf fichier_out "["; 14 for i = 0 to n-1 do 15 if (i > 0) then 16 Printf.fprintf fichier_out " ;"; 17 Printf.fprintf fichier_out "%i\n" i; 18 done; 19 Printf.fprintf fichier_out "]\n"; 20 end with End_of_file -> begin 21 close_out fichier_out; 22 end; 23 ;; Ex.4 : Manipulation de tables de hachage - en OCaml et en C — Q31. En OCaml, en utilisant des listes de paires de type (cle * valeur) list, rap- peler comment créer une table associative vide, comment ajouter une valeur dans une table associative, comment rechercher une valeur associée à une cle, et comment supprimer une paire cle * valeur. Vous pouvez utiliser les fonctions de la bibliothèque standard (du module List) ou réécrire les vôtres. Correction : Correction à travailler vous-même 18/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 1 (* §1 Manipulation avec des listes de couples (clé,valeur) 2 et les fonctions List.*assoc du module List *) 3 4 let tab = [("Paul",16); ("Pierre",15); ("François",8); ("Annie",13); ֒→ ("Corinne",7)];; 5 assert(7 = List.assoc "Corinne" tab);; (* 7 *) 6 assert(List.mem_assoc "Corinne" tab);; (* true *) 7 let tab2 = List.remove_assoc "Corinne" tab;; 8 (* tab2 = [("Paul",16); ("Pierre",15); ("François",8); ("Annie",13) ֒→ ];; *) 9 assert(not (List.mem_assoc "Corinne" tab2));; — Q32. En OCaml, même question avec les fonctions du module Hashtbl qui sont Hashtbl.create taille, Hashtbl.add table cle valeur, Hashtbl.find table cle et Hashtbl.remove table cle. Correction : 1 (* §2 Avec le module Hashtbl *) 2 3 let tab3 = Hashtbl.create 10;; 4 Hashtbl.add tab3 0 "Luke";; 5 Hashtbl.add tab3 0 "Leia";; 6 Hashtbl.add tab3 4 "Vador";; 7 assert("Leia" = (Hashtbl.find tab3 0));; 8 Hashtbl.remove tab3 0;; 9 assert("Luke" = (Hashtbl.find tab3 0));; — Q33. (plus difficile et plus long) En OCaml, proposez une implémentation écrite à la main des quatre fonctions précédentes, pour avoir des tables associatives implémen- tées à l’aide d’une table de hachage, qui résout les collisions par hachage simple. On supposera fixé les types des clés et des valeurs, et on supposera avoir une fonction de hachage h : cle -> int. Correction : Correction à travailler vous-même 19/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 1 (* §3 Implémentation plus manuelle avec une table de hachage *) 2 3 type ('u, 'a) tablehachage = { 4 h: 'u -> int; 5 tab: ('u * 'a) list array; 6 };; 7 8 let h_exemple cle = (3 * cle + 14) mod 10;; 9 10 let creer_table_vide h taille = 11 { h = h; tab = Array.make taille [] } 12 ;; 13 14 let recherche t cle = 15 let indice = t.h cle in 16 List.assoc cle t.tab.(indice) 17 ;; 18 19 let supprime t cle = 20 let indice = t.h cle in 21 t.tab.(indice)
Correction DS4 - 3h 02 février 2022 données (i, j), pour 0 ≤ i < n et 0 ≤ j < m (matrice identité rectangle). Quelle est la complexité temporelle asymptotique de votre fonction initialise_matrice, en fonction de n et m ? — Q35. Écrire une fonction void affiche_matrice(int n, int m, int mat[n][m]) qui affiche une matrice de taille n * m. Quelle est la complexité temporelle asympto- tique de votre fonction affiche_matrice, en fonction de n et m ? — Q36. Écrire une fonction void somme_matrice(int n, int m, int mat1[n][m], int mat2[n][m], int res[n][m]) qui effectue la somme terme à terme de tous les élé- ments de deux matrices mat1 et mat2 passés en paramètres et qui range le résultat dans la matrice res passé en paramètre. On supposera que les deux pointeurs sont différents de res. Quelle est la complexité temporelle asymptotique de votre fonction somme_matrice, en fonction de n et m ? — Q37. Pourquoi a-t-il été nécessaire de passer la matrice res en paramètre supplémen- taire ? Peut-on faire autrement ? — Q38. Écrire une fonction void produit_matrice(int n, int m, int p, int mat1[n][p], int mat2[p][m], int res[n][m]) qui effectue le produit de matrice des deux ma- trices mat1 et mat2 passés en paramètres et qui range le résultat dans la matrice res passé en paramètre. Attention ce n’est pas un produit terme à terme, mais le produit matriciel tel que vu en cours de maths. On supposera que les deux pointeurs sont différents de res. Quelle est la complexité temporelle asymptotique de votre fonction produit_matrice, en fonction de n, m et p ? — Q39. Écrire une fonction void puissance_matrice(int n, int mat[n][n], int k, int res[n][n]) qui effectue la puissance de la matrice carrée mat élevée à la puissance k, et qui range le résultat dans la matrice res passé en paramètre. On supposera que le pointeur de mat est différent de res. Quelle est la complexité temporelle asymptotique de votre fonction puissance_matrice, en fonction de k et n ? Correction : 1 void initialise_matrice(int n, int m, int mat[n][m]) { 2 for (int i = 0; i < n; i += 1) { 3 for (int j = 0; j < m; j += 1) { 4 if (i == j) { 5 mat[i][j] = 1; 6 } else { 7 mat[i][j] = 0; 8 } 9 } 10 } 11 } Cette fonction initialise_matrice est en temps en O(n ∗ m) car elle est constituée de deux boucles for simples imbriquées. Correction à travailler vous-même 21/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 1 void affiche_tableau(int n, int* tab) { 2 printf("["); 3 if (n > 0) { 4 printf("%i", tab[0]); 5 } 6 for (int i = 1; i < n; i += 1) { 7 printf("; %i", tab[i]); 8 } 9 printf("]\n"); 10 } Cette fonction affiche_tableau est en temps en O(n) car elle est constituée d’une seule boucle for simple. 1 void affiche_matrice(int n, int m, int mat[n][m]) { 2 printf("["); 3 if (n > 0) { 4 affiche_tableau(m, mat[0]); 5 } 6 for (int i = 1; i < n; i += 1) { 7 printf("; "); 8 affiche_tableau(m, mat[i]); 9 } 10 printf("]\n"); 11 } Cette fonction affiche_matrice est en temps en O(n ∗ m) car elle est constituée de deux boucles for simples imbriquées. 1 void somme_matrice(int n, int m, int mat1[n][m], int mat2[n][m], int ֒→ res[n][m]) { 2 for (int i = 0; i < n; i += 1) { 3 for (int j = 0; j < m; j += 1) { 4 res[i][j] = 0; 5 for (int k = 0; k < p; k += 1) { 6 res[i][j] = res[i][j] + mat1[i][k] * mat2[k][j]; 7 } 8 } 9 } 10 } Une autre solution qui alloue statiquement la mémoire de la matrice résultat sur le tas avec des malloc : Correction à travailler vous-même 22/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 1 int** somme_matrice_2(int n, int m, int mat1[n][m], int mat2[n][m]) { 2 int** res = (int**) malloc(n * sizeof(int)); 3 for (int i=0; i < n; i += 1) { 4 res[i] = (int*) malloc(m * sizeof(int)); 5 } 6 for (int i = 0; i < n; i += 1) { 7 for (int j = 0; j < m; j += 1) { 8 res[i][j] = mat1[i][j] + mat2[i][j]; 9 } 10 } 11 return res; 12 } Ces deux fonctions somme_matrice sont en temps en O(n ∗ m) car elle est constituée de deux boucles for simples imbriquées. 1 int main(void) { 2 int tab[5] = {4, 3, 1, 6, 3}; 3 affiche_tableau(5, &tab[0]); 4 affiche_tableau(5, tab); 5 6 int n = 3; 7 int m = 5; 8 int mat1[n][m]; 9 initialise_matrice(n, m, mat1); 10 affiche_matrice(n, m, mat1); 11 int mat2[n][m]; 12 initialise_matrice(n, m, mat2); 13 affiche_matrice(n, m, mat2); 14 int res[n][m]; 15 somme_matrice(n, m, mat1, mat2, res); 16 affiche_matrice(n, m, res); 17 int res2[n][m]; Correction à travailler vous-même 23/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 18 res2 = somme_matrice_2(n, m, mat1, mat2); 19 affiche_matrice(n, m, res2); 20 } Qui va afficher : 1 [4; 3; 1; 6; 3] 2 [4; 3; 1; 6; 3] 3 [[1; 0; 0; 0; 0] 4 ; [0; 1; 0; 0; 0] 5 ; [0; 0; 1; 0; 0] 6 ] 7 [[1; 0; 0; 0; 0] 8 ; [0; 1; 0; 0; 0] 9 ; [0; 0; 1; 0; 0] 10 ] 11 [[2; 0; 0; 0; 0] 12 ; [0; 2; 0; 0; 0] 13 ; [0; 0; 2; 0; 0] 14 ] 15 [[2; 0; 0; 0; 0] 16 ; [0; 2; 0; 0; 0] 17 ; [0; 0; 2; 0; 0] Correction à travailler vous-même 24/26 © Lilian Besson, 2022
Correction DS4 - 3h 02 février 2022 18 ] ce qui permet de vérifier que la somme des deux matrices identiques de taille 3*5 est correcte. 1 void produit_matrice(int n, int m, int p, int mat1[n][p], int mat2[p][m], ֒→ int res[n][m]) { 2 for (int i = 0; i < n; i += 1) { 3 for (int j = 0; j < m; j += 1) { 4 res[i][j] = 0; 5 for (int k = 0; k < p; k += 1) { 6 res[i][j] = res[i][j] + mat1[i][k] * mat2[k][j]; 7 } 8 } 9 } 10 } Cette fonction produit_matrice est en temps en O(n ∗ m ∗ p) car elle est constituée de trois boucles for simples imbriquées. 1 void recopie_matrice(int n, int mat[n][n], int res[n][n]) { 2 for (int i = 0; i < n; i += 1) { 3 for (int j = 0; j < n; j += 1) { 4 res[i][j] = mat[i][j]; 5 } 6 } 7 } 8 void puissance_matrice(int n, int mat[n][n], int k, int res[n][n]) { 9 initialise_matrice(n, n, res); // res vaut l'identité désormais 10 int tmp[n][n]; // matrice temporaire 11 for (int a = 1; a
Correction DS4 - 3h 02 février 2022 Fin du sujet. Correction à travailler vous-même 26/26 © Lilian Besson, 2022
Vous pouvez aussi lire