Correction DS4 : structures, fichiers, tables associatives et tableaux bidimensionnels en OCaml et C - 3h

La page est créée Clément Lemaitre
 
CONTINUER À LIRE
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