TD 8 : fichiers et images numériques

TD 8 : fichiers et images numériques#

Notes aux enseignants

Dans ce TD et dans le TP, nous ferons la déclaration et l’ouverture du fichier en deux temps, comme dans le cours (dernière version). Les élèves ayant en général du mal avec la gestion des fichiers, avant d’attaquer l’exercice 1 il peut être utile d’écrire au tableau

    ifstream flux;
    flux.open("bonjour.txt");

et de demander aux élèves ce que fait cette instruction, et de même avec ofstream.

En profiter pour rappeler qu’ils doivent relire systématiquement leur cours avant le TD.

Concernant les images, elles n’ont pas été vues en cours. S’arranger pour avoir le temps d’évoquer au moins rapidement la correction des exos sur les images, car l’avoir compris est nécessaire pour le TP.

Exercice 1 : Fichiers

On considère la fonction mystere suivante, avec un exemple d’appel :

void mystere(string nomFichier) {
    ifstream flux;
    flux.open(nomFichier);
    string t;
    int n;
    while ( flux >> t and flux >> n ) {
        cout << t << " " << 2 * n << endl;
    }
    flux.close();
}
    mystere("truc.txt");

Le fichier truc.txt contient les premières lignes suivantes :

Henry 4
Messi 2
Ronaldo 3
Xavi 2
  1. Expliquez ce que fait la fonction mystere. Précisez le format que doit avoir le fichier. Choisissez des noms plus informatifs pour cette fonction et ses variables, et écrivez sa documentation.

    /// BEGIN SOLUTION

    La fonction lit le fichier dont le nom est pris en paramètre. Ce fichier doit contenir une suite de chaînes de caractères (par exemple des noms) suivie chacune d’un entier (par exemple une note). Elle affiche chaque chaîne suivie de l’entier multiplié par 2.

    /** affiche les notes contenues dans un fichier après les avoir
     *  multipliées par deux.
     *  Format: chaque ligne doit être de la forme "<nom> <note>"
     *  @param nomFichier le nom du fichier
     **/
    void afficheNotes(string nomFichier) {
        ifstream flux;
        flux.open(nomFichier);
        string nom;
        int noteSur10;
        while ( flux >> nom and flux >> noteSur10 ) {
            cout << nom << " " << 2 * noteSur10 << endl;
        }
        flux.close();
    }
    

    /// END SOLUTION

  2. Changez la fonction pour que chaque ligne de la sortie soit de la forme :

          Nom : Alfred, note sur 10 : 7, note sur 20 : 14
    

    /// BEGIN SOLUTION

    Il suffit de remplacer l’instruction de la boucle while par :

          cout << "Nom : " << nom << ", note sur 10 : " <<   noteSur10
                                 << ", note sur 20 : " << 2 * noteSur10 << endl;
    

    /// END SOLUTION

Notes aux enseignants

Une des motivations pour faire renvoyer la moyenne comme un entier est que cela simplifie le test automatique avec CHECK.

Exercice 2 : Fichiers

  1. Définissez les deux fonctions suivantes :

    
    /** calcule la moyenne des notes contenues dans un fichier texte
     *  Format: chaque ligne du fichier est de la forme "<nom> <note>"
     *  @param nomFichier le nom du fichier
     *  @return la moyenne des notes, arrondie à sa partie entière
     **/
    int moyenne(string nomFichier);
    
    
    /** lit les notes contenues dans un fichier et en fait un tableau
     *  Format: chaque ligne du fichier est de la forme "<nom> <note>"
     *  @param nomFichier le nom du fichier
     *  @return un tableau contenant les notes
     **/
    vector<int> lit_notes(string nomFichier);
    

    /// BEGIN SOLUTION

    int moyenne(string nomFichier) {
        ifstream flux;
        flux.open(nomFichier);
        string temp;
        int note;
        int somme = 0;
        int nb = 0;
        while ( flux >> temp and flux >> note ) {
            somme += note;
            nb += 1;
        }
        flux.close();
        return somme / nb;
    }
    
    vector<int> lit_notes(string nomFichier) {
        ifstream flux;
        flux.open(nomFichier);
        vector<int> notes;
        string temp;
        int note;
        while ( flux >> temp and flux >> note ) {
            notes.push_back(note);
        }
        flux.close();
        return notes;
    }
    

    /// END SOLUTION

  2. Lorsque cela est possible, écrivez un test.

    /// BEGIN SOLUTION

    Après avoir écrit un fichier de test, on peut faire des tests automatiques :

        CHECK( moyenne("truc.txt") == 3 );
        CHECK( lit_notes("truc.txt") == vector<int>( {4,2,3,2,1,5,5,4,4,5} ) );
    

    /// END SOLUTION

  3. \(\clubsuit\) Comment pourrait-on tester la fonction du premier exercice?

    /// BEGIN SOLUTION

    Cette fonction est plus difficile à tester automatiquement car elle affiche son résultat plutôt que de le renvoyer.

    Il faudrait donc séparer calcul et affichage, à l’aide d’une fonction qui renvoie le résultat comme une chaîne de caractères – que l’on pourra donc facilement tester avec CHECK.

    /// END SOLUTION

Exercice 3 : Images numériques

Pour manipuler informatiquement une image analogique (continue), on doit la numériser, c’est à dire l’encoder sous forme d’une image numérique (discrète). Il en existe deux grandes catégories :

  • Les images vectorielles : une image y est encodée par une combinaison de primitives géométriques (lignes, disques, …) auxquelles sont appliquées des transformations. En savoir plus : https://fr.wikipedia.org/wiki/Image_vectorielle

  • Les images matricielles, ou « carte de points » (de l’anglais bitmap): une image y est constituée d’une matrice – ou tableau ou grille – où chaque case – appellée pixel – possède une couleur qui lui est propre. Il s’agit donc d’une juxtaposition de carrés de couleur formant, dans leur ensemble, une image. En savoir plus : https://fr.wikipedia.org/wiki/Image_matricielle

    Un exemple d'image matricielle

  1. Images en noir et blanc. Le fichier suivant contient une image en noir et blanc au format PBM (Portable Bit Map). Devinez comment fonctionne ce format de fichier et dessinez l’image correspondante.

    P1
    # CREATOR: GIMP PNM Filter Version 1.1
    10 10
    0 0 0 1 1 1 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 1 0
    1 0 0 1 0 0 1 0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1
    1 0 0 1 0 0 1 0 0 1 0 1 0 0 1 1 0 0 1 0 0 0 1 0 0 0 0 1 0 0
    0 0 0 1 1 1 1 0 0 0
    

    Indications : La première ligne précise le format du fichier (texte, image noir et blanc); la deuxième est un commentaire; tout le reste a un rôle! Voir https://fr.wikipedia.org/wiki/Portable_bitmap pour plus d’informations sur ce format de fichier.

    /// BEGIN SOLUTION

    La ligne 10 10 indique qu’il s’agit d’une image de largeur 10 pixels et de hauteur 10 pixels. La suite décrit les pixels de l’image (0 pour blanc, 1 pour noir), en la parcourant de haut en bas et de gauche à droite (comme la lecture d’un texte en français).

    Cela donne : image

    /// END SOLUTION

  2. Images en couleur. Le fichier suivant contient une image en couleur au format PPM (Portable Pix Map). Devinez comment fonctionne ce format de fichier et dessinez l’image correspondante.

    P3
    # CREATOR: GIMP PNM Filter Version 1.1
    3 2
    255
    0 255 0 255 255 255
    255 0 0 0 255 0 255
    255 255 255 0 0
    

    Indication : Quelles sont les trois couleurs primaires usuelles (pour les écrans)?

    /// BEGIN SOLUTION

    La ligne P3 précise le format du fichier (texte, image couleur RGB). La ligne 3 2 indique qu’il s’agit d’une image avec 2 lignes de 3 pixels. La ligne 255 donne la valeur de l’intensité maximale. Ensuite chaque pixel est codé par trois valeurs (RGB = rouge, vert, bleu) comprises entre 0 et l’intensité maximale.

    Cela donne :

    ../_images/drapeau.png

    /// END SOLUTION

Notes aux enseignants

Certains élèves confondent les couleurs primaires de la lumière (synthèse additive : écrans, …): rouge, vert et bleu, et celles de la matière (synthèse soustractive : peinture, …): magenta (rouge), cyan (bleu) et jaune.

Exercice 4 : \(\clubsuit\)

Définissez les fonctions suivantes :


/** compte le nombre de mots d'un fichier texte
 *  @param nomFichier le nom du fichier
 *  @return le nombre de mots contenus dans le fichier
 **/
int word_count(string nomFichier);

/// BEGIN SOLUTION

int word_count(string nomFichier) {
    ifstream flux;
    flux.open(nomFichier);
    int nbr = 0;
    string temp;
    while ( flux >> temp ) {
        nbr += 1;
    }
    flux.close();
    return nbr;
}

/// END SOLUTION


/** compte le nombre de lignes d'un fichier texte
 *  @param nomFichier le nom du fichier
 *  @return le nombre de lignes contenues dans le fichier
 **/
int line_count(string nomFichier);

/// BEGIN SOLUTION

int line_count(string nomFichier) {
    ifstream flux;
    flux.open(nomFichier);
    string ligne;
    int compteur = 0;
    while ( getline(flux, ligne) ) {
        compteur += 1;
    }
    flux.close();
    return compteur;
}

/// END SOLUTION


/** affiche (sur la sortie standard) le contenu d'un fichier texte
 *  @param nomFichier le nom du fichier
 **/
void cat(string nomFichier);

/// BEGIN SOLUTION

void cat(string nomFichier) {
    ifstream flux;
    flux.open(nomFichier);
    string ligne;
    while ( getline(flux,ligne) ) {
        cout << ligne << endl;
    }
    flux.close();
}

/// END SOLUTION


/** copie le contenu d'un fichier texte dans un autre
 *  @param source le nom du fichier source
 *  @param destination le nom du fichier destination
 **/
void copy(string source, string destination);

Indication : la bibliothèque standard fournit la fonction suivante :

/** lit une ligne d'un flux et la stocke dans une chaîne de caractères
 *  @param flux un flux entrant
 *  @param s une chaîne de caractères
 *  @return le flux entrant
 **/
istream getline(istream &flux, string &s);

/// BEGIN SOLUTION

void copy(string source, string destination) {
    ifstream fluxSource;
    fluxSource.open(source);
    ofstream fluxDestination;
    fluxDestination.open(destination);
    string ligne;
    while ( getline(fluxSource, ligne) ) {
        fluxDestination << ligne << endl;
    }
    fluxSource.close();
    fluxDestination.close();
}

/// END SOLUTION

Exercice 5 : \(\clubsuit\)

Devinez ce que fait la fonction mystereEpais suivante et écrire un test :

int mystereEpais(string zut) {
    ifstream bla;
    bla.open(zut);
    int foo = 0;
    char y;
    while ( bla >> y ) {
        foo++;
    }
    bla.close();
    return foo;
}

/// BEGIN SOLUTION

La fonction renvoie le nombre de caractères du fichier en entrée (sans compter les espaces et retours à la ligne : ils sont considérés comme de simples délimiteurs).

Exemple de test :

    CHECK( mystereEpais("truc.txt") == 65 );

/// END SOLUTION

Exercice 6 : \(\clubsuit\)

On dispose de trois fichiers texte nommés note1.txt, note2.txt, et note3.txt. Dans chacun d’eux, chaque ligne contient trois informations, séparées par des espaces : le nom d’un-e étudiant-e, son groupe, une note. Les trois fichiers pourraient par exemple correspondre aux notes dans trois matières pour une même promotion.

Écrivez un programme qui:

  1. Fusionne ces trois fichiers en un seul fichier notes.txt dont chaque ligne aura cinq informations séparées par des espaces : <nom> <groupe> <note1> <note2> <note3>. On vérifiera au passage que les trois fichiers de départ contiennent exactement les mêmes noms d’étudiant-es et dans le même ordre.

  2. Crée un fichier par groupe, qui indiquera, pour chaque étudiant-e de ce groupe, sa note moyenne (moyenne des trois notes), au format <nom> <note_moyenne>. Les fichiers seront nommés groupeB5.txt, groupeA1.txt, groupeC3.txt. Ce programme doit pouvoir fonctionner quel que soit le nombre de groupes et quels que soient les noms de ces groupes.

/// BEGIN SOLUTION

#include <fstream>
#include <iostream>
#include <vector>
using namespace std;

int main() {

    vector<string> fic_notes = {"note1.txt", "note2.txt", "note3.txt"};
    int nb_notes = fic_notes.size();
    int k;
    
    string nom, gpe, nom_fusion;
    double note, somme;

    vector<double> notes(nb_notes);

    // la taille de chaque fichier de notes
    vector<int> taille_fichier(nb_notes);

    for ( int i = 0; i < nb_notes; i++ ) {
            ifstream f;
            f.open(fic_notes[i]);
            taille_fichier[i] = 0;
            // On lit chaque ligne : nom, gpe, note.
            while ( f >> nom >> gpe >> note ) {
                taille_fichier[i]++;
            }
            f.close();
    }
    // Si les fichiers ne sont pas tous de même taille, on s'arrête.
    bool meme_taille = true;
    k = 1;
    while ( k < nb_notes and meme_taille ) {
        meme_taille = (taille_fichier[k] == taille_fichier[0]);
        k++;
    }

    if ( not meme_taille ) {
        cout << "les fichiers de notes ne sont pas de même taille" << endl;
    } else {
        // On recopie nom, groupe, note1 dans le fichier fusionné.
        ifstream note1_lire;
        note1_lire.open(fic_notes[0]);
        ofstream fusion_ecrire;
        fusion_ecrire.open("notes.txt");
        while ( note1_lire >> nom >> gpe >> note ) {
            fusion_ecrire << nom << ' ' << gpe << ' ' << note << endl;
        }
        note1_lire.close();
        fusion_ecrire.close();

        // On recopie les notes suivantes dans le fichier fusionné,
        // avec recopie dans un fichier temporaire à chaque étape.
        // On vérifie au passage que les noms sont dans le même ordre.
        // NB : on aurait pu vérifier aussi les noms des groupes.
        bool erreur_nom = false;
        k = 1;
        while ( not erreur_nom and k < nb_notes ) {
            ifstream fusion_lire;
            fusion_lire.open("notes.txt");
            ofstream temp_ecrire;
            temp_ecrire.open("temp.txt");
            ifstream notek_lire;
            notek_lire.open(fic_notes[k]);

            while ( not erreur_nom
                    and notek_lire >> nom >> gpe
                    and fusion_lire >> nom_fusion >> gpe) {
                erreur_nom = (nom != nom_fusion);
                if ( erreur_nom ) {
                    cout << "pas les mêmes noms" << endl;
                } else {
                    temp_ecrire << nom << ' ' << gpe;
                    for ( int i = 0; i < k; i++) {
                        if ( fusion_lire >> notes[i] ) {
                            temp_ecrire << ' ' << notes[i];
                        }
                    }
                    if ( notek_lire >> notes[k] ) {
                        temp_ecrire << ' ' << notes[k] << endl;
                    }
                }
            }
            temp_ecrire.close();
            fusion_lire.close();
            notek_lire.close();

            // On recopie le fichier temporaire dans le fichier fusionné
            ifstream temp_lire;
            temp_lire.open("temp.txt");
            ofstream fusion_ecrire;
            fusion_ecrire.open("notes.txt");
            while ( temp_lire >> nom >> gpe ) {
                fusion_ecrire << nom << ' ' << gpe;
                for ( int i = 0; i <= k; i++ ) {
                        if ( temp_lire >> notes[i] ) {
                            fusion_ecrire << ' ' << notes[i];
                    }
                }
                fusion_ecrire << endl;
            }
            temp_lire.close();
            fusion_ecrire.close();
            k++;
        }

        if ( not erreur_nom ) {
            // On détermine la liste des groupes.
            // vector<string> groupes(taille_fichier[0]);
            // Au maximum, il y aura un groupe par étudiant.
            vector<string> groupes;
            ifstream f;
            f.open("notes.txt");
            bool present;
            int j;
            while ( f >> nom >> gpe ) {
                present = false;
                j = 0;
                while ( j < groupes.size() and not present ) {
                    present = (gpe == groupes[j]);
                    j++;
                }
                if ( not present ) {
                    groupes.push_back(gpe);
                }
                for ( int i = 0; i < nb_notes; i++ ) {
                    f >> notes[i];
                }
            }
            f.close();

            // On écrit un fichier pour chaque groupe.
            for ( int p = 0; p < groupes.size(); p++ ) {
                ofstream fgpe;
                fgpe.open("groupe" + groupes[p] + ".txt");
                ifstream fusion;
                fusion.open("notes.txt");
                while ( fusion >> nom >> gpe ) {
                    for ( int i = 0; i < nb_notes; i++ ) {
                        fusion >> notes[i];
                    }

                    if ( gpe == groupes[p] ) { // si c'est le même groupe
                        // on calcule la somme des nb_notes notes
                        somme = 0;
                        for ( int n = 0; n < nb_notes; n++ ) {
                            somme += notes[n];
                        }
                        fgpe << nom << ' ' << somme / nb_notes << endl;
                    }
                }
                fgpe.close();
                fusion.close();
            }
        }
    }
    return 0;
}

/// END SOLUTION