Les diverses sections de cette page (en fonction desquelles vous trouverez
quelques liens dans l'encadré à droite) vous mèneront elles-aussi
sur des pistes qui vous permettront d'explorer un peu plus par vous-mêmes,
de valider vos acquis et d'enrichir votre apprentissage.
Puisque nous serons en quelque sorte laboratoire à la fois pour les séances théoriques et
les séances de laboratoire, j'ai fait le choix de construire le cours sous forme de
30 séances (de S00 à
S29) plutôt que sous forme de 15 séances
théoriques et 15 séances de laboratoire. Le dosage
prévu de temps en théorie et de temps en laboratoire (soit environ
moitié-moitié) devrait être respecté.
L'horaire de cette session pour ce cours est d'une grande complexité, du
moins pour votre humble serviteur, et ce pour deux raisons :
Pour cette raison, je vais essayer une nouvelle manière de planifier la
session, séparant les séances et les dates (jointes aux groupes). Prenez soin
d'apprendre votre numéro de groupe pour bien être en mesure de vous repérer.
| Séance |
Détails |
|
S00 |
Au menu :
- Présentation du cours et du plan
de cours
- Les outils dont nous aurons besoin :
- Nous utiliserons
C# 14.0
- Nous ferons des projets .NET 10
- Assurez-vous que votre version de Visual Studio soit à jour!
La raison pour laquelle il est important de mettre
vos outils de développement à jour n'est pas
vraiment le recours à de nouveaux mécanismes du langage ou de la plateforme, mais bien
parce que nous écrirons des programmes faits de plusieurs composants
compilés qui devront parfois être de la même technologie.
Conséquemment,
si vos outils ne sont pas à jour, vous ne pourrez pas faire certains de
vos travaux pratiques.
- Échange sur le contenu du cours, les modalités, les attentes
- Il est possible que nous tenions une séance « en ligne »
pour « roder la mécanique » au cas où la pandémie mettrait du
sable dans l'engrenage (personne ne le souhaite, mais mieux vaut être
prudentes et prudents)
- Réponses aux questions de la classe :
- Présentation d'une petite activité formative :
420KBB--Consignes-activite-revision.pdf
Si vous souhaitez le code du programme principal à partir duquel vous
devrez démarrer, vous pouvez le prendre du fichier PDF
ou encore le
prendre ci-dessous (parfois, copier / coller d'un PDF,
ça donne des résultats suspects) :
// ...
List<Orque> orques = new();
try
{
for(string s = Console.ReadLine(); "" != s; s = Console.ReadLine())
{
orques.Add(new Orque(s));
Console.WriteLine($"Orque créé : {orques[orques.Count - 1].Nom}");
}
}
catch(NomInvalideException e)
{
Console.WriteLine(e.Message);
}
if(Trier(ref orques, out int nbPermutations))
Console.WriteLine("Les orques ont été entrés en ordre alphabétique");
else
Console.WriteLine($"Trier les orques a nécessité {nbPermutations} permutations");
Console.Write("La tribu d'orques est :");
foreach (Orque orque in orques)
Console.Write($" {orque.Nom}");
Après avoir pris un peu de temps pour se « chamailler » avec ce petit
défi de remise en forme, je vous ai proposé un peu de code pour vous aider
à redémarrer vos instincts de programmeuse et de programmeur. L'accent a
été mis sur l'écriture de code simple :
- Écrire des fonctions
- Viser « une vocation par fonction »
- Essayer d'écrire des fonctions qui se limitent à une instruction
quand cela s'avère possible
- Permission accordée d'omettre les accolades d'une structure de
contrôle (for, while,
if, else) quand le bloc se limite à
un seul énoncé (note : ceci est une permission, pas une obligation!)
- Permission accordée d'avoir deux points de sortie dans une
fonction (comme dans Algos.EstVoyelle par
exemple) mais seulement si c'est pertinent et si cela permet d'éviter
du travail inutile (la paresse et le manque de rigueur seront
pénalisés comme à l'habitude)
- ... et permission accordée de se récompenser quand on y parvient, en se donnant le droit
d'utiliser la notation => qui est concise et
élégante
Le code produit en classe suit (note : il y a quelques ajouts et
ajustements dans ce code en comparaison avec ce que nous avons fait
aujourd'hui, mais nous en parlerons à la séance
S01) :
Program.cs
//
// code produit pour vous aider et pour amorcer une réflexion avec vous
//
using MonNamespace;
List<Orque> orques = new();
try
{
for (string s = Console.ReadLine(); "" != s; s = Console.ReadLine())
{
orques.Add(new Orque(s));
Console.WriteLine($"Orque créé : {orques[orques.Count - 1].Nom}");
}
}
catch (NomInvalideException nie)
{
Console.WriteLine(nie.Message);
}
if (Trier(ref orques, out int nbPermutations))
Console.WriteLine("Les orques ont été entrés en ordre alphabétique");
else
Console.WriteLine($"Trier les orques a nécessité {nbPermutations} permutations");
Console.Write("La tribu d'orques est :");
foreach (Orque orque in orques)
Console.Write($" {orque.Nom}");
//
//
//
static void Permuter(ref Orque a, ref Orque b)
{
Orque temp = a;
a = b;
b = temp;
}
static bool Trier(ref List<Orque> orques, out int nbPerms)
{
nbPerms = 0;
Orque[] orq = orques.ToArray();
for (int i = 0; i < orq.Length - 1; ++i)
for (int j = i + 1; j < orq.Length; ++j)
if (!(orq[i].Nom.CompareTo(orq[j].Nom) < 0)) // désordre
{
Permuter(ref orq[i], ref orq[j]);
++nbPerms;
}
orques = orq.ToList();
return nbPerms == 0;
}
Algos.cs
// using...
namespace MonNamespace
{
static class Algos
{
static char [] voyelles = { 'a', 'e', 'i', 'o', 'u', 'y' };
public static bool Contient(char [] tab, char c)
{
foreach (char ch in tab)
if (ch == c)
return true;
return false;
}
public static bool EstVoyelle(char c) =>
Contient(voyelles, char.ToLower(c));
public static int CompterVoyelles(string s)
{
int n = 0;
foreach (char c in s)
if (EstVoyelle(c))
++n;
return n;
}
public static bool EstEntreInclusif(int val, int min, int max) =>
min <= val && val <= max;
}
}
Orque.cs
using static MonNamespace.Algos;
// ...
class NomInvalideException : Exception;
class Orque
{
static bool EstNomValide(string nom) =>
nom != null &&
EstEntreInclusif(nom.Length, 1, 4) &&
Algos.CompterVoyelles(nom) <= 1;
string nom;
public string Nom
{
get => nom;
private init
{
nom = EstNomValide(value) ?
value : throw new NomInvalideException();
//if (!EstNomValide(value))
// throw new NomInvalideException();
//nom = value;
}
}
public Orque(string nom)
{
Nom = nom;
}
}
Suggestions de lecture :
|
|
S01 |
Au menu :
- Quelques améliorations générales à notre solution embryonnaire de
S00 pour l'activité formative
- Accélérer l'exécution du code en réduisant le nombre
d'initialisations d'états immuables à travers une variable
static
Rappel : un objet est immuable s'il ne peut pas être modifié une fois qu'il a été construit
- Quelques mots sur l'idée de classe static, qui permet de pallier en
partie un manque de certains langages comme
C#
ou
Java
- Léger allègement syntaxique rendu possible par ce mécanisme
L'idée est que les classes static
en
C#
servent entre autres de palliatif pour l'absence de vraies
fonctions dans le langage. En effet, tout comme
Java,
C#
ne supporte que les méthodes, qui sont des fonctions membres d'une
classe ou d'une instance, mais ne supporte pas les fonctions en
tant que telles, hors d'une classe).
Ainsi, le programme suivant (https://dotnetfiddle.net/OWcfrC) :
using System;
Point p0 = new(),
p1 = new(1,1);
Console.WriteLine("Distance({0},{1}) == {2}", p0, p1, Distance(p0, p1));
// Note : nous sommes (implicitement) dans la classe Program, donc
// Distance est en fait Program.Distance
static double Distance(Point p0, Point p1) =>
Math.Sqrt(Math.Pow(p0.X - p1.X, 2) + Math.Pow(p0.Y - p1.Y, 2)); // Notez «Math.» trois fois... et pourquoi?
class Point
{
public double X { get; init; }
public double Y { get; init; }
public Point() : this(0, 0) {}
public Point(double x, double y)
{
X = x;
Y = y;
}
}
... offre une méthode Distance qui est un peu verbeuse,
mais qui peut s'écrire plus simplement (https://dotnetfiddle.net/CFsrjf) :
using System;
using static System.Math;
Point p0 = new(),
p1 = new(1,1);
Console.WriteLine("Distance({0},{1}) == {2}", p0, p1, Distance(p0, p1));
// Note : nous sommes (implicitement) dans la classe Program, donc
// Distance est en fait Program.Distance
static double Distance(Point p0, Point p1) =>
Sqrt(Pow(p0.X - p1.X, 2) + Pow(p0.Y - p1.Y, 2)); // On va à l'essentiel!
class Point
{
public double X { get; init; }
public double Y { get; init; }
public Point() : this(0, 0) {}
public Point(double x, double y)
{
X = x;
Y = y;
}
}
... qui est un peu plus léger pour les yeux.
- Retour sur la petite
activité formative proposée à
S00
- Discussion de divers aspects techniques et architecturaux associés à
cette activité
- Avenues de raffinement ou d'optimisation, incluant les questions de
réflexion proposées dans l'énoncé
- Quelques explorations qui nous mèneront vers notre premier travail
pratique, le TP00
À titre de référence, le code d'aujourd'hui est à peu près le suivant. Ce code est perfectible; nous ferons bien mieux plus tard dans la session.
Je vous laisse le soin d'écrire le programme principal :
Soldat.cs
// ...
class NomInvalideException : Exception;
abstract class Soldat
{
public string Nom { get; private init; }
public Soldat(string nom)
{
Nom = nom;
}
public abstract void Saluer();
}
Orque.cs
// ...
using static MonNamespace.Algos;
class Orque : Soldat
{
const int LG_NOM_MIN = 1,
LG_NOM_MAX = 4;
static bool EstNomValide(string s) =>
EstEntreInclusif(s.Length, LG_NOM_MIN, LG_NOM_MAX) &&
CompterVoyelles(s) <= 1;
static string ValiderNom(string s) =>
EstNomValide(s) ? s : throw new NomInvalideException();
public Orque(string nom)
: base(ValiderNom(nom))
{
}
public override void Saluer()
{
Console.WriteLine($"MOI {Nom.ToUpper()}, MOI PUE");
}
}
}
En espérant que cela vous soit utile!
Nous avons aussi discuté sommairement du
clonage et de l'idiome
NVI.
À titre de référence, le code produit lors de cette séance pour
démontrer le
clonage était :
Image[] images = new Image[]
{
new Jpeg(ConsoleColor.Magenta),
new Png(ConsoleColor.Green),
new Bmp(ConsoleColor.Blue)
};
// non, pas le droit!
//foreach (Image img in images)
//{
// img.Dessiner();
// img = ModifierPeutÊtre(img); // <-- ceci serait illégal
// img.Dessiner();
//}
for (int i = 0; i != images.Length; ++i)
{
images[i].Dessiner();
images[i] = ModifierPeutÊtre(images[i]);
images[i].Dessiner();
}
////////////////////////
static Image ModifierPeutÊtre(Image img)
{
// 0 : créer un backup
Image backup = img.Cloner();
// 1 : modifier img
img.Teinte = ConsoleColor.Red;
// 2 : demander si on veut conserver les modifs
Console.WriteLine("Conserver les modifs? ");
// 2a : si oui, on retourne img
// 2b : sinon, on retourne le backup
if (Console.ReadKey(true).Key == ConsoleKey.O)
return img;
return backup;
}
// il existe une interface ICloneable, qui expose une méthode Clone
// ... mais ne l'utilisez pas :) Pour des détails, voir :
// https://codeql.github.com/codeql-query-help/csharp/cs-class-implements-icloneable/
abstract class Image
{
public ConsoleColor Teinte { get; set; }
protected Image(ConsoleColor teinte)
{
Teinte = teinte;
}
// Idiome NVI : non-virtual interface
public void Dessiner()
{
ConsoleColor pre = Console.ForegroundColor;
Console.ForegroundColor = Teinte;
DessinerImpl(); // varie selon les enfants
Console.ForegroundColor = pre;
}
protected abstract void DessinerImpl();
public abstract Image Cloner();
}
class Jpeg : Image
{
public Jpeg(ConsoleColor teinte) : base(teinte)
{
}
protected override void DessinerImpl()
{
Console.WriteLine($"Jpeg {Teinte}");
}
protected Jpeg(Jpeg autre) : base(autre.Teinte)
{
}
// spécialisation covariante
public override Jpeg Cloner() => new (this);
}
class Bmp : Image
{
public Bmp(ConsoleColor teinte) : base(teinte)
{
}
protected override void DessinerImpl()
{
Console.WriteLine($"Bmp {Teinte}");
}
protected Bmp(Bmp autre) : base(autre.Teinte)
{
}
public override Bmp Cloner() => new (this);
}
class Png : Image
{
public Png(ConsoleColor teinte) : base(teinte)
{
}
protected override void DessinerImpl()
{
Console.WriteLine($"Png {Teinte}");
}
protected Png(Png autre) : base(autre.Teinte)
{
}
public override Png Cloner() => new (this);
}
En fin de séance, j'ai fait une petite
activité dirigée d'un système à deux assemblages, soit le code client (qui était une application console,
donc un exécutable), et le code serveur (qui était une
bibliothèque de classes – une
DLL).
Distribution du TP00
Le code auquel nous en sommes arrivés pour le client était le suivant (j'ai
pris quelques libertés pour vous divertir) :
using Arsenal;
FabriqueArmes fab = new ();
IArme p = fab.CréerArme(Arsenal.Gravité.violent);
p.Frapper();
p = fab.CréerArme(Arsenal.Gravité.délicat);
p.Frapper();
Le code auquel nous en sommes arrivés pour le serveur était le suivant
(encore une fois avec quelques libertés) :
namespace Arsenal
{
public interface IArme
{
void Frapper(); // rappel : les membres d'une interface sont implicitement publics
}
class Masse : IArme // note : par défaut, les membres d'un namespace sont internal
{
public void Frapper()
{
Console.WriteLine("POURRRRH");
}
}
class Chainsaw : IArme
{
public void Frapper()
{
Console.WriteLine("FVRRRRRRRR!");
}
}
public enum Gravité { violent, délicat }
public class FabriqueArmes
{
public IArme CréerArme(Gravité grav) =>
grav == Gravité.délicat ? new Masse() : new Chainsaw();
}
}
Nous avons ensuite survolé les consignes du
TP00.
Si votre serveur pour le
TP00 fonctionne correctement, le programme de test suivant...
//
// ... code de test (note : le namespace se nomme Consommateur)
//
using GénérateurId;
using static Consommateur.Tests;
var fab = new FabriqueGénérateurs();
Test(fab, "Séquentiel", "ID", TypeGénérateur.Séquentiel);
Test(fab, "Recycleur", "ID", TypeGénérateur.Recycleur);
Test(fab, "Aléatoire", "ID", TypeGénérateur.Aléatoire, 3);
Test(fab, "Partagé", "ID", TypeGénérateur.Partagé, 3);
Test(fab, "Recycleur", "ID", TypeGénérateur.Recycleur);
Test(fab, "Partagé", "ID", TypeGénérateur.Partagé);
foreach (var (clé, valeur) in fab.ObtenirStatistiques())
Console.WriteLine($"{clé} a été instancié {valeur} fois");
using GénérateurId;
using System;
using System.Collections.Generic;
//
//
//
static class Tests
{
public static void Test(FabriqueGénérateurs fab, string nom, string préfixe, TypeGénérateur type)
{
IGénérateurId p = fab.Créer(type, préfixe);
var lst = new List<Identifiant>();
Console.Write($"{nom}, pige initiale :\n\t");
for (int i = 0; i != 10; ++i)
{
lst.Add(p.Prendre());
Console.Write($"{lst[lst.Count - 1]} ");
}
Console.WriteLine();
foreach (var n in lst)
p.Rendre(n);
Console.Write($"{nom}, pige post-remise :\n\t");
for (int i = 0; i != 10; ++i)
Console.Write($"{p.Prendre()} ");
Console.WriteLine();
}
public static void Test(FabriqueGénérateurs fab, string nom, string préfixe, TypeGénérateur type, int germe)
{
IGénérateurId p = fab.Créer(type, préfixe, germe);
var lst = new List<Identifiant>();
Console.Write($"{nom}, pige initiale :\n\t");
for (int i = 0; i != 10; ++i)
{
lst.Add(p.Prendre());
Console.Write($"{lst[lst.Count - 1]} ");
}
Console.WriteLine();
foreach (var n in lst)
p.Rendre(n);
Console.Write($"{nom}, pige post-remise :\n\t");
for (int i = 0; i != 10; ++i)
Console.Write($"{p.Prendre()} ");
Console.WriteLine();
}
}
// ...
... devrait donner un affichage comme le suivant (il peut y avoir certaines
différences dans les cas « partagé » et « aléatoire », mais il y a des limites
à ces différences – le test utilise un germe choisi – alors consultez votre chic prof si vous avez des doutes) :
Séquentiel, pige initiale :
ID00000 ID00001 ID00002 ID00003 ID00004 ID00005 ID00006 ID00007 ID00008 ID00009
Séquentiel, pige post-remise :
ID00010 ID00011 ID00012 ID00013 ID00014 ID00015 ID00016 ID00017 ID00018 ID00019
Recycleur, pige initiale :
ID00000 ID00001 ID00002 ID00003 ID00004 ID00005 ID00006 ID00007 ID00008 ID00009
Recycleur, pige post-remise :
ID00009 ID00008 ID00007 ID00006 ID00005 ID00004 ID00003 ID00002 ID00001 ID00000
Aléatoire, pige initiale :
ID19236 ID45716 ID56688 ID13007 ID36732 ID11833 ID16397 ID62078 ID22853 ID24902
Aléatoire, pige post-remise :
ID32911 ID53056 ID45554 ID01984 ID05379 ID59249 ID08091 ID56063 ID49708 ID31213
Partagé, pige initiale :
ID19236 ID45716 ID56688 ID13007 ID36732 ID11833 ID16397 ID62078 ID22853 ID24902
Partagé, pige post-remise :
ID32911 ID53056 ID45554 ID01984 ID05379 ID59249 ID08091 ID56063 ID49708 ID31213
Recycleur, pige initiale :
ID00000 ID00001 ID00002 ID00003 ID00004 ID00005 ID00006 ID00007 ID00008 ID00009
Recycleur, pige post-remise :
ID00009 ID00008 ID00007 ID00006 ID00005 ID00004 ID00003 ID00002 ID00001 ID00000
Partagé, pige initiale :
ID14127 ID38283 ID32702 ID05286 ID65150 ID29877 ID33647 ID35818 ID24609 ID32588
Partagé, pige post-remise :
ID19316 ID15965 ID33013 ID04178 ID45250 ID58540 ID33793 ID27893 ID56175 ID43324
Séquentiel a été instancié 1 fois
Recycleur a été instancié 2 fois
Aléatoire a été instancié 1 fois
Partagé a été instancié 1 fois
|
|
S02 |
Au menu :
- Petits trucs pour vous aider à écrire
DomaineIdentifiants.Formater :
https://dotnetfiddle.net/ESdlzq
- Retour bref sur les thèmes des séances précédentes :
- Retour sur l'exercice de créer une bibliothèque à liens dynamiques,
et d'une création d'un petit
système client / serveur
- schéma de conception
Interface et sa
réification en C#
- implémentation(s) d'une interface
- schéma de conception
Fabrique
- écriture d'un client pour ce service
- utilité de ce type d'architecture
Je n'ai pas fait le même exemple dans tous mes groupes, mais l'un de ceux que j'ai faits pour
expliquer l'intérêt des interfaces dans un langage comme C# ou Java dans lequel
seul l'héritage simple
d'implémentation est permis (il n'est pas possible pour une classe d'avoir plus d'une classe parent
directe dans ces
langages) était la catégorisation d'espèces animales. Rappel : cet exemple
est farfelu, et je ne suis pas un biologiste!
Notre premier exemple allait comme suit :
class Animal
{
// ...
}
class Oiseau : Animal
{
// ...
}
class Insecte : Animal
{
// ...
}
class OiseauVolant : Oiseau
{
public void Voler() { /* ... */ }
// ...
}
class OiseauNonVolant : Oiseau
{
// ...
}
class InsecteVolant : Insecte
{
public void Voler() { /* ... */ }
// ...
}
class InsecteNonVolant : Insecte
{
}
Constatant que cette approche ne me permettait pas de placer
ensemble tout ce qui vole (le seul parent commun à
OiseauVolant et InsecteVolant est
Animal et rien ne permet de croire que tout
Animal sait voler!), nous avions retravaillé la hiérarchie
comme suit :
class Animal
{
// ...
}
class Volant : Animal
{
public void Voler() { /* ... */ } // virtual?
}
class NonVolant : Animal
{
// ...
}
class OiseauVolant : Volant
{
// ...
}
class OiseauNonVolant : NonVolant
{
// ...
}
class InsecteVolant : Volant
{
// ...
}
class InsecteNonVolant : NonVolant
{
// ...
}
Cette fois, ceci ne permettait pas de placer ensemble tous
les oiseaux (ou tous les insectes). Zut!
Heureusement, C#
et Java
compensent (en partie) l'absence d'héritage multiple
d'implémentation par la possibilité d'implémenter de multiples
interfaces. Ainsi, on peut obtenir une solution plus intéressante et
plus complète comme suit :
class Animal
{
...
}
class Oiseau : Animal
{
...
}
class Insecte : Animal
{
...
}
interface IVolant
{
void Voler();
}
class OiseauVolant : Oiseau, IVolant
{
public void Voler() { /* ... */ }
...
}
class OiseauNonVolant : Oiseau
{
...
}
class InsecteVolant : Insecte, IVolant
{
public void Voler() { /* ... */ }
...
}
class InsecteNonVolant : Insecte
{
}
Notez que la classe parent doit apparaître avant les interfaces dans la signature d'une classe, et
qu'une même classe peut implémenter plusieurs interfaces.
- Petit rappel sur les générateurs de nombres pseudoaléatoires et sur
leur bon usage
const int N = 10_000_000;
const int NB_FACES = 6;
Random dé = new();
int[] lancers = new int[12]; // 0 .. 11, et l'indice 0 contiendra 0
for (int i = 0; i != N; ++i)
{
int a = dé.Next(1, NB_FACES + 1),
b = dé.Next(1, NB_FACES + 1);
// Console.WriteLine($"{a} + {b} == {a+b}");
lancers[(a + b) - 1]++;
}
Console.WriteLine($"Après {N} lancers de deux dés à {NB_FACES} faces...");
for (int i = 1; i != lancers.Length; ++i)
Console.WriteLine($"{i + 1} : {lancers[i]}");
- Dans la perspective (pour le
TP00) de la fonction
FabriqueGénérateurs.ObtenirStatistiques :
string texte = "..."; // utilisez le texte de votre choix
Dictionary<string, int> fréquence = new();
foreach(string s in texte.Split(new char[] { ' ', '\t', '\r', '\n' }))
if(s.Trim().Length > 0)
if(fréquence.ContainsKey(s))
fréquence[s]++;
else
fréquence.Add(s, 1);
foreach (var (mot, n) in fréquence)
Console.WriteLine($"Le mot {mot} apparaît {n} fois");
|
|
S03 |
Au menu :
Comment s'orienter sur le site du cours 🙂
- Retour sur S02
- Nombres pseudoaléatoires et l'idée de germe
- Propriétés en lecture seule ou calculées
- Propriétés synthétiques (propriétés de deuxième ordre)
- Écriture concise
Pour un résumé des syntaxes examinées pour les propriétés
using System;
Carré c = new(3); // 3x3
class CôtéInvalideException : Exception;
class Carré
{
public static int LongueurMax => 1 // note : pas « = »
// on aurait aussi pu écrire :
// public static int LongueurMax { get => 1; }
// ... ou encore :
// public static int LongueurMax { get; } = 1; // note : ici, il y a une variable cachée
// // et pas seulement un calcul
public Carré(int côté, ConsoleColor couleur)
{
Côté = côté;
Couleur = couleur;
}
public ConsoleColor Couleur // propriété de premier ordre sans validation
{
get; private init;
}
int côté;
public int Côté // propriété de premier ordre avec validation
{
get => côté;
private init
{
côté = value > 0 ? value : throw new CôtéInvalideException();
}
}
public int Aire { get => Côté * Côté; } // propriété calculée, version « longue »
public int Surface => 4 * Côté; // propriété calculée, version « courte »
}
À titre de rappel, chaque notation a son utilité. Par exemple :
enum Lancer { Pile, Face }
class Cenne // pour jouer à pile ou face
{
Random Dé { get; } = new(); // cool : crée un Random au début de la
// vie de l'objet et le conserve par la suite
// Random Dé => new(); // NOOON! Créerait un Random à chaque get (quelle horreur!)
public Lancer Jouer() => Dé.Next() % 2 == 0 ? Lancer.Pile : Lancer.Face;
}
- Gestion des
erreurs au sens large :
-
fins d'exécution brusques (en
C# :
Environment.Exit)
-
valeurs de retour de fonctions (p. ex. : valeurs sentinelles)
-
exceptions
- À propos du sain usage des
exceptions :
- Mention brève des idées de :
- Travail sur le TP00
Si votre TP00 semble
bien fonctionner, voici quelques tests que vous pouvez envisager.
- Un test validant que vous donnez bel et bien tous les
identifiants avec un générateur séquentiel (vous pouvez faire
quelque chose de semblable avec un générateur recycleur) :
// ...
IGénérateurId p = new FabriqueGénérateurs().Créer(TypeGénérateur.Séquentiel);
ushort val = p.Prendre().Valeur;
Console.WriteLine($"Premier identifiant pris : {val}"); // devrait être 0
try
{
for(;;) // boucle infinie (« for ever »)
val = p.Prendre().Valeur;
}
catch(BanqueVideException)
{
}
Console.WriteLine($"Dernier identifiant pris : {val}"); // devrait être 65535
- Un test validant que vous donnez bel et bien tous les
identifiants avec un générateur aléatoire (ou avec le générateur
partagé), et que l'algorithme choisi est raisonnablement rapide :
// ...
IGénérateurId p = new FabriqueGénérateurs().Créer(TypeGénérateur.Aléatoire);
int n = 0;
ushort val = p.Prendre().Valeur;
++n;
try
{
for(;; ++n) // boucle infinie (« for ever »)
val = p.Prendre().Valeur;
}
catch(BanqueVideException)
{
}
Console.WriteLine($"Nombre d'identifiants pris : {n}"); // devrait être 65536
|
|
S04 |
Au menu :
- Remise « papier » du
TP00
Par la suite :
- Comment s'orienter sur le site du cours 🙂
-
À propos du sain usage des
exceptions
(suite) :
- pourquoi diverses sortes d'exceptions?
- coûts des
exceptions
- chemin jamais pris
- coût d'un throw
- coût d'un try
- coût d'un catch
- Les entiers (int,
uint, short,
ushort, etc.) et ce qui se passe aux frontières de ces types
- comprendre le comportement d'un débordement sur un entier signé
ou non-signé
Démystifier les
qualifications d'accès public,
protected, private,
internal, et pourquoi static n'a
rien à voir avec ces mots 🙂
- Constructeurs de délégation
Quelques tests de vitesse (et pourquoi ces détails sont importants!)
// note : nous avons fait plusieurs variations de ce qui suit
using System;
using System.Diagnostics;
const int N = 100_000_000;
int[] vals = CréerTableau(N);
System.Diagnostics.Stopwatch sw = new();
sw.Start();
int combien = Compter(vals);
sw.Stop();
Console.WriteLine($"Compté {combien} impairs en {sw.ElapsedMilliseconds} ms");
static bool EstPair(int n) => n % 2 == 0;
static bool EstImpair(int n) => !EstPair(n);
static int Compter(int[] vals)
{
int combien = 0;
foreach (int n in vals)
{
try
{
int res = F(n);
++combien;
}
catch (ZutException)
{
}
}
return combien;
}
static int F(int n)
{
if (EstPair(n))
throw new ZutException();
return n;
}
static int[] CréerTableau(int n)
{
int[] tab = new int[n];
for (int i = 0; i != tab.Length; ++i)
//if(i % 100 == 0)
tab[i] = 2 * i;
//else
// tab[i] = 2 * i + 1;
return tab;
}
class ZutException : Exception;
using System;
using System.Text;
const int N = 100_000_000;
for (int i = 1; i <= N; i *= 10)
{
System.Diagnostics.Stopwatch sw = new();
sw.Start();
string s = CréerTexteMeilleur(i, 'A');
sw.Stop();
Console.WriteLine($"Créé une chaîne de longueur {s.Length} en...");
Console.WriteLine($"\t{sw.ElapsedMilliseconds} ms");
}
static string CréerTexteLent(int n, char c)
{
string s = "";
for (int i = 0; i != n; ++i)
s += c;
return s;
}
static string CréerTexteRapide(int n, char c)
{
StringBuilder sb = new();
for (int i = 0; i != n; ++i)
sb.Append(c);
return sb.ToString();
}
static string CréerTexteMeilleur(int n, char c)
{
return new(c, n);
}
- Ajouter des éléments dans une List<T> :
using System;
using System.Diagnostics;
const int N = 100_000_000;
for(int i = 1; i <= N; i *= 10)
{
List<int> lst = new();
Stopwatch sw = new();
sw.Start();
for (int j = 0; j != i; ++j)
lst.Add(3); // 3 est une valeur quelconque; c'est pas important laquelle ici
sw.Stop();
Console.WriteLine($"Ajout à la fin, {i} éléments : {sw.ElapsedMilliseconds} ms");
sw = new();
sw.Start();
for (int j = 0; j != i; ++j)
lst.Insert(0, 3); // 3 est une valeur quelconque; c'est pas important laquelle ici
sw.Stop();
Console.WriteLine($"Ajout au début, {i} éléments : {sw.ElapsedMilliseconds} ms");
}
N'oubliez pas de remettre
TP00 en format imprimé au début de la séance |
|
S05 |
Au menu :
- Q00
- Sujet : bases de programmation orientée objet
avec
C#
(constructeurs, propriétés, invariants, immuabilité ou pas, etc.)
- On fait le
TP00
- Introduction à la sérialisation (avec format
JSON, qui est
le format en vogue en ce moment)
- Démystifier les
qualifications d'accès public,
protected, private,
internal, et pourquoi static n'a
rien à voir avec ces mots 🙂
- On présente le
TP01
- Travail sur le TP01
Le programme principal imposé pour le
TP01 est le suivant :
TP01-Program.cs
Note : pour certains aspects du
TP01, vous aurez besoin d'informations qui
seront présentées à la séance
S06 alors commencez par écrire les classes pour lesquelles
vous savez déjà ce qu'il est nécessaire de savoir!
L'exemple de consommation d'un fichier
JSON vu en classe
était :
using System.Text.Json;
using System.Text.Json.Serialization;
string s = "";
using (StreamReader sr = new("../../../fichier.json"))
{
for (string str = sr.ReadLine(); str != null; str = sr.ReadLine())
s += $"{str}\n";
}
Caractères cars = JsonSerializer.Deserialize<Caractères>(s);
Console.WriteLine($"Nom : {cars.NomSurface}");
foreach (Caractère c in cars.Cars)
{
ConsoleColor avant = Console.ForegroundColor;
Console.ForegroundColor = c.Couleur;
Console.WriteLine($"Symbole {c.Symbole}");
Console.ForegroundColor= avant;
}
//
// classes représentant les types de données lus
//
class Caractère
{
public char Symbole { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public ConsoleColor Couleur { get; set; }
}
class Caractères
{
public string NomSurface { get; set; }
public List<Caractère> Cars { get; set; }
}
... et le fichier
JSON lui-même
était :
{
"NomSurface": "Surf",
"Cars": [
{
"Symbole": "$",
"Couleur": "Red"
},
{
"Symbole": "*",
"Couleur": "Blue"
}
]
}
|
|
S06 |
Au menu :
Pour une implémentation des schémas de conception
Singleton et
Observateur, les deux à travers
un gestionnaire de clavier, voir ceci (qui ressemble à ce que nous avons fait en classe) :
var ges = GesClavier.Instance;
SignalFin signal = new();
ges.Abonner(new Assassin(signal));
ges.Abonner(new Afficheur());
ges.Abonner(new VersEst());
ges.Abonner(new VersNord());
ges.Abonner(new VersOuest());
ges.Abonner(new VersSud());
for (; !signal.Fini;)
{
ges.Exécuter();
}
class SignalFin
{
public bool Fini { get; private set; } = false;
public void Provoquer() { Fini = true; }
}
interface IRéactionTouche
{
void Réagir(char c);
}
class Assassin : IRéactionTouche
{
SignalFin Signal { get; init; }
public Assassin(SignalFin signal)
{
Signal = signal;
}
public void Réagir(char c)
{
if (char.ToLower(c) == 'q')
Signal.Provoquer(); // Environment.Exit(0);
}
}
class VersEst : IRéactionTouche
{
public void Réagir(char c)
{
if (char.ToLower(c) == 'd')
Console.WriteLine("EST");
}
}
class VersNord : IRéactionTouche
{
public void Réagir(char c)
{
if (char.ToLower(c) == 'w')
Console.WriteLine("NORD");
}
}
class VersOuest : IRéactionTouche
{
public void Réagir(char c)
{
if (char.ToLower(c) == 'a')
Console.WriteLine("OUEST");
}
}
class VersSud : IRéactionTouche
{
public void Réagir(char c)
{
if (char.ToLower(c) == 's')
Console.WriteLine("SUD");
}
}
class Afficheur : IRéactionTouche
{
public void Réagir(char c)
{
Console.WriteLine($"Touche pressée : {c}");
}
}
class GesClavier
{
public void Exécuter()
{
char c = Console.ReadKey(true).KeyChar;
foreach (IRéactionTouche p in Abonnés)
p.Réagir(c);
}
List<IRéactionTouche> Abonnés { get; } = new();
public void Abonner(IRéactionTouche p)
{
Abonnés.Add(p);
}
public static GesClavier Instance { get; } = new();
GesClavier() // <-- note : privé
{
// initialisation des états du singleton
}
}
Pour un exemple connexe avec délégués :
var ges = GesClavier.Instance;
SignalFin signal = new();
ges.Abonner(new Assassin(signal).MEURS);
ges.Abonner(new Afficheur().Réagir);
ges.Abonner(new VersEst().Réagir);
ges.Abonner(new VersNord().Réagir);
ges.Abonner(new VersOuest().Réagir);
ges.Abonner(new VersSud().Réagir);
for (; !signal.Fini;)
{
ges.Exécuter();
}
class SignalFin
{
public bool Fini { get; private set; } = false;
public void Provoquer() { Fini = true; }
}
delegate void RéactionTouche(char c);
class Assassin
{
SignalFin Signal { get; init; }
public Assassin(SignalFin signal)
{
Signal = signal;
}
public void MEURS(char c)
{
if (char.ToLower(c) == 'q')
Signal.Provoquer(); // Environment.Exit(0);
}
}
class VersEst
{
public void Réagir(char c)
{
if (char.ToLower(c) == 'd')
Console.WriteLine("EST");
}
}
class VersNord
{
public void Réagir(char c)
{
if (char.ToLower(c) == 'w')
Console.WriteLine("NORD");
}
}
class VersOuest
{
public void Réagir(char c)
{
if (char.ToLower(c) == 'a')
Console.WriteLine("OUEST");
}
}
class VersSud
{
public void Réagir(char c)
{
if (char.ToLower(c) == 's')
Console.WriteLine("SUD");
}
}
class Afficheur
{
public void Réagir(char c)
{
Console.WriteLine($"Touche pressée : {c}");
}
}
class GesClavier
{
public void Exécuter()
{
char c = Console.ReadKey(true).KeyChar;
foreach (RéactionTouche f in Abonnés)
f(c);
}
List<RéactionTouche> Abonnés { get; } = new();
public void Abonner(RéactionTouche p)
{
Abonnés.Add(p);
}
public static GesClavier Instance { get; } = new();
GesClavier() // <-- note : privé
{
// initialisation des états du singleton
}
}
|
|
S07
|
Au menu :
|
|
S08 |
Au menu :
- Q01
- Sujet : quelques schémas de conception vus
jusqu'ici (interface, fabrique, clonage) de même que les
dictionnaires et votre compréhension des idées de membre d'instance
et de membre de classe
- Premiers pas vers une manière plus intelligente de programmer :
paramétrer un algorithme de recherche
- Introduction à la programmation générique
- Exemple de Afficher<T>, et pourquoi une version non-générique
fonctionnerait tout autant dans ce cas
- Exemple de TriBulles<T>, accompagné de
Permuter<T>, avec une classe X qui
est IComparable<X> en comparaison avec une
classe Y qui ne l'est pas
- Exemple de Pile d'entiers avec une
List<int> à titre de substrat
- Exemple de Pile<T> avec une
List<T> à titre de substrat
- Exemple de Pile<T> avec des
noeuds à titre de substrat
- Exemple de Trouver<T> où
T doit être IEquatable<T>
- Exercices de programmation à l'aide
d'algorithmes génériques et
d'expressions λ :
exercice-apprivoiser-genericite-lambda.html
- Solutionnaire :
exercice-apprivoiser-genericite-lambda--solutionnaire.html
- Travail sur le
TP01
Pour une Pile<T> reposant sur une List<T>
à titre de substrat :
using Sytem.Collections.Generic;
class PileVideException : Exception;
class Pile<T>
{
List<T> Substrat{ get; } = new();
public bool EstVide => Substrat.Count == 0;
public void Push(T val)
{
Substrat.Add(val);
}
public T Pop()
{
T val = Peek();
Substrat.RemoveAt(Substrat.Count - 1);
return val;
}
public T Peek()
{
if (EstVide)
throw new PileVideException();
return Substrat[Substrat.Count - 1];
}
}
Pour une Pile<T> dont le substrat est fait de noeuds :
using Sytem.Collections.Generic;
class PileVideException : Exception;
class Pile<T>
{
class Noeud
{
public T Valeur{ get; init; }
public Noeud ? Prédécesseur{ get; set; } = null;
public Noeud(T val)
{
Valeur = val;
}
}
Noeud ? Tête { get; set; } = null;
public bool EstVide => Tête == null;
public void Push(T val)
{
Noeud p = new(val);
p.Prédécesseur = Tête;
Tête = p;
}
public T Pop()
{
T val = Peek();
Tête = Tête.Prédécesseur;
return val;
}
public T Peek()
{
if (EstVide)
throw new PileVideException();
return Tête.Valeur;
}
}
|
|
S09 |
Au menu :
Certaines et certains d'entre vous ont exprimé le
souhait de faire d'autres exercices, alors pour votre bon plaisir... Si
vous l'estimez pertinent, vous pouvez réutiliser d'autres algorithmes
de votre cru dans vos implémentations (vous ne pouvez pas utiliser des
fonctions déjà implémentées dans la bibliothèque standard du langage,
le but étant de vous pratiquer et d'apprendre!).
S09.0 – É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!) :
Note : ici comme en général, évitez de modifier
src (le code client ne vous aimerait pas si vous le faisiez!)
Note : ceci peut se faire élégamment avec des
permutations. Vous ne pourrez pas utiliser Permuter
directement sur src, malheureusement,
mais c'est une tare de
C#
et de l'implémentation de List<T>, pas un
problème avec votre algorithme. Au choix, pour permuter les éléments aux
indices i et j, vous pouvez utiliser le
classique :
T temp = lst[i]; lst[i] = lst[j]; lst[j] = temp;
...directement, ou encore écrire :
(lst[i], lst[j]) = (lst[j], lst[i]);
... ce qui fonctionnera aussi. Une autre approche est de faire un
T[] à partir de la List<T> (avec
ToArray), de réaliser l'algorithme sur ce
T[] en appelant Permuter, puis de
refaire une List<T> avec le résultat (en
utilisant ToList).
S09.1 – É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!) :
S09.2 – Écrivez l'algorithme
Inverser<T>(List<T> lst) qui modifie lst
et ne retourne rien. Cette fonction doit inverser l'ordre des
éléments de lst, de telle sorte que le
programme suivant :
List<int> lst = new();
Inverser(lst);
Afficher(lst);
lst = new(){ 1 };
Inverser(lst);
Afficher(lst);
lst = new(){ 1, 2 };
Inverser(lst);
Afficher(lst);
lst = new(){ 2,3,5,7,11 };
Inverser(lst);
Afficher(lst);
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
inversé les éléments... d'une séquence vide!) :
S09.3 – Écrivez le prédicat
EstPalindrome<T>(List<T> lst) qui retourne
true seulement si lst est un
palindrome. Un palindrome est une séquence qui a la même forme si on
l'observe de gauche à droite ou de droite à gauche, comme par exemple
un tableau contenant 1,3,3,1 ou le texte
"laval". Implémentez cette fonction sans allouer de mémoire.
S09.4 – Écrivez le prédicat
SontTous<T> prenant en paramètre une
List<T> et un prédicat applicable à un T,
et retournant true seulement si tous les
éléments de la List<T> satisfont le
prédicat.
S09.5 – Écrivez le prédicat
AuMoinsUn<T> prenant en paramètre une
List<T> et un prédicat applicable à un T,
et retournant true seulement si au moins
un élément de la List<T> satisfait le
prédicat.
S09.6 – Écrivez le prédicat
Aucun<T> prenant en paramètre une List<T>
et un prédicat applicable à un T,
et retournant true seulement si aucun
élément de la List<T> ne satisfait le
prédicat.
Si ce mode de pensée par algorithmes vous apporte du bonheur, je vous
recommande cette célèbre conférence de 2013
par
Sean Parent, un excellent programmeur, qui contient des
moments dont on parle encore plus d'une décennie plus tard :
https://www.youtube.com/watch?v=W2tWOdzgXHA (c'est en
C++ mais c'est pas le langage qui est important, c'est la
façon de penser!)
|
|
S10 |
Au menu : je dois m'absenter le 1er octobre car je donne une conférence
à Québec. Ceci impacte un seul de mes trois groupes, mais par souci
d'équité, je ne donnerai pas de nouvelle matière aux autres groupes non
plus. Je serai donc (sauf le 1eroctobre!) en classe mais en soutien aux
gens qui en ont besoin. Profitez-en pour apporter vos questions!
|
|
S11 |
Au menu :
- Retour sur Q01
- On se fait une petite collection simpliste :
- Nous avons fait une liste
simplement chaînée de T
- Nous l'avons ensuite raffinée car certaines de ses fonctions
étaient beaucoup trop coûteuses
- Nous avons ajusté cette collection pour qu'il soit
possible de la traverser avec foreach
- Nous abons couvert le mot clé
default
pour initialiser un objet avec sa valeur par défaut (0
pour un int, 0.0
pour un double,
null pour une
string,
etc.)
- On rend cette ListeSimple<T> « parcourable » avec foreach en
implémentant IEnumerable<T> pour la
collection et IEnumerator<T> pour l'objet
capable de la parcourir
- On ajoute une classe Tableau<T>, elle aussi IEnumerable<T>, pour
voir un deuxième cas (différent du premier) d'implémentation de cette
interface
- Survol des algorithmes génériques implémentés aux séances
S08 et
S09 pour voir lesquels gagneraient en utilité si on élargissait leur
signature pour accepter en paramètre des IEnumerable<T>
Certaines et certains d'entre vous ont exprimé le
souhait de faire d'autres exercices, alors pour votre bon plaisir. Si
vous l'estimez pertinent, vous pouvez réutiliser d'autres algorithmes
de votre cru dans vos implémentations (vous ne pouvez pas utiliser des
fonctions déjà implémentées dans la bibliothèque standard du langage,
le but étant de vous pratiquer et d'apprendre!).
S11.0 – Écrivez le prédicat
EstTrié<T> prenant en paramètre une
List<T> et retournant true
seulement si les éléments sont en ordre. Exigez que
T implémente IComparable<T>.
S11.1 – Écrivez le prédicat
EstTrié<T> prenant en paramètre une
List<T> et une fonction de comparaison trilatérale, et
retournant true seulement si les éléments
sont en ordre sur la base de la fonction de comparaison. Note : une
fonction de comparaison trilatérale est une fonction acceptant en
paramètre deux T et retournant un
int; le int retourné sera inférieur
à zéro si le premier T précède le second,
égal à zéro si les deux T sont
équivalents, et supérieur à zéro sur le premier T
suit le second.
S11.2 – Écrivez la fonction
UnionEnsembliste<T> prenant en paramètre deux List<T>
et retournant l'union ensembliste de ces deux
List<T>, donc une List<T> triée qui
contiendra une occurrence de chaque élément se trouvant dans l'une ou
l'autre des deux List<T> passées en
paramètre. Précondition : chacune des deux
List<T> passées en paramètre est triée avant l'appel et les
deux sont triées selon les mêmes critères. Par exemple, le programme
suivant :
List<int> a = new(){ 2,3,5,7,11 };
List<int> b = new(){ 1,2,3,4,5,6,7 };
Afficher(UnionEnsembliste(a, b));
static void Afficher<T>(List<T> lst)
{
foreach(T obj in lst)
Console.Write($"{obj} ");
Console.WriteLine();
}
... affichera ce qui suit :
S11.3 – Écrivez la fonction
IntersectionEnsembliste<T> prenant en paramètre deux List<T>
et retournant l'intersection ensembliste de ces deux
List<T>, donc une List<T> triée qui
contiendra une occurrence de chaque élément se trouvant à la fois dans
les deux List<T> passées en paramètre
Précondition : chacune des deux List<T>
passées en paramètre est triée avant l'appel et les deux sont triées
selon les mêmes critères. Par exemple, le programme suivant :
List<int> a = new(){ 2,3,5,7,11 };
List<int> b = new(){ 1,2,3,4,5,6,7 };
Afficher(IntersectionEnsembliste(a, b));
static void Afficher<T>(List<T> lst)
{
foreach(T obj in lst)
Console.Write($"{obj} ");
Console.WriteLine();
}
... affichera ce qui suit :
Pour ListeSimple<T>, nous en sommes arrivés
à :
// ...
class ListeVideException : Exception;
internal class ListeSimple<T> : IEnumerable<T>
{
class Noeud
{
public T Valeur { get; init; }
public Noeud Succ { get; set; } = null;
public Noeud (T val)
{
Valeur = val;
}
}
Noeud Tête { get; set; } = null;
Noeud Queue { get; set; } = null;
public int Count { get; private set; } = 0;
public bool EstVide => Tête == null;
// Complexité : O(1) (constante)
public void AjouterDébut(T val)
{
Noeud p = new(val);
if (EstVide)
Queue = p;
p.Succ = Tête;
Tête = p;
++Count;
}
// Complexité : O(1) constante
public void AjouterFin(T val)
{
if (EstVide)
AjouterDébut(val);
else
{
Noeud p = Queue;
p.Succ = new(val);
Queue = Queue.Succ;
++Count;
}
}
// Complexité : O(1) (constante)
public void SupprimerDébut()
{
if (EstVide)
throw new ListeVideException();
Tête = Tête.Succ;
--Count;
if (EstVide)
Queue = null;
}
public T Premier => !EstVide ?
Tête.Valeur : throw new ListeVideException();
public T Dernier => !EstVide ?
Queue.Valeur : throw new ListeVideException();
// Complexité : O(n) (linéaire... Ouf!)
public ListeSimple<T> Dupliquer()
{
ListeSimple<T> autre = new();
for (Noeud p = Tête; p != null; p = p.Succ)
autre.AjouterFin(p.Valeur);
return autre;
}
public IEnumerator<T> GetEnumerator() =>
new Énumérateur(this);
IEnumerator IEnumerable.GetEnumerator() =>
new Énumérateur(this);
class Énumérateur : IEnumerator<T>
{
Noeud Cur { get; set; }
public Énumérateur(ListeSimple<T> source)
{
Cur = new(default);
Cur.Succ = source.Tête;
}
public bool MoveNext()
{
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;
}
}
// ...
J'ai ensuite adapté TiTableau pour le
rendre IEnumerable<int>. Si nous avions
fait un Tableau<T> semblable à
List<T>, nous en serions à peu près arrarrivés
à ceci :
// ...
// List<T> « des pauvres »
internal class Tableau<T> : IEnumerable<T>
{
T[] Vals { get; set; }
public int Count { get; private set; } = 0;
public int Capacity { get; private set; } = 0;
public bool EstVide => Count == 0;
public bool EstPlein => Count == Capacity;
public Tableau()
{
Vals = new T[0];
}
public void Add(T val)
{
if (EstPlein)
Croître();
Vals[Count] = val;
++Count;
}
// rôle : accroître la capacité du Tableau
private void Croître()
{
int nouvelleCapacité = Capacity != 0 ?
Capacity * 2 : 8; // arbitraire
T[] nouveauTab = new T[nouvelleCapacité];
for(int i = 0; i != Count; ++i)
nouveauTab[i] = Vals[i];
Vals = nouveauTab;
Capacity = nouvelleCapacité;
}
public IEnumerator<T> GetEnumerator() =>
new Énumérateur(this);
IEnumerator IEnumerable.GetEnumerator() =>
new Énumérateur(this);
class Énumérateur : IEnumerator<T>
{
public Énumérateur(Tableau<T> source)
{
Source = source;
}
Tableau<T> Source { get; init; }
int Indice { get; set; } = -1;
public bool MoveNext()
{
if (Indice == Source.Count - 1)
return false;
++Indice;
return true;
}
public void Reset() { }
public void Dispose() { }
public T Current => Source[Indice];
object IEnumerator.Current => Source[Indice];
}
public T this[int indice]
{
get => Vals[indice];
set => Vals[indice] = value;
}
}
`// ...
N'oubliez pas de remettre
TP01 en format imprimé au début de la séance
|
|
S12 |
Au menu :
- Q02
- Sujets : écrire un algorithme générique, utiliser un
algorithme générique, écrire une classe générique, utiliser une
expression lambda
écrire une
sucharge d'opérateur (les opérateurs seront pour une prochaine
fois)
- Solutionner les exercices de la séance
S09
Solution à S09.0 – RotaterGauche<T>
using System;
using System.Collections.Generic;
Afficher(RotaterGauche(new List<int>(){ 2 }));
Afficher(RotaterGauche(new List<int>(){}));
Afficher(RotaterGauche(new List<int>(){ 2, 3 }));
Afficher(RotaterGauche(new List<int>(){ 2, 3, 5, 7, 11 }));
static void Afficher<T>(IEnumerable<T> elems)
{
foreach(T e in elems)
Console.Write($"{e} ");
Console.WriteLine();
}
static List<T> RotaterGauche<T>(List<T> src)
{
List<T> dest = new(src);
for(int i = 1; i < dest.Count; ++i)
(dest[i-1], dest[i]) = (dest[i], dest[i-1]); // permuter
return dest;
}
En ligne :
https://dotnetfiddle.net/DtYILR
Solution à S09.1 – RotaterDroite<T>
using System;
using System.Collections.Generic;
Afficher(RotaterDroite(new List<int>(){ 2 }));
Afficher(RotaterDroite(new List<int>(){}));
Afficher(RotaterDroite(new List<int>(){ 2, 3 }));
Afficher(RotaterDroite(new List<int>(){ 2, 3, 5, 7, 11 }));
static void Afficher<T>(IEnumerable<T> elems)
{
foreach(T e in elems)
Console.Write($"{e} ");
Console.WriteLine();
}
static List<T> RotaterDroite<T>(List<T> src)
{
List<T> dest = new(src);
for(int i = dest.Count - 1; i > 0; --i)
(dest[i-1], dest[i]) = (dest[i], dest[i-1]); // permuter
return dest;
}
En ligne :
https://dotnetfiddle.net/hVzrRI
Solution à S09.2 –
Inverser<T>
using System;
using System.Collections.Generic;
Test(new List<int>());
Test(new List<int>(){ 2 });
Test(new List<int>(){});
Test(new List<int>(){ 2, 3 });
Test(new List<int>(){ 2, 3, 5, 7 });
Test(new List<int>(){ 2, 3, 5, 7, 11 });
static void Test<T>(List<T> lst)
{
Console.Write("Avant : "); Afficher(lst);
Inverser(lst);
Console.Write("Après : "); Afficher(lst);
}
static void Afficher<T>(IEnumerable<T> elems)
{
foreach(T e in elems)
Console.Write($"{e} ");
Console.WriteLine();
}
static void Inverser<T>(List<T> lst)
{
int milieu = lst.Count / 2;
for(int i = 0; i < milieu; ++i)
(lst[i], lst[lst.Count - i - 1]) = (lst[lst.Count - i - 1], lst[i]);
}
En ligne :
https://dotnetfiddle.net/24tp51
Solution à S09.3 –
EstPalindrome<T>
using System;
using System.Collections.Generic;
Test(new List<int>(){ 2 });
Test(new List<int>(){});
Test(new List<int>(){ 2, 3 });
Test(new List<int>(){ 1, 2, 2, 1 });
Test(new List<int>(){ 1, 2, 3, 2, 1 });
Test(new List<int>(){ 1, 3, 3, 2, 1 });
static void Test<T>(List<T> lst) where T : IEquatable<T>
{
Console.Write($"EstPalindrome : {EstPalindrome(lst)} pour la séquence : ");
Afficher(lst);
}
static void Afficher<T>(IEnumerable<T> elems)
{
foreach(T e in elems)
Console.Write($"{e} ");
Console.WriteLine();
}
static bool EstPalindrome<T>(List<T> lst) where T : IEquatable<T>
{
int milieu = lst.Count / 2;
for(int i = 0; i < milieu; ++i)
if(!lst[i].Equals(lst[lst.Count - i - 1]))
return false;
return true;
}
En ligne :
https://dotnetfiddle.net/FDkn76
Solution à S09.4 –
SontTous<T>, à S09.5 –
AuMoinsUn<T> et à S09.6 –
Aucun<T>
using System;
using System.Collections.Generic;
Test(new List<int>(){ 1, 2, 3, 4 });
Test(new List<int>(){ 2, 4, 6 });
Test(new List<int>(){ 1, 3, 5, 7, 9 });
static void Test(List<int> lst)
{
Console.Write("Séquence : "); Afficher(lst);
if(SontTous(lst, n => n % 2 == 0)) Console.WriteLine("Tous les éléments sont pairs");
if(AuMoinsUn(lst, n => n % 2 == 0)) Console.WriteLine("Au moins un élément est pair");
if(Aucun(lst, n => n % 2 == 0)) Console.WriteLine("Aucun élément n'est pair");
}
static void Afficher<T>(IEnumerable<T> elems)
{
foreach(T e in elems)
Console.Write($"{e} ");
Console.WriteLine();
}
// précondition : src n'est pas vide
static bool SontTous<T>(IEnumerable<T> src, Func<T, bool> pred)
{
foreach(T e in src)
if(!pred(e))
return false;
return true;
}
// précondition : src n'est pas vide
static bool AuMoinsUn<T>(IEnumerable<T> src, Func<T, bool> pred)
{
foreach(T e in src)
if(pred(e))
return true;
return false;
}
// précondition : src n'est pas vide
static bool Aucun<T>(IEnumerable<T> src, Func<T, bool> pred)
{
foreach(T e in src)
if(pred(e))
return false;
return true;
}
En ligne :
https://dotnetfiddle.net/6Iqkhg
Vous avec probablement remarqué que les trois derniers prédicats
sont... similaires. C'est parce que ce sont trois facettes d'une
même idée, comme le sont les opérateurs mathématiques (pour tout élément x, pred(x)
sera vrai), (il
existe un élément x tel que
pred(x) sera vrai) et (il n'existe pas d'élément x
tel que pred(x) sera vrai).
Ainsi, on peut exprimer deux d'entre eux en termes du troisième,
comme par exemple :
// précondition : src n'est pas vide
static bool SontTous<T>(IEnumerable<T> src, Func<T, bool> pred)
{
foreach(T e in src)
if(!pred(e))
return false;
return true;
}
// précondition : src n'est pas vide
static bool AuMoinsUn<T>(IEnumerable<T> src, Func<T, bool> pred) =>
!SontTous(src, e => !pred(e));
// précondition : src n'est pas vide
static bool Aucun<T>(IEnumerable<T> src, Func<T, bool> pred) =>
SontTous(src, e => !pred(e));
En ligne :
https://dotnetfiddle.net/Z2tUQG
- Solutionner les exercices de la séance
S11
Solution à S11.0 –
EstTrié<T>(List<T>)
using System;
using System.Collections.Generic;
Test(new List<int>());
Test(new List<int>(){ 3 });
Test(new List<int>(){ 1, 2, 3, 4 });
Test(new List<int>(){ 2, 6, 4 });
Test(new List<int>(){ 1, 3, 5, 7, 9 });
static void Test<T>(List<T> lst) where T : IComparable<T>
{
Console.Write(EstTrié(lst) ? "Séquence triée : " : "Séquence non triée : ");
Afficher(lst);
}
static void Afficher<T>(IEnumerable<T> elems)
{
foreach(T e in elems)
Console.Write($"{e} ");
Console.WriteLine();
}
static bool EstTrié<T>(List<T> lst) where T : IComparable<T>
{
for(int i = 1; i < lst.Count; ++i)
if(lst[i-1].CompareTo(lst[i]) > 0)
return false;
return true;
}
En ligne :
https://dotnetfiddle.net/7ZQToV
Solution à S11.1 –
EstTrié<T>(List<T>,Func<T,T,int>)
using System;
using System.Collections.Generic;
Test(new List<int>());
Test(new List<int>(){ 3 });
Test(new List<int>(){ 1, 2, 3, 4 });
Test(new List<int>(){ 2, 6, 4 });
Test(new List<int>(){ 1, 3, 5, 7, 9 });
static void Test<T>(List<T> lst) where T : IComparable<T>
{
Console.Write(EstTrié(lst, (a,b) => a.CompareTo(b)) ?
"Séquence triée : " : "Séquence non triée : ");
Afficher(lst);
}
static void Afficher<T>(IEnumerable<T> elems)
{
foreach(T e in elems)
Console.Write($"{e} ");
Console.WriteLine();
}
static bool EstTrié<T>(List<T> lst, Func<T,T,int> crit)
{
for(int i = 1; i < lst.Count; ++i)
if(crit(lst[i-1], lst[i]) > 0)
return false;
return true;
}
En ligne :
https://dotnetfiddle.net/mYuStz
Solution à S11.2 –
UnionEnsembliste<T>. Une solution possible serait :
using System;
using System.Collections.Generic;
List<int> a = new(){ 2,3,5,7,11 };
List<int> b = new(){ 1,2,3,4,5,6,7 };
Afficher(UnionEnsembliste(a, b));
Afficher(UnionEnsembliste(a, new List<int>()));
Afficher(UnionEnsembliste(new List<int>(), b));
static void Afficher<T>(IEnumerable<T> elems)
{
foreach(T e in elems)
Console.Write($"{e} ");
Console.WriteLine();
}
static List<T> UnionEnsembliste<T>(List<T> a, List<T> b) where T : IComparable<T>
{
List<T> dest = new();
int ia = 0, ib = 0;
for(; ia < a.Count && ib < b.Count; )
{
T e;
if(a[ia].CompareTo(b[ib]) < 0)
{
e = a[ia];
++ia;
}
else if(a[ia].CompareTo(b[ib]) > 0)
{
e = b[ib];
++ib;
}
else
{
e = a[ia]; // ou e = b[ib]; , au choix
++ia;
++ib;
}
if(dest.Count == 0 || dest[dest.Count - 1].CompareTo(e) != 0)
dest.Add(e);
}
// une seule des deux boucles fera du travail à ce stade
for(; ia < a.Count; ++ia)
if(dest.Count == 0 || dest[dest.Count - 1].CompareTo(a[ia]) != 0)
dest.Add(a[ia]);
for(; ib < b.Count; ++ib)
if(dest.Count == 0 || dest[dest.Count - 1].CompareTo(b[ib]) != 0)
dest.Add(b[ib]);
return dest;
}
En ligne :
https://dotnetfiddle.net/PifIhM
Cela dit, il y a beaucoup de répétition de code dans cette
implémentation, ce qui obscurcit un peu le propos. On peut la
retravailler un peu :
// ...
static List<T> UnionEnsembliste<T>(List<T> a, List<T> b) where T : IComparable<T>
{
List<T> dest = new();
int ia = 0, ib = 0;
static void AjouterFin(List<T> dest, T elem)
{
if(dest.Count == 0 || dest[dest.Count - 1].CompareTo(elem) != 0)
dest.Add(elem);
}
for(; ia < a.Count && ib < b.Count; )
{
if(a[ia].CompareTo(b[ib]) < 0)
AjouterFin(dest, a[ia++]);
else if(a[ia].CompareTo(b[ib]) > 0)
AjouterFin(dest, b[ib++]);
else
{
AjouterFin(dest, a[ia]); // ou AjouterFin(dest, b[ib]);
++ia;
++ib;
}
}
// une seule des deux boucles fera du travail à ce stade
for(; ia < a.Count; ++ia)
AjouterFin(dest, a[ia]);
for(; ib < b.Count; ++ib)
AjouterFin(dest, b[ib]);
return dest;
}
... ou on peut simplement être paresseuse ou paresseux et réutiliser SupprimerDoublons<T> :
using System;
using System.Collections.Generic;
List<int> a = new(){ 2,3,5,7,11 };
List<int> b = new(){ 1,2,3,4,5,6,7 };
Afficher(UnionEnsembliste(a, b));
Afficher(UnionEnsembliste(a, new List<int>()));
Afficher(UnionEnsembliste(new List<int>(), b));
static void Afficher<T>(IEnumerable<T> elems)
{
foreach(T e in elems)
Console.Write($"{e} ");
Console.WriteLine();
}
static List<T> UnionEnsembliste<T>(List<T> a, List<T> b)
where T : IComparable<T>, IEquatable<T>
{
List<T> dest = Concaténer(a, b); // voir S08
dest.Sort();
return SupprimerDoublons(dest); // voir S08
}
Pour une autre version, cette fois en ligne :
https://dotnetfiddle.net/VgwFOR
Solution à S11.3 –
IntersectionEnsembliste<T>
using System;
using System.Collections.Generic;
List<int> a = new(){ 2,3,5,7,11 };
List<int> b = new(){ 1,2,3,4,5,6,7 };
Afficher(IntersectionEnsembliste(a, b));
Afficher(IntersectionEnsembliste(a, new List<int>()));
Afficher(IntersectionEnsembliste(new List<int>(), b));
static void Afficher<T>(IEnumerable<T> elems)
{
foreach(T e in elems)
Console.Write($"{e} ");
Console.WriteLine();
}
// précondition : a est triée
// précondition : b est triée
static List<T> IntersectionEnsembliste<T>(List<T> a, List<T> b)
where T : IComparable<T>
{
List<T> dest = new();
for(int ia = 0, ib = 0; ia != a.Count && ib != b.Count; )
{
int res = a[ia].CompareTo(b[ib]);
if(res < 0)
++ia;
else if (res > 0)
++ib;
else if(dest.Count == 0 || dest[dest.Count - 1].CompareTo(a[ia]) != 0)
{
dest.Add(a[ia]); // ou b[ib]
++ia; ++ib;
}
}
return dest;
}
En ligne :
https://dotnetfiddle.net/PnRZfU
|
|
S13 |
Au menu :
- Premier contact avec la programmation parallèle et concurrente en
C#
- Discussion sur la
Cache
- Discussion sur le
faux-partage
L'exemple utilisé en classe pour illustrer le
faux-partage ressemblait à :
using System;
using System.Threading;
using System.Diagnostics;
const int N = 25_000;
var tab = CréerTableau(N * N);
for(int i = 1; i <= 16; ++i)
{
var (r0, dt0) = Tester(() => CompterSiMT(tab, n => n % 2 != 0, i));
if(i < 10) // bof, mais je ne me souviens plus du code de formatage...
Console.WriteLine($"Compté {r0} impairs avec {i} fils en {dt0} ms");
else
Console.WriteLine($"Compté {r0} impairs avec {i} fils en {dt0} ms");
}
static short[] CréerTableau(int n)
{
short[] tab = new short[n];
for (int i = 0; i != tab.Length; ++i)
tab[i] = (short)(i * 2 + 1);
return tab;
}
static (T rés, long dt) Tester<T>(Func<T> f)
{
var sw = new Stopwatch();
sw.Start();
T rés = f();
sw.Stop();
return (rés, sw.ElapsedMilliseconds);
}
static int CompterSiMT(short[] tab, Func<short, bool> pred, int nthrs)
{
var thrs = new Thread[nthrs-1]; // initialisés à null en C#
var nimpairs = new int[nthrs]; // initialisés à 0 en C#
int tailleBloc = tab.Length / nthrs;
for(int i = 0; i < thrs.Length; ++i)
{
int monIndice = i;
int début = i * tailleBloc; // inclus
int fin = (i + 1) * tailleBloc; // exclue
thrs[i] = new Thread(() =>
{
int m = 0;
for (; début != fin; ++début)
if (pred(tab[début]))
++m;
nimpairs[monIndice] = m;
//for (; début != fin; ++début)
// if (pred(tab[début]))
// ++nimpairs[monIndice];
});
}
foreach (var th in thrs)
th.Start();
{
int début = (nthrs - 1) * tailleBloc; // inclus
int fin = tab.Length; // exclue
int m = 0;
for (; début != fin; ++début)
if (pred(tab[début]))
++m;
nimpairs[nthrs - 1] = m;
//for (; début != fin; ++début)
// if (pred(tab[début]))
// ++nimpairs[nthrs - 1];
}
foreach (var th in thrs)
th.Join();
int cumul = 0;
foreach (int n in nimpairs)
cumul += n;
return cumul;
}
Petit exemple inspiré de celui donné en classe (voir
https://dotnetfiddle.net/hbR0Iv
pour une version en-ligne) :
const int N = 1_000_000;
var (r0,dt0) = Test(() =>
{
int n = 0;
var th0 = new Thread(() =>
{
for (int i = 0; i != N; ++i)
++n;
});
var th1 = new Thread(() =>
{
for (int i = 0; i != N; ++i)
++n;
});
th0.Start();
th1.Start();
th1.Join();
th0.Join();
return n;
});
var (r1, dt1) = Test(() =>
{
int n = 0;
var mutex = new object();
var th0 = new Thread(() =>
{
for (int i = 0; i != N; ++i)
lock (mutex)
{
++n;
}
});
var th1 = new Thread(() =>
{
for (int i = 0; i != N; ++i)
lock (mutex)
{
++n;
}
});
th0.Start();
th1.Start();
th1.Join();
th0.Join();
return n;
});
var (r2, dt2) = Test(() =>
{
int n = 0;
var mutex = new object();
var th0 = new Thread(() =>
{
int m = 0;
for (int i = 0; i != N; ++i)
++m;
lock (mutex)
{
n += m;
}
});
var th1 = new Thread(() =>
{
int m = 0;
for (int i = 0; i != N; ++i)
++m;
lock (mutex)
{
n += m;
}
});
th0.Start();
th1.Start();
th1.Join();
th0.Join();
return n;
});
Console.WriteLine($"Sans synchro : {r0} obtenu en {dt0} tics");
Console.WriteLine($"Avec synchro : {r1} obtenu en {dt1} tics");
Console.WriteLine($"Avec synchro : {r2} obtenu en {dt2} tics");
static (T,long) Test<T>(Func<T> f)
{
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
T res = f();
sw.Stop();
return (res, sw.ElapsedTicks);
}
Petit exemple de code qui devrait être rapide mais ne l'est pas... même s'il donne la bonne réponse!
(voir https://dotnetfiddle.net/UL4JLB
pour une version en ligne mais qui est moins gourmande en mémoire car il y a des
limites à ce site) :
const int N = 25_000;
var tab = CréerTableau(N * N);
var (r0, dt0) = Tester(() => CompterMT(1, tab));
Console.WriteLine($"1 fil : compté {r0} impairs en {dt0} ms");
var (r1, dt1) = Tester(() => CompterMT(2, tab));
Console.WriteLine($"2 fils : compté {r1} impairs en {dt1} ms");
var (r2, dt2) = Tester(() => CompterMT(4, tab));
Console.WriteLine($"4 fils : compté {r2} impairs en {dt2} ms");
var (r3, dt3) = Tester(() => CompterMT(8, tab));
Console.WriteLine($"8 fils : compté {r3} impairs en {dt3} ms");
static short[] CréerTableau(int n)
{
short[] tab = new short[n];
for (int i = 0; i != tab.Length; ++i)
tab[i] = (short)(i * 2 + 1);
return tab;
}
static int CompterSi<T>(T[] tab, Func<T, bool> pred, int début, int fin) // début inclus, fin exclue
{
int n = 0;
for (int i = début; i != fin; ++i)
if (pred(tab[i]))
++n;
return n;
}
static (T rés, long dt) Tester<T>(Func<T> f)
{
var sw = new Stopwatch();
sw.Start();
T rés = f();
sw.Stop();
return (rés, sw.ElapsedMilliseconds);
}
static int CompterMT(int nbThreads, short [] tab)
{
int[] nbImpairs = new int[nbThreads]; // initialisé à 0 en C#
int tailleBloc = tab.Length / nbThreads;
Thread[] thrs = new Thread[nbThreads - 1];
for(int i = 0; i != thrs.Length; ++i)
{
int index = i;
int début = index * tailleBloc;
int fin = début + tailleBloc;
thrs[index] = new Thread(() =>
{
for (int j = début; j != fin; ++j)
if (tab[j] % 2 != 0)
++nbImpairs[index];
});
}
foreach (var th in thrs) th.Start();
////
{
int début = (nbThreads - 1) * tailleBloc;
int fin = tab.Length;
for (int j = début; j != fin; ++j)
if (tab[j] % 2 != 0)
++nbImpairs[nbThreads - 1];
}
////
foreach (var th in thrs) th.Join();
int somme = 0;
foreach (int n in nbImpairs)
somme += n;
return somme;
}
Petit exemple de code qui devrait être rapide et l'est... avec un tout petit changement!
(voir https://dotnetfiddle.net/Jxerel
pour une version en ligne, mais qui est moins gourmande en mémoire) :
const int N = 25_000;
var tab = CréerTableau(N * N);
for(int i = 1; i <= 16; ++i)
{
var (r, dt) = Tester(() => CompterMT(i, tab));
Console.WriteLine($"{i} fil(s) : compté {r} impairs en {dt} ms");
}
static short[] CréerTableau(int n)
{
short[] tab = new short[n];
for (int i = 0; i != tab.Length; ++i)
tab[i] = (short)(i * 2 + 1);
return tab;
}
static int CompterSi<T>(T[] tab, Func<T, bool> pred, int début, int fin) // début inclus, fin exclue
{
int n = 0;
for (int i = début; i != fin; ++i)
if (pred(tab[i]))
++n;
return n;
}
static (T rés, long dt) Tester<T>(Func<T> f)
{
var sw = new Stopwatch();
sw.Start();
T rés = f();
sw.Stop();
return (rés, sw.ElapsedMilliseconds);
}
static int CompterMT(int nbThreads, short [] tab)
{
int[] nbImpairs = new int[nbThreads]; // initialisé à 0 en C#
int tailleBloc = tab.Length / nbThreads;
Thread[] thrs = new Thread[nbThreads - 1];
for(int i = 0; i != thrs.Length; ++i)
{
int index = i;
int début = index * tailleBloc;
int fin = début + tailleBloc;
thrs[index] = new Thread(() =>
{
int nb = 0;
for (int j = début; j != fin; ++j)
if (tab[j] % 2 != 0)
nb++;
nbImpairs[index] = nb;
});
}
foreach (var th in thrs) th.Start();
////
{
int début = (nbThreads - 1) * tailleBloc;
int fin = tab.Length;
int nb = 0;
for (int j = début; j != fin; ++j)
if (tab[j] % 2 != 0)
++nb;
nbImpairs[nbThreads - 1] = nb;
}
////
foreach (var th in thrs) th.Join();
int somme = 0;
foreach (int n in nbImpairs)
somme += n;
return somme;
}
|
|
S14 |
Au menu : à venir |
|
S15 |
Au menu : à venir |
|
S16 |
Au menu : à venir |
|
S17 |
Au menu : à venir |
|
S18 |
Au menu : à venir |
|
S19 |
Au menu : à venir |
|
S20 |
Au menu : à venir |
|
S21 |
Au menu : à venir |
|
S22 |
Au menu : à venir |
|
S23 |
Au menu : à venir |
|
S24 |
Au menu : à venir |
|
S25 |
Au menu : à venir |
|
S26 |
Au menu : à venir |
|
S27 |
Au menu : à venir |
|
S28 |
Au menu : à venir |
|
S29 |
Au menu :
- Chic examen final
- Vous avez droit à toute documentation écrite (pas
d'ordinateur, pas d'IA générative, pas d'ami(e)s, pas d'Internet)
|
Vous trouverez ici quelques documents, la plupart petits, qui peuvent vous
donner un petit coup de pouce occasionnel.
Les consignes des travaux pratiques suivent.