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.
Puisque nous serons en quelque sorte laboratoire à la fois pour les séances théoriques et
les séances de laboratoire, j'ai fait le choix de construire le cours sous forme de
30 séances (de S00 à
S29, ou plutôt à S28 car les jours fériés
introduisent du chaos dans ma planification) plutôt que sous forme de 15 séances
théoriques et 15 séances de laboratoire. Le dosage
prévu de temps en théorie et de temps en laboratoire (soit environ
moitié-moitié) devrait être respecté.
Date |
Séance |
Détails |
22 et 23 août
|
S00 |
Au menu :
- Présentation du cours et du plan
de cours
- Les outils dont nous aurons besoin :
- Nous utiliserons
C# 12.0
- Nous ferons des projets .NET 8
- Assurez-vous que votre version de Visual Studio soit à jour!
- Échange sur le contenu du cours, les modalités, les attentes
- Il est possible que nous tenions une séance « en ligne »
pour « roder la mécanique » au cas où la pandémie mettrait du
sable dans l'engrenage (personne ne le souhaite, mais mieux vaut être
prudentes et prudents)
- Réponses aux questions de la classe :
- Présentation d'une petite activité formative :
420KBB--Consignes-activite-revision.pdf
Si vous souhaitez le code du programme principal à partir duquel vous
devrez démarrer, vous pouvez le prendre du fichier PDF
ou encore le
prendre ci-dessous (parfois, copier / coller d'un PDF...) :
// ...
List<Orque> orques = new List<Orque>();
try
{
for(string s = Console.ReadLine(); "" != s; s = Console.ReadLine())
{
orques.Add(new Orque(s));
Console.WriteLine($"Orque créé : {orques[orques.Count - 1].Nom}");
}
}
catch(NomInvalideException ex)
{
Console.WriteLine(ex.Message);
}
if(Trier(ref orques, out int nbPermutations))
{
Console.WriteLine("Les orques ont été entrés en ordre alphabétique");
}
else
{
Console.WriteLine($"Trier les orques a nécessité {nbPermutations} permutations");
}
Console.Write("La tribu d'orques est :");
foreach (Orque orque in orques)
Console.Write($" {orque.Nom}");
Après avoir pris un peu de temps pour se « chamailler » avec ce petit
défi de remise en forme, je vous ai proposé un peu de code pour vous aider
à redémarrer vos instincts de programmeuse et de programmeur. L'accent a
été mis sur l'écriture de code simple :
- Écrire des fonctions
- Viser « une vocation par fonction »
- Essayer d'écrire des fonctions qui se limitent à une instruction
quand cela s'avère possible
- ... et se récompenser quand on y parvient, en se donnant le droit
d'utiliser la notation => qui est concise et
élégante
Le code produit en classe suit :
Program.cs
//
// code produit pour vous aider ce matin et pour amorcer une réflexion avec vous
//
using MonNamespace;
List<Orque> orques = new List<Orque>();
try
{
for (string s = Console.ReadLine(); "" != s; s = Console.ReadLine())
{
orques.Add(new Orque(s));
Console.WriteLine($"Orque créé : {orques[orques.Count - 1].Nom}");
}
}
catch (NomInvalideException ex)
{
Console.WriteLine(ex.Message);
}
if (Trier(ref orques, out int nbPermutations))
{
Console.WriteLine("Les orques ont été entrés en ordre alphabétique");
}
else
{
Console.WriteLine($"Trier les orques a nécessité {nbPermutations} permutations");
}
Console.Write("La tribu d'orques est :");
foreach (Orque orque in orques)
Console.Write($" {orque.Nom}");
//
// la fonction qui suit est bien trop compliquée; faudra la revisiter...
//
static bool Trier(ref List<Orque> orques, out int nbPermutations)
{
nbPermutations = 0;
Orque[] orq = orques.ToArray();
for (int i = 0; i < orq.Length - 1; ++i)
for (int j = i + 1; j < orq.Length; ++j)
if (orq[i].Nom.CompareTo(orq[j].Nom) > 0) // désordre
{
Permuter(ref orq[i], ref orq[j]);
++nbPermutations;
}
orques = orq.ToList();
return nbPermutations == 0;
}
OutilsTexte.cs
// using...
static class OutilsTexte
{
static char [] voyelles = { 'a', 'e', 'i', 'o', 'u', 'y' };
public static bool Contient(char [] tab, char c)
{
foreach (char ch in tab)
if (ch == c)
return true;
return false;
}
public static bool EstVoyelle(char c) =>
Contient(voyelles, char.ToLower(c));
public static int CompterVoyelles(string s)
{
int n = 0;
foreach (char c in s)
if (EstVoyelle(c))
++n;
return n;
}
}
Algos.cs
// using...
static class Algos
{
public static bool EstEntreInclusif(int val, int min, int max) =>
min <= val && val <= max;
}
Orque.cs
using static MonNamespace.Algos;
using static MonNamespace.OutilsTexte;
// ...
class NomInvalideException : Exception { }
class Orque
{
const int LG_NOM_MIN = 1,
LG_NOM_MAX = 4;
static bool EstNomValide(string nom) =>
EstEntreInclusif(nom.Length, LG_NOM_MIN, LG_NOM_MAX) &&
OutilsTexte.CompterVoyelles(nom) <= 1;
string nom;
public string Nom
{
get => nom;
private init
{
nom = EstNomValide(value) ?
value : throw new NomInvalideException();
//if (!EstNomValide(value))
// throw new NomInvalideException();
//nom = value;
}
}
public Orque(string nom)
{
Nom = nom;
}
}
|
26 et 27 août
|
S01 |
Au menu :
- Quelques mots sur l'idée de classe static, qui permet de pallier en
partie à un manque de certains langages comme
C#
ou
Java
- Léger allègement syntaxique rendu possible par ce mécanisme
- Retour sur la petite
activité formative proposée à
S00
- Discussion de divers aspects techniques et architecturaux associés à
cette activité
- Avenues de raffinement ou d'optimisation
- Quelques explorations qui nous mèneront vers notre premier travail
pratique, le TP00
À titre de référence, le code d'aujourd'hui est à peu près le suivant. Ce code est perfectible; nous ferons bien mieux plus tard dans la session.
Je vous laisse le soin d'écrire le programme principal :
Soldat.cs
// ...
class NomInvalideException : Exception { }
abstract class Soldat
{
public string Nom { get; private init; }
public Soldat(string nom)
{
Nom = nom;
}
public abstract void Saluer();
}
Orque.cs
// ...
class Orque : Soldat
{
const int LG_NOM_MIN = 1,
LG_NOM_MAX = 4;
static bool EstNomValide(string s) =>
EstEntreInclusif(s.Length, LG_NOM_MIN, LG_NOM_MAX) &&
CompterVoyelles(s) <= 1;
static string ValiderNom(string s) =>
EstNomValide(s) ? s : throw new NomInvalideException();
public Orque(string nom)
: base(ValiderNom(nom))
{
}
public override void Saluer()
{
Console.WriteLine($"MOI {Nom}, MOI PUE");
}
}
}
En espérant que cela vous soit utile!
Nous avons aussi discuté sommairement du
clonage.
À titre de référence, le code produit lors de cette séance pour
démontrer le
clonage était :
Image[] images = new Image[]
{
new Jpeg(ConsoleColor.Magenta),
new Png(ConsoleColor.Green),
new Bmp(ConsoleColor.Blue)
};
// non, pas le droit!
//foreach (Image img in images)
//{
// img.Dessiner();
// img = ModifierPeutÊtre(img); // <-- ceci serait illégal
// img.Dessiner();
//}
for (int i = 0; i != images.Length; ++i)
{
images[i].Dessiner();
images[i] = ModifierPeutÊtre(images[i]);
images[i].Dessiner();
}
////////////////////////
static Image ModifierPeutÊtre(Image img)
{
// 0 : créer un backup
Image backup = img.Cloner();
// 1 : modifier img
img.Teinte = ConsoleColor.Red;
// 2 : demander si on veut conserver les modifs
Console.WriteLine("Conserver les modifs? ");
// 2a : si oui, on retourne img
// 2b : sinon, on retourne le backup
if (Console.ReadKey(true).Key == ConsoleKey.O)
return img;
return backup;
}
// il existe une interface ICloneable, qui expose une méthode Clone
// ... mais ne l'utilisez pas :) Pour des détails, voir :
// https://codeql.github.com/codeql-query-help/csharp/cs-class-implements-icloneable/
abstract class Image
{
public ConsoleColor Teinte { get; set; }
public Image(ConsoleColor teinte)
{
Teinte = teinte;
}
// Idiome NVI : non-virtual interface
public void Dessiner()
{
ConsoleColor pre = Console.ForegroundColor;
Console.ForegroundColor = Teinte;
DessinerImpl(); // varie selon les enfants
Console.ForegroundColor = pre;
}
protected abstract void DessinerImpl();
public abstract Image Cloner();
}
class Jpeg : Image
{
public Jpeg(ConsoleColor teinte) : base(teinte)
{
}
protected override void DessinerImpl()
{
Console.WriteLine($"Jpeg {Teinte}");
}
protected Jpeg(Jpeg autre) : base(autre.Teinte)
{
}
// spécialisation covariante
public override Jpeg Cloner() => new (this);
}
class Bmp : Image
{
public Bmp(ConsoleColor teinte) : base(teinte)
{
}
protected override void DessinerImpl()
{
Console.WriteLine($"Bmp {Teinte}");
}
protected Bmp(Bmp autre) : base(autre.Teinte)
{
}
public override Bmp Cloner() => new (this);
}
class Png : Image
{
public Png(ConsoleColor teinte) : base(teinte)
{
}
protected override void DessinerImpl()
{
Console.WriteLine($"Png {Teinte}");
}
protected Png(Png autre) : base(autre.Teinte)
{
}
public override Png Cloner() => new (this);
}
En fin de séance, j'ai fait une petite
activité dirigée d'un système à deux composants (code client, qui était une application console, et code serveur, qui était une
bibliothèque de classes – une DLL).
Le code auquel nous en sommes arrivés pour le client était le suivant (j'ai
pris quelques libertés pour vous divertir) :
using Arsenal;
FabriqueArmes fab = new ();
IArme p = fab.CréerArme(Arsenal.Gravité.violent);
p.Frapper();
p = fab.CréerArme(Arsenal.Gravité.délicat);
p.Frapper();
Le code auquel nous en sommes arrivés pour le serveur était le suivant
(encore une fois avec quelques libertés) :
namespace Arsenal
{
public interface IArme
{
void Frapper();
}
class Masse : IArme
{
public void Frapper()
{
Console.WriteLine("POURRRRH");
}
}
class Chainsaw : IArme
{
public void Frapper()
{
Console.WriteLine("FVRRRRRRRR!");
}
}
public enum Gravité { violent, délicat }
public class FabriqueArmes
{
public IArme CréerArme(Gravité grav) =>
grav == Gravité.délicat ? new Masse() : new Chainsaw();
}
}
Nous avons ensuite survolé les consignes du
TP00.
Si votre serveur pour le
TP00 fonctionne correctement, le programme de test suivant...
// ... code de test (note : le namespace se nomme Consommateur)
using GénérateurId;
using static Consommateur.Tests;
var fab = new FabriqueGénérateurs();
Test(fab, "Séquentiel", "ID", TypeGénérateur.Séquentiel);
Test(fab, "Recycleur", "ID", TypeGénérateur.Recycleur);
Test(fab, "Aléatoire", "ID", TypeGénérateur.Aléatoire, 3);
Test(fab, "Partagé", "ID", TypeGénérateur.Partagé, 3);
Test(fab, "Recycleur", "ID", TypeGénérateur.Recycleur);
Test(fab, "Partagé", "ID", TypeGénérateur.Partagé);
foreach (var (clé, valeur) in fab.ObtenirStatistiques())
Console.WriteLine($"{clé} a été instancié {valeur} fois");
// ... placer ce qui suit dans une classe «static» nommée Tests
public static void Test(FabriqueGénérateurs fab, string nom, string préfixe, TypeGénérateur type)
{
IGénérateurId p = fab.Créer(type, préfixe);
var lst = new List<Identifiant>();
Console.Write($"{nom}, pige initiale :\n\t");
for (int i = 0; i != 10; ++i)
{
lst.Add(p.Prendre());
Console.Write($"{lst[lst.Count - 1]} ");
}
Console.WriteLine();
foreach (var n in lst)
p.Rendre(n);
Console.Write($"{nom}, pige post-remise :\n\t");
for (int i = 0; i != 10; ++i)
Console.Write($"{p.Prendre()} ");
Console.WriteLine();
}
public static void Test(FabriqueGénérateurs fab, string nom, string préfixe, TypeGénérateur type, int germe)
{
IGénérateurId p = fab.Créer(type, préfixe, germe);
var lst = new List<Identifiant>();
Console.Write($"{nom}, pige initiale :\n\t");
for (int i = 0; i != 10; ++i)
{
lst.Add(p.Prendre());
Console.Write($"{lst[lst.Count - 1]} ");
}
Console.WriteLine();
foreach (var n in lst)
p.Rendre(n);
Console.Write($"{nom}, pige post-remise :\n\t");
for (int i = 0; i != 10; ++i)
Console.Write($"{p.Prendre()} ");
Console.WriteLine();
}
// ...
... devrait donner un affichage comme le suivant (il peut y avoir certaines
différences dans les cas « partagé » et « aléatoire », mais il y a des limites
à ces différences – le test utilise un germe choisi – alors consultez votre chic prof si vous avez des doutes) :
Séquentiel, pige initiale :
ID00000 ID00001 ID00002 ID00003 ID00004 ID00005 ID00006 ID00007 ID00008 ID00009
Séquentiel, pige post-remise :
ID00010 ID00011 ID00012 ID00013 ID00014 ID00015 ID00016 ID00017 ID00018 ID00019
Recycleur, pige initiale :
ID00000 ID00001 ID00002 ID00003 ID00004 ID00005 ID00006 ID00007 ID00008 ID00009
Recycleur, pige post-remise :
ID00009 ID00008 ID00007 ID00006 ID00005 ID00004 ID00003 ID00002 ID00001 ID00000
Aléatoire, pige initiale :
ID19236 ID45716 ID56688 ID13007 ID36732 ID11833 ID16397 ID62078 ID22853 ID24902
Aléatoire, pige post-remise :
ID32911 ID53056 ID45554 ID01984 ID05379 ID59249 ID08091 ID56063 ID49708 ID31213
Partagé, pige initiale :
ID19236 ID45716 ID56688 ID13007 ID36732 ID11833 ID16397 ID62078 ID22853 ID24902
Partagé, pige post-remise :
ID32911 ID53056 ID45554 ID01984 ID05379 ID59249 ID08091 ID56063 ID49708 ID31213
Recycleur, pige initiale :
ID00000 ID00001 ID00002 ID00003 ID00004 ID00005 ID00006 ID00007 ID00008 ID00009
Recycleur, pige post-remise :
ID00009 ID00008 ID00007 ID00006 ID00005 ID00004 ID00003 ID00002 ID00001 ID00000
Partagé, pige initiale :
ID14127 ID38283 ID32702 ID05286 ID65150 ID29877 ID33647 ID35818 ID24609 ID32588
Partagé, pige post-remise :
ID19316 ID15965 ID33013 ID04178 ID45250 ID58540 ID33793 ID27893 ID56175 ID43324
Séquentiel a été instancié 1 fois
Recycleur a été instancié 2 fois
Aléatoire a été instancié 1 fois
Partagé a été instancié 1 fois
|
29 et 30 août
|
S02 |
Au menu :
-
Propriétés : get,
set et init
- Retour sur l'idiome
NVI, survolé à
S01
- Retour sur l'exercice de créer une bibliothèque à liens dynamiques,
et d'une création d'un petit
système client / serveur
- schéma de conception
Interface et son implémentation en C#
- implémentation(s) de cette interface
- schéma de conception
Fabrique
- écriture d'un client pour ce service
- utilité de ce type d'architecture
- Petit rappel sur les générateurs de nombres pseudoaléatoires et sur
leur bon usage
const int N = 10_000_000;
const int NB_FACES = 6;
Random dé = new();
int[] lancers = new int[12]; // 0 .. 11, et la 0 sera à 0
for (int i = 0; i != N; ++i)
{
int a = dé.Next(1, NB_FACES + 1),
b = dé.Next(1, NB_FACES + 1);
// Console.WriteLine($"{a} + {b} == {a+b}");
lancers[(a + b) - 1]++;
}
Console.WriteLine($"Après {N} lancers de deux dés à {NB_FACES} faces...");
for (int i = 1; i != lancers.Length; ++i)
Console.WriteLine($"{i + 1} : {lancers[i]}");
string texte = "..."; // utilisez le texte de votre choix
Dictionary<string, int> fréquence = new();
foreach(string s in texte.Split(new char[] { ' ' }))
if(fréquence.ContainsKey(s))
fréquence[s]++;
else
fréquence.Add(s, 1);
foreach (var (mot, n) in fréquence)
Console.WriteLine($"Le mot {mot} apparaît {n} fois");
|
2 sept.
|
s/o
|
Fête du travail
(jour férié)
|
3 sept.
|
?
|
Jour selon l'horaire du lundi
La Fête du travail
crée du chaos dans mon horaire alors on se limitera
à travailler sur le TP00
pour éviter que la synchronisation ne se brise entre mes
trois groupes.
|
5 et 6 sept.
|
S03 |
Au menu :
N'oubliez pas de remettre la version électronique par Colnet
le 6
septembre 2024 à 23 h 59 au début de la séance S04. Ceci constituera la remise officielle de votre
travail. Je vous rappelle toutefois que vous devez imprimer ce travail et
que je corrigerai la version imprimée que vous me remettrez au début du
prochain cours, soit au début de la séance
S04. Imprimez le tout avant le
cours pour éviter que nous ne perdions du temps!
|
9 et 10 sept.
|
S04 |
Au menu :
Pour une implémentation des schémas de conception
Singleton et
Observateur, les deux à travers
un gestionnaire de clavier, voir ceci (qui ressemble à ce que nous avons fait en classe) :
var ges = GesClavier.GetInstance();
var croc = new CroqueMort();
ges.Abonner(new Terminateur(croc));
ges.Abonner(new ÀDroite(ConsoleKey.D));
ges.Abonner(new EnHaut(ConsoleKey.W));
ges.Abonner(new ÀGauche(ConsoleKey.A));
ges.Abonner(new EnBas(ConsoleKey.S));
// ges.Abonner(new Afficheur());
while (!croc.Terminé())
ges.Exécuter();
//
// Deux schémas de conception (Design Patterns)
// ce matin : Singleton, Observateur
//
interface IRéactionClavier
{
void Réagir(ConsoleKeyInfo clé);
}
class Afficheur : IRéactionClavier
{
public void Réagir(ConsoleKeyInfo clé)
{
Console.Write(clé.KeyChar);
}
}
interface ISignalFin
{
void Signaler();
bool Terminé();
}
class CroqueMort : ISignalFin
{
bool Fin { get; set; } = false;
public void Signaler() => Fin = true;
public bool Terminé() => Fin;
}
class Terminateur : IRéactionClavier
{
ISignalFin Signaleur { get; init; }
public Terminateur(ISignalFin p)
{
Signaleur = p;
}
public void Réagir(ConsoleKeyInfo clé)
{
if (clé.Key == ConsoleKey.Q /*ConsoleKey.Escape*/)
Signaleur.Signaler();
}
}
class GesClavier
{
static GesClavier singleton = null;
List<IRéactionClavier> Abonnés { get; } = new();
public void Abonner(IRéactionClavier p)
{
Abonnés.Add(p);
}
GesClavier()
{
}
public static GesClavier GetInstance()
{
//
// note : dangereux si multithreading
//
if (singleton == null)
singleton = new();
return singleton;
}
public void Exécuter()
{
var clé = Console.ReadKey(true);
foreach (var p in Abonnés)
p.Réagir(clé);
}
}
//
// diantre, beaucoup de répétition de code n'est-ce
// pas? mais on va dormir là-dessus pour le moment :)
//
class ÀDroite : IRéactionClavier
{
ConsoleKey Touche { get; init; }
public ÀDroite(ConsoleKey clé)
{
Touche = clé;
}
public void Réagir(ConsoleKeyInfo clé)
{
if (clé.Key == Touche)
Console.WriteLine("Est");
}
}
class EnHaut : IRéactionClavier
{
ConsoleKey Touche { get; init; }
public EnHaut(ConsoleKey clé)
{
Touche = clé;
}
public void Réagir(ConsoleKeyInfo clé)
{
if (clé.Key == Touche)
Console.WriteLine("Nord");
}
}
class ÀGauche : IRéactionClavier
{
ConsoleKey Touche { get; init; }
public ÀGauche(ConsoleKey clé)
{
Touche = clé;
}
public void Réagir(ConsoleKeyInfo clé)
{
if (clé.Key == Touche)
Console.WriteLine("Ouest");
}
}
class EnBas : IRéactionClavier
{
ConsoleKey Touche { get; init; }
public EnBas(ConsoleKey clé)
{
Touche = clé;
}
public void Réagir(ConsoleKeyInfo clé)
{
if (clé.Key == Touche)
Console.WriteLine("Sud");
}
}
N'oubliez pas de remettre le
TP00!
|
12 et 13 sept.
|
S05 |
Au menu :
Pour les ami(e)s des groupes 00001
et 00002, vous recevrez une
invitation Teams pour suivre ce cours à distance. J'espère vous y voir!
|
16 et 17 sept.
|
S06 |
Je serai absent cette semaine car je donne des conférences à
CppCon. Vous pourrez suivre mes
aventures sur :
../../../Sujets/Orthogonal/cppcon2024.html
|
19 et 20 sept.
|
S07
|
Je serai absent cette semaine car je donne des conférences à
CppCon. Vous pourrez suivre mes
aventures sur :
../../../Sujets/Orthogonal/cppcon2024.html
|
23 et 24 sept.
|
S08 |
Au menu :
|
26 et 27 sept.
|
S09 |
Au menu :
- Q00
- Premiers pas vers une manière plus intelligente de programmer :
paramétrer un algorithme de recherche
- Introduction à la programmation générique
- Exemple de Afficher<T>, et pourquoi une version non-générique
fonctionnerait tout autant dans ce cas
- Exemple de TriBulles<T>, accompagné de
Permuter<T>, avec une classe X qui
est IComparable<X> en comparaison avec une
classe Y qui ne l'est pas
- Exemple de Pile<T> avec une capacité
fixe
- Exemple de Chercher<T> où
T doit être IEquatable<T>
- Exercices de programmation à l'aide
d'algorithmes génériques et
d'expressions λ :
exercice-apprivoiser-genericite-lambda.html
- Travail sur le TP01
|
30 sept. et 1
oct.
|
S10 |
Au menu, plusieurs petits trucs :
Pour une Pile<T> reposant sur une List<T>
à titre de substrat :
using Sytem.Collections.Generic;
class PileVideException : Exception {}
class Pile<T>
{
List<T> Substrat{ get; } = new();
public bool EstVide => Substrat.Count == 0;
public void Empiler(T val)
{
Substrat.Add(val);
}
public T Dépiler()
{
T val = Examiner();
Substrat.RemoveAt(Substrat.Count - 1);
return val;
}
public T Examiner()
{
if (EstVide)
throw new PileVideException();
return Substrat[Substrat.Count - 1];
}
}
Pour une Pile<T> dont le substrat est fait de noeuds :
using Sytem.Collections.Generic;
class PileVideException : Exception {}
class Pile<T>
{
class Noeud
{
public T Valeur{ get; init; }
public Noeud Prédécesseur{ get; set; } = null;
public Noeud(T val)
{
Valeur = val;
}
}
Noeud Tête { get; set; } = null;
public bool EstVide => Tête == null;
public void Empiler(T val)
{
Noeud p = new(val);
p.Précédesseur = Tête;
Tête = p;
}
public T Dépiler()
{
T val = Examiner();
Tête = Tête.Précédesseur;
return val;
}
public T Examiner()
{
if (EstVide)
throw new PileVideException();
return Tête.Valeur`;
}
}
J'ai suggéré aux personnes intéressées par la programmation, en
particulier la programmation générique, cette excellente présentation de
Sean Parent en 2013 :
https://www.youtube.com/watch?v=W2tWOdzgXHA (vous n'êtes pas obligées
ou obligés de la regarder, mais c'est vraiment bien!)
À la demande générale, le code (incomplet mais fonctionnel) de
l'observateur de clavier avec délégués est semblable à ceci :
var ges = GesClavier.GetInstance();
var croc = new CroqueMort();
// ges.Abonner(new Afficheur().Afficher);
ges.Abonner(new Terminateur(croc).Réagir);
ges.Abonner(new ÀDroite(ConsoleKey.D).Réagir);
ges.Abonner(new EnHaut(ConsoleKey.W).Réagir);
ges.Abonner(new ÀGauche(ConsoleKey.A).Réagir);
ges.Abonner(new EnBas(ConsoleKey.S).Réagir);
// ges.Abonner(new Afficheur());
while (!croc.Terminé())
ges.Exécuter();
// Deux schémas de conception (Design Patterns)
// ce matin : Singleton, Observateur
//interface IRéactionClavier
//{
// void Réagir(ConsoleKeyInfo clé);
//}
delegate void RéactionClavier(ConsoleKeyInfo clé);
class Afficheur // : IRéactionClavier
{
public void Afficher(ConsoleKeyInfo clé) // note: nom :)
{
Console.Write(clé.KeyChar);
}
}
interface ISignalFin
{
void Signaler();
bool Terminé();
}
class CroqueMort : ISignalFin
{
bool Fin { get; set; } = false;
public void Signaler() => Fin = true;
public bool Terminé() => Fin;
}
class Terminateur // : IRéactionClavier
{
ISignalFin Signaleur { get; init; }
public Terminateur(ISignalFin p)
{
Signaleur = p;
}
public void Réagir(ConsoleKeyInfo clé)
{
if (clé.Key == ConsoleKey.Q /*ConsoleKey.Escape*/)
Signaleur.Signaler();
}
}
class GesClavier
{
static GesClavier singleton = null;
List<RéactionClavier> Abonnés { get; } = new();
public void Abonner(RéactionClavier p)
{
Abonnés.Add(p);
}
GesClavier()
{
}
public static GesClavier GetInstance()
{
// note : dangereux si multithreading
if (singleton == null)
singleton = new GesClavier();
return singleton;
}
public void Exécuter()
{
var clé = Console.ReadKey(true);
foreach (var réac in Abonnés)
réac(clé);
}
}
// diantre, beaucoup de répétition de code n'est-ce
// pas? mais on va dormir là-dessus pour le moment :)
class ÀDroite // : IRéactionClavier
{
ConsoleKey Touche { get; init; }
public ÀDroite(ConsoleKey clé)
{
Touche = clé;
}
public void Réagir(ConsoleKeyInfo clé)
{
if (clé.Key == Touche)
Console.WriteLine("Est");
}
}
class EnHaut // : IRéactionClavier
{
ConsoleKey Touche { get; init; }
public EnHaut(ConsoleKey clé)
{
Touche = clé;
}
public void Réagir(ConsoleKeyInfo clé)
{
if (clé.Key == Touche)
Console.WriteLine("Nord");
}
}
class ÀGauche // : IRéactionClavier
{
ConsoleKey Touche { get; init; }
public ÀGauche(ConsoleKey clé)
{
Touche = clé;
}
public void Réagir(ConsoleKeyInfo clé)
{
if (clé.Key == Touche)
Console.WriteLine("Ouest");
}
}
class EnBas // : IRéactionClavier
{
ConsoleKey Touche { get; init; }
public EnBas(ConsoleKey clé)
{
Touche = clé;
}
public void Réagir(ConsoleKeyInfo clé)
{
if (clé.Key == Touche)
Console.WriteLine("Sud");
}
}
|
3 et 4 oct.
|
S11 |
Au menu :
- Q01
- On se fait une petite collection simpliste :
- Nous avons fait une liste
simplement chaînée de T
- Nous l'avons ensuite raffinée car certaines de ses fonctions
étaient beaucoup trop coûteuses
- Nous avons ajusté cette collection pour qu'il soit
possible de la traverser avec foreach
- Je me suis permis de mentionner le mot clé
default
pour initialiser un objet avec sa valeur par défaut (0
pour un int, 0.0
pour un double,
null pour une
string,
etc.)
- On rend cette ListeSimple<T> « parcourable » avec foreach en
implémentant IEnumerable<T> pour la
collection et IEnumerator<T> pour l'objet
capable de la parcourir
- Travail sur le TP01
Pour l'implémentation de ListeSimple<T>
dans sa déclinaison énumérable :
class ListVideException : Exception { }
class ListeSimple<T> : IEnumerable<T>
{
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 int Count { get; private set; } = 0;
public void AjouterDébut(T val)
{
Noeud p = new(val);
if (EstVide)
Queue = p;
p.Succ = Tête;
Tête = p;
++Count;
}
public void AjouterFin(T val)
{
Noeud p = new(val);
if (EstVide)
Tête = p;
else
Queue.Succ = p;
Queue = p;
++Count;
}
public void SupprimerDébut()
{
if (EstVide)
throw new ListVideException();
Tête = Tête.Succ;
if (EstVide)
Queue = null;
--Count;
}
class Énumérateur : IEnumerator<T>
{
Noeud Cur { get; set; }
public Énumérateur(ListeSimple<T> src)
{
Cur = new(default);
Cur.Succ = src.Tête;
}
public T Current => Cur.Valeur;
object IEnumerator.Current => Cur.Valeur;
public void Dispose() { }
public bool MoveNext()
{
if (Cur.Succ == null)
return false;
Cur = Cur.Succ;
return true;
}
public void Reset() { }
}
public IEnumerator<T> GetEnumerator() =>
new Énumérateur(this);
IEnumerator IEnumerable.GetEnumerator() =>
new Énumérateur(this);
// SupprimerFin ... ark ark ark je refuse
public T First =>
!EstVide ? Tête.Valeur : throw new ListVideException();
public T Last =>
!EstVide ? Queue.Valeur : throw new ListVideException();
}
|
7 et 8 oct.
|
S12 |
Au menu :
- Retour sur Q01
- Peut-on implémenter une Pile<T> à partir
de ListeSimple<T>?
- Nous implémentons un Tableau<T>
semblable en comportement au type List<T>
de .NET
- Nous adaptons Tableau<T> pour qu'il soit énumérable (qu'il implémente
IEnumerable<T>)
- Si le temps le permet :
- On fait une liste doublement chaînée de T
- On fait une file de T
- Travail sur le TP01
|
10 et 11 oct.
|
s/o
|
Journées de mise à niveau (cours suspendus)
|
14 oct.
|
s/o
|
Action
de grâce
(jour férié)
|
15 oct.
|
?
|
L'Action de grâce crée du chaos dans mon horaire alors on trouvera un
aménagement pour aujourd'hui (du tutorat, peut-être?), je ne sais pas
encore quoi, pour éviter que la synchronisation ne se brise entre mes
trois groupes.
|
17 et 18 oct.
|
S13 |
Au menu :
- Premier contact avec la programmation parallèle et concurrente en
C#
- Discussion sur la
Cache
- Discussion sur le
faux-partage
L'exemple utilisé en classe pour illustrer le
faux-partage ressemblait à :
using System;
using System.Threading;
using System.Diagnostics;
const int N = 25_000;
var tab = CréerTableau(N * N);
for(int i = 1; i <= 16; ++i)
{
var (r0, dt0) = Tester(() => CompterSiMT(tab, n => n % 2 != 0, i));
if(i < 10) // bof, mais je ne me souviens plus du code de formatage...
Console.WriteLine($"Compté {r0} impairs avec {i} fils en {dt0} ms");
else
Console.WriteLine($"Compté {r0} impairs avec {i} fils en {dt0} ms");
}
static short[] CréerTableau(int n)
{
short[] tab = new short[n];
for (int i = 0; i != tab.Length; ++i)
tab[i] = (short)(i * 2 + 1);
return tab;
}
static (T rés, long dt) Tester<T>(Func<T> f)
{
var sw = new Stopwatch();
sw.Start();
T rés = f();
sw.Stop();
return (rés, sw.ElapsedMilliseconds);
}
static int CompterSiMT(short[] tab, Func<short, bool> pred, int nthrs)
{
var thrs = new Thread[nthrs-1]; // initialisés à null en C#
var nimpairs = new int[nthrs]; // initialisés à 0 en C#
int tailleBloc = tab.Length / nthrs;
for(int i = 0; i < thrs.Length; ++i)
{
int monIndice = i;
int début = i * tailleBloc; // inclus
int fin = (i + 1) * tailleBloc; // exclue
thrs[i] = new Thread(() =>
{
int m = 0;
for (; début != fin; ++début)
if (pred(tab[début]))
++m;
nimpairs[monIndice] = m;
//for (; début != fin; ++début)
// if (pred(tab[début]))
// ++nimpairs[monIndice];
});
}
foreach (var th in thrs)
th.Start();
{
int début = (nthrs - 1) * tailleBloc; // inclus
int fin = tab.Length; // exclue
int m = 0;
for (; début != fin; ++début)
if (pred(tab[début]))
++m;
nimpairs[nthrs - 1] = m;
//for (; début != fin; ++début)
// if (pred(tab[début]))
// ++nimpairs[nthrs - 1];
}
foreach (var th in thrs)
th.Join();
int cumul = 0;
foreach (int n in nimpairs)
cumul += n;
return cumul;
}
Attention : le cours du 17
octobre (groupe 3) se donnera à distance,
par Teams car je serai hors du pays ce jour-là).
Petit exemple inspiré de celui donné en classe (voir
https://dotnetfiddle.net/hbR0Iv
pour une version en-ligne) :
const int N = 1_000_000;
var (r0,dt0) = Test(() =>
{
int n = 0;
var th0 = new Thread(() =>
{
for (int i = 0; i != N; ++i)
++n;
});
var th1 = new Thread(() =>
{
for (int i = 0; i != N; ++i)
++n;
});
th0.Start();
th1.Start();
th1.Join();
th0.Join();
return n;
});
var (r1, dt1) = Test(() =>
{
int n = 0;
var mutex = new object();
var th0 = new Thread(() =>
{
for (int i = 0; i != N; ++i)
lock (mutex)
{
++n;
}
});
var th1 = new Thread(() =>
{
for (int i = 0; i != N; ++i)
lock (mutex)
{
++n;
}
});
th0.Start();
th1.Start();
th1.Join();
th0.Join();
return n;
});
var (r2, dt2) = Test(() =>
{
int n = 0;
var mutex = new object();
var th0 = new Thread(() =>
{
int m = 0;
for (int i = 0; i != N; ++i)
++m;
lock (mutex)
{
n += m;
}
});
var th1 = new Thread(() =>
{
int m = 0;
for (int i = 0; i != N; ++i)
++m;
lock (mutex)
{
n += m;
}
});
th0.Start();
th1.Start();
th1.Join();
th0.Join();
return n;
});
Console.WriteLine($"Sans synchro : {r0} obtenu en {dt0} tics");
Console.WriteLine($"Avec synchro : {r1} obtenu en {dt1} tics");
Console.WriteLine($"Avec synchro : {r2} obtenu en {dt2} tics");
static (T,long) Test<T>(Func<T> f)
{
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
T res = f();
sw.Stop();
return (res, sw.ElapsedTicks);
}
Petit exemple de code qui devrait être rapide mais ne l'est pas... même s'il donne la bonne réponse!
(voir https://dotnetfiddle.net/UL4JLB
pour une version en ligne mais qui est moins gourmande en mémoire car il y a des
limites à ce site) :
const int N = 25_000;
var tab = CréerTableau(N * N);
var (r0, dt0) = Tester(() => CompterMT(1, tab));
Console.WriteLine($"1 fil : compté {r0} impairs en {dt0} ms");
var (r1, dt1) = Tester(() => CompterMT(2, tab));
Console.WriteLine($"2 fils : compté {r1} impairs en {dt1} ms");
var (r2, dt2) = Tester(() => CompterMT(4, tab));
Console.WriteLine($"4 fils : compté {r2} impairs en {dt2} ms");
var (r3, dt3) = Tester(() => CompterMT(8, tab));
Console.WriteLine($"8 fils : compté {r3} impairs en {dt3} ms");
static short[] CréerTableau(int n)
{
short[] tab = new short[n];
for (int i = 0; i != tab.Length; ++i)
tab[i] = (short)(i * 2 + 1);
return tab;
}
static int CompterSi<T>(T[] tab, Func<T, bool> pred, int début, int fin) // début inclus, fin exclue
{
int n = 0;
for (int i = début; i != fin; ++i)
if (pred(tab[i]))
++n;
return n;
}
static (T rés, long dt) Tester<T>(Func<T> f)
{
var sw = new Stopwatch();
sw.Start();
T rés = f();
sw.Stop();
return (rés, sw.ElapsedMilliseconds);
}
static int CompterMT(int nbThreads, short [] tab)
{
int[] nbImpairs = new int[nbThreads]; // initialisé à 0 en C#
int tailleBloc = tab.Length / nbThreads;
Thread[] thrs = new Thread[nbThreads - 1];
for(int i = 0; i != thrs.Length; ++i)
{
int index = i;
int début = index * tailleBloc;
int fin = début + tailleBloc;
thrs[index] = new Thread(() =>
{
for (int j = début; j != fin; ++j)
if (tab[j] % 2 != 0)
++nbImpairs[index];
});
}
foreach (var th in thrs) th.Start();
////
{
int début = (nbThreads - 1) * tailleBloc;
int fin = tab.Length;
for (int j = début; j != fin; ++j)
if (tab[j] % 2 != 0)
++nbImpairs[nbThreads - 1];
}
////
foreach (var th in thrs) th.Join();
int somme = 0;
foreach (int n in nbImpairs)
somme += n;
return somme;
}
Petit exemple de code qui devrait être rapide et l'est... avec un tout petit changement!
(voir https://dotnetfiddle.net/Jxerel
pour une version en ligne, mais qui est moins gourmande en mémoire) :
const int N = 25_000;
var tab = CréerTableau(N * N);
for(int i = 1; i <= 16; ++i)
{
var (r, dt) = Tester(() => CompterMT(i, tab));
Console.WriteLine($"{i} fil(s) : compté {r} impairs en {dt} ms");
}
static short[] CréerTableau(int n)
{
short[] tab = new short[n];
for (int i = 0; i != tab.Length; ++i)
tab[i] = (short)(i * 2 + 1);
return tab;
}
static int CompterSi<T>(T[] tab, Func<T, bool> pred, int début, int fin) // début inclus, fin exclue
{
int n = 0;
for (int i = début; i != fin; ++i)
if (pred(tab[i]))
++n;
return n;
}
static (T rés, long dt) Tester<T>(Func<T> f)
{
var sw = new Stopwatch();
sw.Start();
T rés = f();
sw.Stop();
return (rés, sw.ElapsedMilliseconds);
}
static int CompterMT(int nbThreads, short [] tab)
{
int[] nbImpairs = new int[nbThreads]; // initialisé à 0 en C#
int tailleBloc = tab.Length / nbThreads;
Thread[] thrs = new Thread[nbThreads - 1];
for(int i = 0; i != thrs.Length; ++i)
{
int index = i;
int début = index * tailleBloc;
int fin = début + tailleBloc;
thrs[index] = new Thread(() =>
{
int nb = 0;
for (int j = début; j != fin; ++j)
if (tab[j] % 2 != 0)
nb++;
nbImpairs[index] = nb;
});
}
foreach (var th in thrs) th.Start();
////
{
int début = (nbThreads - 1) * tailleBloc;
int fin = tab.Length;
int nb = 0;
for (int j = début; j != fin; ++j)
if (tab[j] % 2 != 0)
++nb;
nbImpairs[nbThreads - 1] = nb;
}
////
foreach (var th in thrs) th.Join();
int somme = 0;
foreach (int n in nbImpairs)
somme += n;
return somme;
}
|
21 et 22 oct.
|
S14 |
Au menu :
Le code du pipeline que nous avons implémenté est, pour l'essentiel :
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.IO;
using System.Diagnostics;
var fini = new bool[3];
var zt = new ZoneTransit<(string, string)>[3]
{
new(), new(), new()
};
var thrs = new Thread[]
{
// lecteur
new Thread(() =>
{
foreach(var nom in args)
zt[0].Ajouter((LireFichier(nom), nom));
fini[0] = true; // dès maintenant, je ne produis plus
}),
// majusculeur
new Thread(() =>
{
while(!fini[0])
{
var lst = zt[0].Extraire();
foreach(var (s, nom) in lst)
zt[1].Ajouter(Majusculer(s, nom));
}
{
var lst = zt[0].Extraire();
foreach(var (s, nom) in lst)
zt[1].Ajouter(Majusculer(s, nom));
}
fini[1] = true; // dès maintenant, je ne produis plus
}),
// censeur
new Thread(() =>
{
while(!fini[1])
{
var lst = zt[1].Extraire();
foreach(var (s, nom) in lst)
zt[2].Ajouter(Censurer(s, nom));
}
{
var lst = zt[1].Extraire();
foreach(var (s, nom) in lst)
zt[2].Ajouter(Censurer(s, nom));
}
fini[2] = true;
}),
// scripteur
new Thread(() =>
{
while(!fini[2])
{
var lst = zt[2].Extraire();
foreach(var (s, nom) in lst)
Écrire(s, nom);
}
{
var lst = zt[2].Extraire();
foreach(var (s, nom) in lst)
Écrire(s, nom);
}
})
};
foreach (var th in thrs) th.Start();
foreach (var th in thrs) th.Join();
static string LireFichier(string nom)
{
using (var fich = new StreamReader(nom))
return fich.ReadToEnd();
}
static (string, string) Majusculer(string s, string nom)
=> (s.ToUpper(), nom);
static (string,string) Censurer(string s, string nom)
{
string résultat = "";
int début = 0;
string àCensurer = "IF";
int pos = s.IndexOf(àCensurer, début);
while(pos != -1)
{
résultat += s.Substring(début, pos - début);
résultat += "[CENSURÉ]";
début = pos + àCensurer.Length;
pos = s.IndexOf(àCensurer, début);
}
résultat += s.Substring(début);
return (résultat, nom);
}
static void Écrire(string s, string nom)
{
using (var sw = new StreamWriter(nom + ".out"))
sw.Write(s);
}
static class Algos
{
public static void Permuter<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
}
class ZoneTransit<T>
{
object mutex = new();
List<T> data = new();
public void Ajouter(T elem) // à rediscuter (API suspecte)
{
lock (mutex)
data.Add(elem);
}
public List<T> Extraire()
{
List<T> lst = new();
lock (mutex)
Algos.Permuter(ref lst, ref data);
return lst;
}
}
|
24 et 25 oct.
|
S15 |
Au menu :
- Q02
- Revisiter le problème de l'incrémentation concurrente d'un entier
vu à la séance S13
- Revisiter l'enjeu des booléens du pipeline vu à la séance
S14
- Exercice en deux temps. Première étape :
- Écrivez un programme qui ouvre un fichier
.cs et, à l'aide d'un dictionnaire, compte le nombre
d'occurrences de chaque mot. Pour fins de simplicité, un mot sera
une séquence de caractères séparé par un ou plusieurs blancs,
Ainsi, le texte « if(a < b) a= b; »
contient les mots "if(a",
"<", "b)",
"a=" et "b;"
- Ensuite, affichez à la console les mots et leur nombre
d'occurrences en ordre lexicographique (ordre du dictionnaire...
On parle d'un dictionnaire comme le Robert, le Larousse ou le
Multi ici, pas du type Dictionary<K,V>
de
C#)
- Ensuite, affichez à la console les mots et leur nombre
d'occurrences en ordre décroissant de nombre d'occurrences
- Puis, deuxième étape :
- Transformez ce programme pour qu'il traite un tableau de
fichiers
- Séparez ce tableau en deux et donnez à un fil d'exécution la
première moitié des fichiers à traiter, puis à l'autre fil
d'exécution la deuxième moitié des fichiers à traiter
- Quand les deux fils d'exécution ont terminé, fusionnez leurs
dictionnaires respectifs avant d'afficher les résultats
- S'il vous reste du temps, il y a une étape additionnelle à faire
(venez me voir, je vous expliquerai!)
|
28 et 29 oct.
|
S16 |
Au menu :
- On fait Q02 ensemble
- On fait l'exercice donné à la séance
S15
- Version séquentielle
- Version parallèle à deux fils
Le code proposé à titre de solution pour l'exercice à la séance
S15 fut à peu près :
// on va utiliser args pour les fichiers, parce que
// faire un tableau de noms de fichiers, c'est plate
int moitié = args.Length / 2;
Dictionary<string, int> resGauche = new();
Dictionary<string, int> resDroite = new();
Thread gauche = new(() =>
{
List<Dictionary<string, int>> dicos = new();
for (int i = 0; i != moitié; ++i)
{
string texte = LireFichier(args[i]);
List<string> mots = Tokeniser(texte);
Dictionary<string, int> dico = CompterOccurrences(mots);
dicos.Add(dico);
// dicos.Add(
// CompterOccurrences(
// Tokeniser(
// LireFichier(args[i])
// )
// )
// );
}
foreach (var d in dicos)
resGauche = Fusionner(resGauche, d);
});
Thread droite = new(() =>
{
List<Dictionary<string, int>> dicos = new();
for (int i = moitié; i != args.Length; ++i)
{
string texte = LireFichier(args[i]);
List<string> mots = Tokeniser(texte);
Dictionary<string, int> dico = CompterOccurrences(mots);
dicos.Add(dico);
}
foreach (var d in dicos)
resDroite = Fusionner(resDroite, d);
});
gauche.Start(); droite.Start();
droite.Join(); gauche.Join();
var occurrences = Fusionner(resGauche, resDroite);
var lexi = occurrences.ToArray();
Array.Sort(lexi, (m0, m1) => m0.Key.CompareTo(m1.Key));
foreach (var (mot, n) in lexi)
Console.WriteLine($"{mot} : {n}");
Console.WriteLine();
Console.WriteLine(new string('-', 70));
var decr = occurrences.ToArray();
Array.Sort(decr, (m0, m1) => m1.Value.CompareTo(m0.Value));
foreach (var (mot, n) in decr)
Console.WriteLine($"{mot} : {n}");
Console.ReadLine();
static Dictionary<string, int>
Fusionner(Dictionary<string, int> d0, Dictionary<string, int> d1)
{
Dictionary<string, int> res = new();
foreach(var (mot, n) in d0)
res.Add(mot, n);
foreach (var (mot, n) in d1)
if(res.ContainsKey(mot))
res[mot] += n;
else
res.Add(mot, n);
return res;
}
//string texte = LireFichier("../../../test.txt");
// Console.WriteLine($"Texte complet :\n\"\"\"\n{texte}\n\"\"\"\n");
//List<string> mots = Tokeniser(texte);
//foreach(string mot in mots)
// Console.WriteLine($"\"{mot}\"");
//Console.WriteLine();
//var dico = CompterOccurrences(mots);
//var lexi = dico.ToArray();
//Array.Sort(lexi, (m0, m1) => m0.Key.CompareTo(m1.Key));
//foreach (var (mot, n) in lexi)
// Console.WriteLine($"{mot} : {n}");
//Console.WriteLine();
//var decr = dico.ToArray();
//Array.Sort(decr, (m0, m1) => m1.Value.CompareTo(m0.Value));
//foreach (var (mot, n) in decr)
// Console.WriteLine($"{mot} : {n}");
static Dictionary<string, int> CompterOccurrences(List<string> mots)
{
Dictionary<string, int> dico = new();
foreach (string mot in mots)
if (dico.ContainsKey(mot))
dico[mot]++;
else
dico.Add(mot, 1);
return dico;
}
static bool PeutDébuterMot(char c) => // !char.IsWhiteSpace(c);
char.IsAsciiLetter(c) || c == '_';
static bool PeutPoursuivreMot(char c) => // !char.IsWhiteSpace(c);
char.IsAsciiLetter(c) || char.IsDigit(c) || c == '_';
static int TrouverDébutMot(string s, int pos)
{
for (; pos != s.Length && !PeutDébuterMot(s[pos]); ++pos)
;
return pos != s.Length ? pos : -1;
}
// précondition : on est dans un mot
static int TrouverFinMot(string s, int pos)
{
for (; pos != s.Length && PeutPoursuivreMot(s[pos]); ++pos)
;
return pos != s.Length ? pos : -1;
}
// au sens de «transformer text en jetons qu'on appellera «mots»
static List<string> Tokeniser(string text)
{
List<string> jetons = new();
int pos = TrouverDébutMot(text, 0);
while(pos != -1)
{
int fin = TrouverFinMot(text, pos);
if (fin != -1)
{
jetons.Add(text.Substring(pos, fin - pos));
pos = TrouverDébutMot(text, fin);
}
else
{
jetons.Add(text.Substring(pos));
pos = -1;
}
}
return jetons;
}
static string LireFichier(string nom)
{
using (StreamReader sr = new(nom))
return sr.ReadToEnd();
}
Si vous souhaitez élargir la solution à deux fils d'exécution vers une
solution mieux capable de soutenir de grosses quantités de traitement à
faire, envisagez cette approche :
- Découpez le problème (le tableau de fichiers à traiter) en deux
- Demandez à un fil de s'occuper de la partie de gauche et à un
autre de s'occuper de la partie de droite
- Dans chaque fil, si le nombre de fichiers à traiter est sous un
certain seuil (à vous de voir si c'est dix fichiers, cent fichiers,
mille fichiers, etc.), les traiter séquentiellement, sinon le découper
en deux et recommencer
Cette approche est
récursive au sens où elle repose sur une fonction qui se rappelle
elle-même. Saurez-vous l'implémenter?
|
31 oct. et 1 nov.
|
S17 |
Au menu : activité pratique, en préparation du TP02. Soit le code client suivant :
using System;
using System.Threading;
var bb = new Ardoise();
bb.Abonner(new Caméra());
var noms = new[] { "Bill", "Bob", "Frieda", "Wanda" };
var pub = new int[noms.Length];
for(int i = 0; i != noms.Length; ++i)
pub[i] = bb.AjouterPublieur(noms[i]);
int fini = 0;
var th = new Thread[pub.Length];
for(int i = 0; i != th.Length; ++i)
{
int indice = i;
th[i] = new (() =>
{
var dé = new Random();
for (int n = 0; fini == 0; )
{
if (dé.Next(0, pub.Length) == indice)
bb.Publier(pub[indice], $" : \" message {n++} de {noms[indice]}\"");
Thread.Sleep(dé.Next(400, 500));
}
});
}
foreach (var thr in th) thr.Start();
Console.ReadKey(true);
Interlocked.Exchange(ref fini, 1);
foreach (var thr in th) thr.Join();
class Caméra : IObservateurArdoise
{
public void Nouveauté(string qui, string quoi)
{
Console.WriteLine($"{qui} a dit {quoi}");
}
}
Votre objectif est d'offrir une classe Ardoise
qui exposera au minimum les services
suivants :
- Abonner(IObservateurArdoise) pour accepter un nouvel observateur
- AjouterPublieur(string nom) qui ajoute un publieur nommé nom
et
retourne le id (un int) qui lui sera associé (levez une exception s'il
existe déjà un publieur de ce nom). C'est à l'Ardoise
de trouver une
stratégie pour donner un id différent à chaque publieur
- Publier(int id, string message) qui publiera un message sur l'Ardoise
- RetirerPublieur(int id) qui retire un publieur ayant l'identifiant id
de la liste des publieurs autorisés de l'Ardoise
(levez une exception
s'il n'existe pas de publieur avec cet identifiant)
- Quand une publication sera faite sur l'Ardoise, elle doit informer
ses abonnés (de type IObservateurArdoise) en appelant leur méthode
Nouveauté(string qui, string quoi)
Pour voir si ça tient la route, assurez-vous de tester votre code. Des
exemples possibles de tests (rien d'exhaustif) :
static void StressTestA(int n)
{
var bb = new Ardoise();
var noms = new[] { "Bill", "Bob" };
var pub = new int[noms.Length];
var th = new Thread[pub.Length];
for(int i = 0; i != th.Length; ++i)
{
int indice = i;
th[i] = new (() =>
{
for(int j = 0; j != n; ++j)
{
int id = bb.AjouterPublieur(noms[indice]);
bb.Publier(id, $"Coucou #{j}");
bb.RetirerPublieur(id);
}
});
}
foreach (var thr in th) thr.Start();
foreach (var thr in th) thr.Join();
}
static void StressTestB(int n)
{
Compteur compteur = new();
var bb = new Ardoise();
bb.Abonner(compteur);
var noms = new[] { "Bill", "Bob" };
var pub = new int[noms.Length];
var th = new Thread[pub.Length];
for (int i = 0; i != th.Length; ++i)
{
int indice = i;
th[i] = new (() =>
{
for (int j = 0; j != n; ++j)
{
int id = bb.AjouterPublieur(noms[indice]);
bb.Publier(id, $"Coucou #{j}");
bb.RetirerPublieur(id);
}
});
}
foreach (var thr in th) thr.Start();
foreach (var thr in th) thr.Join();
compteur.Rapport();
}
class Compteur : IObservateurArdoise
{
Dictionary<string, int> Compte { get; } = new();
public void Nouveauté(string qui, string quoi)
{
lock(this)
{
if (Compte.ContainsKey(qui))
Compte[qui]++;
else
Compte.Add(qui, 1);
}
}
public void Rapport()
{
foreach (var (k, v) in Compte)
Console.WriteLine($"{k} a publié {v} fois");
}
}
Si vous y parvenez, voici un petit défi supplémentaire. Soit le code client suivant (note : il y a peu de changements en comparaison avec le précédent),
produisez un affichage coloré par colonnes (demandez à votre chic prof pour une démonstration) :
using System;
using System.Threading;
using System.Collections.Generic;
var bb = new Ardoise();
Messagerie msg = new(10);
bb.Abonner(msg);
var noms = new[] { "Bill", "Bob", "Frieda" };
var pub = new int[noms.Length];
for(int i = 0; i != noms.Length; ++i)
pub[i] = bb.AjouterPublieur(noms[i]);
int fini = 0; // <--
var th = new Thread[pub.Length];
for(int i = 0; i != th.Length; ++i)
{
int indice = i;
th[i] = new Thread(() =>
{
var dé = new Random();
for (int n = 0; fini == 0;) // <--
{
if (dé.Next() % pub.Length == indice)
bb.Publier(pub[indice], $"message {n++} de {noms[indice]}");
Thread.Sleep(dé.Next(400, 500));
}
});
}
foreach (var thr in th) thr.Start();
Thread affichage = new(() =>
{
while(fini == 0)
{
msg.Afficher();
Thread.Sleep(500);
}
});
affichage.Start();
Console.ReadKey(true);
Interlocked.Exchange(ref fini, 1); // <--
foreach (var thr in th) thr.Join();
affichage.Join();
Joyeuse Halloween! 
|
4 et 5 nov.
|
S18 |
Au menu :
- Retour sur l'activité proposée à
S17
- Présentation du TP02
- Survol de détails techniques :
- Types CancellationTokenSource et
CancellationToken
- Méthode d'instance Deconstruct
- Travail sur le TP02
|
7 et 8 nov.
|
S19 |
Au menu :
- Q03
- Travail sur le TP02
- (je vais attendre à la prochaine séance pour apporter de la
nouvelle matière, question de vous donner une chance de démarrer le
TP02 correctement)
|
11 et 12 nov.
|
S20 |
Au menu :
|
14 et 15 nov.
|
S21 |
Au menu :
- Q04
- Q05
- Travail sur le TP02
|
18 et 19 nov.
|
S22 |
Au menu :
- Travail sur le TP02
- J'apporterai ma correction avec moi et j'essaierai de faire
« baisser la pile » le plus possible
|
21 et 22 nov.
|
S23 |
Levée de cours dû au mouvement de grève étudiante
|
25 et 26 nov.
|
S24 |
Au menu :
- Petite activité nommée TP02b
pour faire des points boni
- J'apporterai ma correction avec moi et j'essaierai de faire
« baisser la pile » le plus possible
|
28 et 29 nov.
|
S25 |
Au menu :
|
2 et 3 déc.
|
S26 |
Au menu :
|
5 et 6 déc.
|
S27 |
Au menu :
- On fait la PFI (soyez prêtes, soyez
prêts!)
|
9 et 10 déc.
|
S28 |
Au menu :
|
Vous trouverez ici quelques documents, la plupart petits, qui peuvent vous
donner un petit coup de pouce occasionnel.
Les consignes des travaux pratiques suivent.
Consignes |
Détails supplémentaires |
À remettre... |
Activité de révision
|
Voir S00 et
S01
|
s/o
|
TP00
|
Code de test proposé à la séance
S01
|
Vendredi le 6 septembre 2024
à 23 h 59
Important : la copie électronique livrée par Colnet le
6 septembre est votre version finale, mais je ne corrigerai que la
version papier que vous me remettrez au début du cours suivant (selon les
groupes : le 10 ou le 11
septembre) |
TP01
|
Vendredi le 11 octobre à
23 h 59
Un exemple d'exécution serait wallyd.mp4
(merci à Marc Beaulne
d'avoir préparé cela!)
Le code du programme principal imposé est le suivant (vous pouvez
ajouter des using et c'est tout) :
// ...
// remplir le catalogue
const char SYMBOLE_MÉTAL = '%';
Catalogue.Get.Associer(SYMBOLE_MÉTAL, Catégorie.Métal);
// préparer la surface d'affichage
const int HAUTEUR = 10,
LARGEUR = 20;
Surface surf = new(HAUTEUR, LARGEUR);
// préparer le cadre de la surface
Cadre cadre = new(HAUTEUR, LARGEUR, ConsoleColor.Cyan);
// préparer la zone de messagerie
Messagerie messagerie = new(new(0, HAUTEUR));
// positionner Wallyd
Point2D centre = new(3, 3);
Robot wallyd = new("Wallyd", centre);
wallyd.Équiper(new DétecteurMétal(wallyd, 1.0f));
surf.Ajouter(wallyd);
// placer les déchets sur la surface
const int NB_DÉCHETS = 3;
Random dé = new();
List<Déchet> déchets = new();
var libres = surf.TrouverSi
(
cadre.Exclure,
c => c == default || c == ' '
);
if (libres.Count < NB_DÉCHETS)
throw new SurfacePleineException();
for (int i = 0; i != NB_DÉCHETS; ++i)
{
int n = dé.Next(libres.Count);
déchets.Add(new(SYMBOLE_MÉTAL, Catégorie.Métal, libres[n]));
libres.RemoveAt(n);
}
surf.Ajouter(déchets.ToArray());
while (surf.TrouverSi(c => c == SYMBOLE_MÉTAL).Count > 0)
{
bool trouvé = false;
do
{
PipelineAffichage pipeline = new();
pipeline.Ajouter(Appliquer(cadre));
pipeline.Appliquer
(
GénérerHalo(wallyd.Zone, surf.Dupliquer())
);
var pts = wallyd.Détecter(surf, Catégorie.Métal);
if (pts.Count > 0)
{
trouvé = true;
if (pts[0] == wallyd.Pos)
{
messagerie.Effacer();
messagerie.Afficher
(
$"Déchet collecté à la position {pts[0]}"
);
}
else
{
messagerie.Afficher
(
$"Trouvé {pts.Count} déchet(s)",
$"Déplacement vers {pts[0]}"
);
surf.Retirer(wallyd);
wallyd.DéplacerVers(pts[0]);
surf.Ajouter(wallyd);
wallyd.RéinitialiserPuissance();
}
}
else
{
wallyd.AugmenterPuissance();
messagerie.Effacer();
}
Thread.Sleep(500);
}
while (!trouvé);
wallyd.RéinitialiserPuissance();
}
{
PipelineAffichage pipeline = new();
pipeline.Ajouter(Appliquer(cadre));
pipeline.Appliquer
(
GénérerHalo(wallyd.Zone, surf.Dupliquer())
);
}
static Mutable GénérerHalo(Cercle c, Mutable p)
{
Mutable res = p.Dupliquer();
for (int ligne = 0; ligne != res.Hauteur; ++ligne)
for (int col = 0; col != res.Largeur; ++col)
{
Point2D pt = new(col, ligne);
if (c.Centre.Distance(pt) <= c.Rayon)
res[pt] = new(res[pt].Symbole, res[pt].Avant, ConsoleColor.Green);
}
return res;
}
static Func<Mutable, Mutable> Appliquer(Cadre p)
{
return m =>
{
Mutable res = m.Dupliquer();
for (int i = 0; i != p.Hauteur; ++i)
for (int j = 0; j != p.Largeur; ++j)
{
var c = p[i, j];
if (c.Symbole != default)
res[i, j] = c;
}
return res;
};
}
static Func<Mutable, Mutable> AppliquerSurface(Surface surf)
{
return m =>
{
Mutable res = m.Dupliquer();
for (int i = 0; i != surf.Hauteur; ++i)
for (int j = 0; j != surf.Largeur; ++j)
{
var c = surf[i, j];
if (c.Symbole != default)
res[i, j] = c;
}
return res;
};
}
class SurfacePleineException : Exception { }
Le code des fonctions fournies par vos chics profs pour la classe Surface
est :
// ...
public List<Point2D> TrouverSi
(
Func<Point2D, bool> exclure,
Func<char, bool> pred
)
{
List<Point2D> pts = new();
for (int ligne = 0; ligne != Hauteur; ++ligne)
for (int col = 0; col != Largeur; ++col)
{
Point2D pt = new(col, ligne);
if (!exclure(pt) && pred(this[pt].Symbole))
pts.Add(pt);
}
return pts;
}
public List<Point2D> TrouverSi(Func<char, bool> pred) =>
TrouverSi(pt => false, pred);
// ...
Le code fourni pour la classe PipelineAffichage
est :
// ...
internal class PipelineAffichage
{
List<Func<Mutable, Mutable>> Transfos = new();
public void Ajouter(Func<Mutable, Mutable> transfo) =>
Transfos.Add(transfo);
public IProjetable Appliquer(Point2D pos, Mutable p)
{
foreach (var transfo in Transfos)
p = transfo(p);
return Afficher(pos, p);
}
public IProjetable Appliquer(Mutable p) =>
Appliquer(new(), p);
// ...
|
TP02
|
Vendredi le 22 novembre à
23 h 59
Note : l'énoncé du TP02
est essentiellement correct, mais il vous est offert « sous réserve » car
il se peut qu'il y ait des changements mineurs (surtout de la
clarification) au cours des prochains jours.
Le code du programme principal imposé est le suivant (vous pouvez
ajouter des using et c'est tout) :
// ...
RobotInfo[] robotsInfo =
{
new(null, 0, "Wallyd", ConsoleColor.Magenta, Catégorie.Métal),
new(null, 1, "Bart", ConsoleColor.Blue, Catégorie.Plastique),
new(null, 2, "Charles", ConsoleColor.Yellow, Catégorie.Organique),
new(null, 3, "Lisa", ConsoleColor.Green, Catégorie.Nucléaire)
};
(char sym, Catégorie cat)[] symCatégorie =
{
('%', Catégorie.Métal),
('*', Catégorie.Plastique),
('&', Catégorie.Plastique),
('/', Catégorie.Organique),
('?', Catégorie.Nucléaire),
};
foreach (var symCat in symCatégorie)
Catalogue.Get.Associer(symCat.sym, symCat.cat);
// préparer la surface d'affichage
const int HAUTEUR = 20,
LARGEUR = 40;
Surface surf = new(HAUTEUR, LARGEUR);
// préparer le cadre de la surface
Cadre cadre = new(HAUTEUR, LARGEUR, ConsoleColor.Cyan);
// préparer la zone de messagerie
Messagerie messagerie = new(new(LARGEUR, 0), HAUTEUR);
// préparer la zone d'informations
Messagerie information = new(new(0, HAUTEUR), 2);
Random dé = new();
List<(Robot r, Catégorie c)> robots =
CréerRobots(robotsInfo, dé, surf, cadre.Exclure);
// placer les déchets sur la surface
const int NB_DÉCHETS = 3;
List<Déchet> déchets =
CréerPollution(symCatégorie, dé, NB_DÉCHETS, surf, cadre.Exclure);
surf.Ajouter(déchets.ToArray());
CanalComm canal = new(robotsInfo.Length);
PipelineAffichage pipe = new (surf, cadre, canal, messagerie, information);
pipe.Démarrer();
CancellationTokenSource src = new();
Thread[] cueilleurs =
CréerCueilleurs
(
robotsInfo, surf, canal, messagerie, information, src.Token
);
Thread lireTouche = new(() =>
{
Console.ReadKey(true);
src.Cancel();
});
lireTouche.Start();
foreach (var c in cueilleurs) c.Start();
foreach (var c in cueilleurs) c.Join();
lireTouche.Join();
pipe.Arrêter();
static List<(Robot robot, Catégorie cat)>
CréerRobots(RobotInfo [] robotsInfo, Random dé, Surface surf, Func<Point2D, bool> exclure)
{
List<(Robot r, Catégorie c)> robots = new();
// Trouver les cases libres
var libres = surf.TrouverSi(exclure,c => c == default || c == ' ');
for (int r = 0; r < robotsInfo.Length; r++)
{
RobotInfo info = robotsInfo[r];
int n = dé.Next(libres.Count);
Robot robot =
CréerRobot(info.nom, libres[n], info.couleur, info.catégorie);
info.robot = robot;
libres.RemoveAt(n);
}
return robots;
}
static Robot
CréerRobot(string nom, Point2D pos, ConsoleColor couleur, Catégorie cat)
{
Robot robot = new(nom, pos, couleur);
robot.Équiper(new Détecteur(robot, 1, cat));
return robot;
}
static List<Déchet>
CréerPollution((char sym, Catégorie cat)[] symCatégorie, Random dé,
int nbDéchets,
Surface surf, Func<Point2D, bool> exclure)
{
List<Déchet> déchets = new();
var libres = surf.TrouverSi(exclure, c => c == default || c == ' ');
if (libres.Count < nbDéchets * symCatégorie.Length)
throw new SurfacePleineException();
foreach (var symCat in symCatégorie)
{
for (int d = 0; d != nbDéchets; ++d)
{
int n = dé.Next(libres.Count);
déchets.Add(new(symCat.sym, symCat.cat, libres[n]));
libres.RemoveAt(n);
}
}
return déchets;
}
static Thread[] CréerCueilleurs
(RobotInfo[] robotsInfo, Surface surf, CanalComm canal,
Messagerie messagerie, Messagerie information, CancellationToken jeton)
{
Dictionary<char, int> collectés = new();
var cueilleurs = new Thread[robotsInfo.Length];
for (int r = 0; r < robotsInfo.Length; r++)
{
var (robot, noPort, _, _, cat) = robotsInfo[r];
var catalogue = Catalogue.Get;
cueilleurs[r] = new (() =>
{
// tant qu'il reste des déchets à ramasser
while (!jeton.IsCancellationRequested &&
surf.TrouverSi(c => catalogue.Est(c, cat)).Count > 0)
{
bool trouvé = false;
// Trouver et ramasser le déchet
do
{
canal.PublierSur(noPort, new(robot.Zone, robot.Couleur, robot.Symbole));
var pts = robot.Détecter(surf, cat);
if (pts.Count > 0)
{
trouvé = true;
if (pts[0] == robot.Position)
{
char c = surf[pts[0]].Symbole;
lock (collectés)
if (collectés.ContainsKey(c))
collectés[c]++;
else
collectés.Add(c, 1);
var pos = robot.Position;
messagerie.Ajouter
(
robot.Couleur,
$"Déchet collecté à la position {pos}"
);
surf.Retirer(pos);
}
else
{
messagerie.Ajouter
(
robot.Couleur,
$"Trouvé {pts.Count} déchet(s)",
$"Déplacement vers {pts[0]}"
);
robot.DéplacerVers(pts[0]);
robot.RéinitialiserPuissance();
}
string s = "Collectés : ";
lock (collectés)
{
foreach (var (sym, n) in collectés)
s += $"{sym} x {n}; ";
information.Ajouter(ConsoleColor.White, s, "Pressez une touche pour terminer");
}
}
else
{
robot.AugmenterPuissance();
}
Thread.Sleep(500);
}
while (!trouvé);
robot.RéinitialiserPuissance();
}
});
}
return cueilleurs;
}
class RobotInfo
{
public Robot? robot;
public int noPort;
public string nom;
public ConsoleColor couleur;
public Catégorie catégorie;
public RobotInfo(Robot? robot, int noPort, string nom,
ConsoleColor couleur, Catégorie catégorie)
{
this.robot = robot;
this.noPort = noPort;
this.nom = nom;
this.couleur = couleur;
this.catégorie = catégorie;
}
public void Deconstruct(out Robot? robot, out int noPort,
out string nom, out ConsoleColor couleur,
out Catégorie catégorie)
{
robot = this.robot;
noPort = this.noPort;
nom = this.nom;
couleur = this.couleur;
catégorie = this.catégorie;
}
}
class SurfacePleineException : Exception;
À noter :
- Le type RobotInfo qui offre les
informations descriptives pour un Robot,
de même que la variable robotsInfo
utilisée pour décrire les instances de Robot
pour cette simulation
- La variable symCatégorie qui explique
à quelle catégorie chaque symbole de déchet devra être associé
- La présence de deux instances de Messagerie,
soit messagerie (qui sera à la droite de
surf, qui sera la Surface de
simulation) et information (qui se
trouvera juste en dessous de surf)
- La
fabrique CréerRobots, qui crée des
robots sur la base de plusieurs RobotInfo
- La
fabrique CréerRobot qui crée un
Robot sur la base de quelques informations clés
- La
fabrique CréerPollution, qui...
crée de la pollution
- La
fabrique CréerCueilleurs, qui crée
les fils d'exécution qui contrôlent les robots et les démarre
- Le fil d'exécution lireTouche, qui lit
une touche et arrête l'exécution du programme
Si vous avez eu de la difficulté avec le TP01,
vous trouverez une implémentation fonctionnelle sur
TP01-Wallyd.zip mais notez que vous n'avez
absolument aucune obligation de l'utiliser : vous pouvez utiliser, comme
base de travail, votre propre TP01, ou la
version proposée par vos chics profs, ou encore un savant hybride des
deux... L'important est que vous compreniez ce que vous faites!
Important : je sais que plusieurs d'entre vous utilisez les tabulations
par défaut de Visual Studio, qui correspondent à quatre espaces.
Ça ne me dérange pas, mais personnellement ça m'agace alors j'utilise une
indentation à trois espaces. Quand les cours se donnaient sur les
postes du Collège, nous les faisions préparer en conséquence!
Si vous utilisez mes
sources à titre de point de départ, assurez-vous que les fichiers seront
correctement indentés (Édition → Avancé → Mettre le document en forme)
avant de commencer à programmer, sinon le nouveau code sera indenté de
quatre espaces, le code existant sera indenté de trois espaces, et le tout
sera difficile à comprendre. Vous ne voudriez pas perdre des points dû à
une indentation incorrecte, n'est-ce pas?
|
TP02b
|
Vendredi le 6 décembre à
23 h 59
Le code du programme principal imposé mais incomplet est le suivant :
// ...
RobotInfo[] robotsInfo =
{
new(null, 0, "Wallyd", ConsoleColor.Magenta, Catégorie.Métal),
new(null, 1, "Bart", ConsoleColor.Blue, Catégorie.Plastique),
new(null, 2, "Charles", ConsoleColor.Yellow, Catégorie.Organique),
new(null, 3, "Lisa", ConsoleColor.Green, Catégorie.Nucléaire)
};
(char sym, Catégorie cat)[] symCatégorie =
{
('%', Catégorie.Métal),
('*', Catégorie.Plastique),
('&', Catégorie.Plastique),
('/', Catégorie.Organique),
('?', Catégorie.Nucléaire),
('*', Catégorie.Recyclable),
};
foreach (var symCat in symCatégorie)
Catalogue.Get.Associer(symCat.sym, symCat.cat);
// préparer la surface d'affichage
const int HAUTEUR = 20,
LARGEUR = 40;
Surface surf = new(HAUTEUR, LARGEUR);
// préparer le cadre de la surface
Cadre cadre = new(HAUTEUR, LARGEUR, ConsoleColor.Cyan);
// préparer la zone de messagerie
Messagerie messagerie = new(new(LARGEUR, 0), HAUTEUR);
// préparer la zone d'informations
Messagerie information = new(new(0, HAUTEUR), 2);
Random dé = new();
List<(Robot r, Catégorie c)> robots =
CréerRobots(robotsInfo, dé, surf, cadre.Exclure);
// placer les déchets sur la surface
const int NB_DÉCHETS = 10;
List<Déchet> déchets =
CréerPollution(symCatégorie, dé, NB_DÉCHETS, surf, cadre.Exclure);
surf.Ajouter(déchets.ToArray());
CanalComm<InfoCanalComm> canal = new(robotsInfo.Length);
PipelineAffichage pipe = new(surf, cadre, canal);
CancellationTokenSource cts0 = new();
CancellationTokenSource cts1 = new();
Task collectes = Task.WhenAny
(
ExécuterCollecteAsync
(
robotsInfo, surf, canal, messagerie, information, cts0.Token, cts1
), ArrêterAsync(cts0)
);
while (!cts1.Token.IsCancellationRequested)
{
pipe.Rafraichir();
messagerie.Afficher();
information.Afficher();
}
pipe.Rafraichir();
messagerie.Afficher();
information.Afficher();
// Ici la méthode ExécuterCollecteAsync
// Ici la méthode ArrêterAsync
static List<(Robot robot, Catégorie cat)>
CréerRobots(RobotInfo[] robotsInfo, Random dé, Surface surf, Func<Point2D, bool> exclure)
{
List<(Robot r, Catégorie c)> robots = new();
// Trouver les cases libres
var libres = surf.TrouverSi(exclure, c => c == default || c == ' ');
for (int r = 0; r < robotsInfo.Length; r++)
{
RobotInfo info = robotsInfo[r];
int n = dé.Next(libres.Count);
Robot robot =
CréerRobot(info.nom, libres[n], info.couleur, info.catégorie);
info.robot = robot;
libres.RemoveAt(n);
}
return robots;
}
static Robot
CréerRobot(string nom, Point2D pos, ConsoleColor couleur, Catégorie cat)
{
Robot robot = new(nom, pos, couleur);
robot.Équiper(new Détecteur(robot, 1, cat));
return robot;
}
static List<Déchet>
CréerPollution((char sym, Catégorie cat)[] symCatégorie, Random dé,
int nbDéchets,
Surface surf, Func<Point2D, bool> exclure)
{
List<Déchet> déchets = new();
var libres = surf.TrouverSi(exclure, c => c == default || c == ' ');
if (libres.Count < nbDéchets * symCatégorie.Length)
throw new SurfacePleineException();
foreach (var symCat in symCatégorie)
{
for (int d = 0; d != nbDéchets; ++d)
{
int n = dé.Next(libres.Count);
déchets.Add(new(symCat.sym, symCat.cat, libres[n]));
libres.RemoveAt(n);
}
}
return déchets;
}
class RobotInfo
{
public Robot? robot;
public int noPort;
public string nom;
public ConsoleColor couleur;
public Catégorie catégorie;
public RobotInfo(Robot? robot, int noPort, string nom,
ConsoleColor couleur, Catégorie catégorie)
{
this.robot = robot;
this.noPort = noPort;
this.nom = nom;
this.couleur = couleur;
this.catégorie = catégorie;
}
public void Deconstruct(out Robot? robot, out int noPort,
out string nom, out ConsoleColor couleur,
out Catégorie catégorie)
{
robot = this.robot;
noPort = this.noPort;
nom = this.nom;
couleur = this.couleur;
catégorie = this.catégorie;
}
}
class SurfacePleineException : Exception { }
// ...
|
PFI
|
|
|