531 lines
13 KiB
Markdown
531 lines
13 KiB
Markdown
# TP : Simulateur de Navigateur Web — Piles et Files en action
|
|
|
|
> **Thème** : Structures de données linéaires (Pile, File)
|
|
|
|
---
|
|
|
|
## Contexte
|
|
|
|
Chaque jour, vous utilisez un navigateur web (Chrome, Firefox, Safari...). Avez-vous déjà remarqué comment fonctionnent les boutons **← Retour** et **→ Suivant** ? Et comment Netflix gère votre file d'attente "**À regarder ensuite**" ?
|
|
|
|
Dans ce TP, vous allez recréer ces mécanismes en utilisant les structures **Pile** et **File**.
|
|
|
|
---
|
|
|
|
## Partie 1 : Implémentation des structures
|
|
|
|
### Exercice 1 : La classe Pile
|
|
|
|
Implémentez une classe `Pile` avec les méthodes suivantes :
|
|
|
|
```python
|
|
class Pile:
|
|
"""Structure de données LIFO (Last In First Out)."""
|
|
|
|
def __init__(self):
|
|
"""Initialise une pile vide."""
|
|
pass
|
|
|
|
def empile(self, element):
|
|
"""Ajoute un élément au sommet de la pile."""
|
|
pass
|
|
|
|
def depile(self):
|
|
"""
|
|
Retire et renvoie l'élément au sommet.
|
|
Renvoie None si la pile est vide.
|
|
"""
|
|
pass
|
|
|
|
def est_vide(self):
|
|
"""Renvoie True si la pile est vide."""
|
|
pass
|
|
|
|
def sommet(self):
|
|
"""
|
|
Renvoie l'élément au sommet sans le retirer.
|
|
Renvoie None si la pile est vide.
|
|
"""
|
|
pass
|
|
|
|
def taille(self):
|
|
"""Renvoie le nombre d'éléments dans la pile."""
|
|
pass
|
|
|
|
def __repr__(self):
|
|
"""Affichage de la pile."""
|
|
pass
|
|
```
|
|
|
|
**Tests à effectuer :**
|
|
```python
|
|
>>> p = Pile()
|
|
>>> p.est_vide()
|
|
True
|
|
>>> p.empile("A")
|
|
>>> p.empile("B")
|
|
>>> p.empile("C")
|
|
>>> p.sommet()
|
|
'C'
|
|
>>> p.depile()
|
|
'C'
|
|
>>> p.taille()
|
|
2
|
|
```
|
|
|
|
---
|
|
|
|
### Exercice 2 : La classe File
|
|
|
|
Implémentez une classe `File` avec les méthodes suivantes :
|
|
|
|
```python
|
|
class File:
|
|
"""Structure de données FIFO (First In First Out)."""
|
|
|
|
def __init__(self):
|
|
"""Initialise une file vide."""
|
|
pass
|
|
|
|
def enfile(self, element):
|
|
"""Ajoute un élément en queue de file."""
|
|
pass
|
|
|
|
def defile(self):
|
|
"""
|
|
Retire et renvoie l'élément en tête de file.
|
|
Renvoie None si la file est vide.
|
|
"""
|
|
pass
|
|
|
|
def est_vide(self):
|
|
"""Renvoie True si la file est vide."""
|
|
pass
|
|
|
|
def tete(self):
|
|
"""
|
|
Renvoie l'élément en tête sans le retirer.
|
|
Renvoie None si la file est vide.
|
|
"""
|
|
pass
|
|
|
|
def taille(self):
|
|
"""Renvoie le nombre d'éléments dans la file."""
|
|
pass
|
|
|
|
def __repr__(self):
|
|
"""Affichage de la file."""
|
|
pass
|
|
```
|
|
|
|
**Tests à effectuer :**
|
|
```python
|
|
>>> f = File()
|
|
>>> f.est_vide()
|
|
True
|
|
>>> f.enfile("Premier")
|
|
>>> f.enfile("Deuxième")
|
|
>>> f.enfile("Troisième")
|
|
>>> f.tete()
|
|
'Premier'
|
|
>>> f.defile()
|
|
'Premier'
|
|
>>> f.taille()
|
|
2
|
|
```
|
|
|
|
---
|
|
|
|
## Partie 2 : Simulateur de navigateur web
|
|
|
|
### Principe de fonctionnement
|
|
|
|
Un navigateur utilise **deux piles** pour gérer l'historique :
|
|
|
|
- **Pile `historique`** : contient les pages visitées (la page actuelle est au sommet)
|
|
- **Pile `suivant`** : contient les pages "en avant" (après avoir cliqué sur Retour)
|
|
|
|
| Action | Effet |
|
|
|--------|-------|
|
|
| Visiter une page | Empile dans `historique`, vide `suivant` |
|
|
| Clic sur ← Retour | Dépile `historique` → empile dans `suivant` |
|
|
| Clic sur → Suivant | Dépile `suivant` → empile dans `historique` |
|
|
|
|
---
|
|
|
|
### Exercice 3 : La classe Navigateur
|
|
|
|
```python
|
|
class Navigateur:
|
|
"""Simulateur de navigateur web avec historique."""
|
|
|
|
def __init__(self, page_accueil="https://www.google.com"):
|
|
"""
|
|
Initialise le navigateur avec une page d'accueil.
|
|
|
|
:param page_accueil: (str) URL de la page d'accueil
|
|
"""
|
|
self.historique = Pile()
|
|
self.suivant = Pile()
|
|
# À compléter : empiler la page d'accueil
|
|
|
|
def page_actuelle(self):
|
|
"""
|
|
Renvoie l'URL de la page actuellement affichée.
|
|
|
|
:return: (str) URL de la page actuelle ou None
|
|
"""
|
|
# À compléter
|
|
pass
|
|
|
|
def visiter(self, url):
|
|
"""
|
|
Visite une nouvelle page.
|
|
- Empile l'URL dans l'historique
|
|
- Vide la pile 'suivant' (on ne peut plus avancer)
|
|
|
|
:param url: (str) URL de la page à visiter
|
|
"""
|
|
# À compléter
|
|
pass
|
|
|
|
def retour(self):
|
|
"""
|
|
Revient à la page précédente (bouton ←).
|
|
- Dépile l'historique
|
|
- Empile la page actuelle dans 'suivant'
|
|
|
|
:return: (str) URL de la nouvelle page actuelle ou None si impossible
|
|
"""
|
|
# À compléter
|
|
pass
|
|
|
|
def avancer(self):
|
|
"""
|
|
Avance à la page suivante (bouton →).
|
|
- Dépile 'suivant'
|
|
- Empile dans l'historique
|
|
|
|
:return: (str) URL de la nouvelle page actuelle ou None si impossible
|
|
"""
|
|
# À compléter
|
|
pass
|
|
|
|
def peut_reculer(self):
|
|
"""Renvoie True si le bouton Retour est actif."""
|
|
# À compléter
|
|
pass
|
|
|
|
def peut_avancer(self):
|
|
"""Renvoie True si le bouton Suivant est actif."""
|
|
# À compléter
|
|
pass
|
|
|
|
def afficher_etat(self):
|
|
"""Affiche l'état actuel du navigateur."""
|
|
retour = "←" if self.peut_reculer() else "✗"
|
|
avancer = "→" if self.peut_avancer() else "✗"
|
|
print(f"[{retour}] [{avancer}] | {self.page_actuelle()}")
|
|
```
|
|
|
|
**Tests à effectuer :**
|
|
```python
|
|
>>> nav = Navigateur()
|
|
>>> nav.afficher_etat()
|
|
[✗] [✗] | https://www.google.com
|
|
|
|
>>> nav.visiter("https://www.wikipedia.org")
|
|
>>> nav.visiter("https://www.youtube.com")
|
|
>>> nav.visiter("https://www.github.com")
|
|
>>> nav.afficher_etat()
|
|
[←] [✗] | https://www.github.com
|
|
|
|
>>> nav.retour()
|
|
'https://www.youtube.com'
|
|
>>> nav.afficher_etat()
|
|
[←] [→] | https://www.youtube.com
|
|
|
|
>>> nav.retour()
|
|
'https://www.wikipedia.org'
|
|
>>> nav.avancer()
|
|
'https://www.youtube.com'
|
|
|
|
>>> nav.visiter("https://www.python.org")
|
|
>>> nav.afficher_etat()
|
|
[←] [✗] | https://www.python.org
|
|
>>> nav.peut_avancer()
|
|
False # La pile 'suivant' a été vidée
|
|
```
|
|
|
|
---
|
|
|
|
### Exercice 4 : Historique complet
|
|
|
|
Ajoutez une méthode pour afficher tout l'historique de navigation :
|
|
|
|
```python
|
|
def afficher_historique(self):
|
|
"""
|
|
Affiche l'historique complet de navigation.
|
|
La page actuelle est marquée d'une flèche.
|
|
"""
|
|
# À compléter
|
|
pass
|
|
```
|
|
|
|
**Exemple de sortie :**
|
|
```
|
|
Historique de navigation :
|
|
1. https://www.google.com
|
|
2. https://www.wikipedia.org
|
|
3. https://www.youtube.com
|
|
→ 4. https://www.github.com (page actuelle)
|
|
```
|
|
|
|
---
|
|
|
|
## Partie 3 : File d'attente Netflix
|
|
|
|
### Contexte
|
|
|
|
Sur Netflix, vous pouvez ajouter des films à votre liste "**À regarder**". Les films sont regardés dans l'ordre d'ajout (FIFO), mais vous pouvez aussi mettre un film en priorité.
|
|
|
|
---
|
|
|
|
### Exercice 5 : La classe ListeVisionnage
|
|
|
|
```python
|
|
class ListeVisionnage:
|
|
"""Gestionnaire de file d'attente de visionnage."""
|
|
|
|
def __init__(self):
|
|
"""Initialise une liste de visionnage vide."""
|
|
self.file_attente = File()
|
|
self.historique = Pile() # Films déjà regardés
|
|
|
|
def ajouter(self, titre):
|
|
"""
|
|
Ajoute un film/série à la file d'attente.
|
|
|
|
:param titre: (str) Titre du contenu à ajouter
|
|
"""
|
|
# À compléter
|
|
pass
|
|
|
|
def regarder_suivant(self):
|
|
"""
|
|
Regarde le prochain élément de la file.
|
|
- Défile le contenu
|
|
- L'ajoute à l'historique
|
|
|
|
:return: (str) Titre du contenu regardé ou None
|
|
"""
|
|
# À compléter
|
|
pass
|
|
|
|
def prochain(self):
|
|
"""
|
|
Renvoie le prochain contenu sans le retirer.
|
|
|
|
:return: (str) Titre du prochain contenu ou None
|
|
"""
|
|
# À compléter
|
|
pass
|
|
|
|
def revoir_dernier(self):
|
|
"""
|
|
Remet le dernier contenu regardé dans la file (en tête).
|
|
Utile pour revoir un épisode.
|
|
|
|
:return: (str) Titre du contenu remis en file ou None
|
|
"""
|
|
# À compléter (utiliser une file temporaire ou une autre approche)
|
|
pass
|
|
|
|
def afficher(self):
|
|
"""Affiche l'état de la liste de visionnage."""
|
|
print("=== Ma Liste Netflix ===")
|
|
print(f"À regarder : {self.file_attente.taille()} élément(s)")
|
|
print(f"Déjà vus : {self.historique.taille()} élément(s)")
|
|
if not self.file_attente.est_vide():
|
|
print(f"Prochain : {self.prochain()}")
|
|
```
|
|
|
|
**Tests à effectuer :**
|
|
```python
|
|
>>> netflix = ListeVisionnage()
|
|
>>> netflix.ajouter("Stranger Things S5")
|
|
>>> netflix.ajouter("Wednesday S2")
|
|
>>> netflix.ajouter("Squid Game S3")
|
|
>>> netflix.afficher()
|
|
=== Ma Liste Netflix ===
|
|
À regarder : 3 élément(s)
|
|
Déjà vus : 0 élément(s)
|
|
Prochain : Stranger Things S5
|
|
|
|
>>> netflix.regarder_suivant()
|
|
'Stranger Things S5'
|
|
>>> netflix.regarder_suivant()
|
|
'Wednesday S2'
|
|
>>> netflix.afficher()
|
|
=== Ma Liste Netflix ===
|
|
À regarder : 1 élément(s)
|
|
Déjà vus : 2 élément(s)
|
|
Prochain : Squid Game S3
|
|
|
|
>>> netflix.revoir_dernier()
|
|
'Wednesday S2'
|
|
>>> netflix.prochain()
|
|
'Wednesday S2'
|
|
```
|
|
|
|
---
|
|
|
|
### Exercice 6 : File avec priorité
|
|
|
|
Netflix permet aussi de mettre un contenu "en haut de la liste". Ajoutez cette fonctionnalité :
|
|
|
|
```python
|
|
def ajouter_prioritaire(self, titre):
|
|
"""
|
|
Ajoute un contenu en tête de file (sera regardé en premier).
|
|
Nécessite de reconstruire la file.
|
|
|
|
:param titre: (str) Titre du contenu prioritaire
|
|
"""
|
|
# Indice : créer une nouvelle file, enfiler le titre prioritaire,
|
|
# puis transvaser l'ancienne file
|
|
pass
|
|
```
|
|
|
|
**Test :**
|
|
```python
|
|
>>> netflix.ajouter("Film 1")
|
|
>>> netflix.ajouter("Film 2")
|
|
>>> netflix.ajouter_prioritaire("Film URGENT")
|
|
>>> netflix.prochain()
|
|
'Film URGENT'
|
|
```
|
|
|
|
---
|
|
|
|
## Partie 4 : Système Undo/Redo
|
|
|
|
### Exercice 7 : Éditeur de texte simplifié
|
|
|
|
Créez un éditeur qui permet d'annuler (Ctrl+Z) et de refaire (Ctrl+Y) des actions.
|
|
|
|
```python
|
|
class EditeurTexte:
|
|
"""Éditeur de texte avec Undo/Redo."""
|
|
|
|
def __init__(self):
|
|
"""Initialise l'éditeur."""
|
|
self.texte = ""
|
|
self.historique_undo = Pile() # États précédents
|
|
self.historique_redo = Pile() # États annulés
|
|
|
|
def ecrire(self, texte_ajoute):
|
|
"""
|
|
Ajoute du texte.
|
|
Sauvegarde l'état actuel dans l'historique.
|
|
|
|
:param texte_ajoute: (str) Texte à ajouter
|
|
"""
|
|
# À compléter
|
|
pass
|
|
|
|
def effacer(self, n=1):
|
|
"""
|
|
Efface les n derniers caractères.
|
|
|
|
:param n: (int) Nombre de caractères à effacer
|
|
"""
|
|
# À compléter
|
|
pass
|
|
|
|
def undo(self):
|
|
"""
|
|
Annule la dernière action (Ctrl+Z).
|
|
|
|
:return: (bool) True si l'annulation a réussi
|
|
"""
|
|
# À compléter
|
|
pass
|
|
|
|
def redo(self):
|
|
"""
|
|
Refait la dernière action annulée (Ctrl+Y).
|
|
|
|
:return: (bool) True si le redo a réussi
|
|
"""
|
|
# À compléter
|
|
pass
|
|
|
|
def afficher(self):
|
|
"""Affiche le texte actuel."""
|
|
undo_dispo = "Ctrl+Z" if not self.historique_undo.est_vide() else "-----"
|
|
redo_dispo = "Ctrl+Y" if not self.historique_redo.est_vide() else "-----"
|
|
print(f"[{undo_dispo}] [{redo_dispo}]")
|
|
print(f"Texte : \"{self.texte}\"")
|
|
```
|
|
|
|
**Tests :**
|
|
```python
|
|
>>> editeur = EditeurTexte()
|
|
>>> editeur.ecrire("Bonjour")
|
|
>>> editeur.ecrire(" le monde")
|
|
>>> editeur.afficher()
|
|
[Ctrl+Z] [-----]
|
|
Texte : "Bonjour le monde"
|
|
|
|
>>> editeur.undo()
|
|
True
|
|
>>> editeur.afficher()
|
|
[Ctrl+Z] [Ctrl+Y]
|
|
Texte : "Bonjour"
|
|
|
|
>>> editeur.redo()
|
|
True
|
|
>>> editeur.afficher()
|
|
[Ctrl+Z] [-----]
|
|
Texte : "Bonjour le monde"
|
|
|
|
>>> editeur.effacer(6)
|
|
>>> editeur.afficher()
|
|
[Ctrl+Z] [-----]
|
|
Texte : "Bonjour le"
|
|
```
|
|
|
|
---
|
|
|
|
## Bonus : Comparaison des complexités
|
|
|
|
| Opération | Pile (liste Python) | File (liste Python) | File (deux piles) |
|
|
|-----------|---------------------|---------------------|-------------------|
|
|
| Empile/Enfile | O(1) | O(n)* | O(1) amorti |
|
|
| Dépile/Défile | O(1) | O(1) | O(1) amorti |
|
|
| Sommet/Tête | O(1) | O(1) | O(1) |
|
|
| Est_vide | O(1) | O(1) | O(1) |
|
|
|
|
*L'insertion en début de liste Python est en O(n).
|
|
|
|
**Exercice bonus** : Implémentez une file efficace en utilisant deux piles.
|
|
|
|
---
|
|
|
|
## Résumé des notions
|
|
|
|
| Structure | Principe | Analogie | Cas d'usage |
|
|
|-----------|----------|----------|-------------|
|
|
| **Pile** | LIFO | Pile d'assiettes | Historique, Undo, Appels de fonctions |
|
|
| **File** | FIFO | File d'attente | Impression, Streaming, BFS |
|
|
|
|
---
|
|
|
|
Auteurs : Florian Mathieu
|
|
|
|
Licence CC BY NC
|
|
|
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|