TNSI : TD Files⚓︎
Qu'est qu'une File?⚓︎

Ajout File

Ajout File

Retrait File
Def
Une ou
est une Structure de Données, dite linéaire, qui modélise la méthode FIFO (First In First Out)
, ou Premier Arrivé Premier Sorti
.
Intuitivement, on pourra penser à une file d'attente, ou à une queue dans un magasin.
Interface d'une File⚓︎
Pte
L'interface (c'est-à-dire les opérations disponibles) d'une File contient a minima :
- la création d'une file vide
- l'ajout d'un élément en queue de file (qui sera celui du dessous pour une représentation verticale, ou celui de droite pour une représentation horizontale). On dira qu'on
enfile ou
enqueue ,
idéalement en temps constant - le retrait d'un élément en tête de file (qui sera celui du dessus pour une représentation verticale, ou celui de gauche pour une représentation horizontale) et le renvoi de sa valeur. On dira qu'on
défile ou
dequeue ,
idéalement en temps constant .
Ex
On considère l'enchaînement d'opérations ci-dessous.
Écrire à chaque étape l'état de la file f
et la valeur éventuellement renvoyée.
Bien comprendre que la classe File()
et ses méthodes n'ont pas encore été Implémentées (c'est-à-dire n'ont pas été concrètement écrites).
Nous utilisons simplement son Interface.
# On suppose que la classe File est déjà implémentée :
f = File()
f.enfile(3) # ou f.enqueue(3)
f.enfile(5)
f.est_vide()
f.enfile(1)
f.defile() # ou f.dequeue()
f.defile()
f.enfile(9)
f.defile()
f.defile()
f.est_vide()
# On suppose que la classe Pile existe déjà :
f = File()
f.enfile(3) # f = 3
f.enfile(5) # f = 3 5
f.est_vide() # False
f.enfile(1) # f = 3 5 1
f.defile() # val renvoyée : 3 , f = 5 1
f.defile() # val renvoyée : 5 , f = 1
f.enfile(9) # f = 1 9
f.defile() # val renvoyée : 1 , f = 9
f.defile() # val renvoyée : 9 , f = None
f.est_vide() # True
Implémentation d'une File par un Tableau⚓︎
Dans cette partie, un Tableau sera implémenté par le type de données list
de Python
Implémenter une File par un Tableau
-
Implémenter une classe
File
, disposant des méthodes suivantes :File()
: crée une file vide, et initialise le constructeur__init__()
avec un attributdata = []
initialisé à un objet Python vide, de typelist
est_vide()
: indique si la file est vide.enfile(x:int)->None
ouenqueue(x:int)->None
: insère un élément en bas (ou à droite) de la file, en temps constantdefile()
oudequeue()
: renvoie la valeur de l'élément en haut (ou à gauche) de la file ET le supprime de la file.get_tete()
: renvoie la valeur de la tete, sans la supprimer de la File-
une méthode magique
__repr__()
: permet d'afficher la file sous forme agréable lorsque :- l'on tape
>>> f
ou>>> print(f)
dans un Interpréteur Python - l'on utilise l'instruction
print(f)
dans un Script Python (Exemple d'affichage :|•|3|6|2|5|⟂|
où3
est la tête/premier arrivé/premier à sortir, et5
est la queue/dernier arrivé/dernier à sortir, de la file
- l'on tape
-
Modifier le constructeur
__init__()
de sorte que l'on puisse maintenant instancier la classeFile
en lui passant en argument un objet de typelist
de Python:# Créer une File `f` initialisée par la liste Python [1,2,3], # qui sera représentée dans un Terminal par `|•|1|2|3|⟂|` f = File([1,2,3])
-
vider()
: vide la File. Syntaxe :f.vider()
videf
copy()
renvoie une copie profonde de la File (la modification de l'une n'entraine pas la modification de l'autre). Syntaxe :f1 = f.copy()
renverse()
renverse la File. Syntaxe :f.renverse()
renversef
concatene()
: concatène deux Files. Syntaxe :f1.concatene(f2)
concatènef2
à la queue/au dessous/à droite def1
, etf2
est vidée
2.
Quelle est la complexité en temps des opérations enfile()
/enqueue()
et defile()
/dequeue()
?
Conclusion : Le type list
de Python est-il parfaitement adapté pour implémenter une File?
- l'insertion
enfile()
/enqueue()
n'est PAS en temps constant - le retrait
defile()
/dequeue()
se fait en temps constant Cette implémentation, très simple, ne remplit donc pas toutes les conditions en termes de complexité en temps (à cause de l'insertion qui n'est pas en temps constant).
3.
Précisément, quelle méthode dans notre classe File
est le facteur limitant, c'est-à-dire ralentit nos opérations? et pourquoi ?
Notre implémentation répond parfaitement à l'interface qui était demandée. Mais si le cahier des charges obligeait à ce que les opérations enfile()
et defile()
aient lieu en temps constant, en \(O(1)\), notre implémentation ne conviendrait pas.
En cause : notre méthode defile()
agit en temps linéaire (\(O(n)\)) et non pas en temps constant. L'utilisation de la structure de liste de Python (les tableaux dynamiques) provoque, lors de l'instruction self.data.pop(0)
un redimensionnement de la liste, qui voit disparaître son premier élément. Chaque élément doit être recopié dans la case qui précède, avant de supprimer la dernière case. Ceci nous coûte un temps linéaire.
Implémentation d'une File par une Liste Chaînée Double⚓︎
On dispose de la Classe Cellule
qui permet de modéliser une Cellule d'une File :
class Cellule :
def __init__(self, valeur, suivante):
self.valeur = valeur
self.suivante = suivante
Ex
-
À l'aide cette classe
Cellule
, implémenter une classeFile
par une Liste Chaînée Double:- disposant exactement de la même interface que dans l'implémentation par Tableau, mais augmentée d'une méthode
retirer_queue()
:est_vide()
enfile(x:int)->None
/enqueue(x:int)->None
defile()
/dequeue()
get_tete()
: renvoie la valeur de la tete, sans la supprimer de la File- une méthode magique
__repr__()
: permet d'afficher la file sous forme agréable lorsque :- l'on tape
>>> f
ou>>> print(f)
dans un Interpréteur Python - l'on utilise l'instruction
print(f)
dans un Script Python (Exemple d'affichage :|•|3|6|2|5|⟂|
où3
est la tête/premier arrivé/premier à sortir, et5
est la queue/dernier arrivé/dernier à sortir, de la file
- l'on tape
- Modifier le constructeur
__init__()
de sorte que l'on puisse maintenant instancier la classeFile
en lui passant en argument un objet de typelist
de Python:# Créer une File `f` initialisée par la liste Python [1,2,3], # qui sera représentée dans un Terminal par `|•|1|2|3|⟂|` f = File([1,2,3])
vider()
: vide la File. Syntaxe :f.vider()
videf
copy()
renvoie une copie profonde de la File (la modification de l'une n'entraine pas la modification de l'autre). Syntaxe :f1 = f.copy()
retirer_queue()
renvoie la valeur de la queue, ET supprime la queue de la File. On pourra dans cette question, utiliser les méthodesget_valeur()
,get_suivante()
etset_suivante()
de la classeCellule
, que nous avions développées dans le TD sur les Listes Chaînées (Simples, ou Doubles).renverse()
renverse la File. Syntaxe :f.renverse()
renversef
concatene()
: concatène deux Files. Syntaxe :f1.concatene(f2)
concatènef2
à la queue/au dessous/à droite def1
, etf2
est vidée
- et des deux attributs (publics) suivants (=Liste Chaînée Double)
- le premier élément,
tete
, de la File - le dernier élément,
queue
, de la File La classeFile
doit donc pouvoir être utilisée par exemple comme suit:
f = File() f.enfile(5) # ou f.enqueue(5) f.enfile(8) f.defile() # ou f.dequeue()
- le premier élément,
- disposant exactement de la même interface que dans l'implémentation par Tableau, mais augmentée d'une méthode
Étudier la complexité des opérations d'ajout enfile()
/ enqueue()
, et defile()
/ dequeue()
Conclusion : Une Liste Chaînée Double est-elle parfaitement adaptée pour implémenter une File?
La donnée des deux extrémités, permet de réaliser un retrait en tête de liste defile()
/ dequeue()
, enfile()
/ enqueue()
Il est important de de noter que la manière d'utiliser la file (son interface) est indépendante des deux implémentations. Il est fréquent de commencer la programmation avec une implémentation améliorable d'une structure de données. Puis, par la suite, la structure de données pourra être améliorée sans que le code principal n'ait besoin d'être retouché.
Implémentation d'une File avec deux Piles⚓︎
Il est possible d'implémenter une File avec 2 Piles, l'idée est la suivante : on crée une Pile d'Entrée et une Pile de Sortie.
- quand on veut enfiler, on empile sur la pile d'entrée.
- quand on veut défiler, on dépile sur la pile de sortie.
- Lorsque la pile de sortie est vide, on dépile entièrement la pile d'entrée dans la pile de sortie.
Dans l'exercice suivant, on suppose connue et on reprend la structure de Pile
du TD précédent sur les Piles, implémentée (par exemple) avec les Listes Chaînées Simples par deux classes Cellule
et Pile
Ex
- Implémenter une classe
File
avec deux Piles, disposant exactement de la même interface que dans l'implémentation par Tableau, (pas besoin d'implémenter la méthoderetirer_queue()
) :- le constructeur
__init__()
définit une Pile d'Entrée, et une Pile de Sortie - disposant des méthodes suivantes :
est_vide()->bool
enfile(x:int)->None
ouenqueue(x:int)->None
defile()
oudequeue()
get_tete()
: renvoie la valeur de la tete, sans la supprimer de la File- une méthode magique
__repr__()
: permet d'afficher la file sous forme agréable lorsque :- l'on tape
>>> f
ou>>> print(f)
dans un Interpréteur Python - l'on utilise l'instruction
print(f)
dans un Script Python (Exemple d'affichage :|•|3|6|2|5|⟂|
où3
est la tête/premier arrivé/premier à sortir, et5
est la queue/dernier arrivé/dernier à sortir, de la file
- l'on tape
- Modifier le constructeur
__init__()
de sorte que l'on puisse maintenant instancier la classeFile
en lui passant en argument un objet de typelist
de Python:# Créer une File `f` initialisée par la liste Python [1,2,3], # qui sera représentée dans un Terminal par `|•|1|2|3|⟂|` f = File([1,2,3])
vider()
: vide la File. Syntaxe :f.vider()
vide la filef
copy()
renvoie une copie profonde de la File (la modification de l'une n'entraine pas la modification de l'autre). Syntaxe :f1 = f.copy()
renverse()
renverse la File. Syntaxe :f.renverse()
renversef
.concatene()
: concatène deux Files. Syntaxe :f1.concatene(f2)
concatènef2
au dessous/à droite def1
, etf2
est vidée
- la Classe
File
doit pouvoir être utilisée par exemple comme suit:f = File() f.enfile(5) # ou f.enqueue(5) f.enfile(8) f.defile() # ou f.dequeue()
- le constructeur
- Quelle est la complexité des méthodes
enfile()
etdefile()
Conclusion : Une Double Pile est-elle parfaitement adaptée pour implémenter une File?
Le module collections.deque
⚓︎
Page de Référence sur le site python.org
: Module collections.deque
Nous avons présenté 3 implémentations de la structure de file, qui ne peuvent pas être implémentées correctement par une simple liste python, contrairement à la structure de pile.
La bibliothèque standard possède toutefois un module nommé collections
qui contient quelques structures de données supplémentaires, dont le type deque
, qui est une implémentation de file :
from collections import deque
f = deque()
f.append("Premier Arrivé")
f.append("Deuxième")
print(f.popleft())
f.append("Troisième")
while f:
print(f.popleft())
produit la sortie suivante :
Premier Arrivé
Deuxième
Troisième
Avec le type
deque
, les opérations d'ajout et de suppression dans la file sont toutes deux réalisées en temps constant.
deque
dont nous venons de parler, et qui signifie , avec le verbe dequeue / défiler
Exemples d'Utilisation des Files⚓︎
Ex
1.
Chercher sur internet des exemples d'utilisation des Files en informatique.
- Gérer la File des impressions en attente (
spooler ) d'un système d'impression - Mémoriser la File des transactions en attente accédant à une base de données
- Gérer la File d'attente des processus d'un système d'exploitation
Parcours en largeur d'un Graphe
- Citer/Étudier la complexité dans chaque exemple