Les 23 sujets officiels — exercices interactifs + guide méthodologique
assert inclus → utiliser comme guidepass par du code fonctionnelassert qui révèlent un bugassert fournis → valider ton code- au lieu de +), indice off-by-one, condition inversée (< vs <=), boucle sur mauvais rangeassert f(entrée) == sortie_attendueforreturn (retourner None silencieusement)range(1, 12) au lieu de range(1, 13) (décembre exclu)Modifier une liste pendant iteration : for x in liste: if ...: liste.remove(x) → certains éléments sautés. Toujours créer une nouvelle liste ou itérer sur une copie.
Bug derniere_vaccination (sujet 15) : comparer dates comme strings. "20241024" < "20251125" fonctionne en ASCII uniquement si format AAAAMMJJ. Faire attention à l'ordre de comparaison (cherche max, pas min).
Si les tests assert fournis passent mais que quelque chose semble faux : lire la docstring mot à mot. Souvent la fonction doit gérer None ou une liste vide.
Ces patterns apparaissent dans de nombreux sujets. Les maîtriser garantit de résoudre la majorité des questions.
Filtrer une liste → nouvelle liste
# Apparaît dans : 04, 10, 12, 17, 19, 20
def filtrer(elements, condition):
return [e for e in elements if condition(e)]
# OU version explicite
def filtrer(elements, seuil):
resultat = []
for e in elements:
if e["valeur"] > seuil:
resultat.append(e)
return resultat
Accumuler dans un dictionnaire
# Apparaît dans : 02, 17, 18, 19, 20
def par_categorie(elements):
result = {}
for e in elements:
cle = e["categorie"]
if cle not in result:
result[cle] = 0
result[cle] += e["valeur"]
return result
K plus proches voisins (KNN)
# Apparaît dans : 02, 11
from math import sqrt
def distance(a, b):
return sqrt(sum((a[k]-b[k])**2
for k in a))
def k_proches(k, cible, liste):
dists = [(distance(cible, x), x)
for x in liste]
dists.sort(key=lambda t: t[0])
return [x for _, x in dists[:k]]
Récursivité sur dict imbriqué
# Apparaît dans : 05
def total_rec(d):
total = 0
for v in d.values():
if isinstance(v, dict):
total += total_rec(v)
else:
total += v
return total
Lecture CSV
# Apparaît dans : 12, 13, 16, 17
import csv
def lire_csv(nom):
donnees = []
with open(nom, 'r', encoding='utf-8') as f:
lecteur = csv.DictReader(f)
for ligne in lecteur:
donnees.append(dict(ligne))
return donnees
Méthode OOP avec attribut liste
# Apparaît dans : 06, 07, 09, 12, 14, 21
class Conteneur:
def __init__(self):
self.elements = []
def ajouter(self, e):
self.elements.append(e)
def filtrer(self, cond):
return [e for e in self.elements
if cond(e)]
Bugs volontaires courants dans les sujets :
| Type de bug | Exemple concret | Correction |
|---|---|---|
| Opérateur inversé | somme - dico["écart"] (sujet 16) | somme + dico["écart"] |
| Range incomplet | range(1, 12) (sujet 17 — décembre exclu) | range(1, 13) |
Condition < vs <= | if k < 0 au lieu de if k < len(...) | Ajuster la borne |
| Division par mauvais dénominateur | / (len(lst)-1) (sujet 19) | / len(lst) |
| Décennie mal calculée | annee // 10 donne 201 au lieu de 2010 | (annee // 10) * 10 |
| Comparaison ordre max/min inversé | Cherche derniere mais stocke la plus ancienne | Inverser condition < → > |
| Return trop tôt dans boucle | return alerte_valeur_aberrante(val, lim) → sort au premier dict | Stocker résultat, retourner à la fin |
| Accès mauvaise clé du tuple | if distance['presence_renard'] au lieu de caracteristiques | Utiliser la bonne variable |
| Variable non initialisée avant usage | d2 utilisé sans être défini (sujet 14) | Calculer d2 avant |
| Modification liste en cours d'itération | for m in lst: if ...: lst.remove(m) (sujet 04) | Construire une nouvelle liste |
Exercice : identifier la ligne buggée, expliquer pourquoi, proposer la correction.
Bug 1 — Signe inversé (warming stripes)
def moyenne_ecarts(debut, fin, datas):
somme = 0
compteur = 0
for dico in datas:
if debut <= dico["année"] <= fin:
# BUG ICI ↓
somme = somme - dico["écart"]
compteur += 1
return somme / compteur
Correction : somme + dico["écart"]
Bug 2 — Range manque décembre
def solde_annuel(mouvements):
total = 0
# BUG : range(1, 12) exclut le mois 12
for m in range(1, 12):
total += solde_mensuel(mouvements, m)
return total
Correction : range(1, 13)
Bug 3 — Division dénominateur incorrect
def volume_moyen(reservoirs):
somme = 0
for r in reservoirs:
somme += r["volume"]
# BUG : -1 faux
return somme / (len(reservoirs)-1)
Correction : / len(reservoirs)
Bug 4 — Return prématuré dans récursion
def alerte(empreinte, limite):
for cat, val in empreinte.items():
if isinstance(val, dict):
# BUG : return interrompt après 1er dict
return alerte(val, limite)
else:
if val > limite:
return True
return False
Correction : if alerte(val, limite): return True
Run-Length Encoding : compresser une suite d'octets en paires (occurrences, valeur). Ex : [255,255,0,255,255,255] → [2,255,1,0,3,255].
codage_rle est fournie. Implémenter decodage_rle(liste_rle) qui fait l'inverse.def decodage_rle(liste_rle):
resultat = []
i = 0
while i < len(liste_rle):
occ = liste_rle[i]
val = liste_rle[i + 1]
resultat.extend([val] * occ)
i += 2
return resultat
Analyse de données salariales + algorithme KNN pour prédire un salaire en fonction de l'expérience, du niveau d'études et du sexe.
salaire_moyen_condition(employes, champ, valeur) → moyenne des salaires où employe[champ]==valeur. Retourner None si 0 employés.effectif_par_sexe(employes) → dict {'F': n, 'M': m}calcul_ecart_sexe — salaire_moyen_condition('employes', ...) passe une string au lieu de la variable.k_plus_proches et salaire_par_proximite sont fournis, les utiliser.def salaire_moyen_condition(employes, champ, valeur):
filtres = [e for e in employes if e[champ] == valeur]
if len(filtres) == 0:
return None
return sum(e['salaire'] for e in filtres) / len(filtres)
def effectif_par_sexe(employes):
return {
'F': sum(1 for e in employes if e['sexe'] == 'F'),
'M': sum(1 for e in employes if e['sexe'] == 'M')
}
# Bug Q3 : remplacer 'employes' (string) par employes (variable)
Manipulation de dates sous forme de tuples (jour, mois, annee). Fonctions ajouter_jours et jours_dans_mois fournies.
est_bissextile(annee) → True si divisible par 4 ET (pas par 100 OU par 400).determiner_phase(date_regles, date) → phase du cycle (règles, folliculaire, ovulation, lutéale) selon le nb de jours écoulés.calendrier_cycles est fournie, utiliser et tester.def est_bissextile(annee):
return (annee % 4 == 0 and
(annee % 100 != 0 or annee % 400 == 0))
Traitement de listes de plantes et mesures de température. Bug classique : suppression pendant itération.
croissance_moyenne(plantes)dictionnaire_mesure(plantes, mesures)purger_mesures_extremes — elle retire des éléments d'une liste pendant qu'on l'itère → éléments sautés.# Bug : modifie liste_mesures pendant le for
# Correction : construire une nouvelle liste
def purger_mesures_extremes(liste_mesures):
return [m for m in liste_mesures
if 20 <= m['temperature'] <= 25]
Calcul d'empreinte carbone dans un dict potentiellement imbriqué. Nécessite récursion pour les sous-catégories.
total_simple(empreinte) — dict plat, sommer les valeurs.total_rec(empreinte) — dict imbriqué, récursion si valeur est dict.alerte_valeur_aberrante — return alerte(...) sort dès le premier sous-dict, même si la valeur n'est pas aberrante. Corriger : if alerte(...): return Truedef total_simple(empreinte):
return sum(empreinte.values())
def total_rec(empreinte):
total = 0
for v in empreinte.values():
if isinstance(v, dict):
total += total_rec(v)
else:
total += v
return total
# Correction alerte_valeur_aberrante :
# remplacer "return alerte_valeur_aberrante(valeur, limite)"
# par "if alerte_valeur_aberrante(valeur, limite): return True"
Classe Boutique_smoothie avec attribut liste_fruits_disponibles et un dict de recettes. Méthodes à compléter.
smoothie_possible(nom) → True si tous les fruits de la recette sont dans liste_fruits_disponibles.liste_smoothies_possibles() → liste des smoothies réalisables.test_score_proximite() avec des assertions.plus_proche_possible est fournie mais bugguée — regarder si elle inclut le smoothie lui-même.def smoothie_possible(self, nom):
fruits_recette = self.db_smoothies[nom]
for fruit in fruits_recette:
if fruit not in self.liste_fruits_disponibles:
return False
return True
def liste_smoothies_possibles(self):
return [nom for nom in self.db_smoothies
if self.smoothie_possible(nom)]
Simulation d'écosystème : classe Coccinelle et fonction evolution fournies. Écrire des fonctions d'analyse de la simulation.
chasser, reproduction, a_survecu.def simuler(population_initiale, nb_proies_init, nb_jours):
pop = population_initiale[:]
proies = nb_proies_init
historique = []
for _ in range(nb_jours):
pop, proies = evolution(pop, proies)
historique.append((len(pop), proies))
return historique
BCD (Binary Coded Decimal) : chaque chiffre décimal encodé sur 4 bits. 9 → '1001', 0 → '0000'. Problème des flottants IEEE 754.
0.1 + 0.2 ≠ 0.3). Écrire calcul_recettes() qui illustre.convertir_BCD_vers_decimal(liste_quartets).aligner_quartets pour égaliser les longueurs.additionner_nombres_format_BCD.def convertir_BCD_vers_decimal(liste_quartets):
# convention : virgule avant les 2 derniers quartets
chaine = ""
for i, q in enumerate(liste_quartets):
if i == len(liste_quartets) - 2:
chaine += "."
chaine += str(int(q, 2))
return float(chaine)
def aligner_quartets(q1, q2):
while len(q1) < len(q2):
q1 = ['0000'] + q1
while len(q2) < len(q1):
q2 = ['0000'] + q2
return q1, q2
3 classes : Sommet(x,y,z), Face(sommets), Objet3D. Notion de sommets adjacents (partagent une face).
sommets_adjacents(s1, s2) → True si les deux sommets appartiennent à une même face.transformer — crée de nouveaux Sommet mais les faces référencent encore les anciens.def sommets_adjacents(self, s1, s2):
for face in self.faces:
if s1 in face.sommets and s2 in face.sommets:
return True
return False
# Bug transformer : mettre à jour self.sommets ET les faces
# Solution : modifier les coordonnées in-place
def transformer(self, rapport):
for s in self.sommets:
s.x *= rapport
s.y *= rapport
s.z *= rapport
Analyse de consommation d'eau chaude/froide par jour. Détection de fuite (consommation la nuit). Bug dans la moyenne glissante.
total_conso(donnees, jour) → total chaude + froide pour un jour donné.fuite_possible(donnees, jour) → True si consommation entre 1h et 5h du matin.lissage_conso — cas milieu : divise par 2 au lieu de 3.def total_conso(donnees, jour):
return sum(d['chaude'] + d['froide']
for d in donnees if d['jour'] == jour)
def fuite_possible(donnees, jour):
for d in donnees:
if d['jour'] == jour:
h = int(d['heure'][:2])
if 1 <= h <= 5 and (d['chaude']>0 or d['froide']>0):
return True
return False
# Bug lissage : "/ 2" → "/ 3" pour le cas milieu
KNN pour prédire la présence de renards dans un habitat selon 4 caractéristiques. Bug : accès à la mauvaise clé du tuple.
distance(habitat_1, habitat_2) → distance euclidienne sur 4 attributs.distance_d_un_habitat(habitat, habitats) → liste de tuples (distance, habitat).presence_renard : distance['presence_renard'] au lieu de caracteristiques['presence_renard'].def distance(h1, h2):
cles = ['vegetation', 'proximite_eau',
'densite_urbaine', 'disponibilite_proies']
return sqrt(sum((h1[k]-h2[k])**2 for k in cles))
def distance_d_un_habitat(habitat, habitats):
return [(distance(habitat, h), h) for h in habitats]
# Bug presence_renard :
# "if distance['presence_renard']"
# → "if caracteristiques['presence_renard']"
Classes Renard et Refuge. Importation depuis CSV. Méthodes à compléter.
__init__ de Renard (attributs : id, nom, poids float, date_arrivee).__str__ de Renard.lister_peu_corpulents et pourcentage_peu_corpulents (fournies).def __init__(self, identifiant, nom, poids, date_arrivee):
self.identifiant = identifiant
self.nom = nom
self.poids = float(poids) # CSV = string → convertir
self.date_arrivee = date_arrivee
def __str__(self):
return (f"Renard {self.nom} (id={self.identifiant}, "
f"{self.poids}kg, arrivée={self.date_arrivee})")
Données CSV d'un ballon sonde (altitude, température K, longitude, latitude). Conversion Kelvin → Celsius. Recherche d'extrêmes.
recupere_donnees_fichier_csv.conversion_K_en_C(liste_temperatures) → soustraire 273.15.altitude_la_plus_froide → retourner l'altitude associée au minimum de température.def conversion_K_en_C(liste):
return [t - 273.15 for t in liste]
def altitude_la_plus_froide(altitudes, temperatures):
idx_min = temperatures.index(min(temperatures))
return altitudes[idx_min]
Classe Piece avec grille 2D. Simulation d'évacuation vers des sorties. Plusieurs méthodes à compléter ou corriger.
nb_occupants_restants() → somme de toutes les cases de la grille.evacuation(p) → boucler tant que des déplacements sont possibles, retourner le nb de tours.ajouter_sortie pour les directions S et E.choix_sortie : if k < 0 (jamais vrai) → if d2 < distance. La variable d2 n'est jamais calculée.def nb_occupants_restants(self):
return sum(self.grille[i][j]
for i in range(len(self.grille))
for j in range(len(self.grille[i])))
def evacuation(p, silencieux=True):
tours = 0
while p.alerter(silencieux):
tours += 1
return tours
# ajouter_sortie S et E :
# elif direction == "S": self.sorties.append((self.i_max, position))
# elif direction == "E": self.sorties.append((position, self.j_max))
# Bug choix_sortie : calculer d2 et changer "if k < 0" :
# d2 = abs(i-autre_sortie[0]) + abs(j-autre_sortie[1])
# if d2 < distance:
Base SQLite avec tables proprietaire, animal, consultation. Normalisation de numéros de téléphone + requêtes SQL.
normalisation_tel(tel) → ne garder que les chiffres, enlever espaces/points/tirets/lettres.validation_tel(tel) est fournie, écrire ses tests.consultation_vaccination_chat(date) avec JOIN + WHERE + ORDER BY.derniere_vaccination : stocke la plus ancienne (condition < inversée).def normalisation_tel(tel):
return ''.join(c for c in tel if c.isdigit())
-- SQL Q3 :
SELECT a.id, a.nom, p.telephone, c.date
FROM animal a
JOIN proprietaire p ON a.id_proprietaire = p.id
JOIN consultation c ON a.id = c.id_animal
WHERE a.espece = 'chat' AND c.type = 'vaccination'
AND c.date > ?
ORDER BY a.id, c.date
# Bug Q4 : "elif date < derniere[id_animal][3]"
# → "elif date > derniere[id_animal][3]"
Données d'anomalies de température mondiale. Visualisation (warming stripes). Régression linéaire. Bug : signe inversé dans la somme.
ecart_temperature(datas, annee) → renvoyer l'écart pour une année donnée.derniere_annee_ecart_negatif est fournie, la comprendre (itère en décrémentant).moyenne_ecarts : somme - dico["écart"] → somme + dico["écart"].graphique : créer les listes annees et ordonnees.def ecart_temperature(datas, annee):
for d in datas:
if d["année"] == annee:
return d["écart"]
return None
# Correction Q3 :
# somme = somme + dico["écart"] (pas -)
# Q4 - compléter graphique :
annees = [d["année"] for d in datas]
ordonnees = [1] * len(datas) # hauteur uniforme
Analyse de mouvements financiers (recettes/dépenses) d'une association. Calcul de soldes.
total_par_type(mouvements) → dict {'recette': x, 'dépense': y}.test_solde_annuel avec assertions. Bug : range(1, 12) exclut décembre.solde_annuel → range(1, 13).def total_par_type(mouvements):
result = {'recette': 0, 'dépense': 0}
for m in mouvements:
result[m['type']] += m['montant']
return result
# Correction solde_annuel :
# range(1, 12) → range(1, 13)
# Tests qui révèlent le bug :
def test_solde_annuel():
mvt = [
{'type':'recette','catégorie':'a','montant':100.0,'mois':12},
{'type':'dépense','catégorie':'b','montant':40.0,'mois':12},
]
assert solde_annuel(mvt) == 60.0 # échoue si range(1,12)
Analyse de températures par zone et décennie. Bug : calcul de décennie incorrect (// 10 au lieu de // 10 * 10).
temperature_moyenne(zone, donnees) → moyenne des températures pour une zone.detection_anomalies(zone, donnees, seuil) → liste des relevés dont la temp dépasse moyenne±seuil.evolution_par_decennie qui révèlent le bug.decennie = (annee // 10) → decennie = (annee // 10) * 10.def temperature_moyenne(zone, donnees):
releves = [r['temperature'] for r in donnees
if r['zone'] == zone]
if not releves: return None
return sum(releves) / len(releves)
# Bug : decennie = (annee // 10) → donne 201 pour 2015
# Correction : decennie = (annee // 10) * 10 → donne 2010
# Test révélateur :
def test_plusieurs_decennies():
r = evolution_par_decennie('Societe', donnees_test)
assert 2010 in r # échoue si bug (clé serait 201)
assert 2020 in r
Gestion de réservoirs par district. Détection de pénuries. Bug : division par len-1.
est_en_penurie(reservoirs, nom) → True si le volume du réservoir nommé est 0.volume_par_district(reservoirs) → dict {district: volume_total}.volume_moyen : / (len(reservoirs)-1) → / len(reservoirs).districts_vulnerables → liste des districts où tous les réservoirs sont en pénurie.def est_en_penurie(reservoirs, nom):
for r in reservoirs:
if r["nom"] == nom:
return r["volume"] == 0
return False
def volume_par_district(reservoirs):
result = {}
for r in reservoirs:
d = r["district"]
result[d] = result.get(d, 0) + r["volume"]
return result
Calcul d'empreinte carbone numérique à partir d'un dict d'usages. Classement par impact. Comparaison entre utilisateurs.
calculer_empreinte(utilisateur) → somme de quantite * EMISSIONS[activite] pour chaque activité.classer_par_impact(utilisateur) → liste des (activite, emission) triée par émission décroissante.comparer et comparer_v2 sont fournis, ajouter des assertions de test. Bug potentiel comparer_v2 : division par zéro si emission1=0.def calculer_empreinte(u):
total = 0
for activite, quantite in u.items():
if activite in EMISSIONS:
total += quantite * EMISSIONS[activite]
return total
def classer_par_impact(u):
impacts = []
for a, q in u.items():
if a in EMISSIONS:
impacts.append((a, q * EMISSIONS[a]))
impacts.sort(key=lambda x: x[1], reverse=True)
return impacts
Système Leitner : carte avec niveau 0→4, date de prochaine révision. Si réponse juste → niveau+1, sinon → niveau 0.
traiter_reponse(self, succes) : si succes → niveau+1 (max 4), date = aujourd'hui + DELAIS[niveau]. Sinon → niveau 0, date = aujourd'hui.extraire_cartes_du_jour(paquet) → cartes dont date_prochaine ≤ aujourd'hui.extraire_cartes_a_renforcer : ajoute la carte après avoir mis à jour niveau_min, donc la première carte du nouveau minimum n'est pas dans la liste.def traiter_reponse(self, succes):
if succes:
self.niveau = min(self.niveau + 1, 4)
else:
self.niveau = 0
self.date_prochaine = date_future(DELAIS[self.niveau])
def extraire_cartes_du_jour(paquet):
aujourd_hui = datetime.date.today()
return [c for c in paquet
if c.date_prochaine <= aujourd_hui]
# Bug extraire_cartes_a_renforcer :
# Quand niveau_min est mis à jour, la carte courante
# n'est pas ajoutée à a_renforcer avant reset.
# Correction : réinitialiser a_renforcer = [carte]
Table ASCII fournie. Décodage de listes de codes ASCII. Conversion binaire → entier → caractère. Gestion des codes hors table.
binaire_vers_decimal(liste_bits) pour convertir une ligne de figure1.dict_ascii (ex: 233 = é, hors ASCII standard).def binaire_vers_decimal(bits):
# bits = tuple de 0 et 1
val = 0
for b in bits:
val = val * 2 + b
return val
def decoder(codes):
result = ""
for c in codes:
if c in dict_ascii:
result += dict_ascii[c]
else:
result += chr(c) # fallback unicode
return result
Classe Transmission : décode une trame binaire (string de 0/1) contenant ID, température, humidité, checksum. int(s, 2) = conversion binaire→décimal.
decoder_id fournie (bits 0–7). Implémenter decoder_temperature (bits 8–15, valeur décalée).decoder_humidite (bits 16–23).est_valide() → vérifier un checksum (somme des bytes ou XOR selon l'énoncé).data.txt : compter les transmissions valides/invalides.def decoder_temperature(self):
# Bits 8-15, valeur entière (ajuster selon énoncé)
self._temperature = int(self._trame[8:16], 2)
def decoder_humidite(self):
self._humidite = int(self._trame[16:24], 2)
# est_valide : lire l'énoncé pour la règle exacte
# Exemple checksum XOR :
def est_valide(self):
n = len(self._trame) // 8
octets = [int(self._trame[i*8:(i+1)*8], 2)
for i in range(n-1)]
xor = 0
for o in octets: xor ^= o
checksum = int(self._trame[-8:], 2)
return xor == checksum