3 octobre
|
T05
|
Au menu :
- Retour sur le problème de la tite_chaine
- Effort de généricité et de rigueur. En vue de
T06,
vous devrez implémenter les algorithmes suivants, de telle sorte qu'ils soient
utilisables dans le respect des contraintes énoncées dans chaque cas. Chacun de
ces algorithmes existe dans la bibliothèque standard sous un autre nom, mais
vous n'avez pas le droit d'utiliser l'algorithme standard correspondant pour
résoudre le problème proposé (vous pouvez évidemment utiliser
std::swap()
ou d'autres algorithmes auxiliaires, mais je ne veux pas que votre
implémentation de est_trie() appelle
is_sorted(), à titre d'exemple). Ça peut sembler beaucoup, mais chacun est relativement
petit, et l'exercice est extrêmement pertinent à faire (de plus, je suspecte
que vous allez vous amuser!) :
- à titre de révision, implémentez inverser(debut,fin) où représente une séquence à
demi-ouverte, de telle sorte que cet algorithme inverse l'ordre des éléments
de cette séquence (p. ex. : deviendrait ). Il faut que cet algorithme soit applicable à un tableau, un
vector ou une list. Testez votre implémentation en vous assurant qu'elle
fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre
impair d'éléments, et aucun élément (cas où debut==fin)
- implémentez rotater_gauche(debut,fin) où représente une séquence à
demi-ouverte, de telle sorte que cet algorithme permute vers la gauche l'ordre des éléments
de cette séquence (p. ex. : deviendrait ). Il faut que cet algorithme soit applicable à un tableau,
un
vector ou une list. Testez votre implémentation en vous assurant qu'elle
fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre
impair d'éléments, et aucun élément (cas où debut==fin)
- implémentez rotater_droite(debut,fin) où représente une séquence à
demi-ouverte, de telle sorte que cet algorithme permute vers la droite l'ordre des éléments
de cette séquence (p. ex. : deviendrait ). Il faut que cet algorithme soit applicable à un tableau,
un
vector ou une list. Testez votre implémentation en vous assurant qu'elle
fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre
impair d'éléments, et aucun élément (cas où debut==fin)
- à titre de révision, implémentez compter(debut,fin,val) où représente une séquence à
demi-ouverte, de telle sorte que cet algorithme retourne le nombre d'éléments
e tels que e==val s'avère (p. ex. : pour , compter(begin(vals),end(vals),3)==1).
Il faut que cet algorithme soit applicable à un tableau, un
vector une list ou une
forward_list. Testez votre implémentation en vous assurant qu'elle
fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre
impair d'éléments, et aucun élément (cas où debut==fin)
- à titre de révision, implémentez compter_si(debut,fin,pred) où représente une séquence à
demi-ouverte, de telle sorte que cet algorithme retourne le nombre d'éléments
e tels que pred(e) s'avère (p. ex. : pour , compter_si(begin(vals),end(vals),[](int n){return n%2!=0;})==4).
Il faut que cet algorithme soit applicable à un tableau, un
vector une list ou une
forward_list. Testez votre implémentation en vous assurant qu'elle
fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre
impair d'éléments, et aucun élément (cas où debut==fin)
- implémentez trouver(debut,fin,val) où représente une séquence à
demi-ouverte, de telle sorte que cet algorithme retourne un itérateur sur
l'élément
e tel que e==val s'avère. Il faut que cet
algorithme soit applicable à un tableau, un
vector une list ou une
forward_list. Testez votre implémentation en vous assurant qu'elle
fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre
impair d'éléments, et aucun élément (cas où debut==fin)
- implémentez trouver_si(debut,fin,pred) où
représente une séquence à demi-ouverte, de telle sorte que cet algorithme
retourne un itérateur sur l'élément
e tel que pred(e) s'avère. Il faut que cet
algorithme soit applicable à un tableau, un
vector une list ou une
forward_list. Testez votre implémentation en vous assurant qu'elle
fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre
impair d'éléments, et aucun élément (cas où debut==fin)
- implémentez est_trie(debut,fin) où
représente une séquence à demi-ouverte, de telle sorte que cet algorithme
retourne true seulement si la séquence est triée.
Il faut que cet algorithme soit applicable à un tableau, un
vector une list ou une
forward_list. Testez votre implémentation en vous assurant qu'elle
fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre
impair d'éléments, et aucun élément (cas où debut==fin)
- implémentez est_trie(debut,fin,pred) où
représente une séquence à demi-ouverte, de telle sorte que cet algorithme
retourne true seulement si la séquence est triée
en fonction du prédicat pred. Il faut que cet
algorithme soit applicable à un tableau, un
vector une list ou une
forward_list. Testez votre implémentation en vous assurant qu'elle
fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre
impair d'éléments, et aucun élément (cas où debut==fin)
- implémentez supprimer_duplicats(debut,fin) où
représente une séquence à demi-ouverte triée, de telle sorte que cet
algorithme supprime les duplicats de cette séquence.
Il faut que cet algorithme soit applicable à un tableau, un
vector ou une list. Testez votre implémentation en vous assurant qu'elle
fonctionne avec une séquence contenant un nombre pair d'éléments, un nombre
impair d'éléments, et aucun élément (cas où debut==fin).
Cette fonction ne peut évidemment pas vraiment éliminer les éléments
de la séquence (elle ne connaît pas le conteneur et doit fonctionner même avec
un tableau!) alors pour « éliminer » un élément, elle doit le placer à la fin
de la séquence. Sa valeur de retour est la « nouvelle fin » de la séquence (un
itérateur sur le premier des éléments « éliminés »)
- Il serait facile de tricher en réalisant cet exercice, mais je vous invite
fortement à ne pas le faire, et à tirer profit de ce qu'ils peuvent vous
enseigner
Prudence : mercredi avec horaire du lundi
|
9 octobre
|
T06
|
Au menu :
- À la fin du cours, remise de :
- la fonction est_trie(debut,fin,pred)
- la fonction rotater_droite(debut,fin)
Pour ne pas que vous sombriez dans l'ennui, voici une activité à
faire pour T07... Ceci peut
sembler beaucoup, mais c'est pas si mal... si vous n'attendez pas jusqu'à la dernière minute avant de
débuter!
Votre expérience avec la programmation générique en
C++ a soulevé en vous, j'en suis sûr, des questions face à la
programmation générique dans d'autres langages. C'est sûrement plus simple, vous
disiez-vous, avec un langage comme
C#, n'est-ce pas?
Alors voici : nous allons écrire en
C# les « mêmes » algorithmes
que ceux mis au point en
C++ depuis
T05.
Programmation générique avec
C#
Le piège : la programmation générique en
C#
est superficiellement semblable à la programmation générique en
C++. Par exemple, une fonction pour permuter deux valeurs peut s'exprimer
comme suit (si on escamote le
mouvement, que
C#
ne supporte pas) :
C++ (sans mouvement) |
C# |
template <class T>
void swap(T &a, T &b) {
T temp = a;
a = b;
b = temp;
}
// ...
struct Point {
int x {},
y {};
Point() = default;
constexpr Point(int x, int y) : x{ x }, y{ y } {
}
};
#include <string>
int main() {
using std::string;
int i0 = 3,
i1 = 5;
swap(i0, i1);
string s0 = "Tigidou",
s1 = "Yo";
swap(s0, s1);
Point p0{ 2, 3 },
p1{ -1, 0 };
swap(p0, p1);
}
|
using System;
namespace Bla
{
class Point
{
public int X { get; set; } = 0;
public int Y { get; set; } = 0;
Point(int x, int y)
{
X = x;
Y = y;
}
Point()
{
}
}
class Program
{
static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
static void Main(string [] args)
{
int i0 = 3,
i1 = 5;
Swap(ref i0, ref i1);
string s0 = "Tigidou",
s1 = "Yo";
Swap(ref s0, ref s1);
Point p0 = new Point(2, 3),
p1 = new Point(-1, 0);
Swap(ref p0, ref p1);
}
}
}
|
Jusqu'ici, en se basant sur la fonction
template<class T> void swap(T&,T&) /
static void Swap<T>(ref T,ref T), les différences entre les deux langages
semblent bien mineures.
Une subtilité qui ne vous affectera pas pour ce travail est que la
programmation générique en
C#
ne fonctionne que pour les classes. Pour vous aider à utiliser des types
« primitifs »,
C#
fait du Boxing (créer un objet pour remplacer le primitif) et du Unboxing
(refaire un primitif à partir d'un objet) automatiquement. Ceci semble
transparent, mais ce n'est pas gratuit (un new est
fait en cachette à chaque fois) alors si vous utilisez
C#
professionnellement, portez attention aux coûts cachés de cette pratique.
Vous trouverez des exemples dans
../../../Sujets/Divers--cdiese/Genericite-lambdas.html
Expressions λ
En
C#,
les expressions λ sont moins puissantes que celles de
C++, mais elles sont aussi plus simples. Par exemple, une λ qui
accepte un nombre et retourne son carré serait :
C# |
C++ |
(x) => x * x
|
[](auto x){ return x * x; }
|
Pour
C#, l'équivalent des std::function
est un Func. Ainsi, ceci affichera 9 et Pair :
C# |
C++ |
Func<int,int> carré = (x) => x * x;
Func<int,int,bool> sommePaire = (x,y) =>
{
return (x + y) % 2 == 0;
};
Console.WriteLine(carré(3));
if (sommePaire(3,5))
{
Console.WriteLine("Pair");
}
|
function<int(int)> carre = [](auto x){ return x * x; };
function<bool(int,int)> somme_paire = [](int x, int y) {
return (x + y) % 2 == 0;
};
cout << carre(3);
if (somme_paire(3, 5)) {
cout << "Pair";
}
|
... affichera 9 et Pair.
Parcourir une collection générique avec
C#
Le langage
C#,
comme bien d'autres langages, permet d'exprimer des algorithmes génériques sur
des collections (terme
.NET pour ce que
C++ appelle des
conteneurs).
Vous trouverez de l'information et des exemples à ce sujet sur
../../../Sujets/Divers--cdiese/Enumerateur-sliste.html
où est présenté un énumérateur (terme
.NET pour ce que
C++ appelle des
itérateurs).
Votre mandat
Votre mandat sera donc d'écrire les algorithmes couverts depuis
T05, mais en
C#.
Pour des raisons qui seront expliquées plus bas, vous ne pourrez pas avoir
exactement les mêmes signatures dans les deux langages, alors les consignes
détaillées seront légèrement différentes.
Le programme principal sera imposé :
namespace AlgosGénériques
{
class Program
{
static void Main(string[] args)
{
Tests.TesterInverser();
Tests.TesterRotaterGauche();
Tests.TesterRotaterDroite();
Tests.TesterCompter();
Tests.TesterCompterSi();
Tests.TesterTrouver();
Tests.TesterTrouverSi();
Tests.TesterEstTrié();
Tests.TesterSupprimerDuplicats();
}
}
}
Le code de test sera lui aussi imposé :
J'ai mis tous les tests dans une même classe, pour faciliter les choses.
Comme vous pouvez le voir avec le programme principal ci-dessus, la syntaxe pour
accéder à un membre d'un namespace, à un membre
d'une classe ou à un membre d'un objet est la même (on utilise le
'.'). |
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static AlgosGénériques.Algos;
namespace AlgosGénériques
{
class Tests
{
|
La méthode Afficher() affichera chaque élément d'une séquence.
Remarquez le recours à une chaîne préfixée par un $.
Ceci provoque ce qu'on appelle du String Interpolation, ce qui
remplacera le code entre accolades par la valeur de l'expression qu'il
représente (ici, la valeur de la variable obj). |
static void Afficher<T>(IEnumerable<T> e)
{
Console.Write("{ ");
foreach (T obj in e)
{
Console.Write($"{obj} ");
}
Console.Write("} ");
}
|
Pour alléger le tout, chaque algorithme sera testé par une paire de
méthodes : une première qui réalisera un test sur un paramètre ou un groupe de
paramètres, et... |
private static void TesterInverser<C,T>(C coll, Func<IEnumerable<T>,C> f)
where C : IEnumerable<T>
{
Console.Write("Avant inversion : ");
Afficher(coll);
coll = Inverser(coll, f);
Console.Write("Après inversion : ");
Afficher(coll);
Console.WriteLine();
Console.WriteLine(new string('-', 70));
}
|
... une autre qui appellera la première avec divers groupes de
paramètres.
TesterInverser() est un exemple :
- Tout d'abord, notez qu'Inverser() est
générique, et opère sur un IEnumerable<T>
- La première prend une collectione de type C,
applique Inverser() dessus, et reconstruit un
C qu'elle retournera grâce à une fonction f
capable de transformer un IEnumerable<T> en
C
- Les tests réalisés se font sur des tableaux et des
List de int et de float,
incluant une collection vide
Remarquez le where dans la première des deux fonctions : ceci exprime une
contrainte sur le type C
|
public static void TesterInverser()
{
TesterInverser(
new int[] { 2, 3, 5, 7, 11 },
new Func<IEnumerable<int>, int[]>((e) => e.ToArray())
);
TesterInverser(
new float [] { 2.5f, 3.5f, 5.5f, 7.5f },
new Func<IEnumerable<float>, float[]>((e) => e.ToArray())
);
TesterInverser(
new List<int> { 2, 3, 5, 7, 11 },
new Func<IEnumerable<int>, List<int>>((e) => e.ToList())
);
TesterInverser(
new List<int>(),
new Func<IEnumerable<int>, List<int>>((e) => e.ToList())
);
}
|
Les tests pour les algorithmes RotaterGauche()
et RotaterDroite() sont sur le même modèle
que ceux sur Inverser(). |
private static void TesterRotaterGauche<C, T>(C coll, Func<IEnumerable<T>, C> f)
where C : IEnumerable<T>
{
Console.Write("Avant permutation à gauche : ");
Afficher(coll);
coll = RotaterGauche(coll, f);
Console.Write("Après permutation à gauche : ");
Afficher(coll);
Console.WriteLine();
Console.WriteLine(new string('-', 70));
}
public static void TesterRotaterGauche()
{
TesterRotaterGauche(
new int[] { 2, 3, 5, 7, 11 },
new Func<IEnumerable<int>, int[]>((e) => e.ToArray())
);
TesterRotaterGauche(
new float[] { 2.5f, 3.5f, 5.5f, 7.5f },
new Func<IEnumerable<float>, float[]>((e) => e.ToArray())
);
TesterRotaterGauche(
new List<int> { 2, 3, 5, 7, 11 },
new Func<IEnumerable<int>, List<int>>((e) => e.ToList())
);
TesterRotaterGauche(
new List<int>(),
new Func<IEnumerable<int>, List<int>>((e) => e.ToList())
);
}
private static void TesterRotaterDroite<C, T>(C coll, Func<IEnumerable<T>, C> f)
where C : IEnumerable<T>
{
Console.Write("Avant permutation à droite : ");
Afficher(coll);
coll = RotaterDroite(coll, f);
Console.Write("Après permutation à droite : ");
Afficher(coll);
Console.WriteLine();
Console.WriteLine(new string('-', 70));
}
public static void TesterRotaterDroite()
{
TesterRotaterDroite(
new int[] { 2, 3, 5, 7, 11 },
new Func<IEnumerable<int>, int[]>((e) => e.ToArray())
);
TesterRotaterDroite(
new float[] { 2.5f, 3.5f, 5.5f, 7.5f },
new Func<IEnumerable<float>, float[]>((e) => e.ToArray())
);
TesterRotaterDroite(
new List<int> { 2, 3, 5, 7, 11 },
new Func<IEnumerable<int>, List<int>>((e) => e.ToList())
);
TesterRotaterDroite(
new List<int>(),
new Func<IEnumerable<int>, List<int>>((e) => e.ToList())
);
}
|
Les tests pour compter les occurrences d'un élément dans une collection
sont sans doute les plus simples du lot.
Quand vous écrirez votre propre algorithme Compter(),
notez que vous voudrez probablement exiger (par une clause where) que le type
T des éléments de votre collection soient
IComparable, car comparer deux objets en
C#
est une tâche plus complexe qu'en
C++, où utiliser == suffit souvent |
private static void TesterCompter<T>(IEnumerable<T> coll, T obj)
{
Console.Write("La collection ");
Afficher(coll);
Console.WriteLine($"contient {Compter(coll, obj)} occurrence(s) de {obj}");
Console.WriteLine(new string('-', 70));
}
public static void TesterCompter()
{
TesterCompter(new int[] { 2, 3, 5, 7, 11, 3 }, 3);
TesterCompter(new List<int> { 2, 3, 5, 7, 11, 3 }, 7);
TesterCompter(new List<string> { "J'aime", "mon", "prof" }, "prof");
TesterCompter(new List<string>(), "prof");
}
private static void TesterCompterSi<T>(IEnumerable<T> coll, Func<T,bool> pred, string crit)
{
Console.Write("La collection ");
Afficher(coll);
Console.WriteLine($"contient {CompterSi(coll, pred)} occurrence(s) respectant le critère {crit}");
Console.WriteLine(new string('-', 70));
}
public static void TesterCompterSi()
{
TesterCompterSi(new int[] { 2, 3, 5, 7, 11 }, (n) => n % 2 == 0, "pair");
TesterCompterSi(new int[] { 2, 3, 5, 7, 11 }, (n) => n % 2 != 0, "impair");
TesterCompterSi(new int[] { 2, 3, 5, 7, 11 }, (n) => n < 0, "négatif");
}
|
Vos algorithmes Trouver() et
TrouverSi() devront retourner des IEnumerator<T>.
Le modèle de
C#
ne permet pas de modéliser un objet non-trouvé par un itérateur sur la fin d'une
séquence; en retour, les énumérateurs de
C#
sont des objets, et tous les objets de ce langage sont créés dynamiquement et
manipulés par des références (sortes de pointeurs), donc il est possible de
retourner null pour représenter un échec de la
recherche. |
private static void TesterTrouver<T>(IEnumerable<T> coll, T obj)
{
Console.Write("La collection ");
Afficher(coll);
var it = Trouver(coll, obj);
if (it != null)
{
Console.WriteLine($"contient {obj}");
}
else
{
Console.WriteLine($"ne contient pas {obj}");
}
Console.WriteLine(new string('-', 70));
}
public static void TesterTrouver()
{
TesterTrouver(new int[] { 2, 3, 5, 7, 11 }, 2);
TesterTrouver(new List<int> { 2, 3, 5, 7, 11 }, 7);
TesterTrouver(new List<int> { 2, 3, 5, 7, 11 }, 8);
TesterTrouver(new int[0], 8);
}
private static void TesterTrouverSi<T>(IEnumerable<T> coll, Func<T,bool> pred, string crit)
{
Console.Write("La collection ");
Afficher(coll);
var it = TrouverSi(coll, pred);
if (it != null)
{
Console.WriteLine($"contient au moins un objet respectant le critère {crit}");
}
else
{
Console.WriteLine($"ne contient pas d'objet respectant le critère {crit}");
}
Console.WriteLine(new string('-', 70));
}
public static void TesterTrouverSi()
{
TesterTrouverSi(new int[] { 2, 3, 5, 7, 11 }, (n) => n == 2, "vaut 2");
TesterTrouverSi(new List<int> { 2, 3, 5, 7, 11 }, (n) => n % 2 == 0, "pair");
TesterTrouverSi(new List<int> { 2, 3, 5, 7, 11 }, (n) => n < 0, "négatif");
TesterTrouverSi(new int[0], (n) => n == 8, "vaut 8");
}
|
Les tests pour EstTrié(), avec ou sans un
prédicat d'ordonnancement, ne sont pas vraiment plus complexes que les tests
précédents.
En fait, EstTrié() est probablement l'algorithme
qui ressemblera le plus à celui que vous aurez exprimé en
C++ |
private static void TesterEstTrié<T>(IEnumerable<T> coll) where T : IComparable
{
Console.Write("La collection ");
Afficher(coll);
if (EstTrié(coll))
{
Console.WriteLine("est triée");
}
else
{
Console.WriteLine("n'est pas triée");
}
Console.WriteLine(new string('-', 70));
}
private static void TesterEstTrié<T>(IEnumerable<T> coll, Func<T,T,bool> pred, string crit)
where T : IComparable
{
Console.Write("La collection ");
Afficher(coll);
if (EstTrié(coll, pred))
{
Console.WriteLine($"est triée en ordre {crit}");
}
else
{
Console.WriteLine($"n'est pas triée en ordre {crit}");
}
Console.WriteLine(new string('-', 70));
}
public static void TesterEstTrié()
{
TesterEstTrié(new int[] { 2, 3, 5, 7, 11 });
TesterEstTrié(new int[] { 2, 5, 3, 7, 11 });
TesterEstTrié(new int[] { 11, 7, 5, 3, 2 }, (x,y) => y < x, "décroissant");
TesterEstTrié(new int[] { 2, 3, 5, 7, 11 }, (x, y) => y < x, "décroissant");
TesterEstTrié(new List<string> { "J'aime", "mon", "prof" });
}
|
Enfin, SupprimerDuplicats() aura un
comportement semblable à celui de son cousin en
C++.
Notez que le test de base a deux clauses where,
l'une s'appliquant à la collection à parcourir et l'autre s'appliquant aux
éléments de cette collection. Ça vous donnera une idée de la syntaxe à utiliser
dans de telles circonstances
|
private static void TesterSupprimerDuplicats<C, T>(C coll, Func<IEnumerable<T>, C> f)
where C : IEnumerable<T>
where T : IComparable
{
Console.Write("Avant suppression des doublons : ");
Afficher(coll);
coll = SupprimerDuplicats(coll, f);
Console.WriteLine();
Console.Write("Après suppression des doublons: ");
Afficher(coll);
Console.WriteLine();
Console.WriteLine(new string('-', 70));
}
public static void TesterSupprimerDuplicats()
{
TesterSupprimerDuplicats(
new int[] { 2, 3, 5, 7, 11 },
new Func<IEnumerable<int>, int[]>((e) => e.ToArray())
);
TesterSupprimerDuplicats(
new int[] { 2, 3, 3, 5, 7, 11 },
new Func<IEnumerable<int>, int[]>((e) => e.ToArray())
);
TesterSupprimerDuplicats(
new List<int>{ 2, 3, 5, 7, 11, 11, 11 },
new Func<IEnumerable<int>, List<int>>((e) => e.ToList())
);
}
}
}
|
Voilà, vous avez le code de test et plusieurs pistes. Il ne vous reste plus
qu'à coder les méthodes!
Prudence : mardi avec horaire du lundi
|
22 octobre
|
T08
|
Au menu :
- Pour le reste de la séance, et pour la prochaine, un petit exercice (à
remettre au début de T09). Soit le code suivant :
#include <ostream>
#include <istream>
#include <string>
#include <string_view>
#include <algorithm>
#include <memory>
#include <vector>
using namespace std;
template <class T, class U>
constexpr bool est_entre_demi_ouvert(const T &val, const U &seuilmin, const U &seuilmax) {
return seuilmin <= val && val < seuilmax;
}
template <class T, class U>
constexpr bool est_entre_inclusif(const T &val, const U &seuilmin, const U &seuilmax) {
return seuilmin <= val && val <= seuilmax;
}
class ContrainteNonRespectee { };
template <class T, class Pred>
const T& valider_contrainte(Pred pred, const T &val) {
return !pred(val)? throw ContrainteNonRespectee{} : val;
}
class Monstre {
string nom_;
public:
Monstre(string_view nom) : nom_{ nom } {
}
string nom() const {
return nom_;
}
virtual ostream& hurler(ostream&) const = 0;
virtual void mettre_a_jour(ostream&, istream&) = 0;
virtual ~Monstre() = default;
//
// VOTRE CODE ICI
//
};
ostream& operator<<(ostream &os, const Monstre &monstre) {
return monstre.hurler(os);
}
bool operator==(const Monstre &a, const Monstre &b) {
return a.nom() == b.nom();
}
bool operator!=(const Monstre &a, const Monstre &b) {
return !(a == b);
}
class Bestiole : public Monstre {
double agacement_; // [0..1)
public:
Bestiole(string_view nom, double agacement)
: Monstre{ nom },
agacement_{ valider_contrainte([](double val) {
return est_entre_demi_ouvert(val, 0.0, 1.0);
}, agacement) }
{
}
friend void RendreMoinsAchalant(Bestiole&);
friend void RendrePlusAchalant(Bestiole&);
double agacement() const {
return agacement_;
}
ostream& hurler(ostream &os) const override {
return os << "Je suis " << nom() << " la bestiole, achalant a " << agacement() * 100 << "%";
}
void mettre_a_jour(ostream &out, istream &in) override {
out << R"(Vos options :
1) rendre plus achalant
2) rendre moins achalant
Votre choix: )";
int choix;
while(in >> choix && !est_entre_inclusif(choix, 1, 2))
out << R"(Vos options :
1) rendre plus achalant
2) rendre moins achalant
Votre choix: )";
switch (choix) {
case 1:
RendrePlusAchalant(*this);
break;
case 2:
RendreMoinsAchalant(*this);
break;
}
}
//
// VOTRE CODE ICI
//
};
class Bibitte : public Monstre {
int puanteur_; // [1 .. 100]
double mechancete_; // [0..1)
public:
Bibitte(string_view nom, int puanteur, double mechancete)
: Monstre{ nom },
puanteur_{ valider_contrainte([](int val) {
return est_entre_inclusif(val, 1, 100);
}, puanteur) },
mechancete_{ valider_contrainte([](double val) {
return est_entre_demi_ouvert(val, 0.0, 1.0);
}, mechancete) }
{
}
friend void Laver(Bibitte&);
friend void Salir(Bibitte&);
int puanteur() const {
return puanteur_;
}
double mechancete() const {
return mechancete_;
}
ostream& hurler(ostream &os) const override {
return os << "Je suis " << nom() << " la bibitte; puant a " << puanteur() << "% et mechant a " << mechancete() * 100 << "%";
}
void mettre_a_jour(ostream &out, istream &in) override {
out << R"(Vos options :
1) laver
2) salir
Votre choix: )";
int choix;
while(in >> choix && !est_entre_inclusif(choix, 1, 2))
out << R"(Vos options :
1) laver
2) salir
Votre choix: )";
switch (choix) {
case 1:
Laver(*this);
break;
case 2:
Salir(*this);
break;
}
}
//
// VOTRE CODE ICI
//
};
void Laver(Bibitte &b) {
b.puanteur_ = 1;
}
void Salir(Bibitte &b) {
b.puanteur_ = min(100, b.puanteur_ + 5);
}
void RendreMoinsAchalant(Bestiole &b) {
b.agacement_ -= b.agacement_ * 0.5;
}
void RendrePlusAchalant(Bestiole &b) {
b.agacement_ += (1.0 - b.agacement_) * 0.5;
}
template <class T>
unique_ptr<T> modifier_potentiellement(unique_ptr<T> p, ostream &out, istream &in) {
//
// VOTRE CODE ICI
//
}
#include <iostream>
int main() {
/*
vector<unique_ptr<Monstre>> v {
make_unique<Bibitte>("Joe", 30, 0.75),
make_unique<Bestiole>("Fred", 0.23),
make_unique<Bestiole>("Bill", 0.8),
make_unique<Bibitte>("Zebda", 66, 0.11),
make_unique<Bestiole>("Guy", 0.99)
};
*/
vector<unique_ptr<Monstre>> v;
v.emplace_back(unique_ptr<Monstre> { new Bibitte{ "Joe", 30, 0.75 }});
v.emplace_back(unique_ptr<Monstre> { new Bestiole{ "Fred", 0.23 }});
v.emplace_back(unique_ptr<Monstre> { new Bestiole{ "Bill", 0.8 }});
v.emplace_back(unique_ptr<Monstre> { new Bibitte{ "Zebda", 66, 0.11 }});
v.emplace_back(unique_ptr<Monstre> { new Bestiole{ "Guy", 0.99 }});
for (auto & p : v) {
cout << "Avant: " << *p << '\n';
p = modifier_potentiellement(std::move(p), cout, cin);
cout << "Apres: " << *p << '\n';
}
}
Vous aurez remarqué que quelques sections de code manquent, sans doute
effacées par une créature immonde ou féroce. Votre mandat est de compléter
ce programme pour qu'il redevienne fonctionnel.
Que doit faire ce programme, me direz-vous? Simple :
- La fonction modifier_potentiellement()
doit offrir à l'usager de mettre à jour le Monstre
reçu en paramètre. La méthode polymorphique
mettre_a_jour() d'un Monstre vous
aidera en ce sens
- Une fois la mise à jour réalisée,
modifier_potentiellement() devra demander à l'usager s'il est
satisfait des changements. Si l'usager répond par l'affirmative, alors la
mise à jour sera conservée, sinon elle ne le sera pas
- Une manière simple d'implémenter ceci est de faire un duplicat de
l'objet au début de modifier_potentiellement(),
de modifier l'une des deux versions de l'objet en gardant l'autre
intacte, et de retourner l'objet modifié ou celui qui ne l'est pas, selon
le cas. Cependant, vous avez le droit d'être créatifs dans votre
démarche, dans la mesure où c'est propre, où ça fonctionne et où ça ne
provoque pas de fuites de ressources.
N'hésitez pas à poser des questions. Cet exercice est à faire sur une
base individuelle.
|