Les enregistrements

4SI

Objectifs

À la fin de ce chapitre, l’élève doit être capable de :

  • reconnaître les situations où les structures déjà connues ne suffisent plus ;
  • définir un nouveau type groupant des données de natures différentes ;
  • déclarer ce type et les objets qui en relèvent, en algorithmique et en Python ;
  • accéder à une donnée élémentaire à l’intérieur d’une structure, même emboîtée ;
  • construire et parcourir une collection de telles structures ;
  • ranger cette collection selon l’une quelconque de ses données.

Plan du chapitre

  1. Introduction
  2. L’enregistrement
  3. L’enregistrement imbriqué
  4. Le tableau d’enregistrements
  5. Les enregistrements en Python
  6. Les erreurs les plus fréquentes
  7. Retenons

Rappel de 3ème SI. Les parties 1, 2, 3.1, 3.2 et 4 reprennent le cours de 3ème. Nouveau en 4ème : la comparaison de deux dates (paragraphe 3.3) et les questions des applications qui l’utilisent.

1. Introduction

Activité 1

Un professeur veut traiter par programme les informations de sa classe. Pour chaque élève, il doit retenir un numéro, un nom, un groupe, une moyenne, et le fait que l’élève soit interne ou externe.

Avec les seules structures étudiées jusqu’ici, il déclare cinq tableaux :

Tableau012345
Num1272341830
NomYassine TrabelsiNour BouzidMehdi ChaabaneRania JendoubiSkander AyariInes Gharbi
Groupe"A""B""A""C""B""A"
Moyenne13.5016.259.7512.5017.7511.25
InterneVraiFauxFauxVraiFauxVrai

Questions

  1. Pourquoi le professeur ne peut-il pas ranger ces cinq informations dans un seul tableau ?
  2. Le professeur veut classer ses élèves par moyenne croissante. Il trie le tableau Moyenne. Que devient son programme ?
  3. Que faudrait-il pour que les cinq informations d’un même élève restent liées entre elles ?

Réponse

Un tableau ne contient que des éléments de même type. Or le numéro est un entier, le nom une chaîne de caractères, le groupe un caractère, la moyenne un réel et le statut un booléen : cinq types différents, donc cinq tableaux.

Si le professeur trie le seul tableau Moyenne, les quatre autres ne bougent pas. La moyenne de l’élève placé en première position n’est plus la sienne : elle se retrouve en face du nom d’un autre. Le programme continue de fonctionner et n’affiche aucune erreur, mais toutes les informations sont fausses.

Pour que les cinq informations restent liées, il faudrait qu’elles forment un seul élément, que l’on déplace d’un bloc. C’est ce que permet l’enregistrement.

En détail

Ces cinq tableaux sont dits parallèles : l’élève d’indice i est décrit par Num[i], Nom[i], Groupe[i], Moyenne[i] et Interne[i]. Rien dans le programme ne garantit ce parallélisme ; il repose entièrement sur la discipline du programmeur.

2. L’enregistrement

2.1 Définition

Définition. Un enregistrement est un type de données défini par l’utilisateur, qui groupe un nombre fini de données appelées champs, de types éventuellement différents.

Chaque champ porte un nom et un type. Un champ a exactement les mêmes propriétés qu’une variable du même type : on peut l’affecter, le lire, l’afficher et l’utiliser dans un calcul.

L’enregistrement se distingue du tableau sur deux points : ses éléments peuvent être de types différents, et on les désigne par un nom, non par un indice.

TableauEnregistrement
Types des élémentstous identiqueséventuellement différents
Désignation d’un élémentpar un indice, T[i]par un nom, E.Champ
Nombre d’élémentsfixé à la déclarationfixé à la déclaration

2.2 Déclaration

Le type enregistrement n’existe pas d’avance : c’est l’utilisateur qui le crée. Deux formes de déclaration sont possibles.

Première forme : l’objet seul. Lorsqu’un seul objet est concerné et qu’aucun autre objet ne s’y réfère, l’enregistrement se déclare directement dans le tableau de déclaration des objets.

Syntaxe — tableau de déclaration des objets

ObjetType / Nature
nom_objetEnregistrement
    Nom_champ1 : type champ1
    Nom_champ2 : type champ2
    …
Fin

Seconde forme : un nouveau type. Dès qu’il faut un tableau de tels enregistrements, ou qu’un module en reçoit un en paramètre, le type doit porter un nom : il se déclare alors dans le tableau de déclaration des nouveaux types.

Syntaxe

Tableau de déclaration des nouveaux types

Nouveau type
Nom_type = Enregistrement
               Nom_champ1 : type champ1
               Nom_champ2 : type champ2
               …
           Fin

Tableau de déclaration des objets

ObjetType / Nature
nom_objetNom_type

Exemple. Le type Eleve décrit un élève de la classe.

TDNT

Nouveau type
Eleve = Enregistrement
            Num : Entier
            Nom : Chaîne de caractères
            Groupe : Caractère
            Moyenne : Réel
            Interne : Booléen
        Fin

TDO

ObjetType / Nature
EEleve

Remarque importante. Un type se déclare avant d’être utilisé : le tableau de déclaration des nouveaux types précède toujours le tableau de déclaration des objets.

En détail

Le champ Num est un numéro d’ordre dans la classe, compris entre 1 et 35 : c’est un entier, et il le reste. La règle est différente pour un numéro de carte d’identité, qui est un identifiant : on ne fait aucun calcul dessus, et un numéro commençant par zéro perdrait ce premier chiffre s’il était déclaré comme entier. Un tel numéro se déclare en chaîne de caractères.

2.3 Accès à un champ

Un champ se désigne par le nom de l’objet, un point, puis le nom du champ.

Syntaxe algorithmique

nom_objet.nom_champ

Cette écriture désigne une variable ordinaire, du type du champ. Elle s’emploie partout où une variable de ce type est permise.

E.Num ← 12
E.Nom ← "Yassine Trabelsi"
E.Groupe ← "A"
E.Moyenne ← 13.50
E.Interne ← Vrai

Remarque importante. Le point est obligatoire. Écrit seul, Num désigne une variable qui n’existe pas ; écrit seul, E désigne l’enregistrement entier.

2.4 Ce qui se fait champ par champ

Trois opérations ne s’appliquent jamais à un enregistrement entier : la lecture, l’affichage et la comparaison.

Lire et afficher

Chaque champ se lit et s’affiche séparément, comme une variable de son type.

Écrire("Numéro : ")
Lire(E.Num)
Écrire("Nom et prénom : ")
Lire(E.Nom)
Écrire("Moyenne : ")
Lire(E.Moyenne)
Écrire("Élève n° ", E.Num, " : ", E.Nom, ", moyenne ", E.Moyenne)

Lire(E) et Écrire(E) n’ont pas de sens : chaque champ a son type, donc son format de saisie et d’affichage.

Comparer

Deux enregistrements ne se comparent pas : E1 > E2, E1 < E2 et E1 = E2 n’ont pas de sens. Dire qu’un élève est « supérieur » à un autre ne signifie rien tant qu’on n’a pas dit sur quoi porte la comparaison.

On compare donc selon un champ, choisi en fonction de la question posée.

Si E1.Moyenne > E2.Moyenne Alors
    Écrire(E1.Nom, " a la meilleure moyenne")
Sinon
    Écrire(E2.Nom, " a la meilleure moyenne")
FinSi

Le champ choisi change la réponse. Sur la moyenne, Nour Bouzid, 16.25, l’emporte sur Yassine Trabelsi, 13.50 ; sur le numéro, 12 contre 7, c’est Yassine Trabelsi qui a le plus grand.

Il arrive qu’un seul champ ne suffise pas, parce que la réponse dépend de plusieurs champs à la fois. On les examine alors l’un après l’autre, du plus important au moins important. La comparaison de deux dates, au paragraphe 3.3, en est l’exemple type.

Remarque importante. On ne compare jamais deux enregistrements, on compare leurs champs : un seul lorsqu’il suffit à répondre, plusieurs, pris l’un après l’autre, lorsqu’il ne suffit pas.

2.5 Ce qui se fait d’un bloc : l’affectation

Deux enregistrements de même type s’affectent en une seule instruction. Tous les champs sont recopiés.

Syntaxe algorithmique

E1 ← E2

L’instruction E1 ← E2 équivaut à recopier les champs un à un :

E1.Num ← E2.Num
E1.Nom ← E2.Nom
E1.Groupe ← E2.Groupe
E1.Moyenne ← E2.Moyenne
E1.Interne ← E2.Interne

C’est cette possibilité qui rend l’échange de deux élèves possible dans un tri.

Remarque importante. Les deux enregistrements doivent être exactement du même type. Deux enregistrements qui auraient les mêmes champs sous deux noms de types différents ne s’affectent pas l’un à l’autre.

Opération sur un enregistrement entierPermise
Affecter, E1 ← E2oui, entre deux enregistrements de même type
Lire, Lire(E)non, champ par champ
Afficher, Écrire(E)non, champ par champ
Comparer, E1 > E2, E1 = E2non, selon un champ ou plusieurs champs

3. L’enregistrement imbriqué

3.1 Principe et déclaration

Un champ peut lui-même être un enregistrement. On parle alors d’enregistrement imbriqué.

Une date de naissance est composée de trois entiers : un jour, un mois et une année. Ces trois entiers n’ont de sens qu’ensemble. Les déclarer comme trois champs séparés de Eleve serait possible, mais on perdrait la possibilité de traiter la date comme un tout.

Remarque importante. Le type du champ se déclare avant le type qui l’utilise. Dans le tableau de déclaration des nouveaux types, Date précède donc Eleve.

TDNT

Nouveau type
Date = Enregistrement
           Jour, Mois, Annee : Entier
       Fin
Eleve = Enregistrement
            Num : Entier
            Nom : Chaîne de caractères
            Groupe : Caractère
            Moyenne : Réel
            Interne : Booléen
            Naissance : Date
        Fin

C’est la déclaration définitive du chapitre : toutes les parties suivantes l’utilisent.

En détail

La règle « un type se déclare avant d’être utilisé » s’applique donc deux fois : entre le tableau des nouveaux types et celui des objets, et à l’intérieur même du tableau des nouveaux types. Déclarer Eleve avant Date est une erreur sanctionnée.

3.2 Accès en cascade

On descend d’un niveau par point, autant de fois qu’il y a de niveaux.

ÉcritureDésigneType obtenu
E.Nomle nom de l’élèveChaîne de caractères
E.Naissancela date de naissance entièreDate
E.Naissance.Anneel’année de naissanceEntier
E.Naissance.Jour ← 15
E.Naissance.Mois ← 3
E.Naissance.Annee ← 2009
Écrire("Né en ", E.Naissance.Annee)

E.Naissance est un enregistrement de type Date. Les règles du paragraphe 2.4 s’y appliquent : il s’affecte d’un bloc, E1.Naissance ← E2.Naissance, mais il ne se lit, ne s’affiche et ne se compare que champ par champ.

3.3 Comparer deux dates

Pour savoir lequel de deux élèves est le plus âgé, un seul champ de la date ne suffit pas. L’année ne départage pas deux élèves nés la même année ; le mois ne départage pas deux élèves nés le même mois de la même année. C’est le cas annoncé au paragraphe 2.4 : plusieurs champs décident, et on les examine l’un après l’autre, du plus significatif au moins significatif.

Les instructions suivantes rangent dans la variable booléenne avant la réponse à la question « E1 est-il né avant E2 ? ».

Si E1.Naissance.Annee ≠ E2.Naissance.Annee Alors
    avant ← E1.Naissance.Annee < E2.Naissance.Annee
Sinon
    Si E1.Naissance.Mois ≠ E2.Naissance.Mois Alors
        avant ← E1.Naissance.Mois < E2.Naissance.Mois
    Sinon
        avant ← E1.Naissance.Jour < E2.Naissance.Jour
    FinSi
FinSi

Le raisonnement est toujours le même : si le champ le plus significatif diffère, c’est lui qui décide ; sinon, on passe au suivant.

Autre écriture. La même réponse s’obtient en une seule condition, qui énumère les trois façons d’être né avant : une année plus petite, ou la même année et un mois plus petit, ou la même année, le même mois et un jour plus petit.

avant ← (E1.Naissance.Annee < E2.Naissance.Annee)
        Ou ((E1.Naissance.Annee = E2.Naissance.Annee)
            Et (E1.Naissance.Mois < E2.Naissance.Mois))
        Ou ((E1.Naissance.Annee = E2.Naissance.Annee)
            Et (E1.Naissance.Mois = E2.Naissance.Mois)
            Et (E1.Naissance.Jour < E2.Naissance.Jour))

Les deux écritures donnent toujours le même résultat. Dans la seconde, chaque comparaison est entre parenthèses : Et est prioritaire sur = et sur <.

Exemple.

Naissance de E1Naissance de E2Champ qui décideavant
28/07/200815/03/2009l’annéeVrai
15/03/200902/11/2009le moisVrai
20/07/200828/07/2008le jourVrai
15/03/200915/03/2009le jourFaux
02/11/200915/03/2009le moisFaux

Remarque importante. Comparer les trois champs séparément est faux. L’expression (D1.Annee < D2.Annee) Et (D1.Mois < D2.Mois) Et (D1.Jour < D2.Jour) rend Faux pour les dates 28/07/2008 et 15/03/2009, alors que la première est bien antérieure à la seconde.

En détail

La comparaison est stricte : deux dates égales donnent Faux. Deux élèves nés le même jour ne sont nés ni l’un avant l’autre.

La variable avant reçoit à chaque fois le résultat d’une comparaison, jamais Vrai ou Faux écrits en dur.

L’élève le plus âgé est celui dont la date de naissance est la plus ancienne. Les deux superlatifs sont inversés l’un par rapport à l’autre, et c’est une source d’erreur fréquente.

4. Le tableau d’enregistrements

4.1 Principe et déclaration

Un tableau ne contient que des éléments de même type, et le type Eleve en est un : un tableau d’enregistrements est donc un tableau ordinaire, dont chaque case contient un enregistrement entier.

Chaque case contient les six champs d’un élève. Le champ Naissance, lui-même un enregistrement, est encadré à l’intérieur de la case.

Syntaxe — tableau de déclaration des nouveaux types

nom_type_tableau = Tableau de n nom_type_enregistrement

TDNT — à la suite de Date et de Eleve :

Nouveau type
TabEleve = Tableau de 35 Eleve

TDO

ObjetType / Nature
TTabEleve
NEntier

La taille déclarée est 35, l’effectif maximal d’une classe. La taille effective N est le nombre d’élèves réellement saisis : 1 ≤ N ≤ 35. Tous les traitements portent sur les indices 0 à N - 1.

4.2 Accès à un champ

On choisit d’abord quel élève, ensuite quelle information. L’indice vient donc avant le point.

ÉcritureDésigne
T[i]l’élève de rang i, en entier
T[i].Moyennela moyenne de l’élève de rang i
T[i].Naissance.Anneel’année de naissance de l’élève de rang i

Remarque importante. L’ordre ne s’inverse jamais. T.Moyenne[i] n’existe pas : T n’a pas de champ Moyenne, ce sont ses éléments qui en ont un.

Exemple de référence. Les six élèves qui suivent servent à vérifier tous les modules de ce chapitre. Ici N vaut 6.

Les valeurs de chaque case suivent l’ordre des champs du TDNT : Num, Nom, Groupe, Moyenne, Interne, puis les trois champs de Naissance.

4.3 Le remplissage

Deux écritures sont possibles, et toutes deux sont justes. La première confie la saisie d’un élève à un module séparé ; la seconde écrit cette saisie directement dans la boucle.

Première écriture : un module de saisie

Un module parcourt le tableau, un autre saisit un élève.

Procédure Remplir (@T : TabEleve, N : Entier)
DEBUT
    Pour i de 0 à N - 1 Faire
        Saisir_Eleve(T[i])
    Fin Pour
FIN

TDOL

ObjetType / Nature
iEntier
Saisir_EleveProcédure
Procédure Saisir_Eleve (@E : Eleve)
DEBUT
    Répéter
        Écrire("Numéro : ")
        Lire(E.Num)
    Jusqu'à (E.Num ≥ 1) Et (E.Num ≤ 35)
    Écrire("Nom et prénom : ")
    Lire(E.Nom)
    Répéter
        Écrire("Groupe (A à D) : ")
        Lire(E.Groupe)
    Jusqu'à E.Groupe ∈ ["A".."D"]
    Répéter
        Écrire("Moyenne : ")
        Lire(E.Moyenne)
    Jusqu'à (E.Moyenne ≥ 0) Et (E.Moyenne ≤ 20)
    Écrire("Interne (Vrai / Faux) : ")
    Lire(E.Interne)
    Écrire("Jour de naissance : ")
    Lire(E.Naissance.Jour)
    Écrire("Mois de naissance : ")
    Lire(E.Naissance.Mois)
    Écrire("Année de naissance : ")
    Lire(E.Naissance.Annee)
FIN

N.B. On suppose la date de naissance saisie valide.

Remarque importante. Un champ imbriqué se saisit champ terminal par champ terminal, avec son chemin complet : Lire(E.Naissance.Jour), et non Lire(E.Naissance).

En détail

Les deux procédures sont en passage par référence, @, puisqu’elles modifient ce qu’elles reçoivent. T[i] est passé directement à Saisir_Eleve : c’est un Eleve complet, et c’est bien lui qui est rempli, non une copie.

Les bornes des contrôles de saisie viennent toutes de la situation : 35 élèves au plus dans une classe, quatre groupes de A à D, une moyenne entre 0 et 20.

Seconde écriture : la saisie dans la boucle

La même saisie s’écrit aussi sans module séparé. Le corps de Saisir_Eleve devient le corps de la boucle, et E y est remplacé par T[i].

Procédure Remplir (@T : TabEleve, N : Entier)
DEBUT
    Pour i de 0 à N - 1 Faire
        Répéter
            Écrire("Numéro : ")
            Lire(T[i].Num)
        Jusqu'à (T[i].Num ≥ 1) Et (T[i].Num ≤ 35)
        Écrire("Nom et prénom : ")
        Lire(T[i].Nom)
        // les autres champs se saisissent de la même façon, sur T[i]
    Fin Pour
FIN

TDOL

ObjetType / Nature
iEntier

Laquelle choisir

SituationÉcriture
La saisie d’un élève ne sert qu’au remplissagela seconde, tout dans un module
La saisie d’un élève sert ailleurs, par exemple pour ajouter un élève seulla première, avec un module dédié
L’énoncé demande explicitement un module de saisiela première

Remarque importante. Un module séparé n’est jamais obligatoire par principe. Il le devient lorsque le même traitement sert à deux endroits, ou lorsque l’énoncé le demande.

Application résolue 1 — La classe

On reprend la classe du paragraphe 4.2 : le tableau T contient les N élèves de la classe, déclarés avec les types Date, Eleve et TabEleve du chapitre.

Travail demandé

  1. Écrire l’algorithme de la fonction Nb_Internes(T, N) qui retourne le nombre d’élèves internes.
  2. Écrire l’algorithme de la fonction Nb_Groupe(T, N, g) qui retourne le nombre d’élèves du groupe g.
  3. Écrire l’algorithme de la fonction Moyenne_Classe(T, N) qui retourne la moyenne de la classe.
  4. Écrire l’algorithme de la fonction Position(T, N, x) qui retourne la position de l’élève de numéro x, et -1 si aucun élève ne porte ce numéro.
  5. Écrire l’algorithme de la fonction Meilleur(T, N) qui retourne la position de l’élève qui a la meilleure moyenne.
  6. Écrire l’algorithme de la fonction Plus_Age(T, N) qui retourne la position de l’élève le plus âgé.
  7. Écrire l’algorithme de la procédure Trier_Moyenne(T, N) qui range les élèves par ordre croissant de leur moyenne.
  8. Un élève propose de ranger la classe du plus âgé au plus jeune en triant sur la seule année de naissance. Écrire l’algorithme de ce tri, puis montrer sur l’exemple qu’il ne répond pas à la question.
  9. Écrire l’algorithme de la procédure Trier_Age(T, N) qui range les élèves du plus âgé au plus jeune.

N.B. Le tableau T est supposé déjà rempli. Les numéros, les moyennes et les dates de naissance sont deux à deux distincts.

Voir le corrigé

1. Nb_Internes

On parcourt tout le tableau et l’on compte les élèves qui vérifient la condition.

Fonction Nb_Internes (T : TabEleve, N : Entier) : Entier
DEBUT
    nb ← 0
    Pour i de 0 à N - 1 Faire
        Si T[i].Interne Alors
            nb ← nb + 1
        FinSi
    Fin Pour
    Retourner nb
FIN

TDOL

ObjetType / Nature
nb, iEntier

Sur l’exemple, cette fonction retourne 3.

En détail

Le champ Interne est déjà un booléen : il est à lui seul la condition du Si. L’écriture Si T[i].Interne = Vrai Alors donne le même résultat, mais compare un booléen à un booléen pour obtenir un booléen.

Le nombre d’élèves à examiner est connu, N : la boucle est un Pour.

2. Nb_Groupe

Seule la condition change par rapport à la question 1. Le groupe cherché est reçu en paramètre.

Fonction Nb_Groupe (T : TabEleve, N : Entier, g : Caractère) : Entier
DEBUT
    nb ← 0
    Pour i de 0 à N - 1 Faire
        Si T[i].Groupe = g Alors
            nb ← nb + 1
        FinSi
    Fin Pour
    Retourner nb
FIN

TDOL

ObjetType / Nature
nb, iEntier

Sur l’exemple, Nb_Groupe(T, 6, "A") retourne 3.

En détail

Le groupe cherché est un paramètre, non une valeur écrite dans l’algorithme. Une fonction qui ne saurait compter que le groupe "A" devrait être réécrite pour chacun des trois autres.

3. Moyenne_Classe

On cumule les moyennes dans un accumulateur, puis on divise par l’effectif.

Fonction Moyenne_Classe (T : TabEleve, N : Entier) : Réel
DEBUT
    s ← 0
    Pour i de 0 à N - 1 Faire
        s ← s + T[i].Moyenne
    Fin Pour
    Retourner s / N
FIN

TDOL

ObjetType / Nature
sRéel
iEntier

Sur l’exemple, la somme des moyennes vaut 81.00 et la fonction retourne 13.5.

4. Position

On avance tant que l’élève n’est pas trouvé et que le tableau n’est pas épuisé.

Fonction Position (T : TabEleve, N, x : Entier) : Entier
DEBUT
    i ← 0
    Tant que (i < N) Et (T[i].Num ≠ x) Faire
        i ← i + 1
    Fin Tant que
    Si i < N Alors
        p ← i
    Sinon
        p ← -1
    FinSi
    Retourner p
FIN

TDOL

ObjetType / Nature
i, pEntier

Sur l’exemple, Position(T, 6, 23) retourne 2 et Position(T, 6, 99) retourne -1.

En détail

Contrairement aux trois questions précédentes, on ne sait pas d’avance combien d’élèves il faudra examiner : la recherche s’arrête dès que l’élève est trouvé. La boucle est donc un Tant que, et non un Pour.

La valeur -1 est une sentinelle : ce n’est pas un indice possible, elle peut donc porter l’échec de la recherche. Une seule fonction suffit là où il faudrait sinon un booléen pour dire si l’élève existe, et un entier pour dire où il est.

La condition d’arrêt est une conjonction, et l’ordre des deux tests n’est pas libre : i < N doit être écrit en premier, car T[i].Num n’a pas de sens lorsque i vaut N.

5. Meilleur

On garde la position du meilleur élève rencontré, et on la remplace chaque fois qu’on trouve mieux.

Fonction Meilleur (T : TabEleve, N : Entier) : Entier
DEBUT
    p ← 0
    Pour i de 1 à N - 1 Faire
        Si T[i].Moyenne > T[p].Moyenne Alors
            p ← i
        FinSi
    Fin Pour
    Retourner p
FIN

TDOL

ObjetType / Nature
p, iEntier

Sur l’exemple, cette fonction retourne 4 : Skander Ayari, dont la moyenne est 17.75.

Remarque importante. La fonction retourne une position, non une moyenne. Pour afficher le nom du meilleur élève, on écrit T[Meilleur(T, N)].Nom. La position donne accès à tous les champs de l’élève ; la valeur seule n’en donnerait aucun.

En détail

La position de référence est initialisée à 0, et la boucle part de 1 : on ne compare jamais le premier élément à lui-même. On n’initialise jamais avec une valeur écrite en dur : sur un champ pouvant être négatif, une initialisation à zéro donnerait un résultat qu’aucun élément ne porte.

6. Plus_Age

La question a la forme de la question 5, mais la comparaison porte sur une date : elle demande les trois champs, examinés l’un après l’autre, comme au paragraphe 3.3.

Cette même comparaison de dates servira de nouveau à la question 9. Écrite deux fois, elle serait deux fois à relire et deux fois à corriger : on la confie donc à une fonction, Avant, que les deux modules appelleront.

Fonction Plus_Age (T : TabEleve, N : Entier) : Entier
DEBUT
    p ← 0
    Pour i de 1 à N - 1 Faire
        Si Avant(T[i].Naissance, T[p].Naissance) Alors
            p ← i
        FinSi
    Fin Pour
    Retourner p
FIN

TDOL

ObjetType / Nature
p, iEntier
AvantFonction

La fonction Avant reçoit deux dates et rend Vrai si la première est strictement antérieure à la seconde. Son corps reprend les instructions du paragraphe 3.3, écrites sur deux paramètres de type Date.

Fonction Avant (D1, D2 : Date) : Booléen
DEBUT
    Si D1.Annee ≠ D2.Annee Alors
        test ← D1.Annee < D2.Annee
    Sinon
        Si D1.Mois ≠ D2.Mois Alors
            test ← D1.Mois < D2.Mois
        Sinon
            test ← D1.Jour < D2.Jour
        FinSi
    FinSi
    Retourner test
FIN

TDOL

ObjetType / Nature
testBooléen

Sur l’exemple, Plus_Age retourne 5 : Ines Gharbi, née le 20/07/2008.

En détail

Ines Gharbi et Mehdi Chaabane sont nés le même mois de la même année : c’est le jour qui les départage, 20 contre 28. La troisième branche de Avant est donc bien celle qui décide ici.

On cherche le plus âgé, donc la date la plus ancienne, donc celle pour laquelle Avant rend Vrai. C’est l’inversion signalée au paragraphe 3.3.

La fonction Avant ne comporte qu’un seul Retourner, placé après le dernier FinSi.

7. Trier_Moyenne

On emploie le tri à bulles optimisé. On teste sur le champ Moyenne, et l’on permute les élèves entiers.

Procédure Trier_Moyenne (@T : TabEleve, N : Entier)
DEBUT
    NC ← N
    Répéter
        Permut ← Faux
        Pour i de 0 à NC - 2 Faire
            Si T[i].Moyenne > T[i + 1].Moyenne Alors
                Aux ← T[i]
                T[i] ← T[i + 1]
                T[i + 1] ← Aux
                Permut ← Vrai
            FinSi
        Fin Pour
        NC ← NC - 1
    Jusqu'à Non(Permut) Ou (NC = 1)
FIN

TDOL

ObjetType / Nature
NC, iEntier
AuxEleve
PermutBooléen

Résultat sur l’exemple

Chaque case est restée entière : le numéro, le nom, le groupe et la date ont suivi la moyenne. C’est la réponse au problème posé par l’activité 1.

Remarque importante. On compare un champ, T[i].Moyenne, mais on échange l’enregistrement entier, T[i]. La variable Aux est donc de type Eleve, et non de type Réel. Déclarée Réel, elle ne pourrait recevoir T[i], et échanger les seules moyennes reproduirait exactement la dissociation des tableaux parallèles.

En détail

Le champ de comparaison n’a aucune influence sur la forme de l’algorithme. Trier par nom croissant demanderait T[i].Nom > T[i + 1].Nom, et rien d’autre ne changerait.

Les six moyennes de l’exemple sont deux à deux distinctes : c’est cette unicité qui rend le résultat unique. Sans elle, deux élèves de même moyenne pourraient se retrouver dans un ordre ou dans l’autre selon la méthode de tri employée.

8. Trier sur l’année de naissance

L’année de naissance est un entier : elle se compare directement, avec >, exactement comme la moyenne à la question 7. Seul le chemin d’accès est plus long.

Procédure Trier_Annee (@T : TabEleve, N : Entier)
DEBUT
    NC ← N
    Répéter
        Permut ← Faux
        Pour i de 0 à NC - 2 Faire
            Si T[i].Naissance.Annee > T[i + 1].Naissance.Annee Alors
                Aux ← T[i]
                T[i] ← T[i + 1]
                T[i + 1] ← Aux
                Permut ← Vrai
            FinSi
        Fin Pour
        NC ← NC - 1
    Jusqu'à Non(Permut) Ou (NC = 1)
FIN

TDOL

ObjetType / Nature
NC, iEntier
AuxEleve
PermutBooléen

Résultat sur l’exemple

Les années sont bien rangées. Mais Mehdi Chaabane, né le 28/07/2008, est placé avant Ines Gharbi, née le 20/07/2008, qui est pourtant plus âgée que lui de huit jours. L’année seule ne départage pas deux élèves nés la même année : ils restent dans leur ordre d’origine, qui n’a aucune raison d’être le bon. Ce tri ne répond donc pas à la question.

Remarque importante. Ce n’est pas l’imbrication qui oblige à écrire une fonction de comparaison : T[i].Naissance.Annee est un entier, et deux entiers se comparent directement. Ce qui l’oblige, c’est que la réponse dépend de plusieurs champs.

En détail

Le TDOL de Trier_Annee ne contient aucune fonction : la comparaison s’écrit dans la condition du Si. C’est ce qui le distingue de celui de Trier_Age, à la question suivante.

9. Trier_Age

Pour ranger les élèves du plus âgé au plus jeune, il faut comparer les dates entières. La condition du Si devient un appel à la fonction Avant de la question 6.

Procédure Trier_Age (@T : TabEleve, N : Entier)
DEBUT
    NC ← N
    Répéter
        Permut ← Faux
        Pour i de 0 à NC - 2 Faire
            Si Avant(T[i + 1].Naissance, T[i].Naissance) Alors
                Aux ← T[i]
                T[i] ← T[i + 1]
                T[i + 1] ← Aux
                Permut ← Vrai
            FinSi
        Fin Pour
        NC ← NC - 1
    Jusqu'à Non(Permut) Ou (NC = 1)
FIN

TDOL

ObjetType / Nature
NC, iEntier
AuxEleve
PermutBooléen
AvantFonction

Résultat sur l’exemple

Les deux premiers élèves ont changé de place par rapport à la question 8 : Ines Gharbi précède maintenant Mehdi Chaabane.

Ce que l’on compareCommentExemple
Un champ de type simpledirectement, avec <, > ou =T[i].Moyenne, T[i].Naissance.Annee
Une donnée qui dépend de plusieurs champschamp par champ, dans une fonction si la comparaison sert plusieurs foisAvant(T[i + 1].Naissance, T[i].Naissance)

En détail

Les paramètres de Avant sont donnés dans l’ordre T[i + 1] puis T[i], et non l’inverse : il faut permuter lorsque l’élément suivant est plus ancien que l’élément courant. Écrire Avant(T[i].Naissance, T[i + 1].Naissance) trierait du plus jeune au plus âgé.

Le TDOL de Trier_Age contient Avant, celui de Trier_Annee n’en contient aucune. Un module appelé figure au tableau de déclaration des objets locaux de celui qui l’appelle, et de lui seul.

5. Les enregistrements en Python

5.1 Le dictionnaire

Python ne possède pas de type enregistrement. Trois représentations sont possibles, et une seule convient.

ReprésentationVerdict
Le tupleses éléments ne peuvent pas être modifiés
La classehors programme de quatrième année
Le dictionnairec’est celui que nous retenons

Un dictionnaire est un ensemble de couples clé : valeur. Les clés sont uniques : elles jouent exactement le rôle des noms de champs.

5.2 Déclaration et accès

Syntaxe Python

nom_enregistrement = dict(champ1 = type1(), champ2 = type2(), …)

E = dict(Num = int(), Nom = str(), Groupe = str(),
         Moyenne = float(), Interne = bool())
Type algorithmiquePython
Entierint()
Réelfloat()
Caractère, Chaîne de caractèresstr()
Booléenbool()

Un champ se désigne par la clé entre crochets, et non par un point.

AlgorithmiquePython
E.Num ← 12E['Num'] = 12
Lire(E.Num)E['Num'] = int(input("Numéro : "))
Écrire(E.Num, E.Nom)print(E['Num'], E['Nom'])

Remarque importante. Python ne connaît pas le type caractère : un caractère est une chaîne d’un seul symbole. Le champ Groupe se déclare donc str(), comme le champ Nom.

Comme en algorithmique, un enregistrement ne s’affiche pas d’un seul coup : on affiche champ par champ, print(E['Nom'], E['Moyenne']).

En détail

Python accepte print(E), qui montre le dictionnaire brut, avec ses accolades et ses clés. Cette écriture n’est pas celle du programme officiel et ne s’écrit pas dans une copie. Elle sert seulement à vérifier son programme sur machine.

5.3 L’imbrication

Un champ peut être un dictionnaire. Deux écritures sont possibles, et donnent le même résultat.

# le type enfant d'abord, comme dans le TDNT
D = dict(Jour = int(), Mois = int(), Annee = int())
E = dict(Num = int(), Nom = str(), Naissance = D)

# ou directement
E = dict(Num = int(), Nom = str(),
         Naissance = dict(Jour = int(), Mois = int(), Annee = int()))

L’accès se fait en cascade, un couple de crochets par niveau.

AlgorithmiquePython
E.NaissanceE['Naissance']
E.Naissance.AnneeE['Naissance']['Annee']

5.4 Le tableau de dictionnaires

Un tableau d’enregistrements se représente par un tableau numpy dont chaque case reçoit un dictionnaire. Comme en algorithmique, on déclare 35 cases, et l’on n’en remplit que N.

AlgorithmiquePython
TabEleve = Tableau de 35 EleveT = array([dict()] * 35)

Remplir case par case

On donne à chaque case son propre dictionnaire, puis on remplit ses champs.

from numpy import array

T = array([dict()] * 35)
for i in range(N):
    T[i] = dict()
    T[i]['Num'] = int(input("Numéro : "))
    T[i]['Nom'] = input("Nom et prénom : ")
    T[i]['Groupe'] = input("Groupe (A à D) : ")
    while not (T[i]['Groupe'] in ["A", "B", "C", "D"]):
        T[i]['Groupe'] = input("Groupe (A à D) : ")

Remarque importante. La ligne T[i] = dict() placée dans la boucle est indispensable. Sans elle, les cases du tableau désignent toutes le même dictionnaire, et le tableau ne contient en réalité qu’un seul élève.

L’instruction array([dict()] * 35) ne crée qu’un seul dictionnaire, et place dans les 35 cases un accès à ce même dictionnaire. Modifier une case modifie donc toutes les autres :

T = array([dict()] * 3)
T[0]['Nom'] = "Yassine"
print(T[1]['Nom'], T[2]['Nom'])    # affiche : Yassine Yassine

Le nom n’a été rangé que dans T[0], et pourtant T[1] et T[2] le contiennent aussi. Avec la ligne T[i] = dict() dans la boucle, chaque case a son propre dictionnaire, et T[1] ne contient aucun champ 'Nom'.

Remplir un enregistrement, puis le ranger

On peut aussi remplir d’abord un dictionnaire E, puis le ranger dans la case. C’est la traduction de T[i] ← E.

T = array([dict()] * 35)
for i in range(N):
    E = dict(Num = int(), Nom = str())
    E['Num'] = int(input("Numéro : "))
    E['Nom'] = input("Nom et prénom : ")
    T[i] = E

Remarque importante. Cette écriture n’est juste que si E est créé dans la boucle, à chaque tour. Créé une seule fois avant la boucle, E serait le même dictionnaire dans toutes les cases : après la saisie de trois élèves, les trois cases contiendraient le dernier.

En détail

Le phénomène est le même dans les deux cas : en Python, ranger un dictionnaire dans une case, ou l’affecter à une autre variable, ne le recopie pas. Les deux noms désignent le même dictionnaire, et modifier l’un modifie l’autre.

C’est sans conséquence dans une permutation, comme celle d’un tri : Aux = T[i], T[i] = T[i + 1], T[i + 1] = Aux échangent simplement les dictionnaires désignés par les deux cases. C’est une erreur si l’on modifie l’un des deux noms en croyant l’autre intact.

Le même piège existe au niveau imbriqué : si la boucle réutilise un même dictionnaire D comme date de naissance, toutes les dates du tableau n’en font qu’une. Chaque enregistrement imbriqué doit recevoir son propre dict().

5.5 Les modules de l’application 1 en Python

def Moyenne_Classe(T, N):
    s = 0
    for i in range(N):
        s = s + T[i]['Moyenne']
    return s / N

def Position(T, N, x):
    i = 0
    while (i < N) and (T[i]['Num'] != x):
        i = i + 1
    if i < N:
        p = i
    else:
        p = -1
    return p

def Avant(D1, D2):
    if D1['Annee'] != D2['Annee']:
        test = D1['Annee'] < D2['Annee']
    else:
        if D1['Mois'] != D2['Mois']:
            test = D1['Mois'] < D2['Mois']
        else:
            test = D1['Jour'] < D2['Jour']
    return test

def Trier_Moyenne(T, N):
    NC = N
    Permut = True
    while Permut and (NC > 1):
        Permut = False
        for i in range(NC - 1):
            if T[i]['Moyenne'] > T[i + 1]['Moyenne']:
                Aux = T[i]
                T[i] = T[i + 1]
                T[i + 1] = Aux
                Permut = True
        NC = NC - 1

En détail

La procédure Trier_Moyenne ne rend rien : un tableau numpy est passé par référence en Python, et les échanges opérés dans la fonction modifient le tableau de l’appelant. C’est le comportement du @ algorithmique.

La boucle Répéter … Jusqu'à n’existe pas en Python. On l’émule par un while dont la variable de contrôle est initialisée à True, ce qui garantit un premier passage. On n’emploie jamais while True suivi d’un break.

Application résolue 2 — Le terrain de foot

Le gardien d’un terrain de foot de quartier note les réservations de la saison 2027. Chaque réservation est décrite par :

  • Numero : un entier, le numéro de la réservation ;
  • Client : une chaîne de caractères, le nom de la personne ou du club ;
  • Terrain : un caractère, "A" ou "B" ;
  • Duree : un entier, la durée réservée en minutes ;
  • Payee : un booléen, vrai si la réservation a été réglée ;
  • Date_Res : la date de la réservation, composée d’un jour, d’un mois et d’une année.

Le terrain accepte au plus 300 réservations par saison.

Exemple — cinq réservations :

Travail demandé

  1. Donner le tableau de déclaration des nouveaux types de cette application.
  2. Écrire l’algorithme de la fonction Nb_Impayees(T, N) qui retourne le nombre de réservations non réglées.
  3. Écrire l’algorithme de la fonction Duree_Terrain(T, N, t) qui retourne la durée totale réservée sur le terrain t, en minutes.
  4. Écrire l’algorithme de la fonction Premiere(T, N) qui retourne la position de la réservation la plus ancienne.
  5. Écrire l’algorithme de la procédure Trier_Date(T, N) qui range les réservations de la plus ancienne à la plus récente.

N.B. Le tableau T est supposé déjà rempli. Deux réservations n’ont jamais lieu le même jour.

Voir le corrigé

1. Déclarations

Nouveau type
Date = Enregistrement
           Jour, Mois, Annee : Entier
       Fin
Reservation = Enregistrement
                  Numero : Entier
                  Client : Chaîne de caractères
                  Terrain : Caractère
                  Duree : Entier
                  Payee : Booléen
                  Date_Res : Date
              Fin
TabReservation = Tableau de 300 Reservation

Remarque importante. Les trois lignes sont dans l’ordre imposé : Date est utilisé par Reservation, qui est lui-même utilisé par TabReservation.

En détail

Le champ de la réservation s’appelle Date_Res, et non Date : Date est déjà le nom d’un type. Un champ et un type qui portent le même nom ne se confondent pas pour la machine, mais ils se confondent pour le lecteur. On l’appelle encore moins Jour, puisque Jour est l’un des trois champs qu’il contient : on écrirait alors T[i].Jour.Jour.

2. Nb_Impayees

Fonction Nb_Impayees (T : TabReservation, N : Entier) : Entier
DEBUT
    nb ← 0
    Pour i de 0 à N - 1 Faire
        Si Non(T[i].Payee) Alors
            nb ← nb + 1
        FinSi
    Fin Pour
    Retourner nb
FIN

TDOL

ObjetType / Nature
nb, iEntier

Sur l’exemple, cette fonction retourne 2 : les réservations 103 et 105.

3. Duree_Terrain

Fonction Duree_Terrain (T : TabReservation, N : Entier, t : Caractère) : Entier
DEBUT
    s ← 0
    Pour i de 0 à N - 1 Faire
        Si T[i].Terrain = t Alors
            s ← s + T[i].Duree
        FinSi
    Fin Pour
    Retourner s
FIN

TDOL

ObjetType / Nature
s, iEntier

Sur l’exemple, Duree_Terrain(T, 5, "A") retourne 270 minutes et Duree_Terrain(T, 5, "B") retourne 240 minutes.

En détail

Le module cumule et filtre en même temps : un Si à l’intérieur d’une boucle de cumul. L’accumulateur est de type Entier, puisque les durées le sont.

4. Premiere

Le module appelle la fonction Avant. Ce n’est pas une fonction prédéfinie : c’est la fonction écrite à l’application 1, question 6, rappelée ci-dessous.

Fonction Premiere (T : TabReservation, N : Entier) : Entier
DEBUT
    p ← 0
    Pour i de 1 à N - 1 Faire
        Si Avant(T[i].Date_Res, T[p].Date_Res) Alors
            p ← i
        FinSi
    Fin Pour
    Retourner p
FIN

TDOL

ObjetType / Nature
p, iEntier
AvantFonction

Sur l’exemple, cette fonction retourne 3 : la réservation 104 du Lycée Ibn Sina, le 28/09/2027.

Fonction Avant (D1, D2 : Date) : Booléen
DEBUT
    Si D1.Annee ≠ D2.Annee Alors
        test ← D1.Annee < D2.Annee
    Sinon
        Si D1.Mois ≠ D2.Mois Alors
            test ← D1.Mois < D2.Mois
        Sinon
            test ← D1.Jour < D2.Jour
        FinSi
    FinSi
    Retourner test
FIN

TDOL

ObjetType / Nature
testBooléen

La fonction est reprise sans modification : elle compare deux dates, quel que soit l’enregistrement qui les contient.

5. Trier_Date

Procédure Trier_Date (@T : TabReservation, N : Entier)
DEBUT
    NC ← N
    Répéter
        Permut ← Faux
        Pour i de 0 à NC - 2 Faire
            Si Avant(T[i + 1].Date_Res, T[i].Date_Res) Alors
                Aux ← T[i]
                T[i] ← T[i + 1]
                T[i + 1] ← Aux
                Permut ← Vrai
            FinSi
        Fin Pour
        NC ← NC - 1
    Jusqu'à Non(Permut) Ou (NC = 1)
FIN

TDOL

ObjetType / Nature
NC, iEntier
AuxReservation
PermutBooléen
AvantFonction

Résultat sur l’exemple

En détail

La variable Aux est de type Reservation, et non de type Date : on compare les dates, mais on échange les réservations entières.

Après le tri, la réservation la plus ancienne occupe la position 0 : sur un tableau trié, la question 4 a une réponse connue d’avance. Ce n’est pas une raison de trier pour la trouver, car le tri coûte beaucoup plus cher qu’un seul parcours.

6. Les erreurs les plus fréquentes

Les erreurs suivantes portent sur les notions du chapitre. Chacune est illustrée par une instruction du cours.

Instruction erronéeInstruction correcteExplication
Lire(E)Lire(E.Num), puis chaque champun enregistrement se lit champ par champ
Écrire(E)Écrire(E.Num, E.Nom)un enregistrement s’affiche champ par champ
Si E1 > E2 AlorsSi E1.Moyenne > E2.Moyenne Alorson compare selon un champ, jamais deux enregistrements
Nom ← "Yassine Trabelsi"E.Nom ← "Yassine Trabelsi"un champ se désigne par le nom de l’objet, un point et le nom du champ
T.Moyenne[i]T[i].Moyennel’indice désigne l’élève, le point désigne le champ
Eleve déclaré avant DateDate déclaré avant Eleveun type s’utilise après avoir été déclaré
Lire(E.Naissance)Lire(E.Naissance.Jour), puis chaque champun champ imbriqué est lui aussi un enregistrement
E.Nom, en PythonE['Nom']en Python, un champ est une clé entre crochets
oubli de T[i] = dict(), en PythonT[i] = dict() dans la bouclesans elle, toutes les cases désignent le même dictionnaire

Remarque importante. Presque toutes ces erreurs ont la même origine : traiter l’enregistrement comme une valeur simple. Un enregistrement se déclare, s’affecte et se range en bloc ; tout le reste se fait champ par champ.

7. Retenons

Enregistrement. Un enregistrement est un type défini par l’utilisateur, qui groupe des champs de types éventuellement différents. Un champ se désigne par le nom de l’objet, un point, puis le nom du champ.

Déclaration. Le type se déclare dans le tableau de déclaration des nouveaux types dès qu’un tableau ou un module s’y réfère. Un type s’écrit toujours avant celui qui l’utilise.

Opérations. Un enregistrement s’affecte d’un bloc, entre deux enregistrements de même type. Il se lit, s’affiche et se compare champ par champ.

Comparaison. On compare selon un champ lorsqu’il suffit à répondre. Lorsque la réponse dépend de plusieurs champs, comme pour une date, on les examine l’un après l’autre, du plus significatif au moins significatif.

Imbrication. Un champ peut être un enregistrement. On y accède en cascade, un point par niveau.

Tableau d’enregistrements. L’indice vient avant le point : T[i].Champ. La taille déclarée est un maximum ; la taille effective N est le nombre d’éléments réellement présents.

Tri. On teste sur un champ, et l’on permute les enregistrements entiers : la variable auxiliaire est du type de l’enregistrement.

Python. L’enregistrement est représenté par un dictionnaire, et le champ par une clé entre crochets. Ranger ou affecter un dictionnaire ne le recopie pas : chaque case d’un tableau doit recevoir son propre dictionnaire.

OpérationAlgorithmiquePython
Déclarer un typeEleve = Enregistrement … FinE = dict(champ = type(), …)
Accéder à un champE.NumE['Num']
Accès en cascadeE.Naissance.AnneeE['Naissance']['Annee']
Affecter un champE.Num ← 12E['Num'] = 12
Déclarer un tableauTableau de 35 Elevearray([dict()] * 35)
Préparer une caseaucune instructionT[i] = dict()
Accéder à un champ d’une caseT[i].MoyenneT[i]['Moyenne']

Teste tes connaissances

Un quiz corrigé question par question, puis des flashcards pour mémoriser les notions du chapitre.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *