User Tools

Site Tools


in204:tds:sujets:td3:part1

This is an old revision of the document!


Partie I – Classe & Fonction Générique

TD3

La classe de la bibliothèque C++ standard dite STL (Standard Template Library) std::vector est définie comme suit :

template <class T, class Alloc=allocator<T>> class vector;

vous trouverez en plus des éléments décrivant la classe dans l’annexe du TD plus d’informations à ce lien.

Pour l’instant, nous ignorons le paramètre Alloc.

Pour pouvoir utiliser cette classe, il est impératif de charger le fichier qui contient la définition de la classe :

#include<vector>

Question n°1

Nous nous intéressons pour trier des éléments dans un tableau dynamique de type std::vector<int>. L’ensemble des fonctions sera créé dans un nom d’espace appelé monomorphic.

Le fichier d’entête simple_sort.hpp expose trois fonctions :

  • populate_with_randoms initialise le vecteur theVector avec theNumberOfValues valeurs entières

tirées au hasard entre theMinValue et theMaxValue,

  • print_vector affiche l'ensemble des valeurs entières contenues dans un vecteur d'entiers,
  • simple_sort tire les valeurs d'un vecteur d'entiers de la plus petite à la plus grande.

Le fichier simple_sort.hpp ressemblera au fichier suivant :

#ifndef simple_sortHPP
#define simple_sortHPP
 
#include<vector>
 
namespace monomorphic
{
    void populate_with_randoms(
        std::vector<int>& theVector,
      	int theNumberOfValues, int theMinValue, int theMaxValue);
 
    void print_vector(const std::vector<int>& anArray);
 
    void simple_sort(std::vector<int>& theValues);
}
 
#endif

Le fichier de code simple_sort.cpp ressemblera au fichier suivant :

#include"simple_sort.hpp"
#include<iostream>
#include<stdlib.h>
 
namespace monomorphic
{
 
void populate_with_randoms(std::vector<int>& theVector,
    int theNumberOfValues, int theMinValue, int theMaxValue)
    {}
 
void print_vector(const std::vector<int>& anArray)
{}
 
void simple_sort(std::vector<int>& theValues)
{}
 
}

Question n°1.1

Écrivez une fonction qui prend un objet de type std::vector<int> et qui ajoute à cet objet un certains nombres de valeurs aléatoires theNumberOfValues comprises entre theMinValue et theMaxValue.

void populate_with_randoms(std::vector<int>& theVector,
    int theNumberOfValues, int theMinValue, int theMaxValue)
{}

Instructions pour tirer une valeur aléatoire

Instructions pour tirer une valeur aléatoire

Fonctions C

La fonction ''rand()'' retourne une valeur alérateoire entre 0 et RAND_MAX.

Pour calculer une valeur aléatoire comprise entre theMinValue et theMaxValue, le code suivant peut-être utilisé :

#include<cstdlib>
 
std::srand(std::time(nullptr));
    // Initialise la racine du générateur de 
    // nombre aléatoire avec la valeur temporelle 
    // actuelle.
int width = theMaxValue - theMinValue;
int randomValue = theMinValue + (std::rand() % width);
    // Retourne une valeur entre theMaxValue et theMinValue

Approche C++

A partir de C++, la bibliothèque STL propose des classes implantant différents générateurs de nombres aléatoires et proposant différentes distributions. Pour les besoins de la question, nous nous intéressons à la classe ''std::uniform_int_distribution'' qui génère des séquences de valeurs pseudo-aléatoires selon une loi de distribution dite uniforme.

#include <random>
#include <iostream>
 
std::random_device rd;  
    // Générateur de nombres alératoire qui est utilisé 
    // comme racine d'une séquence de nombres aléatoires.
std::minstd_rand gen(rd()); 
    // Générateur générant des nombres aléatoires initialisées 
    // par une valeur tirée au hasard par le générateur rd. 
std::uniform_int_distribution<> distribution(theMinValue, theMaxValue);
    // Crée une distribution qui prend une valeur générée par un
    // générateur aléatoire et retournant une valeur uniformément 
    // distribuée entre theMinValue et theMaxValue.
 
int randomValue = distribution(generator);
    // Une valeur générée entre theMinValue et theMaxValue de 
    // manière équiprobable.

Correction

Correction

Question n°1.2

Ecrivez the une fonction qui affiche un objet de type std::vector<int> vers la console qui aura l’entête suivante :

void print_vector(const std::vector<int>& anArray)
{}

Correction

Correction

Question n°1.3

Ajoutez la fonction de tri sur le tableau.

void simple_sort(std::vector<int>& theValues)
{
	for (int i = 0; i<theValues.size(); i++)
	{
		for (int j = i + 1; j< theValues.size(); j++)
		{
			// Compare si les deux elements sont dans le bon ordre.
			if (theValues[i] > theValues[j])
			{
				// Procede a la permutation des deux elements
				int Temp = theValues[i];
				theValues[i] = theValues[j];
				theValues[j] = Temp;
			}
		}
	}
}

Correction

Correction

Question n°1.4

Tester le code que vous avez créé sur un exemple simple, par exemple en créant un tableau de 10 valeurs aléatoires en 1 et 10, l’affichant ensuite, le triant et l’affichant de nouveau.

Correction

Correction

Il suffit d'écrire dans votre fnction main le code suivant :

#include <iostream>
#include"include/simple_sort.hpp"
#include<string>
 
using namespace std;
 
int main()
{
    {
        using namespace monomorphic;
 
        std::vector<int> values;
        populate_with_randoms(values, 10, 1, 10);
        print_vector(values);
        simple_sort(values);
        print_vector(values);
    }

Nous avons dans le code précédent défini une portée débutant par { using namespace monomorphic;. Ceci indique que dans cette portée qui se termine avec le } correspondant nous importons l'ensemble des fonctions contenues dans l'espace de nom monomorphic. Cette importation permet de ne pas avoir à écrire le préfix monomorphic:: devant les fonctions populate_with_randoms, print_vector et simple_sort.

Question n°2

Nous souhaitons étendre cet algorithme de tri fonctionnant sur des entiers à des objets qui peuvent manipuler d’autres éléments que des entiers.

L’ensemble des fonctions sera créé dans un nom d’espace appelé generic. Le fichier d’entête generic_sort.hpp ressemblera au fichier suivant :

#ifndef generic_sortHPP
#define generic_sortHPP
 
#include<vector>
 
namespace generic
{
	// Version générique des fonctions 
	populate_with_randoms
	print_vector;
	simple_sort
}
 
#endif

Question n°2.1

Il n’y aura pas de fichier generic_sort.cpp. Expliquer pourquoi ?

Correction

Correction

  • le fichier '.cpp' qui contient le code définit par le corps des fonctions, l'instanciation des variables globales, des champs statiques, etc.
  • Habituellement, seul le fichier '.hpp' est “inclut” dans le fichier qui va faire appel à la fonction ou qui va céer un objet d'un type qui est définit dans le fichier '.hpp'. Cependant, ce modèle ne fonctionne pas pour les fonctions et classes qui sont définies par des patrons. Il est nécessaire d'inclure le code contenu dans les corps de fonctions dans le fichier qui va faire appel à la fonction. Dans ce cas, cela signifie que l'ensemble du code va être présent dans le fichier '.hpp'.

    Plus précisément, nous pouvons constater que pour la définition des librairies présentes dans la STL (la librairie standard de C++), les fichiers n'ont plus d'extension. Ainsi par exemple, l'entête stdlib.h de C est devenu cstdlib sans extension '.h' pour bien montrer que le fichier inclus mélange définition de protoypes, de types et de patrons.

    Dans le cas de ce projet, nous pouvons :

    • soit intégrer les patrons incluant le code dans le fichier simple_sort.hpp,
      #ifndef SIMPLE_SORT_HPP
      #define SIMPLE_SORT_HPP
       
      #include<iostream>
      #include<random>
      #include<vector>
      #include<algorithm>
      #include<cstdio>
       
      namespace monomorphic
      {
          void populate_with_randoms(
              std::vector<int>& theVector,
            	int theNumberOfValues, int theMinValue, int theMaxValue);
       
          void print_vector(const std::vector<int>& anArray);
       
          void simple_sort(std::vector<int>& theValues);
      }
      namespace generic
      {
          template<typename T, typename genType = int>
          void populate_with_randoms(
              std::vector<T>& theVector,
            	int theNumberOfValues, genType theMinValue, genType theMaxValue)
          {
              ... 
          }
          ...
      }
    • soit créer un fichier simple_sort qui inluera le fichier simple_sort.hpp pour les définitions des prototypes des fonctions implantées dans simple_sort.cpp et les patrons des fonctions qui sera défini dans simple_sort.
      #ifndef SIMPLE_SORT
      #define SIMPLE_SORT
       
      #include<iostream>
      #include<random>
      #include<vector>
      #include<algorithm>
      #include<cstdio>
       
      #include"simple_sort.hpp"
       
      namespace generic
      {
          template<typename T, typename genType = int>
          void populate_with_randoms(
              std::vector<T>& theVector,
            	int theNumberOfValues, genType theMinValue, genType theMaxValue)
          {
              ... 
          }
          ...
      }

    Question n°2.2

    Proposer une réécriture des fonctions populate_with_randoms, print_vector et simple_sort pour qu’elles puissent fonctionner avec n’importe quel autre type comme des double, des float, des short, des unsigned.

    Correction

    Correction

    Nous nous restreignons pour l'instant à des types dénotant des valeurs numériques et supportant nativement une conversion soit implicite soit explicite d'un type entier supporté par le générateur de nombre aléatoire et le type des valeurs stockées dans le tableau dynamique.

    Lorsque nous nous intéressons au prototype de la fonction populate_with_randoms monomorphes :

    void populate_with_randoms(
            std::vector<int>& theVector,
          	int theNumberOfValues, int theMinValue, int theMaxValue);

    Il serait tentant de remplacer toutes les instances de int par un paramètre de type T.
    Ceci donnerait la signature suivante :

    template<typename T>
    void populate_with_randoms(
            std::vector<T>& theVector,
          	T theNumberOfValues, T theMinValue, T theMaxValue)
    {
        std::random_device rd;
        std::minstd_rand gen(rd());
        std::uniform_int_distribution<T> distribution(theMinValue, theMaxValue);
        theVector.clear();
        for(;
            theNumberOfValues > 0;
            theNumberOfValues --)
        {
            theVector.push_back(
            (T)distribution(gen));
        }
    }

    qui en fait serait totalement inadaptée.
    En effet, pour le type T=double, nous nous retrouverons avec la signature :

    void populate_with_randoms<double>(
            std::vector<double>& theVector,
          	double theNumberOfValues, double theMinValue, double theMaxValue)
    {
            std::random_device rd;
            std::minstd_rand gen(rd());
            std::uniform_int_distribution<double> 
                distribution(theMinValue, theMaxValue);
                // !!!! l'instantiation uniform_int_distribution<double> 
                // n'est pas supportée
            theVector.clear();
            for(;
                theNumberOfValues > 0;
                theNumberOfValues --)
                // Fonctionne mais theNumberOfValues n'est pas 
                // a priori une valeur flottante mais un entier !
            {
                theVector.push_back(
                    (double)distribution(gen));
            }
    }

    Il est donc nécessaire de bien séparer les différents types des paramètres :

    • std::vector<int>& theVector ⇒ nous pouvons étendre le support de la fonction à tous les types T génériques correspondant à des types numériques.
    • int theNumberOfValues ⇒ cet élément reste un nombre entier, éventuellement nous pouvons y substituer le type size_t.
    • int theMinValue, int theMaxValue ⇒ le support de la fonction peut être étendu à tous les types supportés par la classe std::uniform_int_distribution.

    Dans ce cas, nous pouvons redéfinir le patron de la fonction comme suit :

    namespace generic 
    {
        template<typename T, typename genType>
        void populate_with_randoms(
            std::vector<T>& theVector,
          	size_t theNumberOfValues,
            genType theMinValue, genType theMaxValue)
        {
            std::random_device rd;
            std::minstd_rand gen(rd());
            std::uniform_int_distribution<genType> distribution(theMinValue, theMaxValue);
            theVector.clear();
            for(;
                theNumberOfValues > 0;
                theNumberOfValues --)
            {
                theVector.push_back(
                    (genType)distribution(gen));
            }
        }
    }

    Pour les fonctions print_vector et simple_sort, la généralisation est simple, puisqu'il n'y a qu'un paramètre à généraliser qui est le type des valeurs stockées dans le tableau dynamique.

    namespace generic
    {
        ...
     
        template<typename T>
        void print_vector(const std::vector<T>& anArray)
        {
            int length = anArray.size();
            std::cout << "[";
            for(int index = 0; index < length - 1; index ++)
                std::cout << anArray[index] << ", ";
            std::cout << anArray[length - 1] << "]" << std::endl;
        }
        template<typename T>
        void simple_sort(std::vector<T>& theValues)
        {
            int length = theValues.size();
            for(int i = 0; i < length-1; i ++)
            {
                for(int j= i+1; j < length; j++)
                {
                    if(theValues[i] > theValues[j])
                        std::swap(theValues[i], theValues[j]);
                }
            }
        }
    }

    Question n°2.3

    Tester vos fonctions génériques pour différents types.  

    Correction

    Correction

    in204/tds/sujets/td3/part1.1600843076.txt.gz · Last modified: 2020/09/23 06:37 by bmonsuez