420-202-RE – Structures de données et programmation orientée objet

Quelques raccourcis :

Ceci est un petit site de support pour le cours 420-202-RE – Structures de données et programmation orientée objet.

Vous trouverez aussi des liens sur divers langages (dont C#, notre outil de prédilection dans ce cours) un peu partout dans http://h-deb.ca/

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

Cliquez sur cette cible pour le plan de cours, sous forme électronique

Pratiques de correction

Je corrige les programmes en appliquant des codes de correction. Vous trouverez ici la liste des codes les plus fréquents.

Ma stratégie de correction en tant que telle (pour le code, à tout le moins) est résumée ici.

Cliquez sur cette cible pour un résumé des principales règles de programmatique en vigueur dans ce cours.

Cliquez sur cette cible pour les normes appliquées dans ce cours, en ce qui a trait au pseudocode

Détail des séances en classe

Date Séance Détails

24 janvier

S00

Au menu :

  • Présentation du cours et du plan de cours
  • Petit exercice de remise en forme

Notez qu'en ce premier cours, nous investissons nos efforts sur deux aspects, soit :

  • Reprendre la forme intellectuelle après le congé des Fêtes, et
  • Mettre en place le vocabulaire et les idées qui guideront nos réflexions en ce début de session

Ça fait un gros cours pour démarrer la session, mais nous réinvestirons le tout cours après cours, alors ça va entrer doucement dans notre tête, et s'intégrer à notre praxis émergente 🙂.

  • Mise en place du vocabulaire de base de la POO, mais sous forme d'un survol seulement, et avec accent sur l'encapsulation. Ainsi, nous avons identifié et situé les termes et idées suivant(e)s :
    • respect des invariants, entre chaque appel d'un service d'un objet
    • identification et garantie du respect des préconditions pour les services d'un objet
    • identification et garantie du respect des postconditions pour les services d'un objet
    • une maxime importante : « un objet est responsable de son intégrité, du début à la fin de sa vie »
    • quelques mots clés qui aident à encadrer la capacité d'un objet à assurer son encapsulation, soit les qualifications d'accès private, protected, public
      • nous n'avons pas vraiment parlé de protected, qui a une utilité réelle mais plus limitée que des deux autres, et dont nous ne pouvons pas traiter pour le moment
      • nous avons toutefois insisté sur le fait que private, la qualification par défaut des membres dans une classe, est ce que nous souhaitons utiliser le plus possible, et nous avons donné quelques raisons pour soutenir cette prise de position
      • nous avons démontré que public, du moins pour les états d'un objet, devrait être évité dans presque tous les cas, cette qualification empêchant de facto l'objet d'assurer son encapsulation
  • Nous avons mis de l'avant quelques mots de vocabulaire plus techniques, soit :
    • les méthodes, qui sont les services (les fonctions, les comportements) d'un objet
    • les attributs, qui sont les états (variables, constantes) d'un objet
    • les constructeurs, qui sont les méthodes un peu spéciales mais Ô combien importantes qui permettent de déterminer l'état initial d'un objet – sans eux, pas d'état initial connu pour un objet, donc pas d'encapsulation pour lui
  • Nous avons discuté du glissement sémantique entre F(obj), où on applique une opération sur une entité , ce qui rejoint l'approche procédurale que nous avons mis en application dans le cours 420-201-RE, et obj.F(), qui sollicite la méthode de l'objet et rend ce dernier actif
  • Nous avons discuté brièvement des accesseurs (méthodes de consultation) et des mutateurs (méthodes de modification), qui sont des familles de services typiques pour les objets
    • La partie get d'une propriété est un exemple d'accesseur
    • La partie set d'une propriété est un exemple de mutateur
    • Il est évidemment possible d'écrire des méthodes qui ont on comportement d'accesseur ou un comportement de mutateur
  • Nous avons montré que C# permet une écriture concise pour ces deux familles importantes de services, soit les propriétés, et nous avons indiqué qu'il s'agit d'une pratique idiomatique dans ce langage, donc d'une pratique que nous allons mettre en application même si elle est plus « décorative » que nécessaire
  • Nous avons abordé le mot static, qui en C# a le sens de « membre de classe » alors qu'un membre qui n'est pas qualifié static est un « membre d'instance »
// ...
Triangle t = new(5, 'A');
t.Dessiner();
// ...
    ... affichera à la console la chose suivante :
    A
   AAA
  AAAAA
 AAAAAAA
AAAAAAAAA

À titre de « petit bonbon », nous avons vu en début de séance que les fonctions qui se limitent à un seul return peuvent être écrites de manière simplifiée, et nous avons appris que votre chic prof, souhaitant vous encourager à écrire de courtes fonctions qui font une et une seule chose (et le font bien!), acceptera cette syntaxe si elle est bien utilisée.

Ainsi, ceci :

static int Carré(int n)
{
   return n * n;
}

... peut s'écrire de manière équivalente sous la forme suivante :

static int Carré(int n) => n * n;

... alors que ceci :

class Nom
{
   const string NOM_DÉFAUT = "Inconnu(e)";
   string valeur;
   public string Valeur
   {
      get
      {
         return valeur;
      }
      private set
      {
         if (value != null && value.Length > 0)
         {
            valeur = value;
         }
         else
         {
            valeur = NOM_DÉFAUT;
         }
      }
   }
   public string Crié
   {
      get
      {
         return Valeur.ToUpper();
      }
   }
   public Nom(string valeur)
   {
      Valeur = valeur;
   }
}

... pourra s'écrire comme suit 

class Nom
{
   const string NOM_DÉFAUT = "Inconnu(e)";
   string valeur;
   public string Valeur
   {
      get => valeur;
      private set
      {
         if (value != null && value.Length > 0)
         {
            valeur = value;
         }
         else
         {
            valeur = NOM_DÉFAUT;
         }
      }
   }
   public string Crié => Valeur.ToUpper(); // note : un get seulement, pas de set
   public Nom(string valeur)
   {
      Valeur = valeur;
   }
}

D'autres simplifications d'écriture viendront plus tard dans la session.

Nous avons enfin défini (et illustré par des exemples) un certain nombre de termes : classe, instance, attribut d'instance, propriété d'instance (avec volets get, set – nous ajouterons sous peu le mot init), constructeur par défaut, constructeur paramétrique, qualifications d'accès private et public (il y en a d'autres), encapsulation, membre d'instance (non-static; nous ajouterons bientôt membre de classe : static), invariant, précondition et postcondition... Ouf!

À propos des termes utilisés, les notes informelles prises en classe étaient comme suit (je vous mets ça « brut ») :

Groupe 01Groupe 02
classe et instance
   Point est une classe
   pt est une instance de la classe Point
   
pt.x est un attribut d'instance du Point nommé pt
   variable membre de pt
   
pt.X est une propriété d'instance du Point nommé pt
   le get est ce qui permet d'en lire la valeur
   le set est ce qui permet d'en modifier la valeur
   
   (une propriété, typiquement, encadre l'accès à un attribut)
   
Point() est un constructeur par défaut

Point(int x, int y) est un constructeur paramétrique

public : tout le monde y a accès
private : seul un Point y a accès

static : membre de classe

(non-static : membre d'instance)

Encapsulation :

* principe selon lequel un objet se porte garant de son
  intégrité, du début à la fin de sa vie
* principe selon lequel un objet garantit le respect de
  ses invariants

Invariant : ce qui s'avère pour un objet entre deux
appels de ses méthodes
classe
  Point

instance
  pt0, pt1
  
attributs d'instance
  pt.x, pt.y
  variables membres d'une instance
  
propriétés d'instance
   pt.X, pt.Y
   get pour lire
   set pour modifier / écrire
   
constructeur
  par défaut, p.ex. Point.Point()
  paramétrique, p.ex. Point.Point(int x, int y)
  
private si possible
public si nécessaire  

encapsulation :

* principe selon lequel un objet garantit son intégrité
  du début à la fin de sa vie
  
* principe selon lequel un objet se porte garant du
  respect de ses invariants

invariant : état qui s'avère en tout temps entre deux
appels de méthodes

précondition : ce à quoi l'appelant d'une fonction s'engage

postcondition : ce à quoi la fonction appelée s'engage

static : membre de classe

(non-static) : membre d'instance

26 janvier

S01

Au menu :

  • Petite séance de programmation collective. Écrivons ensemble un programme qui :
    • Lit un nombre de personnes. Ce nombre doit être un entier strictement positif
    • Pour chaque personne :
      • lit son nom, qui devra être une chaîne de caractères non-vide
      • lit son âge (un entier positif, donc supérieur ou égal à zéro)
      • crée une Personne ayant ce nom et cet âge
      • l'ajoute dans un tableau
  • Une fois toutes les personnes lues, ce programme :
    • Trouvera et affichera le nom de la personne dont le nom est le plus long
    • Affichera l'âge moyen des personnes
  • Pour y arriver, nous définirons une classe Personne telle que :
    • Une Personne aura un nom et un âge
    • Le nom d'une Personne ne pourra être vide. Par défaut, nous utiliserons le nom "INCONNU(E)"
    • L'âge d'une Personne devra se situer entre 0 et 140 inclusivement. Par défaut, nous considérerons qu'une Personne est d'âge zéro

À titre de simplification (et de bonbon de fin de séance), j'ai aussi montré :

  • Comment utiliser foreach dans le cas où une répétitive doit itérer sur tous les éléments d'une séquence et n'a pas besoin de la valeur des indices
  • Comment utiliser l'opérateur ternaire dans les cas où nous souhaitons choisir l'une de deux expressions de même type sur la base d'une condition
  • Comment faire des propriétés automatiques, dans les cas où une classe n'a pas d'invariant garantir
  • J'ai même glissé un (très!) bref mot sur un thème qui nous occupera sous peu, soit celui des exceptions...

Une solution possible est disponible ici.

31 janvier

S02

Au menu :

  • Simplification de la syntaxe lors d'un appel à new
  • Mutateur à la construction seulement : propriétés avec le mot clé init
  • Introduction à l'idée d'objet immuable, particulièrement importante dans un langage comme C# où on accède indirectement aux objets
  • Présentation du labo 00 – Le cryptographe
  • Travail sur le labo 00 – Le cryptographe

Quelques trucs pour vous aider...

Si vous cherchez un exemple simpliste de programme de test interactif, vous pouvez utiliser celui-ci (vous pouvez aussi vous en écrire un plus à votre goût; je ne ramasserai que la classe Crypteur après tout) :

// ...
do
{
   Console.Write("Message à chiffrer? ");
   string texte = Console.ReadLine();
   Console.Write("Clé de chiffrement? ");
   int cléChiffrement = int.Parse(Console.ReadLine());
   Crypteur crypteur = new (texte, cléChiffrement);
   Console.Write("Clé à utiliser pour déchiffer? ");
   int cléDéchiffrement = int.Parse(Console.ReadLine());
   Console.WriteLine($"Message \"déchiffré\" : {crypteur.Déchiffrer(cléDéchiffrement)}");
}
while (Poursuivre());
// ...

... notez que je suppose que vous pouvez écrire la fonction Poursuivre puisque nous l'avons fait à quelques reprises cet automne.

Si vous souhaitez transformer une string en char[], examinez les méthodes d'instance de cette string (portez attention en particulier à celles dont le nom commence par To...).

Si vous souhaitez transformer une string en majuscules, examinez les méthodes d'instance de cette string (portez attention en particulier à celles dont le nom commence par To...).

Si vous souhaitez transformer un char[] en string, sachez que string expose un constructeur paramétrique acceptant un char[] en paramètre. Par exemple, comparez les deux exemples ci-dessous (l'un des deux est plus pertinent que l'autre pour vos fins) :

// ...
char [] cs = { 'a', 'l', 'l', 'o' };
string s = cs.ToString();
Console.Write(s);
// ...
// ...
char [] cs = { 'a', 'l', 'l', 'o' };
string s = new string(cs);
Console.Write(s);
// ...

Si vous souhaitez modifier un char, alors... que diriez-vous de la simple arithmétique? Par exemple :

// ...
char c = 'A';
Console.WriteLine(c); // A
c = (char)(c + 3);
Console.WriteLine(c); // D
// ...

2 février

S03

Au menu :

  • Comment demander de l'aide
  • Comment convertir un objet en string, et pourquoi, si tabChar est un char[], l'expression tabChar.ToString() ne donne pas le même résultat que l'expression new string(tabChar) (note : nous y reviendrons)
  • Les exceptions, qui permettent entre autres de découpler la détection d'une situation atypique (une erreur, dans la grande majorité des cas) de son traitement. Plus en détail :
    • Pourquoi distinguer le moment / le lieu où un problème est détecté du moment / du lieu où il est traité (le cas échéant)
    • Trois mots clés : throw (signaler une situation exceptionelle), try (exécuter du code à risque, avec pour objectif de réagir si un problème survient), catch (gérer la situation signalée)
      • Un quatrième mot, finally, est très important (plus que catch!), mais nous y reviendrons
    • Exceptions et préconditions
    • Exceptions et postconditions
    • Exceptions et respect des invariants
    • Exceptions et constructeurs
  • Définir un type d'exception « maison »
    • Introduction à la syntaxe (qui, en C#, implique l'héritage d'implémentation, sujet que nous ne ferons qu'effleurer aujourd'hui)
  • Revisiter des exemples vus précédemment à la lueur de la matière d'aujourd'hui

S'il reste du temps :

  • Travail sur le labo 00 – Le cryptographe

Le code proposé aujourd'hui était :

  • Exemple de division entière avec levée d'exception :
bool ok = false;
do
{
   try
   {
      Console.Write("Numérateur ?  ");
      int num = int.Parse(Console.ReadLine());
      Console.Write("Dénominateur? ");
      int denom = int.Parse(Console.ReadLine());
      Console.WriteLine($"{num} / {denom} == {DivisionEntière(num, denom)}");
      ok = true;
   }
   catch(FormatException)
   {
      Console.WriteLine("Oups, je refuse (pas un entier); on réessaie");
   }
   catch (DivisionParZéroException)
   {
      Console.WriteLine("Oups, je refuse (dénominateur nul); on réessaie");
   }
}
while (!ok);

		  
// précondition : dénominateur != 0
// postcondition : retourne le quotient du numérateur par le dénominateur
// exception : la fonction ne peut pas satisfaire ses postconditions
//             (en gros, elle ne peut pas faire son travail)
static int DivisionEntière(int numérateur, int dénominateur)
{
   if(dénominateur == 0)
   {
      throw new DivisionParZéroException();
   }
   return numérateur / dénominateur;
}

class DivisionParZéroException : Exception { }
  • Exemple de classe Cercle avec invariant sur le Rayon :
Cercle c0 = LireCercle();
Afficher(c0);

static void Afficher(Cercle c)
{
   Console.WriteLine($"Centre : {c.Centre.X},{c.Centre.Y}, rayon : {c.Rayon}");
}

static Point LirePoint()
{
   Console.Write("X? ");
   int x = int.Parse(Console.ReadLine());
   Console.Write("Y? ");
   int y = int.Parse(Console.ReadLine());
   return new(x, y);
}
static Cercle LireCercle()
{
   Cercle cercle = null;
   do
   {
      Point centre = LirePoint();
      Console.Write("Rayon? ");
      float rayon = float.Parse(Console.ReadLine());
      try
      {
         cercle = new(centre, rayon);
      }
      catch(RayonInvalideException)
      {
         Console.WriteLine("Rayon invalide, on recommence");
      }
   }
   while (cercle == null);
   return cercle;
}

class Point
{
   public int X { get; init; }
   public int Y { get; init; }
   public Point()
   {
      X = 0;
      Y = 0;
   }
   public Point(int x, int y)
   {
      X = x;
      Y = y;
   }
}

class RayonInvalideException : Exception { }

// invariant : Rayon > 0
class Cercle
{
   public Point Centre { get; init; }
   private float rayon;
   public float Rayon
   {
      get => rayon;
      set
      {
         if(value <= 0)
         {
            throw new RayonInvalideException();
         }
         rayon = value;
      }
   }
   public Cercle()
   {
      Centre = new();
      Rayon = 1.0f;
   }
   public Cercle(Point centre, float rayon)
   {
      Centre = centre;
      Rayon = rayon;
   }
}
  • Exemple (simpliste) de manipulation de chaînes de caractères :
string s = "J'aime mon prof";
Console.WriteLine(s);
s = s.ToUpper();
Console.WriteLine(s);
//for(int i = 0; i != s.Length; ++i)
//{
//   Console.Write($"{s[i]} ");
//}
foreach (char c in s)
{
   Console.Write($"{c} ");
}
Console.WriteLine();
char[] tab = s.ToCharArray();
for (int i = 0; i != tab.Length; ++i)
{
   if (char.IsWhiteSpace(tab[i]))
   {
      tab[i] = '#';
   }
}
s = new string(tab);
Console.WriteLine(s);
s = tab.ToString();
Console.WriteLine(s);

7 février

S04

Au menu :

  • Dernière séance pour fignoler le labo 00. C'est le moment idéal pour :
    • Vous assurer de respecter les consignes (relisez-les avec attention!)
    • Tester les cas limites (p. ex. : chiffre une chaîne vide n'est pas une erreur... Ça donne simplement une autre chaîne vide!)
    • Si vous ne l'avez pas encore fait, vous pouvez essayer les tests proposés dans l'énoncé... Si vous ne passez pas tous les tests, c'est qu'il vous reste encore au moins un bogue (et si vous passez tous les tests, ça ne veut pas dire que le code est propre! 🙂)

Vous pouvez aussi essayer le programme de test suivant (ajouter le using requis pour que votre Crypteur soit accessible) :

/// Programme client de tests pour le laboratoire 00
/// classe Crypteur - Hiver 2023
/// 
/// par Vincent Echelard, 2013
/// révisé et amélioré par Pierre Prud'homme, février 2022
/// Ajusté et mis à jour par Patrice Roy, février 2023
/// --------------------------------------------------------------------
using System;

Console.SetWindowSize(Console.LargestWindowWidth, Console.LargestWindowHeight);
Console.SetWindowPosition(0,0);
Console.BackgroundColor = ConsoleColor.White;
Console.ForegroundColor = ConsoleColor.DarkBlue;
Console.Clear();

const int NB_TESTS = 10;
Console.Write("Nom de l'étudiant : ");
string s = Console.ReadLine();
Console.WriteLine(new string('-', 72));

int résultatDesTests = 0;
résultatDesTests += Tester("ALLO", 2, "CNNQ", "Test simple (majuscules)");
résultatDesTests += Tester("Allo", 2, "CNNQ", "Test simple (majuscule/minuscules)");
résultatDesTests += Tester("allo", 2, "CNNQ", "Test simple (minuscules)");
résultatDesTests += Tester("allo", -2, "CNNQ", "Test clef négative (chiffrement)");
résultatDesTests += Tester("ALLO", 1328, "CNNQ", "Test clef très grande (chiffrement)");
résultatDesTests += Tester("Veux-tu 1 café?", 2, "XGWZ-VW 1 ECHÉ?", "Test caractères non-alphabétique (chiffrement)");
// oui, il y a une faute dans la phrase suivante, je sais
résultatDesTests += Tester("Zorglub veux 1 café!", -1332, "FUXMRAH BKAD 1 IGLÉ!", "Test complet (chiffrement)");
résultatDesTests += Tester("Élu par cette crapule", 27, "ÉMV QBS DFUUF DSBQVMF", "Test décalage +1");
résultatDesTests += Tester("Élu par cette crapule", 26, "ÉLU PAR CETTE CRAPULE", "Test de chiffrement sans effet");
résultatDesTests += Tester("élu par cette crapule", 25, "ÉKT OZQ BDSSD BQZOTKD", "Test décalage -1");
Console.Write($"A eu : {résultatDesTests} / {NB_TESTS}");
Console.ReadKey();

static int Tester(string messageÀCrypter, int clefDeChiffrement,
                  string messageCrypté, string nomDuTest)
{
   string espacement = new string(' ', 13);
   int résultatDuTest = 0;
   Crypteur objTest = null;

   try
   {
      objTest = new Crypteur(messageÀCrypter, clefDeChiffrement);
      if (objTest.MessageSecret == messageCrypté)
      {
         résultatDuTest = 1;
         Console.WriteLine($"Réussite --> {nomDuTest} " + 
                           $"Msg à crypter: {messageÀCrypter} - " +
                           $"Clef: {clefDeChiffrement}  " + Environment.NewLine +
                           $"{espacement}Prévu:{messageCrypté} - Obtenu:{objTest.MessageSecret} " + Environment.NewLine);
      }
      else
      {
         Console.WriteLine($"ÉCHEC    --> {nomDuTest} " +
                           $"Msg à crypter: {messageÀCrypter} - " +
                           $"Clef: {clefDeChiffrement}  " + Environment.NewLine +
                           $"{espacement}Prévu:{messageCrypté} - Obtenu:{objTest.MessageSecret} " + Environment.NewLine);
      }
   }
   catch (Exception e)
   {
      Console.WriteLine($"Erreur lors du test " + nomDuTest + " : " + e.Message);
      if (objTest != null)
      {
         Console.WriteLine($"Msg à crypter : {messageÀCrypter}  - Clef : {clefDeChiffrement}   -- Msg obtenu : {objTest.MessageSecret}");
      }
   }
   return résultatDuTest;
}

Pour le reste : des exercices, des exercices, et des exercices!

Exercice 1a

Soit une classe représentant un point de l'espace 2D. Chaque instance de cette classe :

  • Comporte deux attributs « réels » : un x et un y
  • Donne accès en lecture aux valeurs de x et y par le moyen d'une propriété X et Y respectivement
  • Donne accès en écriture aux valeurs de x et de y par le moyen des propriétés X et Y sans les valider puisque toute valeur est acceptable
  • Comporte un constructeur paramétrique qui s'assure que l'instance en création est dans un état valide
  • Comporte un constructeur par défaut
  • Offre une fonction pour calculer la distance entre deux points

Travail à réaliser :

  • En précisant leur qualificateur d'accès, et en précisant s'il s'agit de membres d'instance (non-static) ou de membres de classe (static) :
    • Dressez la liste des attributs de cette classe
    • Dressez la liste des méthodes de cette classe
    • Dressez la liste des propriétés de cette classe
  • Rédigez cette classe et testez-la à l'aide du code client disponible sur le site Web du cours.

Exercice 1b

Soit une classe représentant un point du quadrant 1 de l'espace 2D. Chaque instance de cette classe :

  • Comporte deux attributs « réels » : un x et un y
  • Donne accès en lecture aux valeurs de x et y par le moyen d'une propriété X et Y respectivement
  • Donne accès en écriture aux valeurs de x et de y par le moyen des propriétés X et Y en validant que la valeur est valide pour le quadrant 1
  • Comporte un constructeur paramétrique qui s'assure que l'instance en création est dans un état valide

En cas d'erreur du programme client dans l'utilisation d'une instance de cette classe, la classe doit lever une exception :

  • Dans le cas où le code client tenterait de mettre une valeur négative en X, votre classe devra lever une exception de type CoordonnéeXInvalideException
  • Dans le cas où le code client tenterait de mettre une valeur négative en Y, votre classe devra lever une exception de type CoordonnéeYInvalideException

Travail à réaliser :

  • En précisant leur qualificateur d'accès, et en précisant s'il s'agit de membres d'instance (non-static) ou de membres de classe (static) :
    • Dressez la liste des attributs de cette classe
    • Dressez la liste des méthodes de cette classe
    • Dressez la liste des propriétés de cette classe
  • Rédigez cette classe et testez-la à l'aide du code client disponible sur le site Web du cours.

Exercice 2

Soit une classe représentant un compte bancaire (très simple). Les instances de cette classe devront offrir les services suivants :

  • Une propriété Solde permettant d'en connaître le solde
  • Une méthode Déposer permettant d'effectuer un dépôt
  • Une méthode Retirer permettant d'effectuer un retrait
  • Un constructeur par défaut
  • Un constructeur paramétrique permettant de préciser le solde initial du compte

Vous devez faire en sorte que par défaut, ce compte bancaire soit créé avec un solde à zéro; si le constructeur paramétrique est utilisé, la valeur précisée lors du processus d'instanciation doit être positive ou égale à 0.

Les méthodes permettant d'effectuer les dépôts et les retraits recevront en paramètre le montant à déposer ou à retirer selon le cas, qui doit être un entier positif strictement plus grand que 0, et ne retourneront rien.

Ce type de compte bancaire a un invariant : il ne pourra à aucun moment avoir un solde négatif. En vertu de l'encapsulation, vous devez vous assurer du respect de cet invariant, donc faire en sorte qu'en tout temps votre objet reste valide.

Dans le cas où le code client tenterait de créer une instance en y attribuant un solde négatif, votre classe devra lever une exception de type SoldeInvalideException.

Dans le cas où le code client tenterait de retirer un montant supérieur au solde du compte, ou encore de déposer un montant négatif ou nul, votre objet devra évidemment refuser l'opération et lever une exception de type OpérationInvalideException.

Travail à réaliser :

  • En précisant leur qualificateur d'accès, et en précisant s'il s'agit de membres d'instance (non-static) ou de membres de classe (static) :
    • Dressez la liste des attributs de cette classe
    • Dressez la liste des méthodes de cette classe
    • Dressez la liste des propriétés de cette classe
  • Rédigez cette classe et testez-la.

Une solution possible serait ceci : ClasseCompteBancaire.html

Exercice 3

Plus difficile, car moins directif. Vous développez un jeu impliquant des héros et des monstres. Les règles sont les suivantes :

  • Tout héros a des points de vie, représentés par un nombre entier
  • Tout héros a un nom
  • Tout monstre a des points de vie, représentés par un nombre entier
  • Tout monstre a un nom
  • Le nom d'un monstre doit être d'une longueur maximale de cinq caractères, et ne doit contenir que des consonnes
  • À la construction, un héros a un nombre de points de vie choisi aléatoirement entre 50 et 100 inclusivement
  • À la construction, un monstre a un nombre de points de vie indiqué par un paramètre passé au constructeur
  • Tout héros a une force, un entier dont la valeur est indiquée par un paramètre passé à la construction. Cette valeur doit être entre 10 et 20 inclusivement
  • Un héros peut frapper un autre personnage. Ceci blessera ce personnage en réduisant ses points de vie par une valeur pseudoaléatoire entre et de la force du héros
  • Tout monstre a une force, un entier dont la valeur est indiquée par un paramètre passé à la construction. Cette valeur doit être entre 15 et 25 inclusivement
  • Un monstre peut frapper un autre personnage. Ceci blessera ce personnage en réduisant ses points de vie par une valeur pseudoaléatoire entre et de la force du monstre
  • Un héros est un personnage
  • Un monstre est un personnage
  • Si les points de vie d'un personnage sont inférieurs ou égaux à zéro, alors ce personnage est mort, sinon il est vivant

Travail à réaliser :

  • Rédigez les classes Héros et Monstre
  • Identifiez ce qu'elles ont en commun et placez ces attributs, propriétés et méthodes dans une classe Personnage qui leur servira toutes deux de parent
  • Identifiez les attributs, propriétés, méthodes et constructeurs de chacune des classes que vous envisagez
  • Écrivez un petit programme de test représentant un combat entre un Héros et un Monstre et faisant la démonstration que votre design fonctionne, et respecte les consignes

Exercice 4

Sachant que la couleur de l'affichage de texte dans un écran console est donnée par la propriété Console.ForegroundColor, et sachant qu'il est possible de positionner le curseur où l'affichage de texte se fera à l'aide de la méthode Console.SetCursorPosition, écrivez une classe Carré telle que :

  • Un Carré a une position décrite par un point 2D
    • Note : à l'écran console, le point est le coin en haut et à gauche de l'écran, et les coordonnées en et en sont positives vers la droite et vers le bas respectivement
  • Un Carré a une longueur de côté
  • Un Carré a une couleur, de type ConsoleColor
  • Les caractéristiques d'une instance de Carré sont déterminées à la construction de cet objet
  • Dessiner un Carré dessinera ce carré à la position choisie, de la taille choisie, et à la couleur choisie

Travail à réaliser :

  • Rédigez la classe Carré
  • Identifiez les attributs, propriétés, méthodes et constructeurs de cette classe
  • Écrivez un programme de test dans lequel on trouvera un tableau de références sur des Carré et qui, en itérant à travers ce tableau, affichera ces divers objets à l'écran

9 février

S05

Je serai absent ce matin dû à une rencontre du WG21 à Issaquah. Vous pourrez suivre mes aventures sur ../../Sujets/Orthogonal/wg21-2023-Issaquah.html

14 février

S06

Au menu :

  • Quelques exercices choisis de S04
    • Nous avons investi principalement du temps sur l'exercice 3 (une variante du code vu en classe est disponible ici; notez qu'elle ne comprend pas la méthode Frapper, pas plus que le code de test)
  • L'héritage d'implémentation (dans le respect des limites de C#), effleuré seulement, par lequel une classe peut être une spécialisation d'une autre
    • Relation entre parent et enfant
    • Impact des qualifications private et public
    • Héritage et construction
    • Le mot clé base (introduction)

16 février

S07

Au menu :

  • Finir l'exercice 3 de S04
    • Le problème de la méthode Frapper
  • Parler de soi : le mot clé this
    • Constructeurs de délégation
  • Discussion de quelques relations entre classes
    • Héritage d'implémentation
    • Composition
    • Agrégation
    • Association
    • Sens de chacun
  • Quelle relation privilégier quand plusieurs sont possibles
    • Idée de couplage
    • Idée de cohésion
  • Faire l'exercice 4 de S04

Le code auquel nous sommes arrivé(e)s pour l'exercice 3 de la séance S04 est disponible ici

Le code auquel nous sommes arrivé(e)s pour l'exercice 4 de la séance S04 est disponible ici

Pour les éléments de vocabulaire quant aux relations, informellement, ce que nous avons relevé est :

  • Héritage d'implémentation : verbe être
    • Un Héros est un Personnage
    • Les membres publics (et protégés) du parent (Personnage) font partie de l'enfant (Héros)
    • En C#, un enfant parle de sa partie parent avec le mot base
    • On peut traiter un enfant comme un cas particulier de son parent (ici : si une fonction prend un Personnage en paramètre, je peux lui passer un Héros)
    • Quand on construit un enfant, il faut d'abord construire la partie parent
  • Composition : verbe avoir
    • Un objet contient d'autres objets
    • Une voiture contient un moteur (mettons)
    • Un objet est composé d'autres objets
    • L'objet contenu a une vie délimitée par l'objet qui le contient
  • Agrégation : verbe avoir / utiliser
    • Un peu comme la composition
    • L'objet « contenu » a une vie qui peut chevaucher celle de l'objet qui le « contient »
    • Pensez à des pneus dans une voiture
  • Association :
    • Deux objets se connaissent, et peuvent se parler
  • On veut : faible couplage, forte cohésion
    • Quand on a des choix, on vise les relations au plus faible couplage possible
    • Couplage des relations ci-dessus, du plus fort au plus faible :
      • Héritage d'implémentation
      • Agrégation / composition
      • Association
  • Couplage
    • Essayer d'isoler les changements pour qu'ils aient un impact le plus local possible
    • Plus les trucs sont locaux, mieux c'est (p.ex. : variable locale)
    • Plus les trucs sont privés, mieux c'est
  • Cohésion
    • « Les trucs qui vont ensemble... vont ensemble »

Pour vous faire pratiquer...

  • Petit quiz de vocabulaire. Soit le code ci-dessous :
using System;

// ... programme principal (omis par simplicité)


class ContientBlancsException : Exception { }
class MotVideException : Exception { }
class Mot
{
   private string valeur;
   public string Valeur
   {
      get => valeur;
      private set
      {
         if (value == null || value.Length == 0)
            throw new MotVideException();
         if (Contient(value.ToCharArray(), ' '))
            throw new ContientBlancsException();
         valeur = value.ToLower();
      }
   }
   public int Longueur => Valeur.Length;
   public Mot(string valeur)
   {
      Valeur = valeur;
   }
   private static char[] ObtenirVoyelles() => new char[] { 'a', 'e', 'i', 'o', 'u', 'y' };
   public int NbVoyelles => CompterOccurrences(Valeur, ObtenirVoyelles());
   public int NbConsonnes => Longueur - NbVoyelles;
   private static bool Contient(char [] cs, char c)
   {
      for(int i = 0; i != cs.Length; ++i)
         if (cs[i] == c)
            return true;
      return false;
   }
   private static int CompterOccurrences(string chaîne, char [] caractères)
   {
      int n = 0;
      foreach(char c in chaîne)
         if (Contient(caractères, c))
            ++n;
      return n;
   }
   private static int TrouverPremièreDifférence(string s0, string s1)
   {
      int plusPetit = Math.Min(s0.Length, s1.Length);
      int i = 0;
      while (i != plusPetit && s0[i] == s1[i])
      {
         ++i;
      }
      return i;
   }
   public bool Précède(Mot autre)
   {
      int pos = TrouverPremièreDifférence(Valeur, autre.Valeur);
      return pos == Math.Min(Longueur, autre.Longueur) ? Longueur < autre.Longueur : Valeur[pos] < autre.Valeur[pos];
   }
}

Répondez aux questions suivantes :

  • Que représente une instance de la classe Mot? Soyez aussi précise ou précis que possible
  • Quelles sont les règles qui assurent la validité d'un Mot? Soyez aussi précise ou précis que possible
  • Quelles sont les méthodes d'instance de la classe Mot? (listez leurs noms seulement)
  • Quelles sont les méthodes de classe de la classe Mot? (listez leurs noms seulement)
  • Quelles sont les propriétés d'une instance de la classe Mot? (listez leurs noms seulement)
  • Quels sont les attributs d'une instance de la classe Mot? (listez leurs noms seulement)
  • Dans un programme principal de votre cru, créez deux instances m0 et m1 de Mot avec des chaînes de caractères valides et distinctes l'une de l'autre, puis appelez la méthode Précède correctement pour ensuite afficher laquelle de ces deux instances de Mot apparaîtrait en premier dans un dictionnaire
  • Est-ce que NbConsonnes donnerait la bonne valeur pour un Mot créé de la manière suivante : new Mot("plate-forme")? Expliquez votre réponse
  • Est-ce que NbVoyelles donnerait la bonne valeur pour un Mot créé de la manière suivante : new Mot("Yogourt") ? Expliquez votre réponse

À titre de solutionnaire pour cet exercice :

  • Une instance de la classe Mot représente une chaîne de caractères non-nulle et non-vide, ne contenant que des lettres minuscules et exempte du caractère ' '
  • Un Mot est toujours valide, car son constructeur (et ses propriétés) le garantissent. Cela dit, une string utilisée pour construire un Mot est acceptable si elle est non-nulle, non-vide et est exempte du caractère ' '. Notez que cette string peut contenir d'autres caractères que des lettres minuscules, car la transformation en minuscules est faite par l'instance de Mot elle-même
  • La classe Mot a les méthodes d'instance suivantes : Précède et (à la limite) le constructeur paramétrique

À titre de rappel, une méthode d'instance est une fonction membre non-static

  • La classe Mot a les méthodes de classe suivantes :
    • ObtenirVoyelles
    • Contient
    • CompterOccurrences
    • TrouverPremièreDifférence

À titre de rappel, une méthode de classe est une fonction membre static

  • La classe Mot a les propriétés d'instance suivantes :
    • Valeur
    • Longueur
    • NbVoyelles
    • NbConsonnes

À titre de rappel, une propriété d'instance est une pseudo-variable membre non-static offrant un contrôle d'accès en lecture (get) ou en écriture (set, init)... ou les deux

  • La classe Mot a l'attribut d'instance suivant : valeur

À titre de rappel, un attribut d'instance est variable membre non-static

  • Pour la question suivante : « Dans Main, créez deux instances m0 et m1 de Mot avec des chaînes de caractères valides et distinctes l'une de l'autre, puis appelez la méthode Précède correctement pour ensuite afficher laquelle de ces deux instances de Mot apparaîtrait en premier dans un dictionnaire », une solution possible serait :
// ...
static void Main()
{
   Mot m0 = new Mot(Console.ReadLine()); // n'entrez pas d'espace!
   Mot m1 = new Mot(Console.ReadLine()); // n'entrez pas d'espace non plus!
   if(m0.Précède(m1))
   {
      Console.WriteLine($"{m0.Valeur} précède {m1.Valeur}");
   }
   else
   {
      Console.WriteLine($"{m1.Valeur} précède {m0.Valeur}");
   }
}
  • Pour la question suivante : « Est-ce que NbConsonnes donnerait la bonne valeur pour un Mot créé de la manière suivante : new Mot("plate-forme")? », la réponse est non, car le mot contient un '-' et la propriété NbConsonnes comptera ce caractère comme une consonne (c'est un bogue qu'il faudrait corriger)
  • Pour la question suivante : « Est-ce que NbVoyelles donnerait la bonne valeur pour un Mot créé de la manière suivante : new Mot("Yogourt") ? », la réponse est oui car, même si les voyelles considérées sont toutes minuscules alors que le 'Y' de "Yogourt" est majuscule, le set de la propriété Valeur transforme la chaîne en minuscules avant de la déposer dans l'attribut valeur

21 février

S08

Au menu :

  • Retour sur le labo 00
    • Regard plus spécifique sur le découpage
    • Regard plus spécifique sur le nommage
  • Retour sur le petit quiz de vocabulaire de S07
  • Petit tour d'horizon de la classe string
  • Comparatif de vitesse avec StringBuilder
  • Quand utiliser string et quand utiliser StringBuilder
  • Profiter des membres de classe dans le code client
  • Présentation du labo 01
  • Travail sur le labo 01

23 février

S09

Votre chic prof a dû s'absenter pour des raisons familiales. Toutefois, pour ne pas que vous ne vous ennuyiez trop de lui, il vous laisse ce petit cadeau que vous voudrez peut-être utiliser dans vos tests de la classe Histogramme que vous concevrez dans le cadre de votre labo 01 :

static void AfficherBandes(Histogramme h)
{
   const int LONGUEUR_MAX = 80;
   // pourrait être une fonction
   int nbFreq = 0;
   for(int i = 0; i != Histogramme.NbIntervalles; ++i)
      nbFreq += h.GetFrequenceIntervalle(i);
   // le travail commence ici
   for (int i = 0; i != Histogramme.NbIntervalles; ++i)
   {
      int bMin = Histogramme.GetBorneMin(i),
          bMax = Histogramme.GetBorneMax(i);
      int freq = h.GetFrequenceIntervalle(i);
      Console.Write($"[{bMin,3:0},{bMax,3:0}] : ");
      var proportion = (double)h.GetFrequenceIntervalle(i) / nbFreq;
      Console.WriteLine($"{new string('#', (int)(proportion * LONGUEUR_MAX))} ({proportion * 100:F2}%)");
   }
}

Amusez-vous bien!

28 février

 

Jour de mise à niveau (cours suspendus)

2 mars

 

Jour de mise à niveau (cours suspendus)

7 mars

S10

Au menu :

  • Classes statiques
    • Petit allègement syntaxique sympathique
  • Tableaux dynamiques : le type List<T>

Petit exemple simple avec une List<int> :

using System;
using System.Collections.Generic;

// alternative : List<int> lst = new() { 3,7,11,2,5 };
List<int> lst = new();
foreach(int n in new[]{ 3, 7, 11, 2, 5 })
   lst.Add(n);
// parcourir avec foreach
foreach(int n in lst)
   Console.Write($"{n} ");
Console.WriteLine();
// parcourir avec for
for(int i = 0; i != lst.Count; ++i)
   Console.Write($"{lst[i]} ");
Console.WriteLine();
// trier les éléments en ordre croissant
lst.Sort();
// etc.
  • Tri avec comparateurs (ou : comment rendre le comparateur plus charmant)
    • Qu'est-ce qu'un three-way-compare?
    • Comment l'implémenter efficacement?
  • Si le temps le permet : introduction au polymorphisme

Le chemin que nous avons suivi fut « classique » :

  • Une Forme a un Symbole
  • Un Triangle est une Forme
  • Un Triangle peut se dessiner
  • Un Rectangle est une Forme
  • Un Rectangle peut se dessiner

Ce serait cool de pouvoir écrire une fonction Dessiner(Forme f) qui (a) fait une copie de sauvegarde de la couleur du texte à l'écran, (b) la remplace par la couleur de la Forme, (c) dessine la Forme, puis (d) remet la couleur originale pour le texte à l'écran... sauf que f.Dessiner n'existe pas.

Pour résoudre ce problème, nous avons d'abord inséré une méthode Dessiner non virtuelle dans Forme, pour se rendre compte que ça ne fait rien de pertinent.

Nous avons ensuite virtual (dans la classe Forme) et override (dans les classes Triangle et Rectangle), suite à quoi l'affichage s'est mis à dessiner des trucs colorés à l'écran, pour notre plus grand bonheur!

En bout de ligne, on arrive à quelque chose comme ce qui suit. J'ai ajouté un peu de validation (note : c'est une première étape; nous raffinerons le modèle sous peu) :

using System;

Forme [] formes = new Forme[]
{
   new Rectangle('#', 30, 10),
   new Triangle('A', 7)
};
// Polymorphisme : à partir d'une abstraction (ici : Forme), on
// appelle le service de l'objet réellement pointé (p.ex. : Triangle)
foreach(Forme f in formes)
   f.Dessiner();

class Forme
{
   public char Symbole { get; private init; }
   public Forme(char symbole)
   {
      Symbole = symbole;
   }
   // méthode virtuelle : _peut_ être spécialisée par les enfants
   public void Dessiner()
   {
      // ... quoi mettre ici? on y reviendra ...
   }
}

// invariants :
// - Hauteur strictement positive
class Triangle : Forme
{
   int hauteur;
   public int Hauteur
   {
      get => hauteur;
      private init
      {
         hauteur = value > 0 ? value : throw new ArgumentException();
      }
   }
   public Triangle(char symbole, int hauteur) : base(symbole)
   {
      Hauteur = hauteur;
   }
   // override : je fais le choix explicite de spécialiser une méthode
   // d'un parent (ici : Forme.Dessiner)
   protected override void Dessiner()
   {
      for (int ligne = o; ligne != Hauteur; ++ligne)
      {
         for (int col = 0; col <= ligne; ++col)
            Console.Write(Symbole);
         Console.WriteLine();
      }
   }
}

// invariants :
// - Largeur strictement positive
// - Hauteur strictement positive
class Rectangle : Forme
{
   int largeur;
   int hauteur;
   public int Largeur
   {
      get => largeur;
      private init
      {
         largeur = value > 0 ? value : throw new ArgumentException();
      }
   }
   public int Hauteur
   {
      get => hauteur;
      private init
      {
          hauteur = value > 0 ? value : throw new ArgumentException();
      }
   }
   public Rectangle(char symbole, int largeur, int hauteur) : base(symbole)
   {
      Largeur = largeur;
      Hauteur = hauteur;
   }
   protected override void Dessiner()
   {
      for (int ligne = 0; ligne != Hauteur; ++ligne)
      {
         for (int col = 0; col != Largeur; ++col)
            Console.Write(Symbole);
         Console.WriteLine();
      }
   }
}

Résumé des nouvelles idées de cette partie du cours :

Héritage d'implémentation (enfant est un cas particulier du parent). Bon, c'est pas nouveau d'aujourd'hui, mais...

Polymorphisme :

  • Par une abstraction (p.ex. : le parent), on appelle le service le plus spécialisé de l'objet pointé (p.ex. : enfant)
  • virtual (l'enfant peut spécialiser la méthode)
  • override (l'enfant choisit de spécialiser le service)

9 mars

S11

Au menu :

  • Présentation du labo 02
    • Ce laboratoire sera différent des précédents, au sens où il vous laissera plus de latitude que ses prédécesseurs dans la conception des classes
    • Si vous estimez que certaines des consignes auraient gagné à être exprimées différemment, notez vos raisons et nous en discuterons
      • Pour être clair : c'est un chic labo, mais j'aurais fait certaines choses autrement
      • Cela dit, considérant ce que nous savons jusqu'ici, les consignes sont tout à fait convenables, donc faites ce qui est demandé
      • En retour, j'aimerais savoir ce que vous voyez comme perfectibles dans ce travail, et je l'utiliserai moi-même comme rampe de lancement pour plusieurs idées intéressantes suivant sa remise (nous discuterons entre autres de schémas de conception)
    • Une fois que vous aurez livré votre propre version, nous aurons une discussion sur divers enjeux associés à ce travail...
  • Travail sur le labo 02

N'oubliez pas de remettre le labo 01 (au plus tard à 7 h 59 sinon la boîte de remise sera close!)

14 mars

S12

Au menu :

  • Quelques très petits éléments de notation UML :
    • Boîte décrivant une classe ou une de ses instances
    • Symbole + pour un membre public
    • Symbole - pour un membre privé
    • Nom de la classe
    • Noms des attributs
    • Signatures des méthodes
    • Soulignement pour les membres de classe
  • Visualiser la classe Histogramme du labo 02 sous forme UML
  • Visualiser les classes Coordonnée et Trajet du labo 02 sous forme UML
  • Décrivez en termes de relations OO...
    • Le lien entre un Histogramme et ce qui l'affiche
    • Le lien entre une maman fourmi et des bébés fourmis
    • Le lien entre des profs de diverses disciplines
    • Le lien entre des profs et des parents
    • Le lien entre des profs et des étudiant(e)s
    • Le lien entre le Collège Lionel-Groulx et ses étudiant(e)s
    • Le lien entre un Rectangle et une Forme
    • Le lien entre un Carré et une Forme
    • Le lien entre un Rectangle et un Carré
  • Travail sur le labo 02

16 mars

S13

Au menu :

  • Revisitons (légèrement) l'exercice 03 de la séance S04 en nous demandant quoi faire si des entités qui ne sont pas des instances de Personnage doivent pouvoir attaquer des instances de Personnage
  • Travail sur le labo 02

21 mars

S14

Au menu :

  • Approcher un problème comme celui de créer et initialiser une matrice 2D comme celle du labo 02
    • Variations sur le couplage et la capacité de généraliser
  • Introduction à la conception de structures de données
    • Concevoir un tableau dynamique de int (sorte de List<int> simplifiée)
    • Si le temps le permet, concevoir une pile de int à l'aide de ce tableau dynamique
  • Les exemples ont été mis à votre disposition :
  • S'il reste du temps, travail sur le labo 02

23 mars

S15

Au menu :

  • Travail sur le labo 02

Si vous le souhaitez, un exercice :

  • Un Immeuble est une structure rectangulaire faite d'au moins un Étage
  • Un Étage est une structure rectangulaire faite d'au moins deux Appartement
  • Un Appartement est identifié par son numéro d'étage (numéro basé 0, où 0 est le sous-sol) et sa position (basée 1, pas basée 0) dans l'étage. Ainsi, l'appartement 23 est le 3e appartement de l'étage 2
  • Un Appartement contient de 0 à 7 individus
  • Votre travail est de déterminer comment évacuer un Immeuble lors d'un incendie. Voici comment le calcul doit se faire :
    • Un individu à la fois peut sortir d'un Appartement
    • Sur chaque Étage, il y a un escalier aux deux extrémités du corridor situé entre les Appartement
    • Il peut y avoir un invididu maximum devant chaque Appartement. Pour simplifier le modèle, supposez que les portes des appartements ne soient pas en face l'une de l'autre, p.ex. :
+---+---+---+---+
| 1     | 3     |
+-\ ----+-\ ----+
#  x  x    x  x #
+----\ -+----\ -+
|     2 |     4 |
+---+---+---+---+
  • Selon ce schéma :
    • Il y a quatre Appartement, donc un espace (noté 'x') devant chaque Appartement et
    • Deux escaliers (notés '#')
    • Si un individu est devant l'appartement 3, alors il peut faire deux pas vers la gauche pour atteindre l'escalier de gauche ou un pas à droite pour atteindre l'escalier de droite
  • Pour sortir d'un Immeuble, un individu doit atteindre l'étage 1 (pas l'étage 0) et atteindre un escalier.
  • Un individu fait au maximum un pas par tour. Sortir d'un Appartement compte pour un pas. Monter ou descendre d'un étage par un escalier compte pour un pas. Pour prendre un escalier, il faut au préalable avoir atteint la case adjacente dans un corridor (p. ex. : pour un Étage de cinq appartements, il faut d'abord être soit à la case 0, soit à la case 4 de l'étage pour pouvoir prendre un escalier)
  • Sur un Étage donné, il ne peut y avoir qu'un individu au maximum dans chaque escalier
  • Votre programme doit  :
    • Modéliser cette situation, et
    • Montrer, tour par tour, l'état de la situation, jusqu'à ce que l'immeuble ait été complètement évacué.

Remise du labo 02

28 mars

S16

Au menu :

  • Examen de mi-session

30 mars

S17

Au menu :

  • Concevoir une file d'entiers, à partir d'un substrat que nous avons défini collectivement
    • Ce substrat était une liste doublement chaînée d'entiers

Le code de la classe Liste conçue en classe était :

namespace z
{
   class ListeVideException : Exception { }
   class Liste
   {
      class Noeud
      {
         public int Valeur { get; private init; }
         public Noeud Pred { get; set; } = null;
         public Noeud Succ { get; set; } = null;
         public Noeud(int val)
         {
            Valeur = val;
         }
      }
      Noeud Tête { get; set; } = null;
      Noeud Queue { get; set; } = null;
      public Liste()
      {
      }
      public bool EstVide => Tête == null;
      public int Count { get; private set; } = 0;
      public void AjouterDébut(int valeur)
      {
         Noeud p = new(valeur);
         if(EstVide)
         {
            Tête = Queue = p;
         }
         else
         {
            p.Succ = Tête;
            Tête.Pred = p;
            Tête = p;
         }
         ++Count;
      }
      public void AjouterFin(int valeur)
      {
         Noeud p = new(valeur);
         if(EstVide)
         {
            Tête = Queue = p;
         }
         else
         {
            Queue.Succ = p;
            p.Pred = Queue;
            Queue = p;
         }
         ++Count;
      }
      public int ConsulterDébut() =>
         EstVide ? throw new ListeVideException() : Tête.Valeur;
      public int ConsulterFin() =>
         EstVide ? throw new ListeVideException() : Queue.Valeur;

      public void RetirerDébut()
      {
         if (EstVide)
            throw new ListeVideException();
         Tête = Tête.Succ;
         if (Tête == null)
         {
            Queue = null;
         }
         else
         {
            Tête.Pred = null;
         }
         --Count;
      }
      public void RetirerFin()
      {
         if (EstVide)
            throw new ListeVideException();
         Queue = Queue.Pred;
         if (Queue == null)
         {
            Tête = null;
         }
         else
         {
            Queue.Succ = null;
         }
         --Count;
      }

      public bool Contient(int valeur) => Trouver(valeur) != null;
      //{
      //   for(Noeud p = Tête; p != null; p = p.Succ)
      //      if(p.Valeur == valeur)
      //         return true;
      //   return false;
      //}
      public int Compter(int valeur)
      {
         int n = 0;
         for (Noeud p = Tête; p != null; p = p.Succ)
            if (p.Valeur == valeur)
               ++n;
         return n;
      }
      // trouver le premier noeud contenant la valeur 'valeur'
      private Noeud Trouver(int valeur)
      {
         for (Noeud p = Tête; p != null; p = p.Succ)
            if (p.Valeur == valeur)
               return p;
         return null;
      }
      // supprime la première occurrence de valeur dans la liste,
      // retourne true seulement s'il y a eu suppression
      public bool Supprimer(int valeur)
      {
         Noeud p = Trouver(valeur);
         if(p == null) return false;
         if (p == Tête)
            RetirerDébut();
         else if (p == Queue)
            RetirerFin();
         else
         {
            p.Pred.Succ = p.Succ;
            p.Succ.Pred = p.Pred;
            --Count;
         }
         return true;
      }

      // TEMPORAIRE (PATCH)
      public void Afficher()
      {
         for(Noeud p = Tête; p != null; p = p.Succ)
            Console.Write($"{p.Valeur} ");
      }
   }
}

Le code de la classe File construite en classe sur les fondations du substrat Liste était :

namespace z
{
   class File
   {
      Liste Substrat { get; init; } = new();
      public bool EstVide => Substrat.EstVide;
      public void Enfiler(int valeur)
      {
         Substrat.AjouterDébut(valeur);
      }
      public int Peek() => Substrat.ConsulterFin();
      public int Défiler()
      {
         int valeur = Peek();
         Substrat.RetirerFin();
         return valeur;
      }
   }
}

Enfin, nous avons fait un premier pas (rapide, humble, mais amusant je l'espère) dans le monde de la programmation générique en transformant notre Tableau de la séance S14 en Tableau<T> :

using System;
using System.Collections.Generic;
using System.Diagnostics.SymbolStore;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace z
{
   internal class Tableau<T> where T : IEquatable<T>
   {
      T[] Éléments { get; set; }
      public int Count { get; private set; }
      public int Capacity { get => Éléments.Length; }
      public bool EstVide { get => Count == 0; }
      public bool EstPlein { get => Count == Capacity; }
      public Tableau()
      {
         Count = 0;
         Éléments = new T[0]; // ou Éléments = null
      }
      public void Add(T valeur)
      {
         if (EstPlein)
            Croître();
         Éléments[Count] = valeur;
         ++Count;
      }
      private void Croître()
      {
         int nouvelleCap = Capacity == 0? 64 : Capacity * 2;
         T[] nouvTab = new T[nouvelleCap];
         for (int i = 0; i != Count; ++i)
            nouvTab[i] = Éléments[i];
         Éléments = nouvTab;
      }
      public int Find(T valeur)
      {
         for (int i = 0; i != Count; ++i)
            if (valeur.Equals(Éléments[i]))
               return i;
         return -1;
      }
      public void RemoveAt(int indice)
      {
         for (; indice < Count - 1; ++indice)
            Éléments[indice] = Éléments[indice + 1];
         --Count;
      }
      public void Remove(T valeur)
      {
         int indice = Find(valeur);
         if(indice != -1)
            RemoveAt(indice);
      }
      public T At(int indice) =>
         léments[indice];
   }
}

En espérant que le tout vous ait diverti!

4 avril

S18

Au menu :

  • Votre chic prof doit s'absenter pour raisons personnelles, mais a déposé un chic labo 03 sur Colnet pour votre plus grand bonheur!

Prudence : mardi selon l'horaire du jeudi

6 avril

s/o

Journée pédagogique, cours suspendus

11 avril

S19

Au menu :

  • Retour sur l'examen de mi-session
  • Introduction aux algorithmes génériques
    • Trouver
    • Compter
  • Paramétrer nos algorithmes génériques à l'aide de fonctions d'ordre supérieur
    • TrouverSi
    • CompterSi
  • Quelques exercices

Exercice 0

Écrivez une classe FilePrioritaire<T> ayant les caractéristiques suivantes :

  • Elle utilisera un List<T> (le type List de C#) comme substrat
  • Une FilePrioritaire<T> nouvellement construite sera vide
  • Elle exposera une propriété EstVide qui vaudra true seulement si elle est vide
  • Elle inclura un critère de tri déterminé à la construction
    • Note : un critère de tri sera une fonction de comparaison trilatérale, prenant en paramètre deux T retournant un int
  • Elle exposera une méthode Add acceptant en paramètre un T et l'ajoutant au substrat
  • Elle exposera une méthode Prochain retournant le premier T dans le substrat et l'en extrayant
  • Invariant : les éléments d'une FilePrioritaire<T> seront triés en fonction de son critère de tri
    • Note : utilisez la fonction Trier dans l'exemple ci-dessous pour réaliser ce tri

Code de test possible :

using System;
using System.Collections.Generic;

FilePrioritaire<int> file = new (TriDécroissant);
foreach(int n in new []{ 2, 3, 5, 7, 11 })
   file.Add(n);

while(!file.EstVide)
   Console.WriteLine(file.Prochain()); // 11 7 5 3 2

// ...
static void Trier<T>(List<T> lst, Func<T,T,int> critère) // tri à bulles, pour fins de simplicité
{
   for(int i = 0; i < lst.Count - 1; ++i)
      for(int j = i + 1; j < lst.Count; ++j)
         if(critère(lst[i], lst[j]) > 0)
            PermuterÉléments(lst, i, j);
}
static void PermuterÉléments<T>(List<T> lst, int i, int j)
{
   T elem = lst[i];
   lst[i] = lst[j];
   lst[j] = elem;
}
// ...
static int TriDécroissant(int x, int y) => y - x;
// ...

Affichage attendu :

11
7
5
3
2

Pour le plaisir : pouvez-vous faire une FilePrioritaire<Client> pour une classe Client où chaque client a un nom, un prénom et une dette, et où les clients les plus endettés sont prioritaires sur les moins endettés? Si deux clients sont aussi endettés l'un que l'autre, alors la priorité doit respecter l'ordre lexicographique (ordre croissant de nom, puis de prénom si deux noms sont équivalents).

Solution possible :

class FilePrioritaire<T>
{
   Func<T,T,int> Critère;
   List<T> Elems{ get;init; } = new();
   public FilePrioritaire(Func<T,T,int> critère)
   {
      Critère = critère;
   }
   public void Add(T elem)
   {
      Elems.Add(elem);
      Trier(Elems, Critère);
   }
   public T Prochain()
   {
      T elem = Elems[0];
      Elems.RemoveAt(0);
      return elem;
   }
   public bool EstVide => Elems.Count == 0;
}

Question : est-ce que le substrat est bien choisi? Expliquez votre réponse.

Exercice 01

Sachant que nous avons implémenté les algorithmes TrouverSi et CompterSi. Ajoutons quelques algorithmes génériques à notre banque d'outils :

EX01.0 – Écrivez l'algorithme Permuter<T>(ref T a, ref T b) qui permutera les valeurs de a et de b, de telle sorte que l'exécution du programme suivant :

int i0 = 2, i1 = 3;
Console.WriteLine($"Avant permutation : {i0}, {i1}");
Permuter(ref i0, ref i1);
Console.WriteLine($"Après permutation : {i0}, {i1}");
double d0 = 2.5, d1 = 3.5;
Console.WriteLine($"Avant permutation : {d0}, {d1}");
Permuter(ref d0, ref d1);
Console.WriteLine($"Après permutation : {d0}, {d1}");
string s0 = "allo", s1 = "toi";
Console.WriteLine($"Avant permutation : \"{s0}\", \"{s1}\"");
Permuter(ref s0, ref s1);
Console.WriteLine($"Après permutation : \"{s0}\", \"{s1}\"");

... affiche ce qui suit :

Avant permutation : 2,3
Après permutation : 3,2
Avant permutation : 2.5,3.5
Après permutation : 3.5,2.5
Avant permutation : "allo","toi"
Après permutation : "toi","allo"

EX01.1 – Écrivez l'algorithme RotaterGauche<T>(List<T> src) qui retourne une List<T> contenant un équivalent des éléments de la List<T> reçue en paramètre, mais où les éléments de src ont été décalés à gauche d'une position de manière cyclique (l'élément à la position 0 dans src est placée à la position Count-1 dans la List<T> résultante), de telle sorte que le programme suivant :

using System;
using System.Collections.Generic;
Afficher(RotaterGauche(new List<int>()));
Afficher(RotaterGauche(new List<int>(){ 2,3,5,7,11 }));

static void Afficher<T>(List<T> lst)
{
   foreach(T obj in lst)
      Console.Write($"{obj} ");
   Console.WriteLine();
}

... affiche ce qui suit (notez la première ligne qui est vide, car nous avons opéré sur... une séquence vide!) :


3 5 7 11 2 

Note : essayez d'implémenter cet algorithme sans allouer de mémoire (sans utiliser new).

EX01.2 – Écrivez l'algorithme RotaterDroite<T>(List<T> src) qui retourne une List<T> contenant un équivalent des éléments de la List<T> reçue en paramètre, mais où les éléments de src ont été décalés à droite d'une position de manière cyclique (l'élément à la position Count-1 dans src est placée à la position 0 dans la List<T> résultante), de telle sorte que le programme suivant :

using System;
using System.Collections.Generic;
Afficher(RotaterDroite(new List<int>()));
Afficher(RotaterDroite(new List<int>(){ 2,3,5,7,11 }));

static void Afficher<T>(List<T> lst)
{
   foreach(T obj in lst)
      Console.Write($"{obj} ");
   Console.WriteLine();
}

... affiche ce qui suit (notez la première ligne qui est vide, car nous avons opéré sur... une séquence vide!) :


11 2 3 5 7 

Note : essayez d'implémenter cet algorithme sans allouer de mémoire (sans utiliser new).

EX01.3 – Écrivez la fonction Concaténer<T>(List<T> lst0, List<T> lst1) qui retournera une List<T> contenant les mêmes éléments que lst0, dans l'ordre, suivis des éléments de lst1, dans l'ordre.

EX01.4 – Écrivez l'algorithme Transformer<T,U>(List<T> src, Func<T,U> fct) qui retourne une List<U> contenant un équivalent des éléments de la List<T> reçue en paramètre, mais transformés par application de la fonction fct, de telle sorte que le programme suivant :

using System;
using System.Collections.Generic;

List<int> lst0 = new List<int>(){ 2,3,5,7,11 };
List<double> lst1 = Transformer(lst0, CarréNégatif);
foreach(double x in lst1)
   Console.Write($"{x} ");
Console.WriteLine();
foreach(string s in Transformer(new List<string>(){ "j'aime", "mon", "prof" }, Exclamer))
   Console.Write($"{s} ");
Console.WriteLine();

static double CarréNégatif(int x) => -1.0 * x * x; // nom pas terrible, je sais
static string Exclamer(string s) => s.ToUpper() + "!";

... affiche ce qui suit :

-4 -9 -25 -49 -121 
J'AIME! MON! PROF!

EX01.5 – Écrivez l'algorithme Cumuler<T,U>(List<T> src, Func<U,T,U> accum, U init) qui reçoit en paramètre une List<T>, une fonction applicable à un U et à un T et retournant un U, de même qu'une valeur initiale de type U, et retourne l'accumulation, de telle sorte que le programme suivant :

using System;
using System.Collections.Generic;

Console.WriteLine($"1+2+3+4+5 == {Cumuler(new List<int>(){ 1,2,3,4,5 }, Somme, 0)}");
Console.WriteLine($"1*2*3*4*5 == {Cumuler(new List<int>(){ 1,2,3,4,5 }, Produit, 1.0)}");
Console.WriteLine($"min(2,-3,5,-7,11) == {Cumuler(new List<int>(){ 2,-3,5,-7,9 }, Minimum, int.MaxValue)}");
var mots = new List<string>() { "yo", "man", "genre" };
Console.Write("La somme des longueurs des mots (");
foreach (string s in mots)
   Console.Write($"{s} ");
Console.WriteLine($"\b) est : {Cumuler(mots, CumulLongueur, 0)}");

static int Somme(int x, int y) => x + y;
static double Produit(double x, int y) => x * y;
static int Minimum(int x, int y) => Math.Min(x, y);
static int CumulLongueur(int lg, string s) => lg + s.Length;

... affiche ce qui suit :

1+2+3+4+5 == 15
1*2*3*4*5 == 120
min(2,-3,5,-7,11) == -7
La somme des longueurs des mots (yo man genre) est : 10

EX01.6 – Écrivez la fonction FiltrerSi<T>(List<T> src, Func<T,bool> pred) qui retournera une List<T> contenant les mêmes éléments que src, dans le même ordre, à ceci près que tous les éléments respectant le prédicat pred auront été supprimés.

EX01.7 – Écrivez la fonction RemplacerSi<T>(List<T> src, Func<T,bool> pred, T post) qui retournera une List<T> contenant les mêmes éléments que src, dans l'ordre, mais dont chaque élément satisfaisant le prédicat pred aura été remplacé par post.

13 avril

S20

Au menu :

  • Retour sur le labo 02
    • Rôle d'une classe
    • Rôle d'une fonction
    • Rôle d'un attribut
    • Principe de localité
    • Dangers d'une encapsulation négligente
  • Revisiter le labo 02
  • Travail sur le labo 03

18 avril

S21

Au menu :

  • Résoudre les exercices de la séance S19
  • Revisiter les exercices de la séance S19 à l'aide d'expressions λ

Solution à EX01.0 :

static void Permuter<T>(ref T a, ref T b)
{
   T temp = a;
   a = b;
   b = temp;
}

Solution à EX01.1, version qui ne modifie pas src :

static List<T> RotaterGauche(List<T> src)
{
   List<T> dest = new();
   if(src.Count != 0)
   {
      for(int i = 1; i < src.Count; ++i)
         dest.Add(src[i]);
      dest.Add(src[0]);
   }
   return dest;
}

Solution à EX01.1, version qui modifie src :

static void PermuterÉléments<T>(List<T> lst, int i, int j)
{
   T elem = lst[i];
   lst[i] = lst[j];
   lst[j] = temp;
}
static List<T> RotaterGauche(List<T> src)
{
   for(int i = 1; i < src.Count; ++i)
      PermuterÉléments(src, i, i-1);
   return src;
}

Solution à EX01.2 : je vous laisse la faire en exercice 🙂

Solution (naïve) à EX01.3 :

static List<T> Concaténer<T>(List<T> lst0, List<T> lst1)
{
   List<T> dest = new();
   foreach(T elem in lst0)
      dest.Add(elem);
   foreach(T elem in lst1)
      dest.Add(elem);
   return dest;
}

Solution (moins naïve) à EX01.3 :

static List<T> Concaténer<T>(List<T> lst0, List<T> lst1)
{
   List<T> dest = new(lst0);
   dest.AddRange(lst1);
   return dest;
}

Solution (plus chouette et plus générale) à EX01.3 :

static List<T> Concaténer<T>(params List<T> [] lsts)
{
   List<T> dest = new();
   foreach(var lst in lstsl) dest.AddRange(lst);
   return dest;
}

Solution à EX01.4 :

static List<U> Transformer<T,U>(List<T> src, Func<T,U> fct)
{
   List<U> dest = new();
   foreach(T elem in src)
      dest.Add(fct(elem));
   return dest;
}

Solution à EX01.5 :

static U Cumuler<T,U>(List<T> src, Func<U,T,U> accum, U init)
{
   foreach(T elem in src)
      init = accum(init, elem);
   return init;
}

Solution à EX01.6 :

static List<T> FiltrerSi<T>(List<T> src, Func<T,bool> pred)
{
   List<T> dest = new();
   foreach(T elem in src)
      if(!pred(elem))
         dest.Add(elem);
   return dest;
}

Solution (naïve) à EX01.7 :

static List<T> RemplacerSi<T>(List<T> src, Func<T,bool> pred, T post)
{
   List<T> dest = new();
   foreach(T elem in src)
      if(pred(elem))
         dest.Add(post);
      else
         dest.Add(elem);
   return dest;
}

Solution (moins naïve) à EX01.7 :

static List<T> RemplacerSi<T>(List<T> src, Func<T,bool> pred, T post)
{
   List<T> dest = new();
   foreach(T elem in src)
      dest.Add(pred(elem) ? post : elem);
   return dest;
}

Solution (beaucoup moins naïve) à EX01.7 :

static List<T> RemplacerSi<T>(List<T> src, Func<T,bool> pred, T post) =>
   Transformer(src, elem => pred(elem)? post : elem);
  • Quelques interfaces standards
    • IEquatable<T>
    • IComparable<T>
  • Profiter de l'héritage d'interfaces pour exprimer des algorithmes génériques
    • Écrire Trouver
    • Écrire Filtrer
    • Écrire Remplacer
    • Écrire Conserver

Solution pour Trouver :

static int Trouver<T>(List<T> lst, T val) where T : IEquatable<T>
{
   for (int i = 0; i != lst.Count; ++i)
      if (val.Equals(lst[i]))
         return i;
   return -1;
}
  • Travail sur le laboratoire 03

20 avril

S22

Au menu : vous êtes en grève aujourd'hui...

25 avril

S23

Au menu :

  • Remise du laboratoire 03
  • Quelques interfaces standards (suite)
    • IEnumerable<T>
    • IEnumerator<T>
  • Généraliser les algorithmes génériques
    • Ce qui fait fonctionner foreach
  • Revisiter notre Liste<T> de S17
    • Que faire pour qu'une collection soit IEnumerable<T> et pour que foreach puisse s'appliquer sur elle?
      • Détail technique : le mot clé default
      • Implémentation sur notre Tableau<T> de S17
      • Conséquences de cet ajustement
  • Présentation du laboratoire 04
  • Travail sur le laboratoire 04

Le code fait aujourd'hui suit. Pour la classe Liste<T> :

using System;
using System.Collections;
using System.Collections.Generic;

namespace z
{
   class ListeVideException : Exception { }
   internal class Liste<T> : IEnumerable<T> where T : IEquatable<T>
   {
      class Noeud
      {
         public T Valeur { get; private init; }
         public Noeud Pred { get; set; } = null;
         public Noeud Succ { get; set; } = null;
         public Noeud(T val)
         {
            Valeur = val;
         }
      }
      Noeud Tête { get; set; } = null;
      Noeud Queue { get; set; } = null;
      public Liste()
      {
      }
      public bool EstVide => Tête == null;
      public int Count { get; private set; } = 0;
      public void AjouterDébut(T valeur)
      {
         Noeud p = new(valeur);
         if(EstVide)
         {
            Tête = Queue = p;
         }
         else
         {
            p.Succ = Tête;
            Tête.Pred = p;
            Tête = p;
         }
         ++Count;
      }
      public void AjouterFin(T valeur)
      {
         Noeud p = new(valeur);
         if(EstVide)
         {
            Tête = Queue = p;
         }
         else
         {
            Queue.Succ = p;
            p.Pred = Queue;
            Queue = p;
         }
         ++Count;
      }
      public T ConsulterDébut() =>
         EstVide ? throw new ListeVideException() : Tête.Valeur;
      public T ConsulterFin() =>
         EstVide ? throw new ListeVideException() : Queue.Valeur;

      public void RetirerDébut()
      {
         if (EstVide)
            throw new ListeVideException();
         Tête = Tête.Succ;
         if (Tête == null)
         {
            Queue = null;
         }
         else
         {
            Tête.Pred = null;
         }
         --Count;
      }
      public void RetirerFin()
      {
         if (EstVide)
            throw new ListeVideException();
         Queue = Queue.Pred;
         if (Queue == null)
         {
            Tête = null;
         }
         else
         {
            Queue.Succ = null;
         }
         --Count;
      }

      public bool Contient(T valeur) => Trouver(valeur) != null;
      //{
      //   for(Noeud p = Tête; p != null; p = p.Succ)
      //      if(p.Valeur == valeur)
      //         return true;
      //   return false;
      //}
      public int Compter(T valeur)
      {
         int n = 0;
         for (Noeud p = Tête; p != null; p = p.Succ)
            if (p.Valeur.Equals(valeur))
               ++n;
         return n;
      }
      // trouver le premier noeud contenant la valeur 'valeur'
      private Noeud Trouver(T valeur)
      {
         for (Noeud p = Tête; p != null; p = p.Succ)
            if (p.Equals(valeur))
               return p;
         return null;
      }
      // supprime la première occurrence de valeur dans la liste,
      // retourne true seulement s'il y a eu suppression
      public bool Supprimer(T valeur)
      {
         Noeud p = Trouver(valeur);
         if(p == null) return false;
         if (p == Tête)
            RetirerDébut();
         else if (p == Queue)
            RetirerFin();
         else
         {
            p.Pred.Succ = p.Succ;
            p.Succ.Pred = p.Pred;
            --Count;
         }
         return true;
      }

      // TEMPORAIRE (PATCH)
      public void Afficher()
      {
         for(Noeud p = Tête; p != null; p = p.Succ)
         {
            Console.Write($"{p.Valeur} ");
         }
      }

      class Énumérateur : IEnumerator<T>
      {
         Noeud Cur { get; set; }
         public Énumérateur(Noeud p)
         {
            // se placer juste avant p
            Cur = new Noeud(default);
            Cur.Succ = p;
         }
         public bool MoveNext()
         {
            // essayer d'avancer d'un élément
            // retourner true ssi ça fonctionne
            if (Cur.Succ == null) return false;
            Cur = Cur.Succ;
            return true;
         }

         public void Reset()
         {
         }

         public void Dispose()
         {
         }

         public T Current => Cur.Valeur;

         object IEnumerator.Current => Cur.Valeur;
      }

      public IEnumerator<T> GetEnumerator() =>
         new Énumérateur(Tête);

      IEnumerator IEnumerable.GetEnumerator() =>
         new Énumérateur(Tête);
   }
}

... et pour la classe Tableau<T> :

using System;
using System.Collections;
using System.Collections.Generic;

namespace z
{
   class Tableau<T> : IEnumerable<T> where T : IEquatable<T>
   {
      // indexeur
      public T this[int indice]
      {
         get => Éléments[indice];
         set { Éléments[indice] = value; } // private serait mieux
      }
      T[] Éléments { get; set; }
      public int Count { get; private set; }
      public int Capacity { get => Éléments.Length; }
      public bool EstVide { get => Count == 0; }
      public bool EstPlein { get => Count == Capacity; }
      public Tableau()
      {
         Count = 0;
         Éléments = new T[0]; // ou Éléments = null
      }
      public void Add(T valeur)
      {
         if (EstPlein)
            Croître();
         Éléments[Count] = valeur;
         ++Count;
      }
      // private int[] Croitre(int [] ancien)
      // ...
      // return nouvTab
      // 
      private void Croître()
      {
         int nouvelleCap = Capacity == 0? 64 : Capacity * 2;
         T[] nouvTab = new T[nouvelleCap];
         for (int i = 0; i != Count; ++i)
            nouvTab[i] = Éléments[i];
         Éléments = nouvTab;
      }
      public int Find(T valeur)
      {
         for (int i = 0; i != Count; ++i)
            if (valeur.Equals(Éléments[i]))
               return i;
         return -1;
      }
      public void RemoveAt(int indice)
      {
         for (; indice < Count - 1; ++indice)
            Éléments[indice] = Éléments[indice + 1];
         --Count;
      }
      public void Remove(T valeur)
      {
         int indice = Find(valeur);
         if(indice != -1)
            RemoveAt(indice);
      }
      public T At(int indice)
      {
         return Éléments[indice];
      }

      class Énumérateur : IEnumerator<T>
      {
         Tableau<T> Src { get; init; }
         int indice = -1; // initialement avant le début
         public Énumérateur(Tableau <T> src)
         {
            Src = src;
         }
         public bool MoveNext()
         {
            if (indice == Src.Count - 1) return false;
            ++indice;
            return true;
         }

         public void Reset()
         {
         }

         public void Dispose()
         {
         }

         public T Current => Src[indice];

         object IEnumerator.Current => Src[indice];
      }

      public IEnumerator<T> GetEnumerator() =>
         new Énumérateur(this);

      IEnumerator IEnumerable.GetEnumerator() =>
         new Énumérateur(this);
   }
}

27 avril

S24

Au menu :

  • Trois trucs mineurs mais utiles :
  • Élargir l'utilité de nos algorithmes génériques en les rendant applicables à tout type énumérable
  • Travail sur le laboratoire 04

2 mai

S25

Au menu :

Présentation informelle de la PFI

Pour le plaisir : une petite démo de ce que sera la PFI (vidéo gracieuseté de Maxime Barakatt, fait pour une version antérieure mais valide pour la version de cette année – pour le comportement apparent, le seul changement est que les monstres peuvent désormais manger les personnages! : demo-AS.mp4)

4 mai

s/o

Journée d'examen de français / formation générale (cours suspendus)

9 mai

S26

Au menu :

  • Fonctionnement d'un programme
    • Fonction Main (silencieuse ou pas) et ses paramètres
    • Lancer un programme qui accepte des paramètres... comme celui de la PFI
  • Petite introduction aux entrées / sorties
  • Blocs using
  • Travail sur la PFI

L'exemple d'entrée-sortie sur des flux textes sans bloc using mais avec bloc finally était :

using System;
using System.IO;

string[] mots =
{
   "J'aime", "mon", "prof"
};
StreamWriter sw = new("MotsFavoris.txt");
try
{
   foreach (string mot in mots)
      sw.WriteLine(GénérerTexte(mot));
}
finally
{
   sw.Close();
}

StreamReader sr = new("MotsFavoris.txt");
try
{
   string plusLongue = "";
   for (string s = sr.ReadLine(); s != null; s = sr.ReadLine())
      if (s.Length > plusLongue.Length)
         plusLongue = s;
   Console.WriteLine($"La ligne la plus longue est {plusLongue}");
}
finally
{
   sr.Close();
}

L'exemple d'entrée-sortie sur des flux textes avec bloc using était :

using System;
using System.IO;

string[] mots =
{
   "J'aime", "mon", "prof"
};

using (StreamWriter sw = new("MotsFavoris.txt"))
{
   foreach (string mot in mots)
      sw.WriteLine(GénérerTexte(mot));
}

using (StreamReader sr = new("MotsFavoris.txt"))
{
   string plusLongue = "";
   for (string s = sr.ReadLine(); s != null; s = sr.ReadLine())
      if (s.Length > plusLongue.Length)
         plusLongue = s;
   Console.WriteLine($"La ligne la plus longue est {plusLongue}");
}

11 mai

S27

Au menu :

  • Les sélectives :
    • switch en tant qu'énoncé
    • switch en tant qu'expression
  • Travail sur la PFI

Les exemples vus en classe sont les suivants.

Sélectives – exemple 0

while (true)
   Console.WriteLine(Identifier(Console.ReadKey(true).KeyChar));

// piark-e
static string Identifier(char c)
{
   string message;
   switch (char.ToUpper(c))
   {
      case 'A':
      case 'B':
      case 'C':
      // etc.
      case 'Z':
         message = $"'{c}' : Lettre";
         break;
      case '0':
      case '1':
      // etc.
      case '9':
         message = $"'{c}' : Chiffre";
         break;
      case '.':
      case ',':
      // etc.
      case '#':
         message = $"'{c}' : Ponctuation";
         break;
      default:
         message = $"'{c}' : Autre";
         break;
   }
   return message;
}

Sélectives – exemple 1

while (true)
   Console.WriteLine(Identifier(Console.ReadKey(true).KeyChar));

static string Identifier(char c)
{
   string message = "";
   if (char.IsDigit(c))
      message = $"'{c}' : Chiffre";
   else if (char.IsLetter(c))
      message = $"'{c}' : Lettre";
   else if (char.IsPunctuation(c))
      message = $"'{c}' : Ponctuation";
   else
      message = $"'{c}' : Autre";
   return message;
}

 Sélectives – exemple 2

while (true)
   Console.WriteLine(ExprimerDirection(LireDirection()));

static string ExprimerDirection(Direction dir)
{
   switch (dir)
   {
      case Direction.Droite: return "Vers la droite";
      case Direction.Haut: return "Vers le haut";
      case Direction.Gauche: return "Vers la gauche";
      case Direction.Bas: return "Vers le bas";
      default:
         return "Direction inconnue";
   }
}

static Direction LireDirection()
{
   switch (Console.ReadKey(true).Key)
   {
      case ConsoleKey.RightArrow: return Direction.Droite;
      case ConsoleKey.UpArrow: return Direction.Haut;
      case ConsoleKey.LeftArrow: return Direction.Gauche;
      case ConsoleKey.DownArrow: return Direction.Bas;
   }
   // peut être sous default si vous préférez
   throw new DirectionIllégaleException();
}

class DirectionIllégaleException : Exception { }
enum Direction { Droite, Haut, Gauche, Bas }

 Sélectives – exemple 3

while (true)
   Console.WriteLine(ExprimerDirection(LireDirection()));

static string ExprimerDirection(Direction dir)
{
   string[] messages = new[]
   {
      "Vers la droite",
      "Vers le haut",
      "Vers la gauche",
      "Vers le bas"
   };
   return Direction.Droite <= dir && dir <= Direction.Bas ?
      messages[(int)dir] : "Direction inconnue";
}

static Direction LireDirection()
{
   switch (Console.ReadKey(true).Key)
   {
      case ConsoleKey.RightArrow: return Direction.Droite;
      case ConsoleKey.UpArrow: return Direction.Haut;
      case ConsoleKey.LeftArrow: return Direction.Gauche;
      case ConsoleKey.DownArrow: return Direction.Bas;
   }
   // peut être sous default si vous préférez
   throw new DirectionIllégaleException();
}

class DirectionIllégaleException : Exception { }
enum Direction { Droite, Haut, Gauche, Bas }

 Sélectives – exemple 4

while (true)
   Console.WriteLine(ExprimerDirection(LireDirection()));

static string ExprimerDirection(Direction dir) =>
   dir switch
   {
      Direction.Droite => "Vers la droite",
      Direction.Haut => "Vers le haut",
      Direction.Gauche => "Vers la gauche",
      Direction.Bas => "Vers le bas",
      _ => "Direction inconnue"
   };

static Direction LireDirection() =>
   Console.ReadKey(true).Key switch
   {
      ConsoleKey.RightArrow => Direction.Droite,
      ConsoleKey.UpArrow => Direction.Haut,
      ConsoleKey.LeftArrow => Direction.Gauche,
      ConsoleKey.DownArrow => Direction.Bas,
      _ => throw new DirectionIllégaleException()
   };

class DirectionIllégaleException : Exception { }
enum Direction { Droite, Haut, Gauche, Bas }

 

 

 

16 mai

S28

Au menu :

  • Travail sur la PFI

18 mai

S29

Au menu :

  • Travail sur la PFI

23 mai

 

Examen final

Dernière journée pour remettre la PFI

Petits coups de pouces

Vous trouverez ici quelques documents, la plupart petits, qui peuvent vous donner un petit coup de pouce occasionnel.

Vous trouverez aussi des exemples de code C# dans la section Divers – C# du site, mais notez que je n'ai pas nécessairement harmonisé ces exemples (écrits pour des cours plus avancés, sous forme de survols) aux standards de programmation appliqués dans le présent cours. À lire avec prudence et discrimination, donc.

Travaux pratiques

Si vous cherchez les énoncés des travaux pratiques, vous pourrez entre autres les trouver ci-dessous (note : ce tableau est celui de H2021; je ne l'ai pas mis à jour pour H2023).

Travail Nom Rôle

Labo 00

Le Cryptographe

Travail sur l'encapsulation et la conception de classes

Labo 01

À venir

Travail sur l'héritage et le polymorphisme

Labo 02

À venir

Travail sur les interfaces, l'encapsulation et le code générique

Labo 03

À venir

Travail sur les dictionnaires, les fonctions génériques et les entrées / sorties

PFI

Production finale d'intégration

À venir

Solutionnaires et exemples

Quelques solutionnaires suivent. En espérant que ça vous aide à organiser vos idées!

Programme de test – S04 Exercice 1a

L'exercice 1a de la séance S05 porte sur une classe Point générale dont chaque instance représente un point dans n'importe quel quadrant du plan cartésien. Un programme de test pour cet exercice suit.

//---------------------------------------------------------
// Programme de test de la classe Point
// 
// Ce programme vérifie que la classe Point fait 
// correctement son travail
//
// par Pierre Prud'homme, 2013 (retouché par Patrice Roy, 2020 puis 2023)
//---------------------------------------------------------
using System;

// test du constructeur par défaut
Point pDéfaut = new ();
AfficherPoint("Test du constructeur par défaut", pDéfaut);
AfficherSéparateur();

// test du constructeur paramétrique
Point pQuadrant1 = new (1.1f, 1.1f);
Point pQuadrant2 = new (-2.2f, 2.2f);
Point pQuadrant3 = new (-3.3f, -3.3f);
Point pQuadrant4 = new (4.4f, -4.4f);

AfficherPoint("Test du constructeur paramétrique pour Q1", pQuadrant1);
AfficherSéparateur();
AfficherPoint("Test du constructeur paramétrique pour Q2", pQuadrant2);
AfficherSéparateur();
AfficherPoint("Test du constructeur paramétrique pour Q3", pQuadrant3);
AfficherSéparateur();
AfficherPoint("Test du constructeur paramétrique pour Q4", pQuadrant4);
AfficherSéparateur();

// test du mutateur de x et de y
pQuadrant1.X = 10.10f;
pQuadrant1.Y = 10.10f;
AfficherPoint("Test du mutateur de Q1", pQuadrant1);
AfficherSéparateur();

TesterDistance();


// --------------------------
// fin du programme principal
// --------------------------


static bool AssezProches(float f0, float f1) => Math.Abs(f0 - f1) <= Math.Pow(10, -6);
static void TesterDistanceEx(Point p0, Point p1, float attendu)
{
   float dist = p0.Distance(p1);
   if (AssezProches(dist, attendu))
   {
      Console.WriteLine($"Distance entre [{p0.X},{p0.Y}] et [{p1.X},{p1.Y}] environ {attendu} comme prévu");
   }
   else
   {
      Console.WriteLine($"ERREUR : distance entre [{p0.X},{p0.Y}] et [{p1.X},{p1.Y}] == {dist}, mais {attendu} est attendu");
   }
}
static void TesterDistance()
{
   TesterDistanceEx(new Point(0,0), new Point(0,0), 0);
   TesterDistanceEx(new Point(0,0), new Point(1,0), 1);
   TesterDistanceEx(new Point(0,0), new Point(0,1), 1);
   TesterDistanceEx(new Point(0,0), new Point(1,1), (float) Math.Sqrt(2));
}

static void AfficherPoint(string message, Point p)
{
   Console.WriteLine($"{message} : le point vaut [{p.X}, {p.Y}]");
}
static string CréerSéparateur(int nbCar) => new string('-', nbCar);
static void AfficherSéparateur()
{
   const int NB_CAR_LIGNE = 72;
   Console.WriteLine($"\n{CréerSéparateur(NB_CAR_LIGNE)}\n");
}

Programme de test – S04 Exercice 1b

L'exercice 1b de la séance S04 porte sur une classe PointQuadrant1 générale dont chaque instance représente un point situé dans le premier quadrant du plan cartésien. Un programme de test pour cet exercice suit.

//---------------------------------------------------------
// Programme de test de la classe PointQuadrant1
// 
// Ce programme vérifie que la classe Point fait 
// correctement son travail
//
// par Pierre Prud'homme, 2013 (retouché par Patrice Roy, 2020 puis 2023)
//---------------------------------------------------------
using System;

TesterConstructeurParamétrique();
TesterMutateurs();


// --------------------------
// fin du programme principal
// --------------------------


static void AfficherPoint(string message, PointQuadrant1 p)
{
   Console.WriteLine($"{message} :");
   Console.WriteLine($"Le point vaut [{p.X}, {p.Y}]");
}
static string CréerSéparateur(int nbCar) => new string('-', nbCar);
static void AfficherSéparateur()
{
   const int NB_CAR_LIGNE = 72;
   Console.WriteLine($"\n{CréerSéparateur(NB_CAR_LIGNE)}\n");
}

static void TesterConstructeurParamétrique()
{
   // tests du constructeur paramétrique
   TesterPoint(1.1f, 1.1f);
   TesterPoint(-2.2f, 2.2f);
   TesterPoint(-3.3f, -3.3f);         
   TesterPoint(4.4f, -4.4f);
}

static void TesterPoint(float x, float y)
{
   Console.WriteLine($"Point reçu : [{x}, {y}]");
   try
   {
      PointQuadrant1 p = new (x, y);
      AfficherPoint("Test réussi du constructeur paramétrique pour PointQuadrant1", p);
   }
   catch (CoordonnéeXInvalideException)
   {
      Console.WriteLine("Point invalide en x lors de la construction");
   }
   catch (CoordonnéeYInvalideException)
   {
      Console.WriteLine("Point invalide en y lors de la construction");
   }
   AfficherSéparateur();
}

static void TesterMutateurs()
{
   PointQuadrant1 p = new (0, 0);

   ModifierPoint(p, 11.0f, p.Y);
   ModifierPoint(p, p.X, 11.0f);
   ModifierPoint(p, 111.0f, 111.0f);
   ModifierPoint(p, -22.2f, p.Y);
   ModifierPoint(p, p.X, -22.2f);
   ModifierPoint(p, -222.0f, -222.0f);
}

static void ModifierPoint(PointQuadrant1 p, float x, float y)
{
   Console.WriteLine($"Modification du point : [{x}, {y}]");
   try
   {
      p.X = x;
      p.Y = y;
      Console.WriteLine("Test réussi de la mutation du point");
   }
   catch (CoordonnéeXInvalideException)
   {
      Console.WriteLine("Point invalide en x lors de la modification");
   }
   catch (CoordonnéeYInvalideException)
   {
      Console.WriteLine("Point invalide en y lors de la modification");
   }
   AfficherPoint("Après mutation ", p);
   AfficherSéparateur();
}

Code vu en classe à la séance S06 pour l'exercice 3 de la séance S04

Le code vu en classe suit.

Classe Algos

Notez que cette classe est en évolution alors que la session progresse, et que nous la séparerons éventuellement en plusieurs classes.

using System;
namespace z
{
   class Algos
   {
      public static bool EstEntreInclusif(int val, int min, int max) =>
         min <= val && val <= max;
      static char[] voyelles = { 'a', 'e', 'i', 'o', 'u', 'y' };
      public static bool EstDans(char c, char [] cars)
      {
         foreach(char ch in cars)
            if(ch == c)
               return true;
         return false;
      }
      public static bool EstVoyelle(char c) => EstDans(char.ToLower(c), voyelles);
   }
}

Classe Personnage

using System;
namespace z
{
   class ForceInvalideException : Exception { }
   class Personnage
   {
      int vie;
      public int Vie
      {
         get => vie;
         private set { vie = value; }
      }
      public bool EstMort => Vie <= 0;
      public bool EstVivant => !EstMort;
      string nom;
      public string Nom
      {
         get => nom;
         private init { nom = value; }
      }
      int force;
      public int Force
      {
         get => force;
         private init { force = value; }
      }
      public Personnage(string nom, int vie, int force)
      {
         Nom = nom;
         Vie = vie;
         Force = force;
      }
   }
}

Classe Héros

using System;
namespace z
{
   class Héros : Personnage
   {
      public Héros(string nom, int force)
         : base(nom, GénérerVieInitiale(), ValiderForce(force))
      {
      }
      static int GénérerVieInitiale()
      {
         const int VIE_MIN = 50,
                   VIE_MAX = 100;
         return new Random().Next(VIE_MIN, VIE_MAX + 1); // bof
      }
      const int FORCE_MIN = 10,
                FORCE_MAX = 20;
      static bool EstForceValide(int force) =>
         Algos.EstEntreInclusif(force, FORCE_MIN, FORCE_MAX);
      static int ValiderForce(int force) =>
         EstForceValide(force) ? force : throw new ForceInvalideException();
   }
}

Classe Monstre

using System;
namespace z
{
   class NomInvalideException : Exception { }
   class Monstre : Personnage
   {
      public Monstre(string nom, int vie, int force)
         : base(ValiderNom(nom), vie, ValiderForce(force))
      {
      }
      static bool ContientVoyelles(string s)
      {
         foreach (char c in s)
            if (Algos.EstVoyelle(c))
               return true;
         return false;
      }
      static bool EstNomValide(string nom) =>
         nom != null &&
         nom.Length <= 5 &&
         !ContientVoyelles(nom);
      static string ValiderNom(string nom) =>
         EstNomValide(nom) ? nom : throw new NomInvalideException();
      const int FORCE_MIN = 15,
                FORCE_MAX = 25;
      static bool EstForceValide(int force) =>
         Algos.EstEntreInclusif(force, FORCE_MIN, FORCE_MAX);
      static int ValiderForce(int force) =>
         EstForceValide(force) ? force : throw new ForceInvalideException();
   }
}

Code (complet cette fois) vu en classe à la séance S07 pour l'exercice 3 de la séance S04

Le code vu en classe suit.

Classe Algos

Notez que cette classe est en évolution alors que la session progresse, et que nous la séparerons éventuellement en plusieurs classes. Depuis la version précédente, rien n'a changé.

using System;
namespace z
{
   class Algos
   {
      public static bool EstEntreInclusif(int val, int min, int max) =>
         min <= val && val <= max;
      static char[] voyelles = { 'a', 'e', 'i', 'o', 'u', 'y' };
      public static bool EstDans(char c, char [] cars)
      {
         foreach(char ch in cars)
            if(ch == c)
               return true;
         return false;
      }
      public static bool EstVoyelle(char c) => EstDans(char.ToLower(c), voyelles);
   }
}

Classe Personnage

Depuis la version précédente, nous avons ajouté la méthode Frapper. Nous avons choisi d'offrir une fonction générale à deux paramètres qui pourra être appelée par d'autres de manière à préserver le côté privé du mutateur de Vie.

using System;
namespace z
{
   class ForceInvalideException : Exception { }
   class Personnage
   {
      int vie;
      public int Vie
      {
         get => vie;
         private set { vie = value; }
      }
      public bool EstMort => Vie <= 0;
      public bool EstVivant => !EstMort;
      public void Frapper(Personnage autre, int dégâts)
      {
         autre.Vie -= dégâts;
      }
      string nom;
      public string Nom
      {
         get => nom;
         private init { nom = value; }
      }
      int force;
      public int Force
      {
         get => force;
         private init { force = value; }
      }
      public Personnage(string nom, int vie, int force)
      {
         Nom = nom;
         Vie = vie;
         Force = force;
      }
   }
}

Classe Héros

Depuis la version précédente, nous avons ajouté la méthode Frapper. Nous avons choisi d'offrir une fonction spécialisée à un paramètre qui déléguera le travail plus général vers la méthode à deux paramètres du parent.

using System;
namespace z
{
   class Héros : Personnage
   {
      public Héros(string nom, int force)
         : base(nom, GénérerVieInitiale(), ValiderForce(force))
      {
      }
      static int GénérerVieInitiale()
      {
         const int VIE_MIN = 50,
                   VIE_MAX = 100;
         return new Random().Next(VIE_MIN, VIE_MAX + 1); // bof
      }
      const int FORCE_MIN = 10,
                FORCE_MAX = 20;
      static bool EstForceValide(int force) =>
         Algos.EstEntreInclusif(force, FORCE_MIN, FORCE_MAX);
      static int ValiderForce(int force) =>
         EstForceValide(force) ? force : throw new ForceInvalideException();
      public void Frapper(Personnage autre)
      {
         const int PCT_MIN = 50,
                   PCT_MAX = 100;
         int dégâts = Force * new Random().Next(PCT_MIN, PCT_MAX + 1) / 100;
         Frapper(autre, dégâts);
      }
   }
}

Classe Monstre

Depuis la version précédente, nous avons ajouté la méthode Frapper. Nous avons choisi d'offrir une fonction spécialisée à un paramètre qui déléguera le travail plus général vers la méthode à deux paramètres du parent.

using System;
namespace z
{
   class NomInvalideException : Exception { }
   class Monstre : Personnage
   {
      public Monstre(string nom, int vie, int force)
         : base(ValiderNom(nom), vie, ValiderForce(force))
      {
      }
      static bool ContientVoyelles(string s)
      {
         foreach (char c in s)
            if (Algos.EstVoyelle(c))
               return true;
         return false;
      }
      static bool EstNomValide(string nom) =>
         nom != null &&
         nom.Length <= 5 &&
         !ContientVoyelles(nom);
      static string ValiderNom(string nom) =>
         EstNomValide(nom) ? nom : throw new NomInvalideException();
      const int FORCE_MIN = 15,
                FORCE_MAX = 25;
      static bool EstForceValide(int force) =>
         Algos.EstEntreInclusif(force, FORCE_MIN, FORCE_MAX);
      static int ValiderForce(int force) =>
         EstForceValide(force) ? force : throw new ForceInvalideException();
      public void Frapper(Personnage autre)
      {
         const int PCT_MIN = 50,
                   PCT_MAX = 75;
         int dégâts = Force * new Random().Next(PCT_MIN, PCT_MAX + 1) / 100;
         Frapper(autre, dégâts);
      }
   }
}

Programme de test

Le code de notre programme de test suit.

using System;
Héros héros = new ("Adam", 18);
Monstre monstre = new ("GRR", 75, 20);
Tour àQui = ChoisirProtagoniste();
while(héros.EstVivant && monstre.EstVivant)
{
   Console.WriteLine($"Avant le choc, {héros.Nom} a {héros.Vie} vie");
   Console.WriteLine($"Avant le choc, {monstre.Nom} a {monstre.Vie} vie");
   if(àQui == Tour.héros)
   {
      Console.WriteLine($"{héros.Nom} frappe {monstre.Nom}");
      héros.Frapper(monstre);
      àQui = Tour.monstre;
   }
   else
   {
      Console.WriteLine($"{monstre.Nom} frappe {héros.Nom}");
      monstre.Frapper(héros);
      àQui = Tour.héros;
   }
   Console.WriteLine($"Après le choc, {héros.Nom} a {héros.Vie} vie");
   Console.WriteLine($"Après le choc, {monstre.Nom} a {monstre.Vie} vie");
   Console.WriteLine(new string('-', 60));
}
if (héros.EstMort)
   Console.WriteLine($"{héros.Nom} est mort... snif!");
if (monstre.EstMort)
   Console.WriteLine($"{monstre.Nom} est mort... ouf!");

//////////////////////////

static Tour ChoisirProtagoniste() =>
   new Random().Next() % 2 == 0? Tour.héros : Tour.monstre;
enum Tour { héros, monstre }

Code vu en classe à la séance S07 pour l'exercice 4 de la séance S04

Le code vu en classe suit.

Classe Point

using System;
namespace S04ex04
{
   class Point
   {
      int x;
      int y;
      static bool EstXValide(int x) => x >= 0;
      static bool EstYValide(int y) => y >= 0;
      public int X
      {
         get => x;
         set
         {
            x = EstXValide(value) ? value : throw new ArgumentException(); 
         }
      }
      public int Y
      {
         get => y;
         set
         {
            y = EstYValide(value) ? value : throw new ArgumentException();
         }
      }

      public Point(int x, int y)
      {
         X = x;
         Y = y;
      }
      public Point() : this(0,0)
      {
      }
   }
}

Classe Carré

using System;
namespace S04ex04
{
   class Carré
   {
      public Point Position
      {
         get; private init;
      }
      public int Côté
      {
         get; private init;
      }
      public ConsoleColor Couleur
      {
         get; private init;
      }
      public Carré(Point pos, int côté, ConsoleColor couleur)
      {
         Position = pos;
         Côté = côté;
         Couleur = couleur;
      }
      public void Dessiner()
      {
         ConsoleColor avant = Console.ForegroundColor;
         Console.ForegroundColor = Couleur;
         for (int ligne = 0; ligne != Côté; ++ligne)
         {
            for(int col = 0; col != Côté; ++col)
            {
               Console.SetCursorPosition(Position.X + col, Position.Y + ligne);
               Console.Write('#');
            }
         }
         Console.ForegroundColor = avant;
      }
   }
}

Code de test

using System;

Carré[] carrés = new Carré[]
{
   new Carré(new Point(2, 5), 5, ConsoleColor.Blue),
   new Carré(new Point(15, 8), 3, ConsoleColor.Green),
   new Carré(new Point(7, 12), 6, ConsoleColor.Yellow)
};
Random r = new ();
for(int i = 0; i != 10; ++i)
{
   Console.Clear();
   foreach (Carré c in carrés)
      c.Dessiner();
   System.Threading.Thread.Sleep(1000);
   foreach (Carré c in carrés)
   {
      try
      {
         c.Position.X += r.Next(0, 5) - 2;
         c.Position.Y += r.Next(0, 5) - 2;
      }
      catch(ArgumentException)
      {
      }
   }
}

Code vu en classe lors de la séance S13

Nous avons fait trois petites structures de données dans ce cours, soit :

Classe Tableau

Le code de la classe suit :

using System;
using System.Collections.Generic;
using System.Diagnostics.SymbolStore;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace z
{
   class Tableau
   {
      int[] Éléments { get; set; }
      public int Count { get; private set; }
      public int Capacity { get => Éléments.Length; }
      public bool EstVide { get => Count == 0; }
      public bool EstPlein { get => Count == Capacity; }
      public Tableau()
      {
         Count = 0;
         Éléments = new int[0]; // ou Éléments = null
      }
      public void Add(int valeur)
      {
         if (EstPlein)
            Croître();
         Éléments[Count] = valeur;
         ++Count;
      }
      private void Croître()
      {
         int nouvelleCap = Capacity == 0? 64 : Capacity * 2;
         int[] nouvTab = new int[nouvelleCap];
         for (int i = 0; i != Count; ++i)
            nouvTab[i] = Éléments[i];
         Éléments = nouvTab;
      }
      public int Find(int valeur)
      {
         for (int i = 0; i != Count; ++i)
            if (valeur.Equals(Éléments[i]))
               return i;
         return -1;
      }
      public void RemoveAt(int indice)
      {
         for (; indice < Count - 1; ++indice)
            Éléments[indice] = Éléments[indice + 1];
         --Count;
      }
      public void Remove(int valeur)
      {
         int indice = Find(valeur);
         if(indice != -1)
            RemoveAt(indice);
      }
      public int At(int indice)
      {
         return Éléments[indice];
      }
   }
}

Classe Pile (version reposant sur un Tableau)

Le code de la classe suit (je ne répète pas la classe Tableau par souci d'économie) :

class PileVideException : Exception { }
class Pile
{
   Tableau Substrat { get; init; }
   public Pile()
   {
      Substrat = new();
   }
   public bool EstVide { get => Substrat.EstVide; }
   public void Push(int valeur) // en français : Empiler
   {
      Substrat.Add(valeur);
   }
   public int Peek()
   {
      if (EstVide)
         throw new PileVideException();
      return Substrat.At(Substrat.Count - 1);
   }
   public int Pop()
   {
      int valeur = Peek();
      Substrat.RemoveAt(Substrat.Count - 1);
      return valeur;
   }
}

Classe Pile (version reposant sur des noeuds)

Le code de la classe suit :

class PileVideException : Exception { }
class Pile
{
   class Noeud
   {
      public int Valeur { get; init; }
      public Noeud Prédécesseur { get; set; }
      public Noeud(int valeur)
      {
         Valeur = valeur;
         Prédécesseur = null;
      }
   }
   Noeud Tête { get; set; }
   public Pile()
   {
      Tête = null;
   }
   public bool EstVide { get => Tête == null; }
   public void Push(int valeur) // en français : Empiler
   {
      Noeud p = new(valeur);
      p.Prédécesseur = Tête;
      Tête = p;
   }
   public int Peek()
   {
      if (EstVide)
         throw new PileVideException();
      return Tête.Valeur;
   }
   public int Pop()
   {
      int valeur = Peek();
      Tête = Tête.Prédécesseur;
      return valeur;
   }
}

Valid XHTML 1.0 Transitional

CSS Valide !