Aller au contenu

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 :

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 :

>>> 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 :

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 :

>>> 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

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 :

>>> 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 :

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

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 :

>>> 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é :

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 :

>>> 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.

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 :

>>> 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

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.