Aller au contenu

Liste chaînée mutable (Bac 🎯)⚓︎

Licence Creative Commons
Ce cours est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International.

programme

Sources et crédits pour ce cours

Pour préparer ce cours, j'ai utilisé :

🔖 Synthèse de ce qu'il faut retenir pour le bac

Structures de données mutables ou immuables⚓︎

Point de cours 5

  • Une structure de données est immuable si on ne peut la modifier une fois qu'elle est construite. Évidemment on peut toujours accéder aux données en lecture
  • Une structure de données est mutable si on peut la modifier une fois qu'elle est construite.
  • Cette distinction entre types immuable et mutable n'a de sens que pour des types structurés, les types simples comme les entiers, les flottants, les booléens sont immuables (1 reste égal à 1 !).
Exemple 4 types natifs mutables et immuables en Python

En Python, pour représenter une séquence de données on peut utiliser le type list des tableaux dynamiques qui est mutable ou le type tuple qui est immuable.

🐍 Script Python
>>> tup = (14, 10)
>>> tup[0]
14
>>> tup[0] = 11
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> tad = [14, 10]
>>> tad[0]
14
>>> tad[0] = 11

Quel est l'intérêt d'un type immuable ? Dans certains cas on a besoin de fixer certaines valeurs par exemple lorsqu'elles servent de clefs dans un dictionnaire implémenté par une table de hachage. Une fonction de hachage calcule à partir de la clef l'index de la case du tableau où est stockée la valeur donc la clef ne doit pas être modifiée pour que son image par la fonction de hachage reste inchangée. Ainsi un type mutable ne peut servir de clef dans un dictionnaire.

🐍 Script Python
>>> dico = dict()
>>> dico[tup] = 1
>>> dico
{(14, 10): 1}
>>> dico[tad] = 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Si dans l'implémentation d'une liste chaînée, une cellule est implémentée par un tuple, la liste chaînée est donc immuable.

Exemple 6 listes chaînées immuables avec des tuples

Avec l'implémentation du type abstrait Liste par des cellules implémentées à l'aide de tuple, les opérations queue et inserer renvoient une nouvelle liste. Le type tuple étant immuable en Python, ces listes chaînées sont immuables.

On a également présenté une implémentation à l'aide d'une classe Cellule qui devrait donc permettre de construire des listes chaînées mutables, il nous manquait juste la possibilité de représenter une liste vide.

Listes chaînées mutables⚓︎

Méthode 4 : listes chaînées mutables

Pour construire des listes chaînées mutables, il est naturel d'utiliser le paradigme objet (POO). On reprend l'implémentation avec une classe Cellule mais on intègre cette fois toutes les opérations du type abstrait Liste comme méthodes d'une classe Liste. Cette classe possède un seul attribut tete_liste qui pointe soit vers None (liste vide) ou vers la première cellule de la liste chaînée.

###
class Cellule:bksl-nl """Cellule pour liste chaînée"""bksl-nl bksl-nl def py-undpy-undinitpy-undpy-und(self, elt, suivant):bksl-nl self.element = eltbksl-nl self.suivant = suivantbksl-nl bksl-nlbksl-nl bksl-nlclass Liste: bksl-nl """Liste chaînée mutable"""bksl-nl bksl-nl # Interface minimale du type abstrait Listebksl-nl bksl-nl def py-undpy-undinitpy-undpy-und(self):bksl-nl """Construit une liste vide"""bksl-nl self.tetepy-undliste = Nonebksl-nl bksl-nl def listepy-undvide(self):bksl-nl """Teste si la liste est vide"""bksl-nl return self.tetepy-undliste is Nonebksl-nlbksl-nl def inserer(self, elt):bksl-nl """Insère elt en tête de liste"""bksl-nl self.tetepy-undliste = Cellule(elt, self.tetepy-undliste)bksl-nl bksl-nl def tete(self):bksl-nl """Renvoie l'élement de la cellule en tête de liste"""bksl-nl assert not self.listepy-undvide()bksl-nl return self.tetepy-undliste.elementbksl-nl bksl-nl def queue(self):bksl-nl """Renvoie une nouvelle liste qui commence avec la cellule suivantbksl-nl la cellule en tête de la liste courantebksl-nl """bksl-nl assert not self.listepy-undvide() bksl-nl listepy-undqueue = Liste()bksl-nl listepy-undqueue.tetepy-undliste = self.tetepy-undliste.suivantbksl-nl return listepy-undqueuebksl-nl bksl-nl bksl-nl # Extension de l'interfacebksl-nl bksl-nl def py-undpy-undstrpy-undpy-und(self):bksl-nl if self.listepy-undvide():bksl-nl return '()'bksl-nl return f"({str(self.tetepy-undliste.element)}, {str(self.queue())})"bksl-nl bksl-nl def longueur(self):bksl-nl """Renvoie la longueur de la liste"""bksl-nl # à compléterbksl-nl ...bksl-nl bksl-nl def modifierpy-undtete(self, elt): bksl-nl """Modifie l'éléme t de la cellule en tête de listebksl-nl Méthode de liste mutablebksl-nl """bksl-nl assert not self.listepy-undvide()bksl-nl # à compléterbksl-nl ...bksl-nl bksl-nl def renversepy-undlistepy-unditer(self):bksl-nl """Inverse la séquence des éléments dans la liste, version itérativebksl-nl Méthode de liste mutablebksl-nl """bksl-nl # à compléterbksl-nl lispy-undtmp = Liste()bksl-nl lis = selfbksl-nl while not ...:bksl-nl ...bksl-nl self.tetepy-undliste = lispy-undtmp.tetepy-undlistebksl-nl bksl-nl def renversepy-undlistepy-undrec(self, lispy-undtmp):bksl-nl """Inverse la séquence des éléments dans la liste, version récursivebksl-nl le paramètre lispy-undtmp est une liste vide lors du premier appelbksl-nl Méthode de liste mutablebksl-nl """bksl-nl # à compléterbksl-nl # si liste vide on ne fait rienbksl-nl if self.listepy-undvide():bksl-nl returnbksl-nl # sinon on insère l'élément en tête de liste dans lispy-undtmpbksl-nl lispy-undtmp.inserer(...)bksl-nl # puis on appelle récursivement la méthode sur la queue de la listebksl-nl ...bksl-nl # enfin on fait pointer l'attribut tetepy-undliste de la liste vers lispy-undtmp.tetepy-undlistebksl-nl self.tetepy-undliste = lispy-undtmp.tetepy-undlistebksl-nl bksl-nldef test():bksl-nl l1 = Liste()bksl-nl for k in range(10):bksl-nl l1.inserer(k)bksl-nl assert str(l1) == '(9, (8, (7, (6, (5, (4, (3, (2, (1, (0, ()))))))))))'bksl-nl l1.modifierpy-undtete(99)bksl-nl assert str(l1) == '(99, (8, (7, (6, (5, (4, (3, (2, (1, (0, ()))))))))))'bksl-nl l1.renversepy-undlistepy-undrec(Liste())bksl-nl assert str(l1) == '(0, (1, (2, (3, (4, (5, (6, (7, (8, (99, ()))))))))))'bksl-nl l1.renversepy-undlistepy-unditer()bksl-nl assert str(l1) == '(99, (8, (7, (6, (5, (4, (3, (2, (1, (0, ()))))))))))'bksl-nl print("Tests réussis")bksl-nl bksl-nl bksl-nlbksl-nl

Exercice 7

💻 Saisir ses réponses sur Capytale

Complétez les méthodes longueur, modifier_tete, renverse_liste_iter et renverse_liste_rec de la classe Liste ci-dessous implémentant une liste chaînée mutable.

🐍 Script Python
class Cellule:

    def __init__(self, elt, suivant):
        self.element = elt
        self.suivant = suivant



class Liste:    
    
    def __init__(self):
        self.tete_liste = None
        
    def liste_vide(self):
        return self.tete_liste is None

    def inserer(self, elt):
        self.tete_liste = Cellule(elt, self.tete_liste)
    
    def tete(self):
        assert not self.liste_vide()
        return self.tete_liste.element
        
    def queue(self):
        assert not self.liste_vide()           
        liste_queue = Liste()
        liste_queue.tete_liste = self.tete_liste.suivant
        return liste_queue
    
    def __str__(self):
        if self.liste_vide():
            return 'None'
        return f"({str(self.tete_liste.element)}, {str(self.queue())})"
    
    def longueur(self):
        if self.est_vide():
            return 0
        queue = self.queue()
        return 1 + queue.longueur()
    
    def modifier_tete(self, elt):
        assert not self.liste_vide()
        self.tete_liste.element = elt
   
    def renverse_liste_iter(self):
        tmp = Liste()
        lis = self
        while not lis.liste_vide():
            tmp.inserer(lis.tete())
            lis = lis.queue()
        self.tete_liste = tmp.tete_liste
        
    def renverse_liste_rec(self, lis):
        if self.liste_vide():
            return
        lis.inserer(self.tete())
        self.queue().renverse_liste_rec(lis)
        self.tete_liste = lis.tete_liste        

Effets de bord avec listes chaînées mutables⚓︎

Effets de bord

Les listes chaînées mutables peuvent être modifiées après leur construction. Si deux listes chaînées mutables partagent les mêmes cellules, modifier une liste modifie l'autre par effet de bord, ce qui n'est pas toujours souhaitable.

Exemple 5

On crée une liste mutable liste1 puis on associe la queue de liste1 à une variable liste2. Si on modifie l'élément de la cellule en tête de liste2 c'est aussi la cellule suivante de la tête de liste1qui est donc modifiée par effet de bord.

🐍 Script Python
>>> liste1 = Liste()
>>> liste1.inserer('P')
>>> liste1.inserer('T')
>>> liste1.inserer('T')
>>> liste1.inserer('H')
>>> print(liste1)
(H, (T, (T, (P, None))))
>>> liste2 = liste1.queue()
>>> print(liste2)
(T, (T, (P, None)))
>>> liste2.modifier_tete('F')
>>> >>> print(liste2)
(T, (T, (P, None)))
>>> print(liste1)
(H, (F, (T, (P, None))))

Avant la modification de liste2 :

alt

Après la modification de liste2 :

alt