Cette page reprend un sujet d'entraînement complet, avec deux exercices : les polynômes (représentés avec des listes chaînées) et la nutrition (représentée avec des objets et des classes). Pour chaque exercice, l'énoncé est d'abord reformulé de façon très explicite, puis chaque question est corrigée en détail, avec le raisonnement complet, une trace sur un exemple concret, et le code Python.
src/entrainement_bac_polynomes_nutrition.py, dans le sous-dossier src à côté de cette page, contient tout le code des deux exercices, avec un test pour chaque question. Après l'avoir téléchargé, on l'exécute dans un terminal, dans le dossier où il a été enregistré, avec python3 entrainement_bac_polynomes_nutrition.py (Python doit être installé sur l'ordinateur). Le fichier affiche « OK » pour chaque question s'il n'y a pas d'erreur.
On veut représenter, en mémoire, des polynômes à une variable réelle x. Un polynôme est représenté par une liste chaînée de monômes. Un monôme est représenté par un couple (coefficient, exposant).
Exemple : le polynôme p1(x) = 2x³ − x + 2 est représenté par la liste chaînée :
Chaque monôme est rangé par exposant décroissant : d'abord x³, puis x¹, puis x⁰. Le symbole ∅ marque la fin de la liste (la liste vide).
On dispose d'une structure de données abstraite de liste, avec ces opérations déjà toutes prêtes :
| Opération | Rôle |
|---|---|
creer_liste() | renvoie la liste vide |
est_vide(L) | renvoie True si la liste L est vide, False sinon |
ajouter_en_tete(x, L) | ajoute l'élément x en tête de la liste L |
supprimer_en_tete(L) | supprime l'élément de tête de la liste L |
tete(L) | renvoie l'élément de tête de L |
queue(L) | renvoie la liste L privée de son élément de tête |
p1, représentant le polynôme p1 ci-dessus.tete(p1) ? Que renvoie l'instruction queue(p1) ?p1 est p1(2) = 2×2³ − 2 + 2 = 16. À l'aide des opérations primitives, écrire un algorithme valuation qui prend en argument une liste représentant un polynôme et un nombre x, et qui renvoie l'image de x par ce polynôme.p1 et à p2(x) = 5x⁴ + 3x² + 1.p1p1 = creer_liste() p1 = ajouter_en_tete((2, 0), p1) # on ajoute d'abord le terme constant p1 = ajouter_en_tete((-1, 1), p1) # puis le terme en x p1 = ajouter_en_tete((2, 3), p1) # puis le terme en x³, qui devient la tête
Trace détaillée de la construction, étape par étape :
| Instruction | Valeur de p1 après l'instruction |
|---|---|
p1 = creer_liste() | ∅ |
p1 = ajouter_en_tete((2, 0), p1) | (2, 0) → ∅ |
p1 = ajouter_en_tete((-1, 1), p1) | (-1, 1) → (2, 0) → ∅ |
p1 = ajouter_en_tete((2, 3), p1) | (2, 3) → (-1, 1) → (2, 0) → ∅ |
tete(p1) et queue(p1)tete(p1) renvoie l'élément de tête de la liste, c'est-à-dire (2, 3) (le premier monôme).
queue(p1) renvoie la liste privée de sa tête, c'est-à-dire la liste (-1, 1) → (2, 0) → ∅ (tous les monômes sauf le premier).
valuationx par un polynôme, on additionne la contribution de chaque monôme (coefficient × xexposant), un par un, en parcourant la liste avec queue jusqu'à la liste vide.
coefficient × xexposant, et on y ajoute le résultat de valuation appliqué au reste de la liste (queue).def valuation(liste, x):
if est_vide(liste):
return 0
temp = tete(liste)
return temp[0] * x ** temp[1] + valuation(queue(liste), x)
Trace détaillée de valuation(p1, 2) :
| Appel | temp = tete(liste) | Contribution de temp | Résultat de l'appel |
|---|---|---|---|
valuation((2,3)→(-1,1)→(2,0)→∅, 2) | (2, 3) | 2 × 2³ = 16 | 16 + valuation((-1,1)→(2,0)→∅, 2) |
valuation((-1,1)→(2,0)→∅, 2) | (-1, 1) | -1 × 2¹ = -2 | -2 + valuation((2,0)→∅, 2) |
valuation((2,0)→∅, 2) | (2, 0) | 2 × 2⁰ = 2 | 2 + valuation(∅, 2) |
valuation(∅, 2) | — (liste vide) | — | 0 (cas de base) |
En remontant les appels : 2 + 0 = 2, puis -2 + 2 = 0, puis 16 + 0 = 16. On retrouve bien valuation(p1, 2) = 16, comme annoncé dans l'énoncé.
On découpe donc le problème en deux algorithmes : addition_monome_polynome, qui insère un seul monôme dans un polynôme, et addition_polynome, qui utilise le premier pour insérer tous les monômes d'un polynôme dans un autre, un par un.
Pour insérer un monôme element = (coefficient, exposant) en tête d'une liste non vide dont la tête s'appelle premier, il faut comparer l'exposant de element à celui de premier. Il y a exactement 3 cas :
element est plus grand que celui de premier : element n'a pas d'équivalent dans le polynôme à cet endroit, donc on l'insère directement, avant premier.x, donc on additionne leurs coefficients.
premier par le nouveau monôme, avec ce nouveau coefficient (même exposant).element est plus petit que celui de premier : ce n'est pas encore le bon endroit pour insérer element ; on garde premier en tête, et on recommence la même recherche dans le reste de la liste (queue).queue dans le cas 3, on finit par atteindre la fin de la liste), il n'y a plus rien à comparer : on ajoute simplement element dans une liste vide.
def addition_monome_polynome(element, liste): if est_vide(liste): L = creer_liste() L = ajouter_en_tete(element, L) return L premier = tete(liste) if element[1] > premier[1]: return ajouter_en_tete(element, liste) elif element[1] == premier[1]: nouveau_coeff = premier[0] + element[0] reste = queue(liste) if nouveau_coeff == 0: return reste else: return ajouter_en_tete((nouveau_coeff, element[1]), reste) else: L = addition_monome_polynome(element, queue(liste)) return ajouter_en_tete(premier, L)
element[1] et pas element.exposant ?(coefficient, exposant), donc element[0] est son coefficient et element[1] est son exposant (comme pour n'importe quel tuple : [0] désigne le premier élément, [1] le second).
liste1) dans le second (liste2), avec addition_monome_polynome. S'il ne reste plus de monôme dans liste1 à insérer, le résultat est simplement liste2 (avec tous les monômes déjà insérés).
def addition_polynome(liste1, liste2):
if est_vide(liste1):
return liste2
L = addition_polynome(queue(liste1), liste2)
return addition_monome_polynome(tete(liste1), L)
Cet algorithme insère d'abord les monômes les plus profonds de liste1 (ceux de plus petit exposant), puis remonte vers la tête : le tout dernier monôme inséré est donc tete(liste1), celui de plus grand exposant.
p1 + p2On applique addition_polynome(p1, p2) avec p1 = (2,3) → (-1,1) → (2,0) → ∅ et p2 = (5,4) → (3,2) → (1,0) → ∅. D'après le principe ci-dessus, cela revient à insérer, un par un dans p2, les monômes de p1 en commençant par le dernier : d'abord (2,0), puis (-1,1), puis (2,3).
p2).| Monôme de p1 inséré | Résultat après insertion |
|---|---|
| (départ : copie de p2) | (5,4) → (3,2) → (1,0) → ∅ |
| (2, 0) | (5,4) → (3,2) → (3,0) → ∅ — fusion avec (1,0) : 2+1=3 |
| (-1, 1) | (5,4) → (3,2) → (-1,1) → (3,0) → ∅ — aucun terme en x¹ dans la liste, insertion |
| (2, 3) | (5,4) → (2,3) → (3,2) → (-1,1) → (3,0) → ∅ — aucun terme en x³, insertion |
p1 + p2 = 5x⁴ + 2x³ + 3x² − x + 3
On peut vérifier ce résultat sans passer par les listes chaînées, en additionnant terme à terme :
p1(x) + p2(x) = (2x³ − x + 2) + (5x⁴ + 3x² + 1) = 5x⁴ + 2x³ + 3x² + (−x) + (2 + 1) = 5x⁴ + 2x³ + 3x² − x + 3
C'est exactement ce que renvoie addition_polynome(p1, p2), et c'est ce que confirme le fichier téléchargeable de cette page, qui vérifie ce résultat de deux façons différentes (en construisant la liste, et en calculant valuation sur plusieurs valeurs de x).
valuation, addition_monome_polynome, addition_polynome, et les tests des 4 questions.
Sur les aliments vendus en Europe, les informations nutritionnelles pour 100 g doivent être affichées. Voici celles du riz blanc utilisé dans cet exercice :
| Information | Valeur pour 100 g de riz |
|---|---|
| Énergie | 1532 kJ |
| Lipides | 0,6 g |
| Glucides | 81 g |
| Fibres | 1,3 g |
| Protéines | 7,2 g |
| Sel | 0,01 g |
alimentclass aliment:
def __init__(self, nom, energie, lipide, glucide, fibre, proteine, sel):
self.nom = nom
self.energie = energie
self.lipide = lipide
self.glucide = glucide
self.fibre = fibre
self.proteine = proteine
self.sel = sel
Chaque attribut correspond à une valeur du tableau ci-dessus, pour 100 g de l'aliment.
riz (« riz blanc »), conformément au tableau des informations nutritionnelles ?__repr__ de la classe aliment, qui affiche les informations nutritionnelles d'un objet, comme dans l'exemple ci-dessous :
n grammes de l'objet riz ?salade_de_riz = [(riz, 500), (thon, 200), (tomate, 200), (oeuf_dur, 110), (mais, 300)]
masse_plat qui prend en argument un « plat » (comme ci-dessus) et renvoie sa masse totale, en grammes.energie_plat qui prend en argument un « plat » et renvoie le nombre de kJ pour 100 g de ce plat. On admet qu'on dispose de fonctions semblables pour les lipides, les glucides, les fibres, les protéines et le sel.fiche_info_nutrition qui renvoie un dictionnaire contenant les informations nutritionnelles d'un plat, dans le même esprit que le tableau du riz.rizriz = aliment('riz blanc', 1532, 0.6, 81, 1.3, 7.2, 0.01)
Les 7 valeurs sont données exactement dans l'ordre des paramètres du constructeur __init__ : nom, energie, lipide, glucide, fibre, proteine, sel.
__repr____repr__ doit renvoyer une chaîne de caractères (pas un affichage direct avec print à l'intérieur). Pour obtenir un retour à la ligne entre chaque information, on insère le caractère spécial \n dans la chaîne.
def __repr__(self):
return (f"Intitulé : {self.nom}\n"
f"Énergie : {self.energie} kJ\n"
f"Lipides : {self.lipide} g\n"
f"Glucides : {self.glucide} g\n"
f"Fibres : {self.fibre} g\n"
f"Proteines : {self.proteine} g\n"
f"Sel : {self.sel}g")
Trace de l'affichage dans une console Python :
n grammesriz.glucide donne la quantité de glucides pour 100 g de riz, pas pour n grammes. Pour obtenir la quantité dans n grammes, il faut donc d'abord ramener riz.glucide à 1 gramme (en divisant par 100), puis multiplier par n.
n * riz.glucide / 100
Trace avec n = 75 :
| Étape | Calcul | Résultat |
|---|---|---|
| Glucides pour 1 g de riz | 81 / 100 | 0,81 g |
| Glucides pour 75 g de riz | 75 × 0,81 | 60,75 g |
En une seule instruction Python : 75 * riz.glucide / 100 renvoie bien 60.75.
masse_plat(aliment, masse). Pour obtenir la masse totale, on parcourt cette liste avec une boucle for, et on additionne chaque masse dans une variable accumulateur.
def masse_plat(plat):
masse_totale = 0
for un_aliment, masse in plat:
masse_totale += masse
return masse_totale
Trace détaillée sur salade_de_riz :
| Couple lu | masse | masse_totale après addition |
|---|---|---|
| (riz, 500) | 500 | 0 + 500 = 500 |
| (thon, 200) | 200 | 500 + 200 = 700 |
| (tomate, 200) | 200 | 700 + 200 = 900 |
| (oeuf_dur, 110) | 110 | 900 + 110 = 1010 |
| (mais, 300) | 300 | 1010 + 300 = 1310 |
masse_plat(salade_de_riz) = 1310 g, exactement la valeur donnée par le sujet.
energie_plat (et les fonctions semblables)energie_plat doit renvoyer l'énergie pour 100 g du plat entier — pas la simple somme des énergies affichées sur chaque aliment, qui sont elles-mêmes données pour 100 g de chaque aliment séparément, et pas pour la quantité réellement utilisée.
Il faut donc, pour chaque aliment du plat, calculer sa contribution réelle (en fonction de sa masse dans le plat), les additionner, puis ramener ce total à une base de 100 g du plat entier :
masse dans le plat, sa contribution en énergie est masse * aliment.energie / 100 (même principe qu'à la question 3 : on ramène la valeur « pour 100 g » à la masse réellement utilisée).énergie_totale / masse_totale × 100.def energie_plat(plat):
masse_totale = masse_plat(plat)
energie_totale = 0
for un_aliment, masse in plat:
energie_totale += masse * un_aliment.energie / 100
return energie_totale / masse_totale * 100
Les fonctions lipide_plat, glucide_plat, fibre_plat, proteine_plat et sel_plat se construisent exactement sur le même modèle, en remplaçant energie par l'attribut correspondant.
Une façon simple de vérifier que la formule est correcte : si un plat ne contient qu'un seul aliment, alors, quelle que soit sa masse dans le plat, energie_plat doit redonner exactement la valeur pour 100 g de cet aliment (puisque le plat entier, ramené à 100 g, c'est justement 100 g de cet aliment).
| Plat | masse_totale | energie_totale (avant division) | energie_plat (résultat) |
|---|---|---|---|
| [(riz, 250)] | 250 | 250 × 1532 / 100 = 3830 | 3830 / 250 × 100 = 1532 |
| [(riz, 40)] | 40 | 40 × 1532 / 100 = 612,8 | 612,8 / 40 × 100 = 1532 |
Dans les deux cas, on retrouve bien 1532, la valeur du tableau du riz : la formule ne dépend pas de la masse choisie, comme attendu.
Avec deux aliments fictifs A (1000 kJ pour 100 g) et B (2000 kJ pour 100 g) : si le plat contient autant de A que de B (100 g chacun), le résultat doit être leur moyenne simple, 1500. Si le plat contient 3 fois plus de B que de A (50 g de A, 150 g de B), le résultat doit se rapprocher de B.
| Plat | energie_plat (résultat) |
|---|---|
| [(A, 100), (B, 100)] | 1500 (moyenne simple, quantités égales) |
| [(A, 50), (B, 150)] | 1750 (plus proche de B, qui pèse plus dans le plat) |
Ces deux vérifications, ainsi que celle sur masse_plat, sont exécutées automatiquement dans le fichier téléchargeable de cette page.
fiche_info_nutritiondef fiche_info_nutrition(plat):
return {'Energie': energie_plat(plat),
'Lipides': lipide_plat(plat),
'Glucides': glucide_plat(plat),
'Fibres': fibre_plat(plat),
'Proteines': proteine_plat(plat),
'Sel': sel_plat(plat)}
Sur le plat fictif [(A, 100), (B, 100)] de la vérification précédente (A : 1000 kJ, 10 g de lipides, 20 g de glucides, 1 g de fibres, 5 g de protéines, 0,1 g de sel pour 100 g ; B : le double de chaque valeur), fiche_info_nutrition renvoie :
{'Energie': 1500.0, 'Lipides': 15.0, 'Glucides': 30.0, 'Fibres': 1.5, 'Proteines': 7.5, 'Sel': 0.15}
Ce sont bien les moyennes simples de A et B pour chaque information, cohérentes avec un plat composé pour moitié de A et pour moitié de B.
aliment, masse_plat, energie_plat et les fonctions semblables, fiche_info_nutrition, et les tests des 4 questions.
(coefficient, exposant), triée par exposants décroissants.aliment, chaque valeur nutritionnelle est donnée pour 100 g : pour toute autre quantité, il faut diviser par 100 puis multiplier par la quantité réelle (ou l'inverse).