420KEL – Intégration de techniques nouvelles en informatique de gestion

Quelques raccourcis :

Ceci est un petit site de support pour le cours 420-KEL-LG – Intégration de techniques nouvelles en informatique de gestion. Il se construira au fil des semaines à partir de ce que nous aurons effectivement fait en classe (j'ai mon plan, mais on restera agiles de manière à être le plus adaptés possible à vos besoins et à votre réalité).

Ce cours, étant en partie intensif, en partie à distance et en partie intercalé avec votre stage, suivra un format atypique. Ceci explique que les dates effectives des séances en classe restent (au moins en partie) à déterminer, et que j'aie choisi d'identifier les séances par Sxx (pour « séance xx ») plutôt que par Txx (théorie) ou Lxx (laboratoire).

De manière générale, vos besoins guideront en partie notre démarche et nos choix de contenu. Les problèmes (techniques) que vous rencontrerez en stage pourront être discutés en classe, et nous chercherons à leur apporter des solutions à la fois élégantes et efficaces.

Certaines de nos séances seront virtuelles (les dates sur le calendrier ci-dessous seront identifiées s/o, donc sans objet). Elles seront constituées d'une mise en situation (idéalement en personne, lors de nos séances in vivo) puis de travail pratique lorsque votre horaire le permettra, alimenté par des échanges électroniques. Lorsque nous nous reverrons, nous mettrons en commun nos approches, nos questionnements, et nous présenterons la matière au prochain défi. J'ai calculé l'équivalent de 30 séances, soit 15 séances « théoriques » et 15 séances « pratiques », mais nos séances seront pour l'essentiel des hybrides. Bien que le cours soit siglé 2-2-2, nous aurons des séances de format variable, tout en essayant de nous en tenir aux 60 heures prévues (incluant le volet « à distance »).

J'aimerais vous proposer quatre volumes de programmation orientée objet (entre autres) plutôt touffus, qui pourront vous être utiles dans le cours comme dans votre carrière, mais étant donné que vous êtes déjà en stage, je comprendrais si vous préfériez ne pas vous les procurer. Ces volumes présentent des exemples et des concepts en C++ (principalement), mais aussi en C#, en Java et en VB.NET.

Pour éviter de faire imprimer un trop grand nombre de copies, je ferai circuler une feuille en classe et je n'en ferai imprimer que pour celles et ceux parmi vous qui en font la demande.

Quand je connaîtrai les identifiants pour obtenir ces volumes à la coop, je mettrai à jour les puces ci-dessous :

Je veux ces volumes

Je ne veux pas ces volumes

Alexandre Beauchamp

 

Tristan Chin

 

Félix Gravel

 

Dominic Goudreau-David

 

Jonathan Lajeunesse

 

Vanessa Lépine

 

Matthew Payeur

 
   
   
   
   

 

 

Détail des séances en classe

Date Séance Détails
8 janvier 8 h 30-11 h
S00

Cours au P-116. Au menu :

8 janvier 13 h-16 h S01

Cours au P-116. Au menu :

  • Survol de la différence entre initialisation avec parenthèses et initialisation avec accolades
  • Rôle de la Sainte-Trinité
  • Présentation de l'idiome d'affectation sécuritaire
  • Mention brève de std::locale (nous y reviendrons)
  • Réflexions sur la répartition des responsabilités entre un conteneur et son contenu (dans votre code : entre ListeEntiers et Noeud)
  • Mention brève du mot invariant (nous y reviendrons)
  • Exercice de rédaction d'une version plus efficace de ListeEntiers, représentant une liste simplement chaînée d'entiers

Dans les notes de cours :

À faire pour la séance S02, seul(e) ou en équipe de deux :

  • Améliorer la classe ListeEntiers pour que :
    • La méthode taille() soit de complexité
    • La méthode ajouter() soit de complexité
    • Le constructeur de copie et l'affectation deviennent de complexité
    • Vous assurer que les attributs d'instance que vous aurez ajouté pour y arriver soient tenus à jour de manière cohérente dans l'ensemble des méthodes de ListeEntiers
  • Vous aurez droit à un petit bonus si vous ajustez inverser() pour qu'elle n'ait plus à faire d'allocation dynamique de mémoire
  • Remettez votre code dans un seul fichier .cpp par équipe, avec les noms des équipières et des équipiers à la fois dans le nom du .cpp et dans les commentaires au début du fichier
10 janvier 8 h 30-11 h 30 S02

Cours au P-116. Au menu :

  • Discussions à propos de la représentation des paramètres culturels à l'aide d'instances de std::locale
  • Qu'est-ce qu'un invariant? (survol)
  • Qu'est-ce qu'une précondition? (survol)
  • Qu'est-ce qu'une postcondition? (survol)
  • Qu'est-ce que la complexité algorithmique? (survol)
  • Exercice de rédaction d'une version plus efficace de ListeEntiers, représentant une liste simplement chaînée d'entiers
  • Réflexion sur le problème de la méthode ajouter() de ListeEntiers, dans sa déclinaison initiale
    • discussion de solutions possibles
  • Présentation sommaire de la sémantique de mouvement
    • implémentation pour ListeEntiers
    • comparaison avec la copie
    • comparaison du rôle de la copie et du rôle du mouvement

Pour une version fonctionnelle (outre inverser(), qui reste à ajuster) de ListeEntiers suivant nos modifications, voici :

#include <algorithm>
class ListeEntiers {
   struct Noeud {
      Noeud *succ{}; // ICI
      int val;
      // DEBUT
      Noeud(int val) : val{ val } {
      }
      Noeud(const Noeud &autre) : val{ autre.val } {
      }
      // FIN
   };
   Noeud *tete{}; // ICI
   Noeud *queue{}; // <-
   int nelems{}; // <-
public:
   class ListeVide { };
   // DEBUT
   ListeEntiers() = default;
   // complexité : O(n)
   ListeEntiers(const ListeEntiers &autre) {
      for (auto p = autre.tete; p; p = p->succ)
         ajouter(p->val);
   }
   ~ListeEntiers() {
      while (!est_vide())
         extraire();
   }
   void swap(ListeEntiers &autre) {
      using std::swap;
      swap(tete, autre.tete);
      swap(queue, autre.queue); // <-
      swap(nelems, autre.nelems); // <-
   }
   ListeEntiers& operator=(const ListeEntiers &autre) {
      ListeEntiers{ autre }.swap(*this);
      return *this;
   }

   // ctor de mouvement
   // complexité : O(1)
   ListeEntiers(ListeEntiers &&autre) noexcept {
      tete = autre.tete;
      queue = autre.queue;
      nelems = autre.nelems;
      autre.tete = {};
      autre.queue = {};
      autre.nelems = {};
   }
   // affectation de mouvement
   // complexité : O(n)
   ListeEntiers& operator=(ListeEntiers &&autre) noexcept {
      while (!est_vide()) extraire();
      tete = autre.tete;
      queue = autre.queue;
      nelems = autre.nelems;
      autre.tete = {};
      autre.queue = {};
      autre.nelems = {};
      return *this;
   }

   // FIN
   bool est_vide() const noexcept {
      return tete == nullptr;
   }
   // complexité : O(1)
   void ajouter(int val) {
      if (est_vide())
         queue = tete = new Noeud{ val };
      else {
         queue->succ = new Noeud{ val };
         queue = queue->succ;
      }
      ++nelems; // <-
   }
   int extraire() {
      if (est_vide()) throw ListeVide{};
      auto p = tete;
      tete = tete->succ;
      const int val = p->val;
      delete p;
      --nelems; // <-
      if (est_vide()) queue = {}; // <-
      return val;
   }
   // complexité : O(1)
   int taille() const noexcept {
      return nelems;
   }
   void inverser() { // NOTE : ajuster queue (exercice :) )
      Noeud *nouvelle_tete = {};
      while (tete) {
         auto p = new Noeud{ *tete };
         p->succ = nouvelle_tete;
         nouvelle_tete = p;
         p = tete;
         tete = tete->succ;
         delete p;
      }
      tete = nouvelle_tete;
   }
};
#include <iostream>

ListeEntiers manipuler(ListeEntiers lst) {
   lst.ajouter(-1);
   return lst;
}

int main() {
   enum { N = 10'000 };
   ListeEntiers lst;
   for (int i = 0; i != N; ++i)
      lst = manipuler(std::move(lst));
}
  • Petit truc de débogage : la classe Noisy
  • Exercice de rédaction d'une Liste<T>, représentant une liste simplement chaînée générique, en Java ou en C# (à votre choix)
  • Peu importe que votre code de Liste<T> soit écrit en Java ou en C#, cette classe doit offrir au minimum les services suivants :
    • constructeur par défaut, qui crée une liste vide
    • mécanisme pour dupliquer une liste, donc créer une nouvelle liste distincte de l'originale mais équivalente à cette dernière
      • deux listes a et b sont équivalentes si elles ont le même nombre de noeuds, et si les noeuds de ces deux listes ont les mêmes valeurs dans le même ordre
    • mécanisme permettant d'inverser les éléments d'une liste
    • mécanisme permettant d'ajouter un élément à une extrémité de la liste
    • mécanisme permettant d'extraire un élément à l'autre extrémité de la liste
    • mécanisme permettant de connaître le nombre d'éléments dans la liste
    • mécanisme permettant de tester la liste pour savoir si elle est vide ou non
    • mécanisme pour comparer deux listes et savoir si elles sont équivalentes
    • mécanisme pour afficher les éléments d'une liste sur un flux
    • mécanisme pour vider une liste
  • Consignes d'ordre général :
    • visez à respecter les idiomes de votre langage (si vous faites du Java, faut que ça respecte les pratiques du code Java; si vous faites du C#, faut que ça respecte les pratiques du code C#)
    • utilisez les exceptions de manière judicieuse
    • évitez les fuites de ressources
  • Il faut que votre programme de test fasse au minimum les opérations suivantes à l'aide d'instances de votre type Liste<T> :
    • tester chacun des services ci-dessus
    • valider que si une liste est une copie d'une autre liste, les deux peuvent être modifiées indépendamment l'une de l'autre

La classe Liste<T> est à remettre au début de la séance S04.

10 janvier 13 h-16 h S03

Cours au P-116. Au menu :

  • Échanges en classe sur les itérateurs et sur les foncteurs, de même que sur les λ, pour voir des manières contemporaines de solutionner certains des exercices
  • Programmer par algorithmes
  • Exercices formatifs d'utilisation d'algorithmes standards
  • À la demande du groupe, survol des itérateurs pour accéder à un flux
  • Implémenter quelques algorithmes maison :
    • l'algorithme inverser(debut,fin) qui inverse l'ordre des éléments dans la séquence . Testez votre implémentation avec un tableau de float, un vector<int> et une list<string>. Votre algorithme doit fonctionner avec une séquence vide, et doit donner les bons résultats que le nombre d'éléments y soit pair ou impair. Vous avez le droit d'utiliser std::swap()
    • l'algorithme trouver(debut,fin,val) qui retourne un itérateur sur le premier élément de la séquence qui soit égal à val au sens de l'opérateur ==. Testez votre implémentation avec des itérateurs sur un vector<int> et une list<string>. Si aucun élément n'est trouvé, retournez fin
    • l'algorithme trouver_si(debut,fin,pred) qui retourne un itérateur sur le premier élément e de la séquence tel que pred(e) soit vrai. Testez votre implémentation avec des itérateurs sur un vector<int> et une list<string>. Si aucun élément n'est trouvé, retournez fin
  • Pour celles et ceux qui auront terminé, amusez-vous à implémenter :
    • l'algorithme pivoter_gauche(debut, fin) qui pivote tous les éléments de la séquence d'une position vers la gauche (donc une séquence contenant initialement contiendra en fin de parcours
    • l'algorithme pivoter_droite(debut, fin) qui pivote tous les éléments de la séquence d'une position vers la droite (donc une séquence contenant initialement contiendra en fin de parcours

Notez que ces algorithmes sont pour la plupart implémentés dans <algorithm>, mais l'idée de ces exercices est d'essayer de les implémenter vous-mêmes; lorsque vous les aurez implémentés, vous pourrez comparer votre solution avec celle livrée par le standard si le coeur vous en dit.

Dans les notes de cours :

  • Les foncteurs sont décrits dans POO – Volume 02, pp. 152-173
  • Les λ sont décrites dans POO – Volume 02, pp. 179-190
  • Les algorithmes standards sont survolés dans POO – Volume 02, pp. 87-94
15 janvier 8 h 30-11 h 30 S04

Cours au P-116. Au menu :

Remise de votre classe Liste<T>, au début de la séance S04, en format imprimé.

  • Comparatif Comparatif d'implémentations Java et C# d'une ListeEntiers
    • caractéristiques de l'implémentation Java
    • caractéristiques de l'implémentation C#
  • Survol de l'approche orientée objet, en particulier du principe de substitution de Liskov (ne dériver publiquement une classe d'une autre que si les deux classes ont des invariants mutuellement cohérents)

Retour sur les exercices proposés à la séance S03 : une solution possible pour trouver(debut,fin,val), trouver_si(debut,fin,pred) et pivoter_gauche(debut, fin) (je vous laisse pivoter_droite(debut, fin) en exercice  ).

Ensuite, quelques exercices à réaliser pour S06 :

  • Écrivez l'algorithme trouver_consecutifs(debut,fin,n) qui retournera un itérateur sur le début de la première séquence de n éléments consécutifs de même valeur dans la séquence . Cet algorithme retournera fin si aucune telle séquence n'existe, ou encore si
  • Écrivez l'algorithme plus_longue_sequence(debut,fin), qui retournera une paire (voir std::pair et std::make_pair()) dont le premier élément sera un itérateur sur le début de la plus longue séquence de valeurs consécutives trouvée dans la séquence , et le second élément sera un itérateur sur la fin de cette « sous-séquence ». Veillez à ce que la paire d'itérateurs retournée forme une séquence à demi-ouverte. Si la séquence est vide, alors retournez une paire fin,fin. Si plusieurs séquences distinctes ont la même (plus longue) longueur, alors retournez une paire d'itérateurs sur la première d'entre elles. Vous pouvez vous aider en écrivant des algorithmes auxiliaires. Vous avez le droit d'utiliser std::distance()
  • Écrivez l'algorithme plus_longue_sequence(debut,fin,pred), qui retournera une paire (voir std::pair et std::make_pair()) dont le premier élément sera un itérateur sur le début de la plus longue séquence de valeurs consécutives respectant le prédicat pred trouvée dans la séquence , et le second élément sera un itérateur sur la fin de cette « sous-séquence ». Veillez à ce que la paire d'itérateurs retournée forme une séquence à demi-ouverte. Si la séquence est vide, alors retournez une paire fin,fin. Si plusieurs séquences distinctes ont la même (plus longue) longueur, alors retournez une paire d'itérateurs sur la première d'entre elles. Vous pouvez vous aider en écrivant des algorithmes auxiliaires. Vous avez le droit d'utiliser std::distance()
  • Écrivez l'algorithme inverser_mots(s) qui retourne une chaîne de la même taille que s, mais dont le contenu est l'inverse de l'ordre des mots dans la chaîne s sans toutefois modifier l'ordre et la taille des séquences de blancs dans s. Un caractère c est un blanc si isspace(c,locale{""}). Par exemple :
    • si s est " j'aime   mon prof  ", alors la fonction retournera " prof   mon j'aime  "
    • si s est "yo", alors la fonction retournera "yo"
    • si s est "     yo man", alors la fonction retournera "     man yo"
    • si s est "yo  man", alors la fonction retournera "man  yo"
  • Écrivez l'algorithme inverser_lettres(s) qui retourne une chaîne de la même taille que s, mais dont le contenu est tel que seul l'ordre des symboles dans chacun des mots est inversé. Par exemple :
    • si s est " j'aime   mon prof  ", alors la fonction retournera " emia'j   nom forp  "
    • si s est "yo", alors la fonction retournera "oy"
    • si s est "     yo man", alors la fonction retournera "     oy nam"
    • si s est "yo  man", alors la fonction retournera "oy  nam"

De ces exercices, deux seront recueillis lors de S06 et seront notés au bulletin. Je ne sais malheureusement (!) pas lesquels au moment d'écrire ces lignes, mais je vous les communiquerai plus tard aujourd'hui.

15 janvier 13 h-16 h S05

Cours au P-116. Au menu :

  • Temps pour travailler sur les exercices donnés ce matin, avec support de votre chic prof

Au début de la séance S06, vous devrez remettre les exercices suivants :

  • La fonction inverser_mots()
  • La fonction plus_longue_sequence(debut,fin,pred)

...en format imprimé.

17 janvier 8 h 30-11 h 30 S06

Cours suspendu; le plus jeune de votre chic prof fait de la fièvre... et une vilaine pneumonie...

Remise des exercices suivants :

  • La fonction inverser_mots()
  • La fonction plus_longue_sequence(debut,fin,pred)

...en format imprimé, au début de la séance S06.

J'aimerais que vous m'envoyiez un .cpp contenant le texte de vos algorithmes pendant la semaine débutant le 21 janvier, pour que je puisse les lire d'ici la séance S08. Un fichier par équipe, indenté, signé (vos noms en commentaires dans le code) et bien testé (mais je n'ai pas besoin du code de test; à votre convenance), joint à un message Colnet, me suffira.

17 janvier 13 h-16 h S07

Cours suspendu; le plus jeune de votre chic prof fait de la fièvre... et une vilaine pneumonie...

8 février AM S08

Cours au P-116. Au menu :

Dans les notes de cours :

  • L'idiome RAII est décrit dans POO – Volume 00, Annexe 03 puis dans POO – Volume 01, pp. 206-210

Pour les intéressé(e)s, le code de la fonction distance_() « maison » va comme suit :

#include <iterator>
using namespace std;
template <class It>
   auto distance_(It debut, It fin, forward_iterator_tag) {
      typename iterator_traits<It>::difference_type n{};
      for (; debut != fin; ++debut)
         ++n;
      return n;
   }
template <class It>
   auto distance_(It debut, It fin, random_access_iterator_tag) {
      return fin - debut;
   }
template <class It>
   auto distance_(It debut, It fin) {
      return distance_(debut, fin, typename iterator_traits<It>::iterator_category{});
   }

Aussi, la classe ListeEntiers avec ébauche d'intérateur (incomplète; il manque des morceaux, mais ça donne un aperçu) allait comme suit (ajouts en caractères gras) :

#include <utility>
class ListeEntiers final {
   struct Noeud final {
      Noeud *succ{};
      int val;
      Noeud(int val) : val{ val } {
      }
      Noeud(const Noeud &n) : val{ n.val } {
      }
   };
   Noeud *tete{};
   Noeud *fin{};
   int nelems{};
public:
   //
   //
   //
   class iterator {
      Noeud * cur;
   public:
      using value_type = int;
      using reference = int&;
      using pointer = int*;
      using difference_type = std::ptrdiff_t;
      using iterator_category = std::forward_iterator_tag;
      iterator() : cur{} {
      }
      iterator(Noeud *p) : cur{} {
      }
      iterator& operator++() { // ajouter operator++(int)
         cur = cur->succ;
         return *this;
      }
      bool operator==(const iterator &autre) const {
         return cur == autre.cur; // ajouter operator!=
      }
      int& operator*() { // ajouter version const
         return cur->val;
      }
   };

   iterator begin() { return { tete }; }
   iterator end() { return { }; }
   //
   //
   //
   bool est_vide() const noexcept {
      return !tete;
   }
   int taille() const noexcept {
      return nelems;
   }
   class ListeVide { };
   void clear() {
      while (!est_vide()) extraire();
   }
   ListeEntiers() = default;
   ListeEntiers(const ListeEntiers &lst) {
      for (auto p = lst.tete; p; p = p->succ)
         ajouter(p->val);
   }
   ListeEntiers(ListeEntiers && lst)
      : tete{ lst.tete }, fin{ lst.fin }, nelems{ lst.nelems } {
      lst.tete = {};
      lst.fin = {};
      lst.nelems = {};
   }
   void swap(ListeEntiers &lst) {
      using std::swap;
      swap(tete, lst.tete);
      swap(fin, lst.fin);
      swap(nelems, lst.nelems);
   }
   ListeEntiers& operator=(const ListeEntiers &lst) {
      ListeEntiers{ lst }.swap(*this);
      return *this;
   }
   ListeEntiers& operator=(ListeEntiers && lst) {
      clear();
      tete = lst.tete;
      fin = lst.fin;
      nelems = lst.nelems;
      lst.tete = {};
      lst.fin = {};
      lst.nelems = {};
      return *this;
   }
   ~ListeEntiers() {
      clear();
   }
   void ajouter(int val) {
      auto p = new Noeud{ val };
      if (est_vide())
         tete = fin = p;
      else {
         fin->succ = p;
         fin = p;
      }
      ++nelems;
   }
   int extraire() {
      if (est_vide()) throw ListeVide{};
      auto p = tete;
      tete = tete->succ;
      int val = p->val;
      if (p == fin) fin = {};
      delete p;
      --nelems;
      return val;
   }
   // attention : version de qualité très discutable
   void inverser() {
      Noeud *nouvelleTete = {},
         *nouvelleFin = {};
      if (tete) { // cas particulier pour se charger de nouvelleFin
         auto p = new Noeud{ *tete };
         p->succ = nouvelleTete;
         nouvelleFin = nouvelleTete = p;
         p = tete;
         tete = tete->succ;
         delete p;
      }
      while (tete) {
         auto p = new Noeud{ *tete };
         p->succ = nouvelleTete;
         nouvelleTete = p;
         p = tete;
         tete = tete->succ;
         delete p;
      }
      tete = nouvelleTete;
      fin = nouvelleFin;
   }
};
15 février AM S09

Cours au P-116.  Au menu, les indirections intelligentes. En particulier :

J'ai fait un exemple d'observateur sans délégués :

using System;
using System.Collections.Generic;

interface RéactionClavier
{
   void TouchePressée(char c);
}
class AfficheurTouche : RéactionClavier
{
   public void TouchePressée(char c)
   {
      Console.WriteLine("Touche pressée: {0}", c);
   }
}
interface Signaleur
{
   void Signaler();
}
class Surveillant : RéactionClavier
{
   private Signaleur signaleur;
   private char Touche { get; set; }
   public Surveillant(Signaleur sign, char c)
   {
      if (sign == null) throw new Exception("Signaleur manquant");
      signaleur = sign;
      Touche = c.ToString().ToLower()[0];
   }
   public void TouchePressée(char c)
   {
      if (c.ToString().ToLower()[0] == Touche)
         signaleur.Signaler();
   }
}
class FinDeProgramme : Signaleur
{
   public bool Complété { get; private set; }
   public FinDeProgramme()
   {
      Complété = false;
   }
   public void Signaler()
   {
      Complété = true;
   }
}
class GestionnaireClavier
{
   private List<RéactionClavier> Elems { get; set; }
   private static GestionnaireClavier singleton = null;
   static GestionnaireClavier()
   {
      singleton = new GestionnaireClavier();
   }
   private GestionnaireClavier()
   {
      Elems = new List<RéactionClavier>();
   }
   // note : propriété static
   public static GestionnaireClavier Instance
   {
      get { return singleton; }
   }
   public void Abonner(RéactionClavier réac)
   {
      Elems.Add(réac);
   }
   public void Désabonner(RéactionClavier réac)
   {
      Elems.Remove(réac);
   }
   public void Exécuter()
   {
      var c = Console.ReadKey(true); // n'affiche pas la touche
      foreach (RéactionClavier réac in Elems)
      {
         réac.TouchePressée(c.KeyChar);
      }
   }
}
class Program
{
   static void Main()
   {
      var ges = GestionnaireClavier.Instance;
      ges.Abonner(new AfficheurTouche());
      var fdp = new FinDeProgramme();
      ges.Abonner(new Surveillant(fdp, 'q'));
      while (!fdp.Complété)
      {
         ges.Exécuter();
      }
   }
}

... que j'ai ensuite un peu modernisé :

using System;
using System.Collections.Generic;

interface RéactionClavier
{
   void TouchePressée(char c);
}
class AfficheurTouche : RéactionClavier
{
   public void TouchePressée(char c)
   {
      Console.WriteLine($"Touche pressée: {c}"); // ICI : interpolation de chaînes de caractères
   }
}
interface Signaleur
{
   void Signaler();
}
class Surveillant : RéactionClavier
{
   private Signaleur signaleur;
   private char Touche { get; set; }
   public Surveillant(Signaleur sign, char c)
   {
      signaleur = sign ?? throw new Exception("Signaleur manquant"); // ICI
      Touche = c.ToString().ToLower()[0];
   }
   public void TouchePressée(char c)
   {
      if (c.ToString().ToLower()[0] == Touche)
         signaleur.Signaler();
   }
}
class FinDeProgramme : Signaleur
{
   public bool Complété { get; private set; } = false; // ICI
   public void Signaler()
   {
      Complété = true;
   }
}
class GestionnaireClavier
{
   private List<RéactionClavier> Elems { get; set; } = new List<RéactionClavier>(); // ICI
   static GestionnaireClavier()
   {
      singleton = new GestionnaireClavier();
   }
   private GestionnaireClavier()
   {
   }
   // note : propriété static
   public static GestionnaireClavier Instance { get { return singleton; } } = null; // ICI
   public void Abonner(RéactionClavier réac)
   {
      Elems.Add(réac);
   }
   public void Désabonner(RéactionClavier réac)
   {
      Elems.Remove(réac);
   }
   public void Exécuter()
   {
      var c = Console.ReadKey(true); // n'affiche pas la touche
      foreach (var réac in Elems) // ICI
      {
         réac.TouchePressée(c.KeyChar);
      }
   }
}
class Program
{
   static void Main()
   {
      var ges = GestionnaireClavier.Instance;
      ges.Abonner(new AfficheurTouche());
      var fdp = new FinDeProgramme();
      ges.Abonner(new Surveillant(fdp, 'q'));
      while (!fdp.Complété)
      {
         ges.Exécuter();
      }
   }
}

J'ai aussi fait un exemple d'observateur avec délégués :

using System;
using System.Collections.Generic;

delegate void RappelTouchePressée(char c); // ICI

class AfficheurTouche // plus besoin d'une interface
{
   public void TouchePressée(char c)
   {
      Console.WriteLine($"Touche pressée: {c}");
   }
}
delegate void RappelSignalement(); // ICI
class Surveillant // plus besoin d'une interface
{
   private RappelSignalement signaleur;
   private char Touche { get; set; }
   public Surveillant(RappelSignalement sign, char c)
   {
      signaleur = sign ?? throw new Exception("Signaleur manquant");
      Touche = c.ToString().ToLower()[0];
   }
   public void TouchePressée(char c)
   {
      if (c.ToString().ToLower()[0] == Touche)
         signaleur(); // ICI
   }
}
class FinDeProgramme // plus besoin d'interface
{
   public bool Complété { get; private set; } = false;
   public void Signaler()
   {
      Complété = true;
   }
}
class GestionnaireClavier
{
   private List<RappelTouchePressée> Elems { get; set; } = new List<RappelTouchePressée>(); // ICI
   static GestionnaireClavier()
   {
      singleton = new GestionnaireClavier();
   }
   private GestionnaireClavier()
   {
   }
   // note : propriété static
   public static GestionnaireClavier Instance { get { return singleton; } } = null;
   public void Abonner(RappelTouchePressée réac) // ICI
   {
      Elems.Add(réac);
   }
   public void Désabonner(RappelTouchePressée réac) // ICI
   {
      Elems.Remove(réac);
   }
   public void Exécuter()
   {
      var c = Console.ReadKey(true); // n'affiche pas la touche
      foreach (var réac in Elems)
      {
         réac(c.KeyChar); // ICI
      }
   }
}
class Program
{
   static void Main()
   {
      var ges = GestionnaireClavier.Instance;
      ges.Abonner(new AfficheurTouche().TouchePressée); // ICI
      var fdp = new FinDeProgramme();
      ges.Abonner(new Surveillant(fdp, 'q').TouchePressée); // ICI
      while (!fdp.Complété)
      {
         ges.Exécuter();
      }
   }
}

Voici petit exemple simple de programme affichant à la console des identifiants légaux en C++ (les règles sont les mêmes en C# ou en Java) pris d'un fichier source nommé z.cpp :

#include <locale>
#include <iostream>
#include <vector>
#include <string>
#include <iterator>
#include <fstream>
using namespace std;
/*
   Un identifiant dans la plupart des langages débute par une lettre ou un '_', suivi par une séquence de
   zéro ou plus lettres, chiffres ou '_'. Ça donne une expression régulière comme [_a-zA-Z][_a-zA-Z0-9]+
   si on l'écrit « manuellement » (car il existe des raccourcis dans bien des implémentations d'expressions
   régulières pour ce genre de truc)
   
   Le code qui suit n'est pas optimisé du tout; je l'ai écrit pour vous inspirer
*/
//
// retourne true seulement si c peut débuter un identifiant
//
bool peut_debuter_identifiant(char c) {
   return c == '_' || isalpha(c, locale{ "" });
}
//
// retourne true seulement si c peut poursuivre un identifiant (tout symbole
// pouvant occuper une position autre que la première dans un identifiant)
//
bool peut_poursuivre_identifiant(char c) {
   return c == '_' || isalnum(c, locale{ "" });
}
int main() {
   //
   // lire tout le fichier z.cpp dans le vecteur de char nommé texte
   //
   ifstream in{ "z.cpp" };
   vector<char> texte{
      istreambuf_iterator<char>{ in },
      istreambuf_iterator<char>{}
   };
   for (auto p = begin(texte); p != end(texte); ) {
      //
      // trouver le début du prochain identifiant
      //
      while (p != end(texte) && !peut_debuter_identifiant(*p))
         ++p;
      //
      // lire l'identifiant s'il y a lieu
      //
      if (p != end(texte)) {
         string s = *p;
         for (++p; p != end(texte) && peut_poursuivre_identifiant(*p); ++p)
            s += *p;
         if (!s.empty())
            cout << "Identifiant trouve : \"" << s << "\"" << endl;
      }
   }
}
  • Dictionnaires, Hash Maps et std::map

Voici un petit exemple d'un programme qui compte le nombre d'occurrences des « mots » (au sens de « séquences de caractères délimitées par des blancs », donc pas dans un sens aussi strict que celui du travail pratique proposé aujourd'hui) et affiche chaque mot accompagné de son nombre d'occurrences (dans le désordre), en C++. Le code est très semblable en C#, et l'est à peine moins en Java :

#include <string>
#include <map>
#include <fstream>
#include <iostream>
using namespace std;
int main() {
   map<string, int> mots;
   ifstream in { "z.cpp" };
   for (string s ; in >> s ; mots[ s ]++)
      ;
   for (auto & [mot, occ] : mots)
      cout << "Le mot " << mot << " apparait " << occ << " fois" << endl;
}

En vue d'une remise à la séance S11, je vous propose le travail pratique suivant :

  • Écrivez un programme dans le langage de votre choix (Java, C#, C++, autre si vous le souhaitez et si je vous donne mon accord) qui aura pour rôle de générer une page Web contenant une version formatée d'un fichier source
  • Plus précisément, votre programme réalisera la tâche suivante :
    • prendre en paramètre au démarrage (paramètres à main() / Main()) une séquence contenant des options et des noms de fichiers, peu importe l'ordre
    • les options doivent débuter par '-' ou par '/' (supportez les deux formats)
    • les noms de fichiers doivent être des fichiers sources dans un langage de votre choix
      • je vous suggère de prendre le même langage que celui dans lequel vous développerez le programme, c'est plus amusant comme ça
    • le programme consommera le contenu du fichier source, le transformera (sans modifier l'original) et écrira le résultat de la transformation dans un fichier portant le même nom que le fichier source, mais avec extension .html
    • les transformations qui seront faites doivent dépendre des options choisies à l'appel du programme. Si aucune option n'est choisie, alors le programme doit générer un fichier identique au fichier source, outre le code html qui fera du fichiers en sortie une page Web légale
  • Un exemple de lancement de votre programme (présumant qu'il se nomme cpp2web) serait :
cpp2web -stats -couleur a.cpp b.cpp c.cpp
  • ... qui générerait les fichiers a.cpp.html, b.cpp.html et c.cpp.html de même qu'un fichier de statistiques sur le travail accompli (le nom de ce fichier est à votre choix)

Pour un exécutable « exemple », vous pouvez essayer mon cpp2web.exe mais prenez soin de placer mots_cles.txt dans le même répertoire (le programme trouve ses mots clés dans ce fichier, alors ils doivent être au même endroit)

  • L'idée est de vous amuser avec des techniques nouvelles de programmation. Conséquemment, je vous recommande de faire le travail à l'aide de ce qui suit (ou de l'équivalent dans votre langage de prédilection) :
  • Je m'attends à ce que vous supportiez au moins les options suivantes :
    • une option qui permettra de demander à ce que les mots clés du langage de programmation choisi soient mis en relief (vous pouvez utiliser du CSS et des balises <span> pour y arriver, c'est probablement l'option la plus simple)
    • une option qui permettra de tirer des statistiques sur les données traitées. Je vous propose de compter les mots clés, de compter le nombre d'occurrences de chaque mot (ne comptez pas ce qui ne comprend pas au moins un caractère alphanumérique), et de compter le nombre de nombres (les littéraux comme 0, 3.14159 ou 85). Pas besoin de traiter les suffixes comme U, UL ou f (même si vous le pouvez si vous en avez envie)
      • les statistiques devront être générées dans un fichier texte à la fin de l'exécution du programme
      • les paires mot / nombre d'occurrences devront être listées en ordre décroissant de nombre d'occurrences, puis en ordre lexicographique dans le cas où plusieurs mots apparaissent aussi souvent les uns que les autres
    • évidemment, seules les options demandées par l'usager devront être mises en application (ça fait partie du plaisir)
  • Vous pouvez ajouter des options si vous le souhaitez, mais documentez ce que vous faites
  • Peu importe ce que vous choisirez, n'oubliez pas que certains symboles importants en programmation, comme <, > ou &, jouent un rôle spécial en html et devront être remplacés par leur métacaractère équivalent (donc &lt;, &gt; et &amp; respectivement pour ces trois symboles)
  • Profitez-en pour vous amuser et pour expérimenter!

À la séance S10, il y aura cours, mais je vous laisserai aussi un peu de temps pour travailler sur ce travail pratique, et je répondrai à vos questions. Nous pourrons discuter design, technique et autres trucs qui vous turlupinent. Évidemment, ce cours étant en partie électronique, vous pouvez m'écrire quand vous le souhaitez pour discuter de ces choses. Il se peut que je répondre publiquement (donc au groupe entier) si j'estime que votre question est d'intérêt public.

Vous aurez droit à un bonus si vous traitez correctement les cas suivants :

  • Les commentaires débutant par un // et se terminant par un saut de ligne
  • Les commentaires débutant par un /* et se terminant par un */
  • Les chaînes de caractères sous la forme "abc...def" et celles sous la forme L"abc...def" ou leur équivalent dans le langage que vous aurez choisi
  • Les chaînes de caractères sous la forme R"(abc ... def)" ou leur équivalent dans le langage que vous aurez choisi (en C#, par exemple, on parle de @"abc...def")

Attention, c'est plus croustillant, car il y aura des cas comme /*"allo" int i=3 */, "int /* " ou /* // yeah */ qui seront des tenir-compte, alors c'est un petit bonus par cas traité, et ne vous lancez là-dedans que si le programme de base fonctionne

Je suis en retard dans la correction, mais pour une solution possible à certains exercices (ceux qui devaient être remis la séance S06), voir ceci :

#include <algorithm>
#include <utility>
#include <ostream>
#include <string>
#include <vector>
#include <locale>
#include <tuple>
#include <numeric>


// outils

template <class It, class Pred>
std::pair<It, It> consecutifs(It debut, It fin, Pred pred) {
   return { debut, std::find_if(debut, fin, pred) };
}


template <class It>
std::pair<It, It> consecutifs(It debut, It fin) {
   if (debut == fin) return { debut, fin };
   return consecutifs(debut, fin, [x = *debut](auto val) { return val != x; });
}


template<class Pred>
auto negation_logique(Pred pred) {
   return [=](auto arg) { return !pred(arg);  };
}


auto est_blanc(const std::locale &loc) {
   return [&loc](char c) { return std::isspace(c, loc); };
}


template <class It>
void afficher_sequence(It debut, It fin, std::ostream &os) {
   using namespace std;
   if (debut == fin) return;
   os << *debut;
   for_each(next(debut), fin, [&os](auto val) { os << ' ' << val; });
}


//
// precondition: premier.size() >= second.size()
//
template <class T, class R = T>
R fusionner_alternance(T premier, T second) {
   using namespace std;
   R res;
   for (auto p = begin(premier), q = begin(second); p != end(premier); ++p) {
      res.insert(end(res), *p);
      if (q != end(second)) {
         res.insert(end(res), *q);
         ++q;
      }
   }
   return res;
}



// algos


template <class It>
It trouver_consecutifs(It debut, It fin, int n) {
   using namespace std;
   if (debut == fin || n <= 0) return fin;
   for (auto p = consecutifs(debut, fin); p.second != fin; debut = p.second, p = consecutifs(debut, fin))
      if (distance(p.first, p.second) >= n)
         return p.first;
   return fin;
}


template <class It>
std::pair<It, It> plus_longue_sequence(It debut, It fin) {
   using namespace std;
   auto res = pair{ fin, fin };
   for (auto p = consecutifs(debut, fin); p.second != fin; debut = p.second, p = consecutifs(debut, fin))
      if (distance(p.first, p.second) >= distance(res.first, res.second))
         res = p;
   return res;
}


template <class It, class Pred>
std::pair<It, It> plus_longue_sequence(It debut, It fin, Pred pred) {
   using namespace std;
   auto res = pair{ fin, fin };
   auto p = consecutifs(debut, fin, pred);
   for (; debut = p.second, p = consecutifs(debut, fin, negation_logique(pred)), p.second != fin;
        debut = p.second, p = consecutifs(debut, fin, pred))
      if (distance(p.first, p.second) >= distance(res.first, res.second))
         res = p;
   if (distance(p.first, p.second) >= distance(res.first, res.second))
      res = p;
   return res;
}


template <class It, class Pred>
auto separer_sous_sequences(It debut, It fin, Pred pred) {
   using namespace std;
   vector<pair<It, It>> oui, non;
   for (bool use_neg = pred(*debut); debut != fin; use_neg = !use_neg)
      if (use_neg) {
         auto [d,f] = consecutifs(debut, fin, negation_logique(pred));
         oui.emplace_back(debut, f);
         debut = f;
      } else {
         auto [d,f] = consecutifs(debut, fin, pred);
         non.emplace_back(debut, f);
         debut = f;
      }
      return pair{ oui, non };
}

std::string inverser_mots(std::string s) {
   using namespace std;
   using It = string::iterator;
   if (s.empty()) return s;
   const auto &loc = locale{ "" };
   auto [blancs, mots] = separer_sous_sequences(begin(s), end(s), est_blanc(loc));
   reverse(begin(mots), end(mots));
   auto v = isspace(s.front(), loc) ?
      fusionner_alternance(std::move(blancs), std::move(mots)) :
      fusionner_alternance(std::move(mots), std::move(blancs));
   return accumulate(begin(v), end(v), string{}, [](const string &s, auto &&p) {
      return s + string(p.first, p.second);
   });
}

std::string inverser_lettres(std::string s) {
   using namespace std;
   using It = string::iterator;
   if (s.empty()) return s;
   const auto &loc = locale{ "" };
   auto[blancs, mots] = separer_sous_sequences(begin(s), end(s), est_blanc(loc));
   for (auto & p : mots)
      reverse(p.first, p.second);
   auto v = isspace(s.front(), loc) ?
      fusionner_alternance(std::move(blancs), std::move(mots)) :
      fusionner_alternance(std::move(mots), std::move(blancs));
   return accumulate(begin(v), end(v), string{}, [](const string &s, auto &&p) {
      return s + string(p.first, p.second);
   });
}



// tests

template <class It>
void tester_trouver_consecutifs(It debut, It fin, int n, std::ostream &os) {
   using namespace std;
   afficher_sequence(debut, fin, os);
   os << '\n';
   if (auto pos = trouver_consecutifs(debut, fin, n); pos == fin)
      os << "Pas de sous-sequence de longueur " << n << " dans cette sequence\n" << endl;
   else
      os << "La 1re sous-sequence de longueur " << n << " dans cette sequence debute a la position "
         << distance(debut, pos) << '\n' << endl;
}

template <class It>
void tester_plus_longue_sequence(It debut, It fin, std::ostream &os) {
   using namespace std;
   if (auto res = plus_longue_sequence(debut, fin);  res.first == res.second)
      os << "La sequence est vide\n";
   else {
      os << "La plus longue sequence est de longueur " << distance(res.first, res.second)
         << " et contient ";
      afficher_sequence(res.first, res.second, os);
      os << '\n';
   }
   os << endl;
}

template <class It, class Pred>
void tester_plus_longue_sequence
(It debut, It fin, Pred pred, const std::string &presentation_predicat, std::ostream &os) {
   using namespace std;
   if (auto res = plus_longue_sequence(debut, fin, pred); res.first == res.second)
      os << "La sequence est vide\n";
   else {
      os << "La plus longue sequence de " << presentation_predicat << " est de longueur "
         << distance(res.first, res.second) << " et contient ";
      afficher_sequence(res.first, res.second, os);
      os << '\n';
   }
   os << endl;
}

template <class It>
void tester_inverser_mots(It debut, It fin, std::ostream &os) {
   using namespace std;
   for_each(debut, fin, [&os](const string &s) {
      os << "Avant : \"" << s << "\"\nApres : \"" << inverser_mots(s) << "\"\n" << endl;
   });
}

template <class It>
void tester_inverser_lettres(It debut, It fin, std::ostream &os) {
   using namespace std;
   for_each(debut, fin, [&os](const string &s) {
      os << "Avant : \"" << s << "\"\nApres : \"" << inverser_lettres(s) << "\"\n" << endl;
   });
}


#include <iostream>
int main() {
   using namespace std;
   int vals[] { 1, 2, 2, 2, 3, 3, 4, 4, 4, 4, 5, 6, 6 };
   for (int i = 0; i < 6; ++i)
      tester_trouver_consecutifs(begin(vals), end(vals), i, cout);
   tester_plus_longue_sequence(begin(vals), end(vals), cout);
   tester_plus_longue_sequence(
      begin(vals), end(vals), [](int n) {
         return n % 2 != 0;
      }, "entiers impairs", cout
   );
   string tests[] {
      "   j'aime mon   prof! ",
      "j'aime mon prof!",
      "",
      "j'aime mon   prof! ",
      "   j'aime mon   prof!",
      "x"
   };
   tester_inverser_mots(begin(tests), end(tests), cout);
   tester_inverser_lettres(begin(tests), end(tests), cout);
}
5 avril AM S10

Cours au P-116. Au menu :

12 avril AM S11

Cours au ?-???. Au menu :

(à déterminer)

26 avril AM S12

Cours au ?-???. Au menu :

(à déterminer)

3 mai AM S13

Cours au ?-???. Au menu :

(à déterminer)

6 mai AM S14

Cours au ?-???. Au menu :

(à déterminer)

6 mai PM S15

Cours au ?-???. Au menu :

(à déterminer)

7 mai PM S16

Cours au ?-???. Au menu :

(à déterminer)

8 mai AM S17

Cours au ?-???. Au menu :

(à déterminer)

13 mai AM S18

Cours au ?-???. Au menu :

(à déterminer)

13 mai PM S19

Cours au ?-???. Au menu :

(à déterminer)

14 mai AM S20

Cours au ?-???. Au menu :

(à déterminer)

14 mai PM S21

Cours au ?-???. Au menu :

(à déterminer)

16 mai AM S22

Cours au ?-???. Au menu :

(à déterminer)

16 mai PM S23

Cours au ?-???. Au menu :

  • Chic examen final plein d'amour!

Documents sous forme électronique

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

Exercices de révision  : cliquez sur cette cible pour quelques exercices de révision.

Petits coups de pouces

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

Comment accéder à du code .NET à partir d'un client C++ natif

Introduction aux templates

Introduction aux foncteurs

Introduction aux conteneurs et aux itérateurs

Programmation générique appliquée

Dans la plupart des cas, vous trouverez de l'aide précieuse dans les sections Divers – C++, Au secours Pat et Trucs scouts du site.

Solutionnaires

Les solutionnaires aux exercices auxquels vous avez été confrontés et qui ont déjà été remis sur publiés ci-dessous

Cliquez sur cette cible si vous souhaitez un solutionnaire pour les exercices de révision

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.


Valid XHTML 1.0 Transitional

CSS Valide !