Adloun

Reprendre le code suivant, qui veut supprimer les entrées de petite…

Exercice de TD · niveau 3 (difficile) · NSI (première), chapitre 5 — Les dictionnaires · Itérer, transformer

Énoncé

Reprendre le code suivant, qui veut supprimer les entrées de petite valeur. Prédire ce qu'il fait, puis l'exécuter. Écrire ensuite les deux fonctions correctes : filtrer, qui construit un nouveau dictionnaire, et retirer_petits, qui supprime en place.


for cle in d:
    if d[cle] < 3:
        del d[cle]

Corrigé

Ce que fait le code fautif.


d = {"a": 5, "b": 1, "c": 9, "d": 2}
for cle in d:
    if d[cle] < 3:
        del d[cle]
# RuntimeError: dictionary changed size during iteration

L'erreur est levée au tour suivant la suppression, pas au moment du del — la boucle a d'abord retiré &quot;b&quot;, puis a voulu passer au couple d'après et a constaté que la taille avait changé. Python détecte le problème au lieu de parcourir n'importe quoi ; c'est un service, mais il ne faut pas compter dessus : modifier une valeur en cours d'itération ne lève rien du tout, seule une variation de la taille est repérée.


def filtrer(d, seuil):
    """Nouveau dictionnaire limite aux entrees dont la valeur depasse seuil.

    Postcondition : les cles du resultat sont incluses dans celles de d, et
                    toutes les valeurs du resultat sont > seuil. d est
                    INCHANGE.
    """
    resultat = {}
    for cle, valeur in d.items():
        if valeur > seuil:
            resultat[cle] = valeur
    return resultat


def retirer_petits(d, seuil):
    """Supprime EN PLACE les entrees de valeur <= seuil, en deux temps."""
    a_retirer = [cle for cle, valeur in d.items() if valeur <= seuil]
    for cle in a_retirer:
        del d[cle]
    return d

Les deux temps. La liste a_retirer est constituée pendant l'itération, mais elle ne touche pas au dictionnaire ; les suppressions ont lieu après, sur une boucle qui parcourt la liste et non le dictionnaire. C'est le remède que donne le cours, et il vaut pour toute structure qu'on veut élaguer en la parcourant.

Laquelle des deux écrire ? Presque toujours filtrer. Une fonction qui construit un nouveau dictionnaire ne surprend personne ; une fonction qui modifie son argument agit à distance, sur un objet que l'appelant croyait à lui — c'est le partage de références du chapitre 4. Il faut alors que le nom le crie (retirer_petits, à l'impératif) et que la docstring l'écrive en majuscules. La modification en place ne se justifie que sur de très gros dictionnaires, quand la copie coûterait vraiment.

Vérification.


source = {"a": 5, "b": 1, "c": 9, "d": 2}
assert filtrer(source, 3) == {"a": 5, "c": 9}
assert source == {"a": 5, "b": 1, "c": 9, "d": 2}   # intact

copie = dict(source)
assert retirer_petits(copie, 3) == {"a": 5, "c": 9}
assert copie == {"a": 5, "c": 9}                    # modifie, lui

Les deux assert qui portent sur source et copie après l'appel sont les seuls qui distinguent les deux fonctions : sans eux, les deux tests passeraient sur l'une comme sur l'autre.

Les autres exercices de ce chapitre Le cours du chapitre

Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.