Sommaire : Pratique
corrections_BNS_2026_toutes_sommaire
Corrections BNS 2026
Sommaire
Sujet 1 Sujet 2 Sujet 3 Sujet 4 Sujet 5 Sujet 6 Sujet 7 Sujet 8 Sujet 9 Sujet 10 Sujet 11 Sujet 12 Sujet 13 Sujet 14 Sujet 15 Sujet 16 Sujet 17 Sujet 18 Sujet 19 Sujet 20 Sujet 21 Sujet 22 Sujet 23
Correction Q1
Si la liste originale est [3, 5, 0, 6], la liste obtenue par codage RLE est [1, 3, 1, 5, 1, 0, 1, 6], qui est 2 fois plus longue. Donc non, la liste obtenue par codage RLE n'est pas forcément de longueur inférieure ou égale.
Correction Q2
def decodage_rle(liste_rle):
'''Renvoie la liste d'octets obtenue à partir de la liste liste_rle obtenue
par compression RLE'''
lst = []
for i in range(0, len(liste_rle), 2):
for k in range(liste_rle[i]):
lst.append(liste_rle[i+1])
return lst
def test_codage():
assert codage_rle([255, 255, 0, 255, 255, 255]) == [2, 255, 1, 0, 3, 255]
assert decodage_rle([2, 255, 1, 0, 3, 255]) == [255, 255, 0, 255, 255, 255]
assert decodage_rle([4, 85]) == [85, 85, 85, 85]
assert decodage_rle([1, 9, 1, 250, 1, 128]) == [9, 250, 128]
assert decodage_rle([1, 0]) == [0]Correction Q3
On constate que, si tout se passe bien pour l'image bac_nsi_32.png, l'image bac_nsi_256.png.dec.png obtenue après encodage/décodage de l'image bac_nsi_256.png présente un décalage de l'écriture.
Correction Q4
Il faut modifier la fonction codage_rle pour faire en sorte qu'elle prenne en compte les valeurs dont le nombre dépasse 255. Il suffit de faire plusieurs «paquets» de 255 de cette valeur.
def codage_rle(liste_octets):
'''Renvoie une liste d'octets obtenue par compression RLE'''
liste_rle = []
i = 0
while i < len(liste_octets):
c = liste_octets[i]
k = 1
while i+k < len(liste_octets) and liste_octets[i+k] == c:
k += 1
q = k // 255
r = k % 255
for _ in range(q):
liste_rle.append(255)
liste_rle.append(c)
if r != 0:
liste_rle.append(r)
liste_rle.append(c)
i += k
return liste_rleAutre solution (plus simple !) proposée par Franck Chambon :
def codage_rle(liste_octets):
'''Renvoie une liste d'octets obtenue par compression RLE'''
liste_rle = []
i = 0
while i < len(liste_octets):
c = liste_octets[i]
k = 1
while i+k < len(liste_octets) and liste_octets[i+k] == c and k < 255:
k += 1
liste_rle.append(k)
liste_rle.append(c)
i += k
return liste_rleCorrection Q1
def salaire_moyen_condition(employes, champ, valeur):
'''Renvoie le salaire moyen des employes ayant val comme valeur associée
au champ donné en argument.
Si le nombre d'employés considéré est nul, cette fonction renvoie None'''
s = 0
n = 0
for employe in employes:
if employe[champ] == valeur:
s += employe['salaire']
n += 1
if n == 0:
return None
return s/nCorrection Q2
def effectif_par_sexe(employes):
nf, nh = 0, 0
for employe in employes:
if employe['sexe'] == 'F':
nf += 1
else:
nh += 1
return {'F':nf, 'M':nh}Correction Q3
- Le calcul de
moy_fva renvoyer une erreur caremployesest mis entre guillements.- De plus, le calcul final est faux puisqu'il fait simplement la différence entre
moy_hetmoy_f.
- De plus, le calcul final est faux puisqu'il fait simplement la différence entre
def calcul_ecart_sexe(employes):
'''Renvoie l'écart de salaire en pourcentage pour les femmes
par rapport aux hommes'''
if effectif_par_sexe(employes)['F'] == 0 or effectif_par_sexe(employes)['M'] == 0:
return None
moy_h = salaire_moyen_condition(employes, 'sexe', 'M')
moy_f = salaire_moyen_condition(employes, 'sexe', 'F')
return (moy_h - moy_f) / moy_h * 100
def test_calcul_ecart_sexe():
assert calcul_ecart_sexe([{'experience': 5, 'etudes': 3, 'sexe': 'F', 'salaire': 2400}]) == None
assert calcul_ecart_sexe(donnees.employes) >= 0 and calcul_ecart_sexe(donnees.employes) <= 100Avec les données complètes, cet écart est d'environ 8,6%.
Correction Q4
>>> salaire_par_proximite(donnees_completes.employes, {'experience': 3, 'etudes': 3, 'sexe': 'F'})
2229.6666666666665
>>> salaire_par_proximite(donnees_completes.employes, {'experience': 3, 'etudes': 3, 'sexe': 'M'})
2406.0Il y a un problème dans le code dans le calcul de la distance. En effet, si deux personnes sont de sexe différent, leur distance sera augmentée de 4 par rapport à si elles étaient de même sexe. Ceci n'a pas de raison d'être alors qu'on cherche justement à mesurer les écarts selon le sexe.
Une bonne fonction ```distance``` serait donc :def distance(e1, e2):
'''Renvoie la mesure de distance entre deux personnes.'''
s = 0
s = s + (e1['experience'] - e2['experience'])**2
s = s + (e1['etudes'] - e2['etudes'])**2
return sqrt(s)Correction Q1
def est_bissextile(n):
if n % 400 == 0:
return True
elif n % 4 == 0 and n % 100 != 0:
return True
else:
return FalseCorrection Q2
def determiner_phase(n):
assert n <= 28 and n >= 1
if n <= 5:
return 1
elif n <= 13:
return 2
elif n == 14:
return 3
else:
return 4Correction Q3
def test_ajouter_jours():
assert ajouter_jours((7, 9, 2025), 3) == (10, 9, 2025)
assert ajouter_jours((29, 8, 2025), 3) == (1, 9, 2025)
#pour tester le changement de mois sur un mois à 31 jours
assert ajouter_jours((29, 9, 2025), 3) == (2, 10, 2025)
#pour tester le changement de mois sur un mois à 30 jours
assert ajouter_jours((31, 12, 2025), 1) == (1, 1, 2026)
#pour tester un changement d'année
assert ajouter_jours((28, 2, 2024), 3) == (2, 3, 2024)
#pour tester le changement de mois sur une année bissextile
assert ajouter_jours((28, 2, 2025), 3) == (3, 3, 2025)
#pour tester le changement de mois sur une année non-bissextileCorrection Q4
L'erreur qui apparait
datetime(
ValueError: month must be in 1..12semble nous indiquer que la date n'est pas au bon format.
En effet la documentation de ```iCalendar``` nous dit que la date doit être écrite au format ```AAAAMMJJ```.
Or, la ligne de codedate = str(annee)+str(mois) +str(jour)va produire 202535 pour le 05 mars 2025, au lieu de 20250305.
Pour résoudre ce problème, il faut remplacer cette ligne par :date = str(annee)+'{:02d}'.format(mois) + '{:02d}'.format(jour)_Remarque : le formatage des chaînes de caractères n'est pas au programme..._
Correction Q1
def croissance_moyenne(plantes):
if plantes == []:
return None
s = 0
for plante in plantes:
s += plante.croissance
return s / len(plantes)
def test_croissance_moyenne():
assert croissance_moyenne([]) == None
assert croissance_moyenne(plantes) == 79.0Correction Q2
def dictionnaire_mesure(plantes, mesures):
d = {}
for m in mesures:
if m['plante'] in d:
d[m['plante']].append(m)
else:
d[m['plante']] = [m]
for p in plantes:
if p.nom not in d:
d[p.nom] = []
return d
def test_dictionnaire_mesure():
assert dictionnaire_mesure(plantes, mesures)['Fougère'] == []Correction Q3
Le problème vient de l'instruction remove qui est utilisée dans la boucle et qui modifie la liste en cours de parcours.
Par exemple, le code :lst = [11, 13, 8, 4, 11, 3]
for k in lst:
if k > 10:
lst.remove(k)
print(lst)renverra la liste [13, 8, 4, 3] car la suppression du 11 a créé un décalage.
Correction Q4
Pour éviter le problème précédent, il faut parcourir une copie de liste_mesures.
def purger_mesures_extremes(liste_mesures):
'''
Supprime de la liste toutes les mesures dont la température
n'est pas comprise entre 20 et 25°C inclus.
'''
copy_liste_mesures = liste_mesures.copy()
for mesure in copy_liste_mesures:
if mesure['temperature'] < 20 or mesure['temperature'] > 25:
liste_mesures.remove(mesure)
return liste_mesuresCorrection Q1
def total_simple(empreinte):
'''Fonction qui renvoie l'empreinte carbone totale d'un dictionnaire associant
une empreinte carbone à des noms de catégories'''
d = chargement_json(empreinte)
total = 0
for key in d:
total += d[key]
return total
def empreinte_totale_ada():
print(total_simple('empreinte_ada_agr.json'))Correction Q2
def total_rec(empreinte):
'''Fonction récursive qui renvoie l'empreinte carbone totale représentée
par un dictionnaire dont les valeurs peuvent aussi être des dictionnaires'''
total = 0
for key in empreinte:
if not est_dictionnaire(empreinte[key]):
total += empreinte[key]
else:
total += total_rec(empreinte[key])
return totalCorrection Q3
La fonction n'est pas correcte, car sa structure récursive empêche de parcourir la totalité du dictionnaire. Si un valeur supérieure à 1000 est à la fin du dictionnaire, elle n'est pas détectée.
def alerte_valeur_aberrante(empreinte, limite):
'''
Fonction censée déterminer si au moins une valeur du dictionnaire
dépasse strictement la limite donnée.
'''
valeur_aberrante = False
for categorie, valeur in empreinte.items():
if est_dictionnaire(valeur):
valeur_aberrante = valeur_aberrante or alerte_valeur_aberrante(valeur, limite)
else:
if valeur > limite:
valeur_aberrante = True
return valeur_aberranteCorrection Q4
def test_alerte_valeur_aberrante():
# que des valeurs simples dans le dictionnaire
dico1 = {'a': 15, 'b': 12, 'c': 14, 'd': 8}
assert alerte_valeur_aberrante(dico1, 20) == False, 'test simple, sans valeur aberrante'
dico2 = {'a': 25, 'b': 12, 'c': 14, 'd': 8}
assert alerte_valeur_aberrante(dico2, 20) == True, 'test simple, avec valeur aberrante'
dico3 = {'a': 15, 'b': 22, 'c': 14, 'd': 8}
assert alerte_valeur_aberrante(dico3, 20) == True, 'test simple, avec valeur aberrante'
dico4 = {'a': 15, 'b': 12, 'c': 24, 'd': 8}
assert alerte_valeur_aberrante(dico4, 20) == True, 'test simple, avec valeur aberrante'
dico5 = {'a': 15, 'b': 12, 'c': 14, 'd': 28}
assert alerte_valeur_aberrante(dico5, 20) == True, 'test simple, avec valeur aberrante'
# avec un dictionnaire imbriqué
dico1 = {'a': {'a1': 15, 'a2': 4}, 'b': 12, 'c': 14, 'd': 8}
assert alerte_valeur_aberrante(dico1, 20) == False, 'test avec une imbrication, sans valeur aberrante'
dico2 = {'a': {'a1': 25, 'a2': 4}, 'b': 12, 'c': 14, 'd': 8}
assert alerte_valeur_aberrante(dico2, 20) == True, 'test avec une imbrication, avec valeur aberrante dans l'imbrication'
dico3 = {'a': {'a1': 15, 'a2': 24}, 'b': 12, 'c': 14, 'd': 8}
assert alerte_valeur_aberrante(dico3, 20) == True, 'test avec une imbrication, avec valeur aberrante dans l'imbrication'
dico4 = {'a': {'a1': 15, 'a2': 4}, 'b': 12, 'c': 24, 'd': 8}
assert alerte_valeur_aberrante(dico4, 20) == True, 'test avec une imbrication, avec valeur aberrante hors l'imbrication'
dico5 = {'a': 15, 'b': 22, 'c': {'c1': 14, 'c2': 19}, 'd': 8}
assert alerte_valeur_aberrante(dico5, 20) == True, 'test avec une imbrication, avec valeur aberrante hors l'imbrication'
dico6 = {'a': 15, 'b': 12, 'c': {'c1': 14, 'c2': 29}, 'd': 8}
assert alerte_valeur_aberrante(dico6, 20) == True, 'test avec une imbrication, avec valeur aberrante dans l'imbrication'
dico7 = {'a': 15, 'b': 12, 'c': {'c1': 14, 'c2': 19}, 'd': 28}
assert alerte_valeur_aberrante(dico7, 20) == True, 'test avec une imbrication, avec valeur aberrante hors l'imbrication'
# avec plusieurs dictionnaires imbriqué
dico1 = {'a': {'a1': 15, 'a2': 4}, 'b': 12, 'c': {'c1': 14, 'c2': 19}, 'd': 8}
assert alerte_valeur_aberrante(dico1, 20) == False, 'test avec une imbrication, sans valeur aberrante'
dico1 = {'a': {'a1': 15, 'a2': 4}, 'b': 12, 'c': {'c1': 14, 'c2': 19}, 'd': 8}
dico2 = {'a': {'a1': 15, 'a2': 24}, 'b': 12, 'c': {'c1': 14, 'c2': 19}, 'd': 8}
assert alerte_valeur_aberrante(dico2, 20) == True, 'test avec plusieurs imbrications, avec valeur aberrante dans une première imbrication'
dico3 = {'a': {'a1': 15, 'a2': 4}, 'b': 12, 'c': {'c1': 24, 'c2': 19}, 'd': 8}
assert alerte_valeur_aberrante(dico3, 20) == True, 'test avec plusieurs imbrications, avec valeur aberrante dans une seconde imbrication'
dico4 = {'a': {'a1': 15, 'a2': 4}, 'b': 22, 'c': {'c1': 14, 'c2': 19}, 'd': 8}
assert alerte_valeur_aberrante(dico4, 20) == True, 'test avec plusieurs imbrications, avec valeur aberrante hors des imbrications'
# avec des imbrications à plusieurs niveaux
dico1 = {'a': 15,
'b': {'b1': 12,
'b2': {'b21': 1, 'b22': 18, 'b23': 10},
'b3': 19,
'b4': {'b41': 16, 'b42': 0}},
'c': 14,
'd': 8}
assert alerte_valeur_aberrante(dico1, 20) == False, 'test imbrication à 2 niveaux, sans valeur aberrante'
dico2 = {'a': 15,
'b': {'b1': 12,
'b2': {'b21': 1, 'b22': 18, 'b23': 10},
'b3': 19,
'b4': {'b41': 16, 'b42': 0}},
'c': 14,
'd': 28}
assert alerte_valeur_aberrante(dico2, 20) == True, 'test imbrication à 2 niveaux, avec valeur aberrante hors des imbrications'
dico3 = {'a': 15,
'b': {'b1': 12,
'b2': {'b21': 1, 'b22': 18, 'b23': 10},
'b3': 29,
'b4': {'b41': 16, 'b42': 0}},
'c': 14,
'd': 8}
assert alerte_valeur_aberrante(dico3, 20) == True, 'test imbrication à 2 niveaux, avec valeur aberrante dans le premier niveau'
dico4 = {'a': 15,
'b': {'b1': 12,
'b2': {'b21': 1, 'b22': 28, 'b23': 10},
'b3': 19,
'b4': {'b41': 16, 'b42': 0}},
'c': 14,
'd': 8}
assert alerte_valeur_aberrante(dico4, 20) == True, 'test imbrication à 2 niveaux, avec valeur aberrante dans le second niveau'
dico5 = {'a': 15,
'b': {'b1': 12,
'b2': {'b21': 1, 'b22': 18, 'b23': 10},
'b3': 19,
'b4': {'b41': 26, 'b42': 0}},
'c': 14,
'd': 8}
assert alerte_valeur_aberrante(dico5, 20) == True, 'test imbrication à 2 niveaux, avec valeur aberrante dans le second niveau'Correction Q1
def smoothie_possible(self, nom_smoothie):
'''Retourne True si le smoothie peut être préparé avec les fruits disponibles, False sinon.'''
for fruit in self.db_smoothies[nom_smoothie]:
if fruit not in self.liste_fruits_disponibles:
return False
return TrueCorrection Q2
def liste_smoothies_possibles(self):
'''Retourne la liste des smoothies pouvant être préparés avec les fruits disponibles.'''
possibles = []
for smoothie in self.db_smoothies:
if self.smoothie_possible(smoothie):
possibles.append(smoothie)
return possiblesCorrection Q3
def test_score_proximité():
b = Boutique_smoothie(['Mangue', 'Ananas', 'Banane', 'Fraise'])
assert b.score_proximité('Tropical', 'Rouge') == 0
assert b.score_proximité('Agrume', 'Tropical citron') == 1
assert b.score_proximité('Tropical', 'Tropical citron') == 2
assert b.score_proximité('Tropical', 'Tropical') == 3Correction Q4
Le smoothie renvoyé par la méthode plus_proche_possible n'est pas le bon car il correspond exactement au smoothie passé en paramètre. En effet nulle part dans la fonction on teste si le smoothie n'est pas celui d'origine. Or c'est forcément lui qui aura le plus gros score car il a exactement les mêmes ingrédients.
Correction de la fonction :def plus_proche_possible(self, nom_smoothie_ref):
'''Retourne le nom du smoothie le plus proche de nom_smoothie_ref en termes de fruits communs parmi les smoothies possibles.
En cas d'égalité, retourne le premier trouvé.
'''
max_communs = 0
smoothie_proche = None
for nom_smoothie in self.db_smoothies:
# problème avec nom_smoothie_ref et nom_smoothie identique
# problème si le smoothie n'est pas possible
if nom_smoothie != nom_smoothie_ref and self.smoothie_possible(nom_smoothie):
nb_communs = self.score_proximité(nom_smoothie_ref, nom_smoothie)
if nb_communs > max_communs:
max_communs = nb_communs
smoothie_proche = nom_smoothie
return smoothie_procheCorrection Q5
boutique = Boutique_smoothie(['Mangue', 'Ananas', 'Banane', 'Fraise', 'Citron', 'Kiwi', 'Pomme verte'])
boutique.affichage_possibles()Correction Q1
c1 = Coccinelle('femelle', 10, 2)
c2 = Coccinelle('femelle', 10, 2)
c3 = Coccinelle('male', 10, 2)
cocci = [c1, c2, c3]
proies = 200
for k in range(5):
cocci, proies = evolution(cocci, proies)
print('jour : ', k+1)
print('nombre de coccinelles :', len(cocci))
print('nombre de proies :', proies)
print('-------------')Correction Q2
def simulation_simple(population, nb_proies):
for k in range(30):
population, nb_proies = evolution(population, nb_proies)
if len(population) == 0:
return (len(population), nb_proies, k+1)
if nb_proies == 0:
return (len(population), nb_proies, k+1)
return (len(population), nb_proies, k+1)Lorsqu'on exécute ce code avec les 3 coccinelles précédentes et 1000 pucerons, il renvoie :
(63, 0, 10)Ce qui signifie qu'au bout de 10 jours, il y 63 coccinelles mais plus aucun puceron.
Correction Q3
def chasser(self, nb_proies, nb_coccinelles):
'''
renvoie le nombre de pucerons une fois chassés par les coccinelles,
suivant leur nombre de départ et le nombre de coccinelles
'''
# s'il n'y a aucune coccinelles, le nombre de pucerons est inchangé
if nb_coccinelles == 0:
return nb_proies
# calcul du nombre de pucerons par coccinelle
proies_par_cocci = nb_proies / nb_coccinelles
# le nombre de pucerons mangés est différent suivant
# le nombre de pucerons par coccinelle
if proies_par_cocci > 20:
consomme = random.randint(12, 20)
elif proies_par_cocci > 10:
consomme = random.randint(8, 15)
else:
consomme = random.randint(3, 8)
consomme = min(consomme, nb_proies)
# calcul du nouvau niveau de nutrition suivant le nombre
# de pucerons mangés
if consomme >= 10:
self.niv_nutrition += 1
else:
self.niv_nutrition = max(0, self.niv_nutrition - 1)
# on renvoie le nouveau nombre de pucerons
return nb_proies - consommeCorrection Q4
Modification de la méthode reproduction :
def reproduction(self):
'''
Une femelle avec un niveau de nutrition >= 2 engendre exactement
deux descendants : un mâle et une femelle.
'''
if self.age < 20:
return []
descendants = []
if self.sexe == 'femelle' and self.niv_nutrition >= 2:
descendants.append(Coccinelle('male', 0, 0))
descendants.append(Coccinelle('femelle', 0, 0))
self.niv_nutrition = 0
return descendantsModification de la méthode a_survecu :
def a_survecu(self):
'''
Met à jour l'âge de la coccinelle et indique si elle est encore en vie.
'''
if self.niv_nutrition == 0:
r = random.randint(1,3)
if r == 1:
return False
self.age = self.age + 1
return self.age < self.esperance_de_vieLorsqu'on refait les tests de la question 2, on s'aperçoit qu'au bout de 30 jours, le nombre de coccinelles est de 51. Par contre, le nombre de pucerons est aux environs de 120 000.
Correction Q1
def calcul_recettes():
recette = 0
for k in range(1000):
for j in range(500):
recette += 2.27 + 5.19 + 1.81
return recetteL'exécution de cette fonction donne 4634999.999986519, et non pas 4635000. Le problème est dû à la représentation des flottants.
Correction Q2
def convertir_BCD_vers_decimal(liste_quartets):
part_entiere = ''
for i in range(len(liste_quartets)-2):
part_entiere += str(int(liste_quartets[i], 2))
part_decimale = ''
for i in range(len(liste_quartets)-2, len(liste_quartets)):
part_decimale += str(int(liste_quartets[i], 2))
partie_entiere = int(part_entiere)
partie_decimale = int(part_decimale)/10**len(part_decimale)
return partie_entiere + partie_decimale
assert convertir_BCD_vers_decimal(['0001','0011', '0101', '0110']) == 13.56Correction Q3
Il rajouter la ligne 20 pour corriger les dépassements éventuels.
def additionner_nombres_format_BCD(a, b):
'''
Additionne deux nombres au format BCD, quartet par quartet.
'''
liste_quartets1 = convertir_dec_vers_BCD(a)
liste_quartets2 = convertir_dec_vers_BCD(b)
# Ajustement de la longueur
liste_quartets1, liste_quartets2 = aligner_quartets(liste_quartets1, liste_quartets2)
retenue = 0
resultat = []
longueur_max = max(len(liste_quartets1), len(liste_quartets2))
for i in range(longueur_max):
index = longueur_max - i - 1
# Addition binaire simple des quartets
somme, retenue = additionner_binaire_quartets(liste_quartets1[index], liste_quartets2[index], retenue)
somme, retenue = corriger_BCD(somme, retenue)
resultat.insert(0, somme)
# Gestion de la dernière retenue éventuelle
if retenue == 1:
resultat.insert(0, '0001')
return resultatCorrection Q4
def aligner_quartets(q1: list, q2: list) -> tuple:
'''
Doit équilibrer les deux listes en ajoutant des '0000' à gauche
de la liste la plus courte.
'''
n1 = len(q1)
n2 = len(q2)
if n1 >= n2:
while len(q1) != len(q2):
q2.insert(0, '0000')
else:
while len(q1) != len(q2):
q1.insert(0, '0000')
return q1, q2Correction Q1
def distance(self, sommet):
'''
renvoie la distance de l'objet à sommet
'''
return math.sqrt((self.x-sommet.x)**2 + (self.y-sommet.y)**2 + (self.y-sommet.y)**2)Correction Q2
_Correction proposée par math93_
def sommets_adjacents(self, s1, s2):
'''
Renvoie True si s1 et s2 sont deux sommets consécutifs d'une même face.
Les arêtes ne sont pas orientées.
'''
for face in self.faces:
n = len(face.sommets)
for i in range(n):
a = face.sommets[i]
b = face.sommets[(i + 1) % n]
if (a is s1 and b is s2) or (a is s2 and b is s1):
return True
return FalseCorrection Q3
def estimation_impression(volume, parametres_imprimante):
vol_impression = volume * parametres_imprimante['remplissage']
return vol_impression / parametres_imprimante['vitesse_extrusion']Correction Q4
def transformer(self, rapport):
'''
Applique une transformation d'échelle à l'objet 3D en modifiant directement ses sommets.
'''
sommets = []
for sommet in self.sommets:
sommets.append(
Sommet(sommet.x*rapport, sommet.y*rapport, sommet.z*rapport))
new_obj = Objet3D()
new_obj.sommets = sommets
for face in self.faces:
new_obj.ajouter_face(face.sommets)
return new_objCorrection Q1
def total_conso(donnees, jour):
total = 0
presence_mesure = False
for d in donnees:
if d['jour'] == jour:
presence_mesure = True
total += d['chaude'] + d['froide']
if presence_mesure == False:
return None
return totalCorrection Q2
def fuite_possible(donnees, jour):
k = 0
for d in donnees:
if d['jour'] == jour and '00:00' <= d['heure'] <= '05:00':
if d['chaude'] + d['froide'] > 0:
k += 1
if k >= 3:
return True
else:
k = 0
return FalseCorrection Q3
L'erreur se situe à la ligne 14. Il faut remplacer le 2 par un 3 car la moyenne se fait sur 3 nombres.
def lissage_conso(valeurs):
'''
Calcule une moyenne glissante sur les valeurs.
Pour chaque valeur, on calcule la moyenne avec ses voisins.
'''
lisse = []
for i in range(len(valeurs)):
if i == 0:
m = (valeurs[i] + valeurs[i+1]) / 2
elif i == len(valeurs)-1:
m = (valeurs[i-1] + valeurs[i]) / 2
else:
m = (valeurs[i-1] + valeurs[i] + valeurs[i+1]) / 3
lisse.append(m)
return lisseCorrection Q4
Les autres cas limites sont les cas où la liste valeurs ne contient qu'un seul élément, ou bien aucun élément.
def lissage_conso(valeurs):
'''
Calcule une moyenne glissante sur les valeurs.
Pour chaque valeur, on calcule la moyenne avec ses voisins.
'''
if len(valeurs) == 0:
return None
if len(valeurs) == 1:
return valeurs
lisse = []
for i in range(len(valeurs)):
if i == 0:
m = (valeurs[i] + valeurs[i+1]) / 2
elif i == len(valeurs)-1:
m = (valeurs[i-1] + valeurs[i]) / 2
else:
m = (valeurs[i-1] + valeurs[i] + valeurs[i+1]) / 3
lisse.append(m)
return lisseOn peut maintenant compléter la fonction test_lissage :
def test_lissage():
assert lissage_conso([]) == None
assert lissage_conso([4]) == [4]
assert lissage_conso([4, 10, 10]) == [7, 8, 10]
print('tests passés avec succès')Correction Q1
def distance(habitat_1, habitat_2):
'''
Calcule la distance euclidienne entre deux habitats.
entrée :
- habitat_1 : dictionnaire représentant un habitat.
- habitat_2 : dictionnaire représentant un autre habitat.
sortie :
- float : distance euclidienne entre habitat_1 et habitat_2.
'''
return sqrt((habitat_1['vegetation']-habitat_2['vegetation'])**2 + (habitat_1['proximite_eau']-habitat_2['proximite_eau'])**2 + (habitat_1['densite_urbaine']-habitat_2['densite_urbaine'])**2 + (habitat_1['disponibilite_proies']-habitat_2['disponibilite_proies'])**2) Correction Q2
def distance_d_un_habitat(habitat, habitats):
'''
Calcule la distance entre un habitat et chaque habitat de la liste.
entrée :
- habitat : dictionnaire représentant un habitat.
- habitats : liste de dictionnaires représentant des habitats.
sortie :
- list[tuple] : liste de tuples (distance, habitat) où distance est la distance entre habitat et chaque habitat de la liste.
'''
lst = []
for hab in habitats:
lst.append((distance(habitat, hab), hab))
return lstCorrection Q3
>>> distance_d_un_habitat(nouveau, zones_connues)[:3]
[(7.211102550927978, {'vegetation': 9, 'proximite_eau': 6, 'densite_urbaine': 0, 'disponibilite_proies': 4, 'presence_renard': 1}), (8.660254037844387, {'vegetation': 10, 'proximite_eau': 5, 'densite_urbaine': 9, 'disponibilite_proies': 10, 'presence_renard': 0}), (5.196152422706632, {'vegetation': 8, 'proximite_eau': 5, 'densite_urbaine': 1, 'disponibilite_proies': 6, 'presence_renard': 0})]On retrouve bien les valeurs indiquées dans l'énoncé.
Correction Q4
L'erreur se situe à la ligne 16. Il faut remplacer distance par caracteristiques.
def presence_renard(k, habitat, habitats):
'''
Vérifie si l'habitat donné a plus de k/2 voisins avec des renards.
entrée :
- k : entier représentant le nombre d'habitats à considérer.
- habitat : dictionnaire représentant un habitat.
- habitats : liste de dictionnaires représentant des habitats.
sortie :
- bool : True si l'habitat a plus de k/2 voisins avec des renards, False sinon.
'''
habitats = k_plus_proches(k, habitat, habitats)
n_renards = 0
for habitat in habitats:
distance = habitat[0]
caracteristiques = habitat[1]
if caracteristiques['presence_renard'] == 1:
n_renards += 1
return n_renards > k/2Correction Q5
Les différents tests de la fonction presence_renard montrent que dès que k dépasse 3, la fonction renvoie True.
Correction Q1
def __init__(self, identifiant, nom, poids, date_arrivee):
self.identifiant = identifiant
self.nom = nom
self.poids = poids
self.date_arrivee = date_arriveeCorrection Q2
def __str__(self):
ch = 'Renard ID '
ch += str(self.identifiant)
ch += ' - '
ch += self.nom
ch += ' (Arrivé le '
ch += self.date_arrivee
ch += ')'
return ch>>> renard1 = Renard(200, 'Oscar', 5.1, '2026-01-01')
>>> print(renard1)
Renard ID 200 - Oscar (Arrivé le 2026-01-01)Correction Q3
Le problème vient de l'instanciation de l'objet renard à la ligne 9. L'identifiant et le poids sont récupérés sous la forme d'une chaine de caractères au lieu de nombres.
Méthode corrigée :def importer_donnees(self, nom_fichier):
'''
Fonction qui importe les données des renards à partir d'un fichier CSV.
'''
print(f'Tentative d'importation depuis {nom_fichier}...')
with open(nom_fichier, 'r', encoding='utf-8') as f:
lignes = csv.DictReader(f, delimiter=';')
for ligne in lignes:
renard = Renard(int(ligne['id']), ligne['nom'], float(ligne['poids']), ligne['date_arrivee'])
self.recueillir(renard)'Tests :
>>> refuge1 = Refuge('SOS Goupil', '2 rue du Renard 63150 Renardville')
>>> refuge1.importer_donnees('donnees_renards.csv')
Tentative d'importation depuis donnees_renards.csv...
>>> print(refuge1.liste_renards[0])
Renard ID 101 - Zorro (Arrivé le 2023-01-15)Correction Q4
L'appel à la méthode pourcentage_peu_corpulents renvoie la valeur 50.0
>>> refuge1.pourcentage_peu_corpulents()
50.0Si on observe la longueur de la liste renvoyée par la méthode lister_peu_corpulents, on obtient 15.
>>> len(refuge1.lister_peu_corpulents())
15Si on regarde le nombre total de renards, on obtient 30.
>>> len(refuge1.liste_renards)
30Comme 15 est la moitié de 30, le pourcentage de 50% renvoyé par la méthode pourcentage_peu_corpulents est donc cohérent.
Correction Q1
>>> altitudes, temperatures, longitudes, latitudes = recupere_donnees_fichier_csv('releves_ballon_sonde.csv')On récupère ainsi les listes altitudes, temperatures, longitudes et latitudes.
Correction Q2
def conversion_K_en_C(liste_temperatures):
lst = []
for temp_K in liste_temperatures:
temp_C = round(temp_K - 273.15, 1)
lst.append(temp_C)
return lstTest :
>>> conversion_K_en_C(recupere_donnees_fichier_csv('releves_ballon_sonde.csv')[1])
[15.0, 13.7, 11.7, 7.9, 3.4, 0.5, -2.3, -17.7, -28.9, -42.5, -48.3, -56.0, -56.2, -56.5, -56.5, -56.0, -56.1, -53.0, -50.1, -48.0]Correction Q3
def altitude_la_plus_froide(liste_altitudes, liste_temperatures):
min_temp = 10**3
for temp in liste_temperatures:
if temp < min_temp:
min_temp = temp
lst = []
for i in range(len(liste_altitudes)):
if liste_temperatures[i] == min_temp:
lst.append(liste_altitudes[i])
return (min_temp, lst)Correction Q4
Il faut rajouter la ligne
assert len(liste_longitudes) == len(liste_latitudes)au début de la fonction genere_kml.
Correction Q5
>>> altitudes, temperatures, longitudes, latitudes = recupere_donnees_fichier_csv('releves_ballon_sonde.csv')
>>> genere_kml(longitudes, latitudes)Correction Q6
{: .center .autolight}
Correction Q1
def nb_occupants_restants(self):
''' renvoie le nombre d'occupants restants dans la pièce.
'''
nb_occ = 0
for lst in self.grille:
for nb in lst:
nb_occ += nb
return nb_occCorrection Q2
def evacuation(p, silencieux = True):
''' simule l'évacuation de la pièce et renvoie le nombre de tours nécessaire.
A chaque tour, chacun des occupants se déplace, si possible, d'une case
vers la sortie la plus proche. Si le paramètre silencieux vaut false,
l'état de la pièce à chaque tour est affiché dans la console.
'''
nb_tours = 0
while p.nb_occupants_restants() > 0:
nb_tours += 1
if not p.alerter(silencieux):
break
return nb_toursCorrection Q3
def ajouter_sortie(self, direction, position):
''' permet d'ajouter des sorties à la pièce.
'''
if direction == 'N':
self.sorties.append((0, position))
elif direction == 'O':
self.sorties.append((position, 0))
elif direction == 'S':
self.sorties.append((self.i_max, position))
elif direction == 'E':
self.sorties.append((position, self.j_max))Correction Q4
Analysons le code initial :
def choix_sortie(self, i, j):
''' renvoie la sortie à utiliser pour une personne positionnée sur la ligne i et la colonne j.
A CORRIGER (QUESTION 4) (Pour l'instant, seule la 1ère sortie est utilisée !)
'''
assert len(self.sorties) > 0, 'Aucune sortie'
choix = self.sorties[0]
distance = abs(i - choix[0]) + abs(j - choix[1])
for k in range(1, len(self.sorties)):
autre_sortie = self.sorties[k]
if k < 0:
choix = autre_sortie
distance = d2
return choixLe test à la ligne 10 n'a aucun sens, k ne pouvant jamais être négatif.
Pour corriger cette fonction, il faut parcourir toutes les sorties pour sélectionner la plus proche:def choix_sortie(self, i, j):
''' renvoie la sortie à utiliser pour une personne positionnée sur la ligne i et la colonne j.
A CORRIGER (QUESTION 4) (Pour l'instant, seule la 1ère sortie est utilisée !)
'''
assert len(self.sorties) > 0, 'Aucune sortie'
choix = self.sorties[0]
distance = abs(i - choix[0]) + abs(j - choix[1])
for k in range(1, len(self.sorties)):
d = abs(i - self.sorties[k][0]) + abs(j - self.sorties[k][1])
if d < distance:
choix = self.sorties[k]
distance = d
return choixCorrection Q1
def normalisation_tel(num):
clean_num = ''
for c in num:
if c.isdigit():
clean_num += c
return clean_numCorrection Q2
def test_validation_tel():
'''
Tous les tests doivent passer...
'''
assert validation_tel('0612999012') == True
assert validation_tel('0712999012') == True
assert validation_tel('02.12.99.90.12') == False
assert validation_tel('1612999012') == False
assert validation_tel('12999012') == False
assert validation_tel('0512999012') == False
assert validation_tel('1612999012') == False
print('Les tests de la fonction validation_tel sont passés')Correction Q3
def consultation_vaccination_chat(date):
'''
Renvoie les consultations de vaccination de chats dont la date
est supérieure à la date `date`.
:param: date: date minimale
:return: liste [(id_animal, nom_animal, tel_proprietaire, date_consultation)]
'''
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
resultat = cursor.execute(
'''
SELECT a.id, a.nom , telephone, c.date
FROM proprietaire as p
JOIN animal as a ON p.id = a.id_proprietaire
JOIN consultation as c ON c.id_animal = a.id
WHERE c.date > ? and a.espece = 'chat' and c.motif ='vaccination'
ORDER BY a.id, c.date;
''',
(date,),
)
return list(resultat)Correction Q4
Le problème vient de la ligne
elif date < derniere[id_animal][3]:On teste si date est inférieur à derniere[id_animal][3] alors qu'on doit tester si c'est supérieur.
Le bon code est donc :def derniere_vaccination(consultations):
'''
Renvoie un dictionnaire ayant pour clef l'identifiant de l'animal,
et dont la valeur associée est la dernière consultation de cet animal.
Chaque consultation est un tuple :
(id_animal, nom_animal, tel_proprietaire, date_consultation)
'''
derniere = {}
for consult in consultations:
id_animal = consult[0]
date = consult[3]
if id_animal not in derniere:
derniere[id_animal] = consult
elif date > derniere[id_animal][3]:
derniere[id_animal] = consult
return derniereCorrection Q1
def ecart_temperature(datas, annee):
for dico in datas:
if annee in dico.values():
return dico['écart']
return None
def test_ecart_temperature():
assert ecart_temperature(datas_temperature, 2025) == 1.29
assert ecart_temperature(datas_temperature, 2030) == None
print('tests passés avec succès')Correction Q2
L'instruction
print(derniere_annee_ecart_negatif(datas_temperature))affiche la valeur 1977, qui est la dernière année avec un écart négatif.
Correction Q3
L'erreur vient de la ligne
somme = somme - dico['écart']Il faut ajouter dico['écart'] et non pas le soustraire.
La bonne fonction est donc :def moyenne_ecarts(annee_debut, annee_fin, datas):
'''
Renvoie la moyenne des écarts de température pour la période comprise
entre annee_debut et annee_fin (incluses).
'''
somme = 0
compteur = 0
for dico in datas:
if annee_debut <= dico['année'] and dico['année'] <= annee_fin:
somme = somme + dico['écart']
compteur += 1
return somme / compteurCorrection Q4
def graphique(datas):
'''
Représente visuellement les warming stripes.
'''
fig, ax = plt.subplots(figsize=(10, 2))
# Création d'une palette de couleurs basée sur l'amplitude thermique
cmap = plt.get_cmap('seismic')
temperatures = [dico['écart'] for dico in datas]
max_val = max(max(temperatures), -min(temperatures))
norm = plt.Normalize(-max_val, max_val)
# Création des listes pour les abscisses et ordonnées
annees = []
ordonnees = []
# Remplir les listes annees et ordonnees ici :
for dico in datas:
annees.append(dico['année'])
ordonnees.append(1)
# Génération du graphique
ax.bar(annees, ordonnees, width=1.0, color=cmap(norm(temperatures)))
ax.set_title('Warming Stripes mondiales - Base 1901-2000')
plt.yticks([], []) # Masque l'axe Y car seule la couleur compte
ax.set_xlabel('Année')
plt.tight_layout()
plt.show()Correction Q1
def total_par_type(mouvements, type_mouvement):
total = 0
for mv in mouvements:
if mv['type'] == type_mouvement:
total += mv['montant']
return total
def test_total():
assert total_par_type(mouvements_test, 'dépense') == 2150.0
assert total_par_type(mouvements_test, 'recette') == 2300.0
assert total_par_type(mouvements_test, 'cotisations') == 1200.0
assert total_par_type(mouvements_test, 'voyages') == 0
print('tests passés avec succès')Correction Q2
Le montant annuel des recettes est 2300, le montant annuel des dépenses est 2150. Le solde annuel est donc de 150.
def test_solde_annuel():
assert solde_annuel(mouvements_test) == 150Ce test lève effectivement une erreur.
Correction Q3
Analysons le code de la fonction solde_annuel :
def solde_annuel(mouvements):
'''
Calcule le solde annuel en additionnant les soldes de chaque mois.
'''
total = 0
# Parcourt les mois de l'année pour cumuler le bilan
for m in range(1, 12):
total = total + solde_mensuel(mouvements, m)
return totalL'erreur vient de la ligne 7. Le range ne va pas assez loin, et m s'arrêtera à la valeur 11.
Voici le code corrigé :def solde_annuel(mouvements):
'''
Calcule le solde annuel en additionnant les soldes de chaque mois.
'''
total = 0
# Parcourt les mois de l'année pour cumuler le bilan
for m in range(1, 13):
total = total + solde_mensuel(mouvements, m)
return totalEn appliquant ce code sur le budget complet, on obtient :
le solde annuel sur le fichier complet est de : -3423.1699999999764.
Le solde annuel est donc déficitaire de 3423,17 €.Correction Q1
def temperature_moyenne(zone, donnees):
somme_temperatures = 0
n = 0
for d in donnees:
if d['zone'] == zone:
somme_temperatures += d['temperature']
n += 1
if n == 0:
return None
else:
return somme_temperatures / nCorrection Q2
def detecter_anomalies(zone, seuil, donnees):
t = temperature_moyenne(zone, donnees)
liste_dates = []
for d in donnees:
if d['zone'] == zone and abs(d['temperature'] - t) > seuil:
liste_dates.append(d['date'])
return liste_datesCorrection Q3
def test_zone_inexistante():
'''
Test 1 : Tester une zone qui n'existe pas
À compléter:
1. Appeler evolution_par_decennie avec une zone inexistante
2. Vérifier que le résultat est un dictionnaire vide
'''
assert evolution_par_decennie('Tahiti', donnees_test) == {}
def test_une_seule_decennie():
'''
Test 2: Tester une zone avec données sur une seule décennie
À compléter:
1. Appeler evolution_par_decennie avec la zone appropriée
2. Vérifier que le résultat ne contient qu'une seule décennie (2020)
3. Vérifier la température moyenne
'''
assert len(evolution_par_decennie('Marquises', donnees_test)) == 1
assert evolution_par_decennie('Marquises', donnees_test)[2020] == 26.0
def test_plusieurs_decennies():
'''
Test 3 : Tester une zone avec données sur plusieurs décennies
À compléter:
1. Appeler evolution_par_decennie avec la zone appropriée
2. Vérifier que le résultat contient bien les clés 2010 et 2020
3. Vérifier que les températures moyennes sont cohérentes
'''
resultat = evolution_par_decennie('Societe', donnees_test)
assert 2010 in resultat and 2020 in resultat
assert resultat[2010] == 27.0 and resultat[2020] == 28.5Le problème vient du calcul de la variable decennie à la ligne 80. Il faut multiplier (annee // 10) par 10 pour avoir une décennie correcte.
Correction Q4
def evolution_par_decennie(zone, donnees):
'''
Calcule l'évolution des températures moyennes par décennie pour une zone.
ATTENTION: Cette fonction contient un bug volontaire à détecter et corriger.
Arguments:
zone (str): Nom de l'archipel (ex: 'Societe', 'Tuamotu')
donnees (list): Liste de dictionnaires de relevés
Renvoie:
dict: Dictionnaire {décennie : température_moyenne}
ex: {2010: 27.5, 2020: 28.3}
Renvoie un dictionnaire vide si la zone n'existe pas
'''
# Filtrage des relevés pour la zone
releves_zone = [r for r in donnees if r['zone'] == zone]
if not releves_zone:
return {}
# Regroupement par décennie
temperatures_par_decennie = {}
for releve in releves_zone:
# Extraction de l'année de la date (format: 'YYYY-MM-DD')
annee = int(releve['date'].split('-')[0])
# Calcul de la décennie
decennie = (annee // 10) * 10
if decennie not in temperatures_par_decennie:
temperatures_par_decennie[decennie] = []
temperatures_par_decennie[decennie].append(releve['temperature'])
# Calcul des moyennes
moyennes = {}
for decennie, temperatures in temperatures_par_decennie.items():
moyennes[decennie] = round(sum(temperatures) / len(temperatures), 2)
return moyennesCorrection Q1
def est_en_penurie(liste_reservoirs, nom_reservoir):
for reservoir in liste_reservoirs:
if reservoir['nom'] == nom_reservoir:
taux = reservoir['volume'] / reservoir['capacite']
return taux < 0.2Correction Q2
def volume_par_district(reservoirs):
dico_districts = {}
for reservoir in reservoirs:
nom_district = reservoir['district']
if nom_district in dico_districts:
dico_districts[nom_district] += reservoir['volume']
else:
dico_districts[nom_district] = reservoir['volume']
return dico_districtsCorrection Q3
assert len(reservoirs) > 0
assert volume_moyen(reservoirs) <= max([reservoir['volume'] for reservoir in reservoirs])
assert volume_moyen([{'volume': 50000}, {'volume': 50000}]) == 50000Pour corriger la fonction volume_moyen, il faut envisager le cas où la liste de réservoirs est vide, et diviser par le nombre correct de réservoirs.
def volume_moyen(reservoirs):
'''
Renvoie le volume moyen d'eau disponible dans les réservoirs.
'''
if len(reservoirs) == 0:
return 0
somme_totale = 0
for r in reservoirs:
somme_totale += r['volume']
moyenne = somme_totale / len(reservoirs)
return moyenneCorrection Q4
def volume_par_district(reservoirs):
'''
Renvoie un dictionnaire avec pour chaque district le volume total d'eau disponible.
'''
volumes = {}
for r in reservoirs:
district = r['district']
if district not in volumes:
volumes[district] = 0
volumes[district] += r['volume']
return volumes
def districts_vulnerables(reservoirs, seuil=0.8):
'''
Identifie les districts dont le volume moyen est inférieur à un certain seuil du volume moyen global.
Arguments :
reservoirs : liste de réservoirs
seuil : proportion du volume moyen global pour considérer un district vulnérable (ex: 0.8 pour 80%)
Retour :
liste de noms de districts vulnérables
'''
if not reservoirs:
return []
# Volume moyen global
volume_global_moyen = volume_moyen(reservoirs)
# Volume moyen par district
vol_par_district = volume_par_district(reservoirs)
nb_reservoirs_par_district = {d: 0 for d in vol_par_district}
for r in reservoirs:
nb_reservoirs_par_district[r['district']] += 1
districts_vuln = []
for district, vol_total in vol_par_district.items():
vol_moyen_district = vol_total / nb_reservoirs_par_district[district]
if vol_moyen_district < seuil * volume_global_moyen:
districts_vuln.append(district)
return districts_vuln
vuln = districts_vulnerables(reservoirs, seuil=0.8)
print('Districts vulnérables :', vuln)Correction Q1
def calculer_empreinte(utilisateur):
empreinte = 0
for activite in utilisateur:
empreinte += EMISSIONS[activite] * utilisateur[activite]
return empreinte
assert calculer_empreinte(utilisateur1) == 7490Correction Q2
def classer_par_impact(utilisateur):
dict_impact = {'fort': [], 'moyen': [], 'faible': []}
for activite in utilisateur:
emissions = EMISSIONS[activite] * utilisateur[activite]
if emissions >= 1000:
dict_impact['fort'].append(activite)
elif emissions >= 200:
dict_impact['moyen'].append(activite)
else:
dict_impact['faible'].append(activite)
return dict_impact
assert classer_par_impact(utilisateur2) == {'fort': ['streaming_hd'],
'moyen': ['emails_simples'],
'faible': ['recherches']}Correction Q3
def test_comparer():
diff = comparer(utilisateur4, utilisateur5)
assert diff['emails_simples'] == -200 # (50-100) * 4
assert diff['recherches'] == 350 # (100-50) * 7
# Ajouter vos tests ci-dessous avec justifications
assert diff['streaming_hd'] == 0 # activité absente chez les deux utilisateurs
diff = comparer(utilisateur2, utilisateur4)
assert diff['streaming_hd'] == -500 # (0-5) * 100; activité absente chez un utilisateurCorrection Q4
Si une activité est absente chez l'utilisateur 1, cela entraîne une division par 0. On peut donc plutôt envoyer une valeur par défaut, None par exemple.
def comparer_v2(u1, u2):
'''Compare les émissions de deux utilisateurs pour toutes les activités.
Renvoie un dictionnaire avec, pour chaque activité, l'écart des émissions
sous forme de pourcentage, en proportion de la première émission.'''
ecarts = {}
for activite in EMISSIONS:
quantite1 = 0
quantite2 = 0
if activite in u1:
quantite1 = u1[activite]
if activite in u2:
quantite2 = u2[activite]
emission1 = quantite1 * EMISSIONS[activite]
emission2 = quantite2 * EMISSIONS[activite]
if emission1 == 0:
ecarts[activite] = None
else:
ecarts[activite] = (emission2 - emission1)/emission1 * 100
return ecartsCorrection Q1
def traiter_reponse(self, succes):
if succes:
self.niveau = min(4, self.niveau+1)
else:
self.niveau = 0
self.date_prochaine = date_future(DELAIS[self.niveau])Correction Q2
def extraire_cartes_du_jour(paquet, date_jour):
lst = []
for carte in paquet:
if carte.date_prochaine <= date_jour:
lst.append(carte)
return lstCorrection Q3
L'erreur réside dans le fait d'ajouter des cartes à la liste a_renforcer lorsqu'on trouve une nouvelle valeur minimale: on conserve donc les cartes de l'ancienne valeur minimale. Pour rectifier, il faut réinitialiser cette liste avec seulement la carte correspondante.
def extraire_cartes_a_renforcer(paquet):
'''
Parcourt le paquet et renvoie la liste des cartes ayant le
niveau d'avancement le plus faible.
'''
if len(paquet) == 0:
return []
niveau_min = paquet[0].niveau
a_renforcer = []
for carte in paquet:
if carte.niveau < niveau_min:
niveau_min = carte.niveau
a_renforcer = [carte] # ligne modifiée
elif carte.niveau == niveau_min:
a_renforcer.append(carte)
return a_renforcerCorrection Q1
def bin2dec(t):
'''
renvoie l’entier naturel en base 10 correspondant au tuple t
parametre en entrée:
t: tuple
parametre en sortie:
int
>>> bin2dec((0,1,1,0,0,0,0,1))
97
'''
n = 0 # On initialise n à 0
for i in range(len(t)): # On parcourt le tuple de gauche à droite dans le sens inverse de ses puissances
n += t[i]*2 ** (len(t)-i-1) # On ajoute la puissance de 2 correspondant au rang de i
return n # On renvoie nLorsqu'on utilise cette fonction avec le QR code de la figure 1, on obtient le nom M.Hara.
# implémentation du QR Code de la figure 1:
qrcode_fig1 = ascii.figure1
sol = ''
for lst in qrcode_fig1:
sol += ascii.dict_ascii[bin2dec(lst)]
print(sol)Correction Q2
def qrcode2dec(qrcode):
'''
renvoie une liste d’entiers décimaux correspondant à chacune des lignes du qrcode
parametre en entrée:
qrcode:list de tuples
paramètre en sortie
liste d'entiers
'''
liste_resultat = [] # On initialise une liste vide d'entiers
for ligne in qrcode: # Pour chaque ligne dans le qrcode
liste_resultat.append(bin2dec(ligne)) # on ajoute à liste_resultat la conversion de la ligne en entier
return liste_resultat # On renvoie la liste des résultatsTest avec le QR code de la figure 1:
>>> qrcode2dec(ascii.figure1)
[77, 46, 72, 97, 114, 97]Correction Q3
À l'exécution de la fonction test_dec2str, on obtient l'erreur KeyError: 233. Cela signifie qu'aucune valeur n'est associée à la clé 233 dans le dictionnaire ascii, qui ne contient que des clés entre 0 et 127.
Il faut donc rajouter un test pour vérifier la validité de la clé :def dec2str(liste_dec):
''' entrée: liste d'entiers décimaux
sortie: chaine de caractère formée des caractères correspondant
de la table ascii '''
table_ascii = ascii.dict_ascii
chaine = ''
for entier in liste_dec:
if 0 <= entier <= 127: # Modification: on vérifie que entier est présent comme clé
chaine += table_ascii[entier] # Si oui, on ajoute le caractère
else: # Sinon,
chaine += '□' # On met un caractère d'erreur
return chaineL'exécution de la fonction test_dec2str donne maintenant ceci :
>>> test_dec2str()
Test 1 reussi!
Test 2 reussi!
Test 3 r□ussi!Correction Q4
L'exécution de la fonction str2qrcode avec le message M.Hara renvoie :
[(1, 0, 0, 1, 1, 0, 1),
(1, 0, 1, 1, 1, 0),
(1, 0, 0, 1, 0, 0, 0),
(1, 1, 0, 0, 0, 0, 1),
(1, 1, 1, 0, 0, 1, 0),
(1, 1, 0, 0, 0, 0, 1)]alors qu'elle devrait renvoyer :
[(0, 1, 0, 0, 1, 1, 0, 1),
(0, 0, 1, 0, 1, 1, 1, 0),
(0, 1, 0, 0, 1, 0, 0, 0),
(0, 1, 1, 0, 0, 0, 0, 1),
(0, 1, 1, 1, 0, 0, 1, 0),
(0, 1, 1, 0, 0, 0, 0, 1)]On voit que là où la fonction devrait renvoyer des listes de longueur 8 (correspondant à des octets), elle renvoie des listes de longueur inférieure. Néanmoins les valeurs sont bonnes, il suffit juste de rajouter des 0 à gauche.
def str2qrcode(message):
'''
Convertit une chaine de caractères en liste de tuples binaires.
'''
qrcode = []
table_inverse = {valeur: cle for cle, valeur in ascii.dict_ascii.items()}
for caractere in message:
entier = table_inverse.get(caractere, 63)
binaire_str = bin(entier)[2:]
if len(binaire_str) < 8: # si la longueur de binaire_str est inférieure à 8
binaire_str = '0'*(8-len(binaire_str)) + binaire_str # On le complète par des 0 à gauche
ligne = tuple(int(bit) for bit in binaire_str)
qrcode.append(ligne)
return qrcodeOn vérifie :
>>> str2qrcode('M.Hara')
[(0, 1, 0, 0, 1, 1, 0, 1),
(0, 0, 1, 0, 1, 1, 1, 0),
(0, 1, 0, 0, 1, 0, 0, 0),
(0, 1, 1, 0, 0, 0, 0, 1),
(0, 1, 1, 1, 0, 0, 1, 0),
(0, 1, 1, 0, 0, 0, 0, 1)]On a bien ce qui est attendu.
Correction Q1
En consultant le fichier transmission.py, on remarque qu'il s'agit de méthodes à compléter dans la classe Transmission, en utilisant donc la syntaxe de la POO. Il n'y a donc pas de paramètre trame... ni de valeur à renvoyer : il faut affecter les valeurs aux attributs de la classe. Ce sont donc des _setters_.
On remarque que la classe `Transmission` comporte des *getters*, il est donc préférable de les utiliser pour les tests plutôt que d'accèder directement aux attributs (privés, d'où l'utilisation du `_`).def decoder_temperature(self):
temp_binaire=int(self._trame[16:28], 2)
self._temperature = (temp_binaire - 900) / 10
def decoder_humidite(self):
humidite_dcb = self._trame[28:36]
self._humidite = int(humidite_dcb[:4], 2)*10 + int(humidite_dcb[4:], 2)
# ==========
# Tests
# ==========
t = Transmission('0010101011001000010010001100011000101101')
assert t.get_temperature() == 26.4
assert t.get_humidite() == 62Correction Q2
On vérifie pour chaque bloc de la trame la parité (en comptant les '1' dans le bloc) et on la compare on bit de contrôle.
def est_valide(self):
blocs_trame = [self._trame[0:8], self._trame[8:16], self._trame[16:28], self._trame[28:36]]
bits_controle = self._trame[36:40]
for i in range(4):
nombre_de_1 = 0
for bit in blocs_trame[i]:
if bit == '1':
nombre_de_1 += 1
if nombre_de_1 % 2 != int(bits_controle[i]):
return False
return True Autre version avec une fonction intégrée de calcul de parité d'un bloc:
def est_valide(self):
def parite(bloc):
nombre_de_1 = 0
for bit in bloc:
if bit == '1':
nombre_de_1 += 1
return nombre_de_1 % 2
blocs_trame = [self._trame[0:8], self._trame[8:16], self._trame[16:28], self._trame[28:36]]
bits_controle = self._trame[36:40]
for i in range(4):
if parite(blocs_trame[i]) != int(bits_controle[i]):
return False
return True Correction Q3
On constate une IndexError lors de l'appel à la méthode est_valide. Après analyse du fichier data.txt, on constate que certaines trames ne sont pas valides car elles ne contiennent pas 40 bits.
Il suffit donc d'ajouter un test sur la longueur de la trame au début de la méthode `est_valide`.def est_valide(self):
def parite(bloc):
nombre_de_1 = 0
for bit in bloc:
if bit == '1':
nombre_de_1 += 1
return nombre_de_1 % 2
if len(self._trame) != 40:
return False
blocs_trame = [self._trame[0:8], self._trame[8:16], self._trame[16:28], self._trame[28:36]]
bits_controle = self._trame[36:40]
for i in range(4):
if parite(blocs_trame[i]) != int(bits_controle[i]):
return False
return True Correction Q4
La correction consiste à vérifier la longueur de la trame dans __init__ avant d'appeler decoder(). Si la trame ne fait pas 40 bits, on n'effectue aucun décodage et les attributs restent à None. La méthode est_valide() retourne également False dans ce cas.
Ainsi ```analyse.py``` filtre automatiquement ces trames corrompues grâce au ```if t.est_valide()``` déjà présent.
Nouvelle version du constructeur:def __init__(self, trame):
self._id = None
self._temperature = None
self._humidite = None
self._trame = trame
if len(trame) == 40:
self.decoder()