Atelier : analyser les performances d'une écurie
Ce que ce chapitre apporte
- Traduire un énoncé en français en une suite d'opérations sur des nombres.
- Choisir entre une boucle et une fonction selon ce que le problème demande.
- Représenter plusieurs objets ayant chacun plusieurs propriétés.
- Reconnaître quand une liste de valeurs doit devenir une liste de dictionnaires.
Les sept chapitres précédents ont installé les cinq outils de base : variables, conditions, boucles, fonctions, structures de données. Cet atelier ne présente rien de neuf. Il pose un problème complet (chronométrer, comparer, classer, décider) et demande de choisir soi-même lequel des cinq sert à quoi, ce qu'aucun exercice ciblé ne fait faire.
Les énoncés sont volontairement donnés sans indiquer la notion à employer. C'est la difficulté du chapitre, et c'est la seule qui ressemble à la programmation réelle.
Atelier à trous. Chaque bloc contient des
TODO: on remplace les..., puis on exécute. Un bloc s'exécute seul, sans voir les précédents : ce dont il a besoin est recopié en tête. Les lignesassertdu bas servent de juge. Si aucune ne proteste et que le message de validation s'affiche, l'exercice est fait. Une correction est dépliable sous chacun ; l'ouvrir avant d'avoir essayé ne coûte rien à personne d'autre qu'à soi.
Déroulé
| Durée | Étape | Notion |
|---|---|---|
| 10 min | Échauffement 1 à 2 | Calcul, unités, fonction qui renvoie un booléen |
| 15 min | Échauffement 3 à 5 | Parcours d'une liste, conditions en cascade, while |
| 10 min | Choisir la représentation | Liste de dictionnaires contre listes parallèles |
| 25 min | Risque, score, médiane | Ordre des conditions, tri par critère, cas pair |
| 10 min | Analyse d'ensemble | Composer les fonctions écrites |
| 15 min | Pour aller plus loin | Filtres, normalisation, tri maison |
Compter une heure et demie. Les cinq premiers problèmes sont indépendants ; la suite forme un tout.
Échauffement : cinq problèmes courts
Chacun tient en quelques lignes. Écrire la solution avant d'ouvrir la correction : la lire d'abord donne l'illusion d'avoir compris.
1. La vitesse moyenne sur 400 mètres
Une voiture parcourt 400 mètres en un temps mesuré au chronomètre. Calculer sa vitesse moyenne en km/h, et annoncer "Street Rocket" si elle atteint ou dépasse 180 km/h.
Le piège est dans les unités : le chronomètre donne des secondes, la distance est en mètres, et la réponse attendue est en kilomètres par heure.
Correction
Le facteur 3,6 n'est pas à retenir par cœur, il se retrouve : un mètre par seconde, c'est 3 600 mètres en une heure, donc 3,6 km/h.
Ici m/s, soit 128,6 km/h : pas de titre. Il faudrait descendre sous 8 secondes.
2. Terminer la course, ou non
Une course fait 50 km, la voiture consomme 8 litres aux 100 km. Écrire une fonction qui dit si le réservoir actuel suffit.
Une fonction plutôt qu'un calcul direct, parce que la question se reposera pour chaque pilote et pour chaque course.
Correction
return carburant_l >= necessaire suffit : la comparaison est déjà un booléen. Écrire if ... : return True else: return False fonctionne, mais dit deux fois la même chose.
3. Le meilleur et le pire tour
À partir d'une liste de temps au tour, trouver le meilleur, le pire, et compter ceux passés sous 60 secondes.
Correction
Deux points méritent l'attention. On initialise avec le premier élément, jamais avec 0 ni avec un nombre « très grand » choisi au hasard : sur une liste de temps négatifs ou tous supérieurs à la borne inventée, la seconde méthode donne un résultat faux sans prévenir.
Et une liste vide ferait échouer temps_tours[0]. C'est le cas limite à traiter dès que la liste vient d'ailleurs que d'une constante écrite juste au-dessus.
4. Le nitro
L'effet du nitro dépend de la vitesse au moment où on l'enclenche : en dessous de 100 km/h il ajoute 40, entre 100 et 180 il ajoute 25, au-delà il n'ajoute que 10. Écrire la fonction qui renvoie la nouvelle vitesse.
Correction
La deuxième condition s'écrit vitesse < 180 et non 100 <= vitesse < 180 : si l'on est arrivé jusque-là, c'est que la première a échoué, donc que la vitesse vaut au moins 100. Réécrire la borne basse n'est pas faux, c'est redondant, et une redondance finit toujours par diverger le jour où l'on modifie l'une des deux.
5. Le compte à rebours
Le décompte avant le départ : 3, 2, 1, puis GO !!!, avec une boucle while.
La fonction renvoie la liste des annonces au lieu de les afficher. C'est ce qui permet de la vérifier, et c'est une bonne habitude en général : une fonction qui rend un résultat se teste, une fonction qui affiche ne se teste pas.
Correction
Le compteur est recopié dans une variable locale plutôt que de modifier depart : une fonction ne touche pas à ce qu'on lui confie, et le paramètre reste disponible si l'on en a besoin plus bas.
Oublier compteur -= 1 donne une boucle infinie : la condition reste vraie pour toujours. Le bloc s'arrête alors de lui-même au bout de quelques secondes et affiche une TimeoutError : c'est presque toujours ce que signale cette erreur.
Une boucle for compteur in range(3, 0, -1) ferait la même chose en une ligne. while s'impose quand on ne sait pas d'avance combien de tours auront lieu ; ici, on le sait.
Le problème complet : classer une écurie
Une écurie compte plusieurs pilotes. De chacun on connaît son nom, sa vitesse maximale en km/h, son temps moyen au tour en secondes, et un indice d'agressivité de 1 à 10.
Choisir la représentation
C'est la première décision, et c'est la plus lourde de conséquences.
La tentation est d'écrire une liste des noms, une des vitesses, une des temps, une des agressivités, et de les parcourir avec le même indice. Ça marche, et ça se dégrade vite : ajouter un pilote oblige à modifier quatre listes, en oublier une décale tout sans provoquer la moindre erreur, et trier par vitesse devient un casse-tête parce qu'il faut réordonner les trois autres à l'identique.
Un pilote est un objet, pas quatre valeurs éparpillées. Il se range dans un dictionnaire, et l'écurie est une liste de dictionnaires.
Le niveau de risque
Le risque se lit sur deux critères à la fois : élevé si la vitesse atteint 200 et l'agressivité 7 ; moyen si la vitesse atteint 180 ou l'agressivité 5 ; faible sinon.
L'ordre des tests n'est pas indifférent. Han, à 202 km/h et 9 d'agressivité, satisfait les deux règles : c'est la première rencontrée qui décide, et la règle « élevé » doit donc être testée en premier.
Correction
La fonction reçoit le pilote entier, pas ses quatre champs un par un. Le jour où le risque dépendra aussi du temps au tour, la signature ne bougera pas.
Résultat : Dom et Han à risque élevé, Letty et Brian à risque moyen, Mia à risque faible. Elle est la seule sous les deux seuils.
Le score global
La formule paraît arbitraire, et elle l'est : c'est une pondération métier, choisie par l'écurie. Ce qui ne l'est pas, c'est sa forme : la vitesse et l'agressivité comptent positivement, le temps au tour négativement, puisqu'un tour rapide est un tour court.
Correction
sorted(ecurie, key=score) trie la liste en la laissant intacte et se sert de la fonction pour comparer. C'est le geste que le chapitre sur les fonctions appelait « passer une fonction en paramètre », et c'est ici qu'il sert vraiment : sans les dictionnaires, ce tri obligerait à réordonner quatre listes à la main.
Classement obtenu : Dom (98,4), Han (94,9), Letty (88,7), Brian (83,5), Mia (75,6). Brian a beau signer le meilleur temps au tour, sa vitesse et son agressivité modestes le placent quatrième : la formule pèse trois critères, et aucun ne décide seul.
La médiane, à la main
La médiane d'une série est la valeur du milieu une fois les valeurs triées, ou la moyenne des deux valeurs centrales si elles sont en nombre pair.
L'écrire sans statistics.median n'est pas un caprice : la difficulté est entièrement dans le cas pair, et c'est en s'y cognant qu'on comprend pourquoi la définition comporte deux branches.
Correction
sorted(valeurs) renvoie une copie triée ; valeurs.sort() trierait la liste de l'appelant, qui ne s'y attend pas. Une fonction qui calcule ne doit pas modifier ce qu'on lui confie.
Sur cinq éléments, n // 2 vaut 2, et triees[2] est bien le troisième : les indices commencent à zéro, et c'est exactement ce qui rend la formule juste.
L'analyse d'ensemble
Rassembler le tout : vitesse moyenne et médiane de l'écurie, pilote le plus rapide, meilleur score, nombre de pilotes à risque élevé.
Correction
Le corps d'analyse ne recalcule rien : il appelle mediane, score et risque. C'est tout l'intérêt de les avoir isolées, et la raison pour laquelle on découpe en fonctions avant d'en avoir besoin.
Le paramètre s'appelle equipe et non ecurie, pour qu'aucune confusion ne soit possible avec la variable globale du même nom : la fonction travaille sur ce qu'on lui passe, et rien d'autre.
Résultat : moyenne 194,2 km/h, médiane 195. Mia, nettement plus lente, tire la moyenne sous la médiane. L'écart reste modeste sur cinq pilotes, mais c'est bien le comportement décrit au chapitre de statistiques.
Pour aller plus loin
Quatre prolongements, du plus simple au plus exigeant.
Filtrer sur plusieurs critères. Trouver les pilotes à risque élevé dont le temps au tour est sous 60 s et la vitesse au moins 190. Une seule boucle suffit.
Normaliser les vitesses entre 0 et 1, par . Que se passe-t-il si tous les pilotes ont la même vitesse ? La réponse est une division par zéro, et c'est le genre de cas qu'un jeu de données réel finit toujours par produire.
Trier sans sorted. Réécrire le classement avec une boucle de tri maison. C'est plus lent et plus long : l'exercice sert à comprendre ce que sorted évite d'écrire, pas à le remplacer.
Ajouter un pilote depuis le clavier. Avec input(), en validant les saisies : une vitesse négative ou un texte à la place d'un nombre ne doit pas faire tomber le programme. Ce dernier point demande le chapitre sur les erreurs.
Synthèse
- Un objet ayant plusieurs propriétés se range dans un dictionnaire ; plusieurs de ces objets, dans une liste de dictionnaires. Jamais une liste par propriété.
- Une fonction reçoit l'objet entier plutôt que ses champs un par un : sa signature survit alors aux évolutions.
- Dans une suite de conditions, l'ordre décide dès que deux règles peuvent être vraies en même temps. La plus restrictive passe en premier.
- Une comparaison est un booléen :
return a >= bse suffit à lui-même. sorted(liste, key=fonction)trie une copie selon un critère calculé.liste.sort()modifie l'original, ce qu'une fonction de calcul ne doit pas faire.- Un maximum ou un minimum s'initialise avec le premier élément, jamais avec une borne inventée, et la liste vide reste à traiter à part.