====== Partie III – Algorithmes génériques======
Dans la conclusion du cours, nous affirmons qu’un intérêt du polymorphisme, c’est de pouvoir écrire un algorithme « générique », c’est à dire un algorithme qui pourra fonctionner pour des objets représentant des données de plusieurs types.
Nous considérons un algorithme de tri très simple qui fonctionne sur des entiers :
void insertion_sort(std::vector& anArray)
{
for (int i = 0; i < anArray.size(); i++)
{
for (int j = i + 1; j < anArray.size(); j++)
{
if (anArray[i] > anArray[j])
std::swap(anArray[i], anArray[j]);
}
}
}
Cet algorithme fonctionne uniquement pour des entiers. Nous nous proposons de transformer cette fonction afin qu’elle puisse aussi bien trier des entiers mais aussi des réels ou des complexes.
===== Question n° 1=====
Pour ce faire, nous concevons une classe « abstraite » ayant comme nom « Base » qui expose les fonctions nécessaires à l’écriture de l’algorithme de tri.
Nous souhaitons utiliser l’algorithme avec la signature suivante :
void insertion_sort(std::vector& anArray)
Quelles sont les méthodes virtuelles que la classe abstraite doit exposer ?
La classe doit exposée une méthode permettant de déterminer si un élément est plus grand que l'autre. Il peut s'agit soit d'une méthode testant si un élément est plus grand que l'autre :
class Base
{
public:
virtual bool isGreater(const Base&) const = 0;
// Retourne true si l'objet courant est plus grand.
...
};
Nous pouvons ajouter éventuellement des méthodes complémentaires comme :
virtual bool isGreaterOrEqual(const Base&) const = 0;
virtual bool isLess(const Base&) const = 0;
virtual bool isLessOrEqual(const Base&) const = 0;
virtual bool isEqual(const Base&) const = 0;
virtual bool isNotEqual(const Base&) const = 0;
Voire éventuellement une fonction ''compareTo'':
virtual int compareTo(const Base&) const = 0;
// retourne -1 si l'instance courante est plus petite que l'instance passée en paramètre
// retourne 0 si elles sont égales
// retourne 1 si l'instance courante est plus grande que l'instance passée en paramètre.
===== Question n° 2=====
==== Question n° 2.1====
Ajouter à la classe de base ''Base'' une méthode purement virtuelle ''print()'' qui affiche le contenu de la classe sur la console.
Il suffit d'ajouter la méthode suivante:
class Base
{
...
public:
...
virtual void print() const = 0;
}
==== Question n° 2.2 ====
Créer une fonction ''print(std::vector anArray)'' qui prend un tableau en paramètre et affiche l’ensemble des éléments contenus dans le tableau sur la console.
Il suffit d'énumérer les éléments dans le tableau ou dans le containeur.
Nous pouvons faire une version orientée tableau qui ressemblera à cette version.
void print(std::vector anArray)
{
std::cout << "[";
int lastIndex = anArray.size()-1;
if(lastIndex >= 0)
{
for(int i = 0; i < lastIndex; i++)
{
anArray[i]->print();
std::cout << ", ";
}
anArray[lastIndex]->print();
}
std::cout << "]";
}
Si nous souhaitons être un peu plus "C++" dans l'esprit, nous accéderons par des itérateurs.
template
void print(iterator theStart, iterator theEnd)
{
std::cout << "[";
if (theStart != theEnd)
{
*theStart->print();
theStart++;
while (theStart != theEnd)
{
std::cout << ", ";
*theStart->print();
}
std::cout << "]";
}
}
===== Question n° 3 =====
===== Question n° 3.1 ====
Créer une classe Entier qui dérive de la classe « abstraite » ''Base'' et qui contient un champ ''m_value'' ayant comme type le type ''int''.
En plus de contenir le champ entier ''m_value'', la classe doit définir le constructeur par défaut, le constructeur de conversion d'une valeur de type ''int'' vers l'objet ''Entier'' ainsi que la conversion d'un objet ''Entier'' vers une valeur de type ''int''.
class Entier : public Base
{
private:
int m_value;
public:
Entier() : m_value(0) {}
Entier(int aValue): m_value(aValue) {}
operator int() { return m_value; }
void print() const { std::cout << m_value; }
bool isGreater(const Base& unEntier) const
{
return m_value > dynamic_cast(unEntier).m_value;
}
};
En plus, il faut définir les fonctions ''print'' et ''isGreater''. La fonction ''print'' se contente d'afficher le contenu de l'entier sur la console, la fonction ''isGreater'' est plus complexe. En effet, l'argument a comme type un type ''Base'' et non pas un type ''Entier''. Il va falloir convertir ce type vers un type ''Entier'' qui est un type hérité, c'est pour cela que nous faisons un ''dynamic_cast(unEntier)'' qui va essayer de convertir si le paramètre ''unEntier'' hérite bien de ''Entier'' ou est un ''Entier''. Sinon, un exception ''std::bad_cast'' sera lancé si la conversion n'est pas possible.
==== Question n° 3.2 ====
Créer une fonction qui crée un tableau de ''theNumberOfValues'' entiers de type Entier
std::vector create_integer_array(
size_t theNumberOfValues)
qui crée un tableau de ''theNumberOfValues'' valeurs commençant par la valeur ''0'' et se terminant à la valeur ''theNumberOfValues-1''.
std::vector create_integer_array(size_t theNumberOfValues)
{
std::vector result;
result.resize(theNumberOfValues);
for (int i = 0; i < theNumberOfValues; i++)
result[i] = new Entier(i);
return result;
}
==== Question n° 3.3 ====
Créer une fonction qui crée un tableau de ''theNumberOfValues'' entiers de type ''Entier'' dont les éléments sont des nombres aléatoires compris entre ''theMinValue'' et ''theMaxValue''.
Cette fonction peut s’implanter comme suit.
std::vector create_random_integer_array(
size_t theNumberOfValues, int theMinValue, int theMaxValue)
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> distr(theMinValue, theMaxValue);
std::vector array(theNumberOfValues);
for (size_t i = 0; i < theNumberOfValues; i++)
array[i] = new Entier(distr(gen));
return array;
}
La fonction initialise un générateur de nombre aléatoire ainsi qu'une distribution uniforme
pour des entiers entre ''theMinValue'' et ''theMaxValue''. Elle utilise ensuite ce générateur
couplé à la distribution pour initialiser les ''theNumberOfValues'' éléments du tableau.
===== Question n°4 =====
Modifier la fonction de tri ''insertion_sort(std::vector& anArray)'' afin de ne plus travailler sur des entiers mais sur des objets dérivant de la classe « abstraite » ''Base'' :
void insertion_sort(std::vector& anArray)
Tester la fonction avec le tableau d’entiers que vous avez créé à la question n°3.3.
La seule ligne à modifier est relative à la comparaison, au lieu d'utiliser l'opérateur de comparaison ''>'', nous
faisons un appel à la méthode ''isGreater'', ce qui donne le code suivant :
void insertion_sort(std::vector& anArray)
{
for (int i = 0; i < anArray.size(); i++)
{
for (int j = i + 1; j < anArray.size(); j++)
{
if (anArray[i]->isGreater(*anArray[j]))
std::swap(anArray[i], anArray[j]);
}
}
}
Pour tester, nous allons écrire la fonction suivante :
void testTriEntier()
{
std::vector entiers = create_random_integer_array(10, -100, 100);
print(entiers);
std::cout << std::endl;
insertion_sort(entiers);
print(entiers);
std::cout << std::endl;
}
===== Question n° 5 =====
Nous souhaitons étendre le code à une classe de réel.
==== Question n° 5.1 ====
Ecrire une nouvelles classe ''Reel'' qui dérive de ''Base'' et qui contient un champ ''m_value'' de type ''double''.
==== Question n° 5.2 ====
Créer une fonction qui crée un tableau de ''theNumberOfValues'' objets de type ''Reel'' dont les éléments sont des nombres aléatoires compris entre ''theMinValue'' et ''theMaxValue''.
Cette fonction peut s’implanter comme suit.
std::vector create_random_double_array(
size_t theNumberOfValues, double theMinValue, double theMaxValue)
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> distr(theMinValue, theMaxValue);
std::vector array(theNumberOfValues);
for (size_t i = 0; i < theNumberOfValues; i++)
array[i] = new Reel(distr(gen));
return array;
}
Vérifier que l’algorithme de tri fonctionne aussi pour cette nouvelle nouvelle classe.
Par analogie à la classe ''Entier'', nous créons la classe réel comme suit :
class Reel : public Base
{
private:
double m_value;
public:
Reel() : m_value(0) {}
Reel(double aValue) : m_value(aValue) {}
operator double() { return m_value; }
void print() const { std::cout << m_value; }
bool isGreater(const Base& unReel) const
{
return m_value > dynamic_cast(unReel).m_value;
}
};
Pour tester, nous utiliserons une fonction similaire à la fonction suivante:
void testTriReel()
{
std::vector reels = create_random_double_array(10, -1, 1);
print(reels);
std::cout << std::endl;
insertion_sort(reels);
print(reels);
std::cout << std::endl;
}