Les diverses sections de cette page (en fonction desquelles vous trouverez
quelques liens dans l'encadré à droite) vous mèneront elles-aussi sur des pistes
qui vous permettront d'explorer un peu plus par vous-mêmes, de valider vos
acquis et d'enrichir votre apprentissage
Date |
Séance |
Détails |
16 février |
S00 |
Au menu :
- Présentation du cours et du plan
de cours, du moins le volet qui nous concerne
- Nos thèmes pour ces trois séances seront (dans le désordre!) :
- Petit exercice de remise en forme :
- Implémentons une liste simplement chaînée d'éléments d'un certain
type T
- Comment la représenterons-nous?
- Quelle serait une bonne interface d'utilisation pour ce type?
- Quels sont les coûts des principales opérations associées à ce
type?
- Si vous souhaitez poursuivre le plaisir, à titre d'exercice
(répétez la réflexion de design dans chaque cas) :
- Transformez la liste simplement chaînée de T
en liste doublement chaînée de T
- Écrivez un TableauDynamique<T>
inspiré, dans son interface, par le (très mal nommé) type
List<T> de la
plateforme .NET
- Rappel sur :
L'activité de cette semaine vous sera présentée au cours de la séance.
- Présentation de l'activité pour cette semaine. Attention :
les activités sont cumulatives, alors ne prenez pas de retard!
- Cette semaine, votre activité sera (à venir)
boites-v0.html
- Notez que le code proposé est en C# 9. Vous pouvez le
« simplifier » (légèrement) avec une version plus récente du
langage.
Pour un exemple d'énumérateurs sur des entiers pairs, voir
https://dotnetfiddle.net/EZ9jFR :
using System;
using System.Collections;
using System.Collections.Generic;
foreach (int n in new Pairs(1, 10))
Console.Write($"{n} "); // 2 4 6 8 10
class Pairs : IEnumerable<int>
{
int Min { get; init; }
int Max { get; init; }
public Pairs(int min, int max)
{
Min = min;
Max = max;
// valider que Min <= Max si vous voulez
}
class ÉnumPairs : IEnumerator<int>
{
int Cur { get; set; }
Pairs Source { get; init; }
public ÉnumPairs(Pairs source)
{
Source = source;
// en C# comme en Java, faut se placer avant le premier élément à parcourir
Cur = Source.Min % 2 == 0 ? Source.Min - 2 : Source.Min - 1;
}
public bool MoveNext()
{
if (Cur + 2 > Source.Max) return false;
Cur += 2;
return true;
}
public int Current => Cur;
object IEnumerator.Current => throw new NotImplementedException();
// Vous devez aussi coder quelques trucs sans intérêt, que vous pouvez faire générer par Visual Studio
public void Reset() { }
public void Dispose() { }
}
// GetEnumerator doit retourner un IEnumerable capable de parcourir les éléments de la séquence
public IEnumerator<int> GetEnumerator() => new ÉnumPairs(this);
// Vous devez aussi coder un autre truc sans intérêt, que vous pouvez faire générer par Visual Studio
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() =>
throw new NotImplementedException();
}
Pour l'exemple de Liste<T> avec énumérateur,
voir
https://dotnetfiddle.net/aK9oxz :
using System;
using System.Collections;
using System.Collections.Generic;
var lst = new Liste<string>();
lst.AjouterIntervalle(new []{ "J'aime", "mon", "prof" });
foreach (var s in lst)
Console.Write($"{s} ");
class ListeVideException : Exception { }
class Liste<T> : IEnumerable<T>
{
public IEnumerator<T> GetEnumerator() =>
new Énumérateur(Tête);
IEnumerator IEnumerable.GetEnumerator() =>
new Énumérateur(Tête);
// un énumérateur de C# / un itérateur de Java, modélise
// une séquence à demi ouverte (debut, fin]
class Énumérateur : IEnumerator<T>
{
Noeud Cur { get; set; }
public Énumérateur(Noeud tête)
{
Cur = new (default);
Cur.Succ = tête;
}
public bool MoveNext()
{
if (Cur.Succ == null)
return false;
Cur = Cur.Succ;
return true;
}
public void Reset() => throw new NotImplementedException();
public void Dispose() { }
public T Current => Cur.Valeur;
object IEnumerator.Current => Cur.Valeur;
}
class Noeud
{
public T Valeur { get; init; }
public Noeud Succ { get; set; } = null;
public Noeud(T val)
{
Valeur = val;
}
}
Noeud Tête { get; set; } = null;
Noeud Queue { get; set; } = null;
public bool EstVide => Tête == null;
public void AjouterDébut(T val)
{
var noeud = new Noeud(val);
if (EstVide)
Tête = Queue = noeud;
else
{
noeud.Succ = Tête;
Tête = noeud;
}
}
public void AjouterFin(T val)
{
Noeud noeud = new (val);
if (EstVide)
Tête = Queue = noeud;
else
{
Queue.Succ = noeud;
Queue = noeud;
}
}
public void SupprimerDébut()
{
if (EstVide)
throw new ListeVideException();
Tête = Tête.Succ;
if (Tête == null)
Queue = null;
}
public T PeekDébut() =>
EstVide? throw new ListeVideException(): Tête.Valeur;
public T PeekFin()
{
if (EstVide)
throw new ListeVideException();
return Queue.Valeur;
}
//public static Liste<T> Dupliquer(Liste<T> src)
//{
// Liste<T> dest = new();
// for (Noeud p = src.Tête; p != null; p = p.Succ)
// dest.AjouterFin(p.Valeur);
// return dest;
//}
public Liste<T> Dupliquer()
{
Liste<T> dest = new();
for (Noeud p = Tête; p != null; p = p.Succ)
dest.AjouterFin(p.Valeur);
return dest;
}
public void AjouterIntervalle(IEnumerable<T> src)
{
foreach(T e in src)
AjouterFin(e);
}
}
|
23 février |
S01 |
Au menu :
- Questions à propos de
boites-v0.html (n'hésitez pas!)
- C'est normal de trouver cela difficile
- C'est un travail qui demande
de réfléchir et de chercher l'élégance, et qui « résiste » aux
« solutions » par force brute, qui fonctionnent en ajoutant du code et
du code... et qui ne sont pas de vraies solutions
- En même temps, réfléchir sur son code, ça fait grandir!
- Schéma de conception
Fabrique
L'esquisse (incomplète et illustrative sans plus) de fabrique de boîtes
faite en classe était :
string s = "mono allo\nch\nmono yo\nch\nmono coucou\nmono bye";
static int TrouverSi(string s, int depuis, Func<char, bool> pred)
{
for (; depuis != s.Length && !pred(s[depuis]); ++depuis)
;
return depuis;
}
static (Sorte, int) FabriquerBoiteGenre(string s, int cur = 0)
{
int finPréfixe = TrouverSi(s, cur, c => c == ' ');
string préfixe = s.Substring(cur, finPréfixe - cur);
Console.WriteLine($"Préfixe trouvé : {préfixe}");
switch(préfixe)
{
case "mono":
{
int finLigne = TrouverSi(s, finPréfixe, c => c == '\n');
// le texte du monon est s.Substring(finPréfixe, finLigne - finPréfixe)
return (Sorte.Mono, finLigne + 1);
}
break;
case "ch":
{
int finLigne = TrouverSi(s, finPréfixe, c => c == '\n');
var (b0, fin0) = FabriquerBoiteGenre(s, finLigne + 1);
var (b1, fin1) = FabriquerBoiteGenre(s, fin0 + 1);
return (Sorte.CH, fin1 + 1);
}
break;
default:
throw new Exception("bof");
}
}
enum Sorte { Mono, CH } // etc.
Le code fait en exemple pour le
clonage était :
Image p = new Bmp(ConsoleColor.DarkBlue);
p.Dessiner();
p = ModifierPeutÊtre(p);
p.Dessiner();
p = new Jpeg(ConsoleColor.Red);
p.Dessiner();
p = ModifierPeutÊtre(p);
p.Dessiner();
static Image ModifierPeutÊtre(Image p)
{
// A) faire un backup de ce vers quoi p pointe
Image backup = p.Cloner();
// B) modifier l'image pointée par p
p.Couleur = ConsoleColor.Cyan;
// C) demander à l'usager s'il / si elle souhaite conserver les modifs7
Console.Write("Nouvelle image : ");
p.Dessiner();
Console.Write("Conserver les modifs? (o / n) ");
char c = char.Parse(Console.ReadLine());
// i) si oui, on retourne p
// ii) sinon, on retourne le backup
return c == 'o' || c == 'O' ? p : backup;
}
abstract class Image
{
public abstract Image Cloner();
public ConsoleColor Couleur { get; set; }
public void Dessiner() // Idiome NVI : non-virtual interface
{
ConsoleColor avant = Console.ForegroundColor;
Console.ForegroundColor = Couleur;
DessinerImpl();
Console.ForegroundColor = avant;
}
protected abstract void DessinerImpl();
protected Image(Image autre) : this(autre.Couleur)
{
}
public Image(ConsoleColor couleur)
{
Couleur = couleur;
}
}
class Bmp : Image
{
public override Bmp Cloner() => new(this);
protected Bmp(Bmp autre) : base(autre) { }
public Bmp(ConsoleColor couleur) : base(couleur) { }
protected override void DessinerImpl()
{
Console.WriteLine($"Bmp de couleur {Couleur}");
}
}
class Png : Image
{
public override Png Cloner() => new(this);
protected Png(Png autre) : base(autre) { }
public Png(ConsoleColor couleur) : base(couleur) { }
protected override void DessinerImpl()
{
Console.WriteLine($"Png de couleur {Couleur}");
}
}
class Jpeg : Image
{
public override Jpeg Cloner() => new(this);
protected Jpeg(Jpeg autre) : base(autre) { }
public Jpeg(ConsoleColor couleur) : base(couleur) { }
protected override void DessinerImpl()
{
Console.WriteLine($"Jpeg de couleur {Couleur}");
}
}
- Présentation de l'activité pour cette semaine. Attention :
les activités sont cumulatives, alors ne prenez pas de retard!
- Cette semaine, votre activité sera (à venir)
boites-v1.html
- Notez que le code proposé est en C# 9. Vous pouvez le
« simplifier » (légèrement) avec une version plus récente du
langage
À titre de rappel :
- Avec le schéma de conception
Fabrique,
une classe ou une fonction est responsable de créer des objets
- Pourquoi? Plusieurs raisons sont possibles :
- Parfois, c'est qu'on a besoin d'une construction en deux temps (p.ex.:
var th = new Thread(...); puis th.Start(),
ou encore lire des données au clavier puis construire l'objet)
- Parfois, c'est pour une question de gestion d'erreurs (p.ex.: un constructeur n'a pas de type de retour, donc sa capacité à signaler un problème est limitée, typiquement aux levées d'exceptions)
- Parfois, on ne veut pas révéler le type d'objet construit, on veut juste exposer une interface,
ou encore on veut choisir le type effectivement construit en fonction des circonstances
- Parfois, les paramètres suppléés ne sont pas appropriés pour un constructeur
- etc.
Pour le schéma de conception
Visiteur, nous avons fait un bref exemple (un peu
simpliste), soit (https://dotnetfiddle.net/DAJeDg) :
using System;
using System.Collections;
using System.Collections.Generic;
var lst = new Liste<string>();
foreach (var s in new[] { "J'aime", "mon", "prof" })
lst.AjouterFin(s);
lst.Accepter(s => Console.Write($"{s} "));
Console.WriteLine();
string plusseLongue = null;
lst.Accepter(s =>
{
if (plusseLongue == null || s.Length > plusseLongue.Length)
plusseLongue = s;
});
Console.WriteLine($"Chaîne la plusse longue : {plusseLongue}");
string plusseCourte = null;
lst.Accepter(s =>
{
if (plusseCourte == null || s.Length < plusseCourte.Length)
plusseCourte = s;
});
Console.WriteLine($"Chaîne la plusse courte : {plusseCourte}");
class ListeVideException : Exception { }
// modélise une liste simplement chaînée dont chaque noeud "contient" un T
class Liste<T> : IEnumerable<T> where T : IEquatable<T>
{
public IEnumerator<T> GetEnumerator() =>
new Énumérateur(Tête);
IEnumerator IEnumerable.GetEnumerator() =>
new Énumérateur(Tête);
// un énumérateur de C# / un itérateur de Java, modélise
// une séquence à demi ouverte (debut, fin]
class Énumérateur : IEnumerator<T>
{
Noeud Cur { get; set; }
public Énumérateur(Noeud tête)
{
Cur = new (default);
Cur.Succ = tête;
}
public bool MoveNext()
{
if (Cur.Succ == null)
return false;
Cur = Cur.Succ;
return true;
}
public void Reset() => throw new NotImplementedException();
public void Dispose() { }
public T Current => Cur.Valeur;
object IEnumerator.Current => Cur.Valeur;
}
class Noeud
{
public T Valeur { get; init; }
public Noeud Succ { get; set; } = null;
public Noeud(T val)
{
Valeur = val;
}
}
Noeud Tête { get; set; } = null;
Noeud Queue { get; set; } = null;
public bool EstVide => Tête == null;
public void AjouterDébut(T val)
{
var noeud = new Noeud(val);
if (EstVide)
Tête = Queue = noeud;
else
{
noeud.Succ = Tête;
Tête = noeud;
}
++Count;
}
public void AjouterFin(T val)
{
Noeud noeud = new (val);
if (EstVide)
Tête = Queue = noeud;
else
{
Queue.Succ = noeud;
Queue = noeud;
}
++Count;
}
public void SupprimerDébut()
{
if (EstVide)
throw new ListeVideException();
Tête = Tête.Succ;
if (Tête == null)
Queue = null;
--Count;
}
public T PeekDébut() =>
EstVide? throw new ListeVideException(): Tête.Valeur;
public T PeekFin()
{
if (EstVide)
throw new ListeVideException();
return Queue.Valeur;
}
//public static Liste<T> Dupliquer(Liste<T> src)
//{
// Liste<T> dest = new();
// for (Noeud p = src.Tête; p != null; p = p.Succ)
// dest.AjouterFin(p.Valeur);
// return dest;
//}
public Liste<T> Dupliquer()
{
Liste<T> dest = new();
for (Noeud p = Tête; p != null; p = p.Succ)
dest.AjouterFin(p.Valeur);
return dest;
}
public int Count { get; private set; } = 0;
public void ForEach(Action<T> f)
{
foreach(T val in this)
f(val);
}
public bool Contains(T elem)
{
foreach(T val in this)
if(val.Equals(elem))
return true;
return false;
}
public Liste()
{
}
public Liste(IEnumerable<T> objs)
{
foreach (T e in objs)
AjouterFin(e);
}
public void Accepter(Action<T> f)
{
ForEach(f);
}
}
Portez évidemment attention à la méthode Accepter...
Notez le lien avec ForEach (dans le cas de Liste<T>, du moins; un
« foreach », au fond, c'est
une version limitée d'un visiteur).
|
1 mars |
S02 |
Lisez ce qui suit avec attention : il s'agit de consignes détaillées pour la remise du travail qui
constituera la note pour la partie « programmation » de ce cours. Le
truc pour avoir un bon résultat est (a) de faire le travail (!); (b) de
respecter les consignes de remise avec précision et (c) d'avoir du
plaisir!
Suggestion : imprimez les consignes de remise, et cochez ce qui est
fait au fûr et à mesure. Un peu de rigueur, beaucoup de bonheur 🙂
Au menu :
Nous allons discuter remise 🙂 Pour chaque équipe de
travail, voici ce que je vais ramasser :
- Une
archive .zip portant le nom des équipières et
des équipiers, p. ex. :
nguyen-bob-tremblay-joanne.zip pour l'équipe de Bob Nguyen et
Joanne Tremblay. J'utiliserai le nom de l'archive pour l'attribution des
points alors respectez la consigne rigoureusement!
- Cette archive devra contenir tous vos fichiers .cs
à l'exception de celui contenant votre programme
principal. Assurez-vous donc que, dans votre solution, le
programme principal soit dans un fichier .cs et que tout le reste soit dans d'autres fichiers sources
- Cette contrainte est dûe au fait que je vais utiliser mon propre code
de test!
- Si vous avez fait le travail dans un autre langage, j'aménagerai
- Je vais présumer que votre code se trouve dans un
namespace nommé Boites
- Si vous ne
respectez pas cette consigne, votre code ne passera pas les tests et vous
perdrez des points
- Je vais présumer que votre code respecte les noms de classes utilisés
dans les énoncés boites-v0.html et
boites-v1.html,
et que le code de test proposé pour ces deux parties de l'énoncé compilera
intégralement si pris tel quel; je parle des classes
Boite, ComboVertical,
ComboHorizontal, FabriqueBoites (incluant le format des string
passées à Créer),
IVisiteur<T> (incluant le paramètre de type
Action) et IVisitable<T>
- Si vous
ne respectez pas cette consigne, votre code ne passera pas les tests et
vous perdrez des points
- Vous devrez aussi livrer un fichier texte portant le nom de votre
équipe, avec l'extension .test, p. ex. :
nguyen-bob-tremblay-joanne.test pour l'équipe de Bob Nguyen et
Joanne Tremblay. J'utiliserai le nom du fichier pour l'attribution des
points alors respectez la consigne rigoureusement!
- Le fichier .test devra contenir une
séquence de lignes qui seront consommées par FabriqueBoites.Créer, à
l'aide d'un programme comme celui-ci (mais plus sophistiqué) :
// on passera votre fichier .test en paramètre à l'appel de Main
// le nom de ce fichier est args[0] dans ce qui suit
var fab = new FabriqueBoites();
var coul = new Couleureur();
var mes = new Mesureur();
using(var sr = new StreamReader(args[0]))
for(var s = LireBoite(sr); s != null; s = LireBoite(sr))
Tester(new Boite(fab.Créer(s)), coul, mes);
// fonctions auxiliaires
static string LireBoite(StreamReader sr)
{
string s = sr.ReadLine();
while(s != null && s.Trim().Length == 0)
s = sr.ReadLine();
if(s != null)
{
for(string ligne = sr.ReadLine(); ligne != null && ligne.Trim().Length != 0; ligne = sr.ReadLine())
s += $"\n{ligne}";
}
return s;
}
static void Tester(IBoite b, params IVisiteur<IBoite>[] viz)
{
Console.WriteLine(b);
foreach (var v in viz)
b.Accepter(v);
}
- Enfin, vous devrez livrer un fichier texte portant le nom de votre
équipe, avec l'extension .res, p. ex. :
nguyen-bob-tremblay-joanne.res pour l'équipe de Bob Nguyen et
Joanne Tremblay. J'utiliserai le nom du fichier pour l'attribution des
points alors respectez la consigne rigoureusement!
Le fichier .res devra contenir le texte qui
devrait s'afficher à l'écran si on utilise le fichier .test
de la même
équipe. Par exemple, si vous avez implémenté le bonus de
MonoCombo (je n'ai pas mis la couleur dans l'affichage) :
nguyen-bob-tremblay-joanne.test |
nguyen-bob-tremblay-joanne.res |
mono J'aime mon "prof"
cv
mono J'aime mon "prof"
mono moi itou
ch
mono J'aime mon "prof"
mono moi itou
ch
cv
mono J'aime mon "prof"
mono moi itou
mono eh ben
ch
cv
mc
mono J'aime mon "prof"
mono moi itou
mono eh ben
|
+-----------------+
|J'aime mon "prof"|
+-----------------+
Boite
Mono 1 x 17
+-----------------+
|J'aime mon "prof"|
|-----------------|
|moi itou |
+-----------------+
Boite
ComboVertical
Boite
Mono 1 x 17
Boite
Mono 1 x 17
+--------------------------+
|J'aime mon "prof"|moi itou|
+--------------------------+
Boite
ComboHorizontal
Boite
Mono 1 x 17
Boite
Mono 1 x 8
+------------------------+
|J'aime mon "prof"|eh ben|
|-----------------| |
|moi itou | |
+------------------------+
Boite
ComboHorizontal
Boite
ComboVertical
Boite
Mono 1 x 17
Boite
Mono 1 x 17
Boite
Mono 3 x 6
+--------------------------+
|+-----------------+|eh ben|
||J'aime mon "prof"|| |
|+-----------------+| |
|-------------------| |
|moi itou | |
+--------------------------+
Boite
ComboHorizontal
Boite
ComboVertical
Boite
MonoCombo
Boite
Mono 1 x 17
Boite
Mono 1 x 19
Boite
Mono 5 x 6
|
Important : pour simplifier l'existence de vos
collègues qui ne supporteront pas tous MonoCombo,
placez les tests qui utilisent des MonoCombo à
la fin de votre fichier .test. Ainsi, nous
pourrons cesser les tests après une première levée d'exception.
Notez que les MonoCombo (préfixe
mc dans le fichier) sont optionnels. Si vous ne les supportez pas,
dans FabriqueBoites.Créer, levez une exception
de type BonusNonSupportéException
Dans votre fichier .res, ne supposez
pas qu'il y ait un message d'erreur affiché si une exception est
levée. De même, dans votre code, n'affichez pas de messages si vous
attrapez une exception (c'est, de toute manière, une bien mauvaise
habitude). Par exemple, si vous n'avez pas implémenté le bonus de
MonoCombo (je n'ai pas mis la couleur dans l'affichage), la
dernière boîte (qui contiendrait un MonoCombo)
ne sera pas instanciée (une exception sera levée) et ne sera donc pas
affichée :
nguyen-bob-tremblay-joanne.test |
nguyen-bob-tremblay-joanne.res |
mono J'aime mon "prof"
cv
mono J'aime mon "prof"
mono moi itou
ch
mono J'aime mon "prof"
mono moi itou
ch
cv
mono J'aime mon "prof"
mono moi itou
mono eh ben
ch
cv
mc
mono J'aime mon "prof"
mono moi itou
mono eh ben
|
+-----------------+
|J'aime mon "prof"|
+-----------------+
Boite
Mono 1 x 17
+-----------------+
|J'aime mon "prof"|
|-----------------|
|moi itou |
+-----------------+
Boite
ComboVertical
Boite
Mono 1 x 17
Boite
Mono 1 x 17
+--------------------------+
|J'aime mon "prof"|moi itou|
+--------------------------+
Boite
ComboHorizontal
Boite
Mono 1 x 17
Boite
Mono 1 x 8
+------------------------+
|J'aime mon "prof"|eh ben|
|-----------------| |
|moi itou | |
+------------------------+
Boite
ComboHorizontal
Boite
ComboVertical
Boite
Mono 1 x 17
Boite
Mono 1 x 17
Boite
Mono 3 x 6
|
Ce que je ferai avec ça :
- Je vais valider votre correspondance entre .test
et .res à partir de mes propres
classes, pour voir si votre code de test est bon!
- Si votre code de test est incorrect, il sera exclu du processus et
votre note sera impactée. Prudence!
- Assurez-vous que votre code de test ne se limite pas à plagier le
mien au texte près, et assurez-vous qu'il soit pertinent; je serai sévère
si je constate de la négligence
- Les paires .test, .res
retenues seront ensuite appliquées, mécaniquement, au code source
de toutes les équipes, et je produirai une note d'exécution sur la base
des résultats
- J'ajouterai mes propres tests au lot
Remettez-moi le tout au plus tard le vendredi
22 mars 2024 en pièce jointe d'un message Colnet (et non pas
dans une boîte de remise!). Ce courriel
doit contenir précisément les trois pièces jointes suivantes :
- Le .zip avec les
.cs (mais pas de programme principal), respectant les consignes
- Le .test respectant le format demandé
- Le .res respectant le format demandé
Si vous ne respectez pas à la lettre les consignes de remise,
vous n'aurez pas de points
|
Vous trouverez ici quelques documents, la plupart petits, qui peuvent vous
donner un petit coup de pouce occasionnel.
Quelques solutionnaires suivent. En espérant que ça vous aide à organiser vos idées!