• Aucun résultat trouvé

Structures de données linéaires 1 : les listes Python

N/A
N/A
Protected

Academic year: 2022

Partager "Structures de données linéaires 1 : les listes Python"

Copied!
10
0
0

Texte intégral

(1)

Structures de données linéaires 1 : les listes Python

En Python, une liste est une structure de donnée linéaire : elle contient différentes données que l’on peut représenter sur une ligne.

On peut accéder à n’importe quelle donnée d’une liste grâce à son indice : on dit que les listes Python sont indexées.

On peut aussi supprimer des données ou en rajouter, à la différence des tableaux qui sont des structures linéaires indexées de taille fixe.

En informatique, on utilise différents modèles de structure linéaire, notamment les tableaux, les listes chainées, les piles et les files. On peut aussi « customiser » un modèle en lui

donnant les fonctions de plusieurs modèles basiques. C’est le cas des listes Python qui combine le concept historique de liste et la notion de tableau.

Structures mutables et structures immuables

En Python, une liste est mutable : on peut modifier ses données.

Un tuple est immuable : on ne peut pas le modifier, mais on peut en créer un nouveau.

Exemple 1

On veut doubler la valeur de chaque nombre d’une liste ou d’un tuple.

On peut conserver la même liste en changeant les valeurs : for i in range(len(lst)):

lst[i] = lst[i] * 2

On doit créer un nouveau tuple :

ntpl = tuple(tpl[i]*2 for i in range(len(tpl)))

lst est la même liste, ntpl est un nouveau tuple… que l’on aurait pu appeler tpl, écrasant l’ancien tuple.

Exemple 2

On veut rajouter une valeur à une liste : lst.append(nouvelle_valeur)

On a ajouté une valeur à la même liste. C’est une opération en temps constant.

On veut rajouter une valeur à un tuple : tpl = tpl + (nouvelle_valeur,)

On vient de créer un nouveau tuple, en recopiant toutes les valeurs du tuple précédent et en rajoutant une valeur. C’est une opération en temps linéaire : on a dû recopier tout l’ancien

(2)

Vous avez travaillé en première sur les tableaux et les listes Python.

On présente dans ce cours trois structures séquentielles : les listes chainées immuables, les piles mutables et les files mutables.

(3)

Structures de données linéaires 2 : les listes chainées, appelées simplement listes dans ce chapitre 1. Description

On s’intéresse au type de liste popularisé par le langage de programmation Lisp (List processing), un des premiers langages de programmation de haut niveau.

Il s’agit de listes chainées immuables. Ce sont des structures de données linéaires avec un accès direct uniquement à l’élément de tête. On ne peut accéder à un élément de la liste qu’en passant par l’élément précédent, d’où le nom de structure séquentielle. Leur taille est variable, contrairement aux tableaux.

Plus précisément, une liste est caractérisée par un ensemble de cellules. Chaque cellule contient une valeur (la tête) et un lien vers la cellule suivante (la queue). Une liste peut être vide.

2. Définition récursive

Une liste est soit une liste vide, soit un couple (e, L), appelé cellule, avec :

e l'étiquette de la cellule (la valeur stockée)

L le reste de la liste

Cette structure n’existe pas en Python, ni en C, ni en Java et doit être implémentée.

3. Définition par l’interface

On peut caractériser une structure de type liste par son interface, son jeu d’instructions.

Ainsi, une structure de type liste disposera généralement des fonctions suivantes.

• liste_vide() : renvoie une liste vide

• ajoute(lst, element) : renvoie une copie de la liste lst en ayant ajouté element en tête de liste. Cette fonction est parfois appelée cons, comme constructeur.

• est_vide(lst) : renvoie un booléen indiquant si la liste lst est vide.

• valeur(lst) : renvoie la première valeur de la liste lst, à partir d’une suite lst = (e, lst2) renvoie la valeur e.

Cette fonction peut être appelée premier_element ou tete (head).

(4)

Exemple - Implémentation de longueur def longueur(lst):

n = 0

while not est_vide(lst):

lst = suite(lst) n += 1

return n

Cette fonction a une complexité en O(n) où n est la longueur de la liste. Dans la mesure du possible, on l’implémentera différemment. On rajoute généralement une variable, à maintenir à jour, contenant le nombre d’éléments de la liste. Plus besoin alors de parcourir toute la liste pour connaitre sa longueur !

Exercice

Écrire la fonction dernier_element en utilisant les fonctions primitives nécessaires.

4. Implémentation fonctionnelle (récursive) def liste_vide():

return None def ajoute(lst, e):

return (e, lst) def valeur(lst):

return lst[0]

def suite(lst):

return lst[1]

def est_vide(lst):

return lst is None

Exercice : programmer la fonction longueur.

D’autres implémentations sont proposées en exercice.

(5)

Structures de données linéaires 3 : les piles 1. Description

Une pile (en anglais stack) est une structure de données linéaire fondée sur le principe

«dernier arrivé, premier sorti » (LIFO pour Last In, First Out), ce qui veut dire que les derniers éléments ajoutés à la pile seront les premiers à être récupérés.

Le fonctionnement est celui d'une pile d'assiettes : on ajoute des assiettes sur la pile, et on les récupère dans l'ordre inverse, en commençant par la dernière ajoutée. Comme avec les listes, on a un accès direct qu’au premier élément de la pile, le sommet.

On utilisera des piles mutables.

Applications

Dans un navigateur web, une pile sert à mémoriser les pages Web visitées. L'adresse de chaque nouvelle page visitée est empilée et l'utilisateur dépile en cliquant le bouton

« Précédent ».

La fonction « Annuler » (Undo) d'un traitement de texte mémorise les modifications apportées au texte dans une pile.

2. Définition par l’interface

Une pile est une structure de donnée « LIFO » munie des fonctions primitives suivantes :

• pile_vide() : renvoie une pile vide

• est_vide(pile) : renvoie un booléen indiquant si la pile est vide

• empiler(pile, element) : rajoute un élément à la pile (push en anglais)

• depiler(pile) : enlève un élément à la pile et le renvoie (pop) Remarque

Pour lire le sommet de la pile sans modifier la pile, on doit le dépiler et le rempiler : def lire_sommet(pile):

sommet = depiler(pile) empiler(pile, sommet) return sommet

3. Implémentation objet

On propose deux types d’implémentation objet des piles, une version à une classe et une version à deux classes.

La version à une classe est plus simple, elle peut être suffisante, mais les puristes préfèrent la version à deux classes qui colle davantage au modèle théorique proche des listes dans

(6)

La pile vide s’obtient avec Pile(None) dans le modèle à deux classes ou Pile(None, None) dans le modèle à une classe. Comme None est un argument par défaut, une pile vide

s’obtient simplement avec pile = Pile(). On peut créer une fonction renvoyant une pile vide : def pile_vide():

return Pile()

4. Utilisation rapide des piles en Python

Un des objectifs de ce cours est d’apprendre à construire soi-même ses structures de données, mais en cas de besoin dans d’autres contextes, pour simuler les piles, le plus simple est d’utiliser les listes Python existantes.

On utilise les fonctions suivantes : def pile_vide():

return []

def empiler(valeur, pile):

pile.append(valeur) def depiler(pile):

return pile.pop() def est_vide(pile):

return pile == []

Exemple pile = [1, 2, 3]

depiler(pile) renvoie 3 et modifie pile qui devient égale à [1, 2].

empiler(pile, 4) ne renvoie rien mais modifie pile qui devient égale à [1, 2, 3, 4].

Avec une classe Avec deux classes

class Pile:

def __init__(self, valeur=None, suivant=None):

self.valeur = valeur self.suivant = suivant

def empiler(self, valeur):

self.suivant = Pile(self.valeur, self.suivant) self.valeur = valeur

def depiler(self):

valeur = self.valeur

self.valeur = self.suivant.valeur self.suivant = self.suivant.suivant return valeur

def est_vide(self):

return self.suivant is None

class Cellule:

def __init__(self, valeur, suivant):

self.valeur = valeur self.suivant = suivant class Pile:

def __init__(self, c=None):

self.cellule = c

def empiler(self, valeur):

self.cellule = Cellule(valeur, self.cellule)

def depiler(self):

valeur = self.cellule.valeur self.cellule = self.cellule.suivant return valeur

def est_vide(self):

return self.cellule is None

(7)

Structures de données linéaires 4 : les files 1. Description

Une file (queue en anglais) est une structure de données basée sur le principe du Premier entré, premier sorti (FIFO : First In, First Out), ce qui veut dire que les premiers éléments ajoutés à la file seront les premiers à être récupérés. Le fonctionnement ressemble à une file d'attente : les premières personnes à arriver sont les premières personnes à sortir de la file.

On accède en écriture, en ajout, à la queue de la file et en lecture à la tête de la file. La lecture d’un élément le supprime de la file.

On utilisera des files mutables.

Applications

- mémoriser temporairement des transactions qui doivent attendre pour être traitées - mémoire tampons (buffers)

- serveurs d’impression (qui traitent les requêtes dans l'ordre dans lequel elles arrivent)

2. Définition par l’interface

Une file peut être définie par les fonctions primitives suivantes :

• file _vide() : renvoie une file vide

• est_vide(file) : renvoie un booléen indiquant si la file est vide

• enfiler(file, element) : rajoute un élément en queue de file (push)

• defiler(file) : enlève un élément en tête de file et le renvoie (pop) Exercice

Écrire une fonction longueur qui détermine la longueur d’une file sans la modifier.

3. Implémentation objet

On peut utiliser une implémentation similaire à celle des piles, mais si defiler renvoie l’élément de tête, enfiler doit placer le nouvel élément à la queue de la file. Pour cela, on doit remonter toute la file. L’opération enfiler est alors en temps linéaire.

On peut envisager le contraire, avec enfiler en temps constant et defiler en temps linéaire.

Avec un attribut désignant la tête de la file et un attribut désignant la queue, les deux

(8)

Implémentation tête à une classe Implémentation tête-queue à une classe class File:

def __init__(self, valeur=None, suivant=None):

self.valeur = valeur self.suivant = suivant

def enfiler(self, valeur):

if self.est_vide():

self.suivant = File() self.valeur = valeur else:

cel = self

while not cel.suivant.est_vide():

cel = cel.suivant

cel.suivant = File(valeur, File()) def defiler(self):

valeur = self.valeur

self.valeur = self.suivant.valeur self.suivant = self.suivant.suivant return valeur

def est_vide(self):

return self.suivant is None

class File:

def __init__(self, valeur=None, suivant=None):

self.valeur = valeur

self.suivant = self.queue = suivant def enfiler(self, valeur):

if self.est_vide():

self.valeur = valeur self.queue = File() self.suivant = self.queue else:

self.queue.valeur = valeur self.queue.suivant = File() self.queue = self.queue.suivant

def defiler(self):

valeur = self.valeur

self.valeur = self.suivant.valeur self.suivant = self.suivant.suivant return valeur

def est_vide(self):

return self.suivant is None

Implémentation tête à deux classes Implémentation tête-queue à deux classes class Cellule:

def __init__(self, valeur, suivant):

self.valeur = valeur self.suivant = suivant class File:

def __init__(self, c=None):

self.cellule = c

def enfiler(self, valeur):

if self.est_vide():

self.cellule = Cellule(valeur, None) else:

cel = self.cellule

while cel.suivant != None:

cel = cel.suivant

cel.suivant = Cellule(valeur, None)

def defiler(self):

valeur = self.cellule.valeur self.cellule = self.cellule.suivant return valeur

def est_vide(self):

class Cellule:

def __init__(self, valeur, suivant):

self.valeur = valeur self.suivant = suivant class File:

def __init__(self, c=None):

self.tete = c self.queue = c def enfiler(self, valeur):

if self.est_vide():

self.queue = Cellule(valeur, None) self.tete = self.queue

else:

self.queue.suivant = Cellule(valeur, None) self.queue = self.queue.suivant def defiler(self):

valeur = self.tete.valeur self.tete = self.tete.suivant return valeur

def est_vide(self):

(9)

Remarque

Dans l’implémentation tête-queue, la méthode defiler n’agit pas sur la queue, donc quand on défile le dernier élément, il va rester présent dans la queue. Cela n’altère pas le

fonctionnement du code, mais si on veut retrouver queue = None après avoir enfilé/défilé sur une file vide, on peut rajouter dans la méthode defiler :

if tete is None:

queue = None

4. Utilisation rapide des files en Python 4.1. Utilisation des listes Python

En Python, pour accéder rapidement à une structure de type liste, on peut utiliser les listes : file = [] crée une file vide.

file.append(valeur) permet d’enfiler une valeur file.pop(0) permet de défiler.

Une autre option est d’utiliser file.insert(0, valeur) pour enfiler et file.pop() pour défiler.

Dans le premier cas, enfiler est en temps constant et défiler en temps linéaire.

Dans le deuxième cas, enfiler est en temps linéaire et défiler en temps constant.

En effet, pop(0) ou insert(0) décalent tous les éléments de la liste.

4.2. Utilisation des deque

Le type deque à importer de la bibliothèque collections fournit une structure de file avec enfiler et defiler en temps constant.

from collections import deque file = deque([]) : créer une file vide file.append(s) : enfiler s

file.popleft() : défiler

Après if ou while, une file vide est interprétée comme False, une file non vide comme True.

Ainsi, une boucle « while not file: » va s’exécuter tant que la file n’est pas vide.

Il existe aussi les méthodes appendleft() et pop() pour ajouter à gauche et supprimer à droite.

(10)

Structures de données linéaires 5 :

implémentation fonctionnelle des piles et des files En programmation fonctionnelle, les objets sont immuables.

Ainsi, les fonctions empiler et depiler ne modifient pas la pile en cours mais renvoient une nouvelle pile.

1. Piles immuables

Une pile immuable est en fait une liste immuable :

• empiler renvoie une copie de la pile avec un élément en plus en tête de pile,

• depiler renvoie le couple (tête de la pile, suite de la pile).

La fonction empiler remplace la fonction ajoute ; la fonction depiler remplace les fonctions valeur et suite.

Exemple

pile = (3, (2, (1, None ) ) )

depiler(pile) renvoie 3 et (2, (1, None) ).

empiler(pile, 4) renvoie (4, (3, (2, (1, None) ) ) ).

Constructeurs

def pile_vide():

return None

def empiler(pile, valeur):

return (valeur, pile) Exercice

Programmer les fonctions depiler, est_vide, longueur.

2. Files immuables

En programmation fonctionnelle, les files sont immuables :

• enfiler renvoie une copie de la file avec un élément en plus en queue de file,

• defiler renvoie le couple (tête de la file, suite de la file).

Exemple

file = (1, (2, (3, None) ) )

defiler(file) renvoie 1 et (2, (3, None) ).

enfiler(file, 4) renvoie (1, (2, (3, (4, None) ) ) ) ).

Constructeur

def file_vide():

return None Exercice

Écrire les fonctions enfiler, defiler, est_vide, longueur.

Références

Documents relatifs

Ecrire une fonction prenant en entrée deux listes de nombres triés par ordre croissant et renvoyant la fusion de ces deux listes (si un nombre apparait dans les deux listes il

● Pour illustrer tout cela, nous allons voir le fonctionnement de 2 structures de données (classiques) : pile et files?. ● Structures données = moyen de stocker des données

Soit la fonction suivante qui prend en entrée une structure nœud arbre racine d’un arbre binaire de recherche et un entier valeur et qui retourne l’arbre

Soit la fonction suivante qui prend en entrée une structure nœud arbre racine d’un arbre binaire de recherche et un entier valeur et qui retourne l’arbre

Elle est d'ailleurs assez peu utilisée, mais permet comme vous le voyez de très facilement coder les autres fonctions telles que map ou filter. Les compréhensions

(ctxb+ctx).selist.[r]

ReserveVM: PROCEDURE [base:PageNurnber.. GetVMBounds: PUBLIC PROCEDURE RETURNS [VMBounds] - BEGIN. FOR ffvrnp INCREASING IN [ffvrnp .• lfvrnp] DO IF

AddressFromPage, DataSegmentHandle, DataSegmentObject, FileSegmentHandle. FileSegmentObject, Object, ObjectHandle, ObjectType, PageCount. PageNumber,