Accueil

Corrigé Sujet N°10

- Analyse de Consommation d'Eau -

Vous pouvez télécharger les éléments suivants en cliquant sur les boutons associés :
Sujet (PDF) Fichier Python Initial
Fichier Python Corrigé

Question 1

Écrire une fonction total_conso qui prend en paramètres une liste de mesures et un jour (chaîne de caractères), et renvoie la consommation totale d'eau (chaude + froide) pour ce jour. La fonction renvoie None si aucune mesure n'existe pour ce jour.
Correction simple : On filtre les mesures du jour concerné, on additionne les consommations chaude et froide. On utilise un booléen pour savoir si au moins une mesure a été trouvée.

def total_conso(donnees, jour):
    """Retourne la consommation totale pour un jour, ou None si aucune mesure."""
    total = 0
    trouve = False
    for mesure in donnees:
        if mesure['jour'] == jour:
            total += mesure['chaude'] + mesure['froide']
            trouve = True
    if not trouve:
        return None
    return total

# Tests
print(total_conso(donnees, "2025-02-04"))   # >> 33
print(total_conso(donnees, "2025-12-25"))   # >> None
                

Correction avancée : On construit d'abord la liste des consommations filtrées, puis on retourne None si vide, ou la somme via sum(). C'est plus concis et sépare clairement le filtrage du calcul.

def total_conso(donnees, jour):
    """Version avancée avec compréhension de liste."""
    consos = [m['chaude'] + m['froide'] for m in donnees if m['jour'] == jour]
    return sum(consos) if consos else None

assert total_conso(donnees, "2025-02-04") == 33
assert total_conso(donnees, "2025-12-25") is None
assert total_conso(donnees, "2025-02-05") == 6
                

Question 2

Écrire une fonction fuite_possible qui renvoie True si une fuite est possible (au moins 3 mesures consécutives entre 00:00 et 05:00 avec consommation totale non nulle), False sinon.
Correction simple : On parcourt les mesures du jour concerné entre 00:00 et 05:00, en maintenant un compteur de mesures non nulles consécutives. Dès qu'une mesure est nulle, on remet le compteur à zéro.

def fuite_possible(donnees, jour):
    """Retourne True si 3 mesures consécutives non nulles entre 00:00 et 05:00."""
    consecutives = 0
    heures_nuit = {"00:00", "01:00", "02:00", "03:00", "04:00", "05:00"}
    for mesure in donnees:
        if mesure['jour'] == jour and mesure['heure'] in heures_nuit:
            conso = mesure['chaude'] + mesure['froide']
            if conso > 0:
                consecutives += 1
                if consecutives >= 3:
                    return True
            else:
                consecutives = 0   # remise à zéro si une mesure est nulle
    return False

# Tests
print(fuite_possible(donnees, "2025-02-04"))  # >> False
print(fuite_possible(donnees, "2025-02-05"))  # >> True (01:00, 02:00, 03:00 non nuls)
                

Correction avancée : On filtre d'abord les mesures pertinentes, puis on cherche 3 valeurs consécutives non nulles dans la liste résultante. On peut factoriser la détection de séquence dans une fonction générique.

def fuite_possible(donnees, jour):
    """Version avancée : filtrage puis recherche de séquence."""
    heures_nuit = {"00:00", "01:00", "02:00", "03:00", "04:00", "05:00"}
    mesures_nuit = [
        m['chaude'] + m['froide']
        for m in donnees
        if m['jour'] == jour and m['heure'] in heures_nuit
    ]
    # Recherche de 3 valeurs non nulles consécutives
    count = 0
    for v in mesures_nuit:
        if v > 0:
            count += 1
            if count >= 3:
                return True
        else:
            count = 0
    return False

assert fuite_possible(donnees, "2025-02-04") == False
assert fuite_possible(donnees, "2025-02-05") == True
                

Question 3

La fonction lissage_conso contient une erreur. Expliquer pourquoi la fonction produit un résultat incorrect avec la liste [10, 20, 30, 40, 50], et proposer une correction.
Identification du problème : Pour les éléments intermédiaires, la fonction calcule la moyenne sur 3 valeurs mais divise par 2 au lieu de 3. Par exemple, pour l'indice 1 (valeur 20) : (10 + 20 + 30) / 2 = 30 au lieu de (10 + 20 + 30) / 3 ≈ 20.

Correction simple : On remplace le diviseur 2 par 3 dans le cas intermédiaire.

def lissage_conso(valeurs):
    """Calcule une moyenne glissante corrigée sur les 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   # CORRECTION : / 3
        lisse.append(m)
    return lisse

# Test
test = [10, 20, 30, 40, 50]
print(lissage_conso(test))
# >> [15.0, 20.0, 30.0, 40.0, 45.0]
                

Correction avancée : On peut tester la correction rigoureusement et rendre la taille de la fenêtre de lissage configurable pour plus de flexibilité.

def lissage_conso(valeurs):
    """Version corrigée avec assertions de test intégrées."""
    lisse = []
    for i in range(len(valeurs)):
        if i == 0:
            m = (valeurs[0] + valeurs[1]) / 2
        elif i == len(valeurs) - 1:
            m = (valeurs[-2] + valeurs[-1]) / 2
        else:
            m = (valeurs[i - 1] + valeurs[i] + valeurs[i + 1]) / 3
        lisse.append(m)
    return lisse

def test_lissage():
    # Test basique
    assert lissage_conso([10, 20, 30, 40, 50]) == [15.0, 20.0, 30.0, 40.0, 45.0]
    # Vérifier que la liste retournée a la même taille
    data = [5, 10, 15, 20]
    assert len(lissage_conso(data)) == len(data)
    # Liste homogène : lissage ne change rien
    assert lissage_conso([5, 5, 5, 5]) == [5.0, 5.0, 5.0, 5.0]
    print("Tous les tests lissage_conso sont passés.")

test_lissage()
                

Question 4

La fonction lissage_conso gère le cas d'une liste à deux valeurs. Identifier un autre cas limite non pris en compte et proposer une solution.
Cas limite non géré : la liste vide. Si valeurs = [], la boucle for i in range(len(valeurs)) ne s'exécute pas, donc la fonction retourne [] sans erreur — mais si l'on accède à valeurs[i + 1] ou valeurs[i - 1] pour une liste d'un seul élément, on obtient une IndexError. Le vrai cas limite non géré est la liste à un seul élément : l'indice 0 déclenche valeurs[1] qui n'existe pas.

Correction simple : On ajoute une garde en début de fonction pour traiter le cas de la liste vide et le cas de la liste à un seul élément.

def lissage_conso(valeurs):
    """Lissage corrigé avec gestion des cas limites."""
    if len(valeurs) == 0:
        return []
    if len(valeurs) == 1:
        return [float(valeurs[0])]  # aucun voisin : on retourne la valeur elle-même

    lisse = []
    for i in range(len(valeurs)):
        if i == 0:
            m = (valeurs[0] + valeurs[1]) / 2
        elif i == len(valeurs) - 1:
            m = (valeurs[-2] + valeurs[-1]) / 2
        else:
            m = (valeurs[i - 1] + valeurs[i] + valeurs[i + 1]) / 3
        lisse.append(m)
    return lisse

# Tests des cas limites
assert lissage_conso([]) == []
assert lissage_conso([42]) == [42.0]
assert lissage_conso([10, 20]) == [15.0, 15.0]
print("Cas limites gérés correctement.")
                

Explication avancée : En programmation défensive, on doit toujours tester les cas aux bords : liste vide, liste unitaire, liste de deux éléments, et liste très longue. Ces cas sont souvent sources de bugs. Ici, la liste unitaire provoquait une IndexError car l'indice 0 vérifie i == 0 (branche premier élément) et tente d'accéder à valeurs[1] qui n'existe pas.