Le Service des sports fait appel à vous dans le cadre du développement d'un système informatique pour la gestion des résultats de la course des 24h Vélo de Louvain-la-Neuve. Vous devez réaliser un programme Python permettant d'encoder et de manipuler les différentes données nécessaires (coureurs, résultats, classements) pour tenir à jour les classements en cours de course. Par exemple, il faut pouvoir afficher en temps réel, sur des écrans disposés dans la ville et sur internet, le classement des meilleurs temps pour le tour du circuit.
Pour cette mission, vous allez devoir implémenter une classe Python permettant de manipuler un tel classement et de le mettre à jour avec de nouveaux résultats. Le classement doit être ordonné, et on ne désire pas refaire le tri pour chaque nouveau résultat: il faut donc une structure de liste ordonnée avec des opérations qui maintiennent cet ordre. Vous utiliserez pour cela une liste chaînée.
A l'issue de cette mission, chacun d'entre vous :
Comment bien préparer la mission?
Assister au cours et regarder les capsules vidéos pour comprendre les notions et concepts qui seront abordés.
Lire attentivement et comprendre le chapitre sur Linked lists dans la partie Objects du syllabus théorie :
- 7 - Linked lists
- Embedded references
- The Node class
- Linked lists as collections
- Linked lists and recursion
- Infinite lists
- Ambiguity between lists and nodes
- Modifying lists
- Wrappers and helpers
- The LinkedList class
Essayer et comprendre le code développé dans le syllabus et qui se trouve aussi dans l'annexe correspondant :
- Appendix - Source code of linked lists
- LinkedList class
- Node class
- Creating and using LinkedList objects
Dans cette mission, vous devrez de nouveau utiliser le framework unittest pour produire des classes de test. Relisez la documentation si vous aviez du mal à créer des tests unitaires lors de la mission précédente.
Répondre aux questions à choix multiples et aux autres questions de démarrage.
Ces questions supposent que vous avez lu le chapitre du syllabus théorique sur les listes chaînées et que vous avez lu et testé le code des classes LinkedList et Node dans l'annexe code des classes LinkedList et Node dans l'annexe correspondante.
Les questions à choix multiples de cette mission sont accessibles en ligne depuis https://inginious.info.ucl.ac.be/course/LSINF1101-PYTHON/mission_11_QCM.
Répondez aux questions suivantes:
Question 1 : Manipuler une liste vide
Cette question concerne l'utilisation de la classe LinkedList dont le code se trouve dans l'annexe suivante du syllabus : Appendix - Source code of linked lists
Utilisez la classe LinkedList pour créer une liste chaînée vide l.
Donnez une instruction Python pour imprimer le contenu de cette liste l.
Quel résultat sera affiché si on essaie d'imprimer la liste l avec l'instruction print(l)? Expliquez.
Donnez une instruction Python pour imprimer la taille de cette liste l .
Quel résultat sera affiché si on essaie d'imprimer la taille avec l'instruction print(l.__length)? Expliquez.
Question 2 : Remplir la liste
Pour ajouter des éléments à une liste vide, quelqu'un a écrit les instructions suivantes:
l = LinkedList() l.add(3) l.add(2) l.add(1) l.print()
Mais quelqu'un d'autre à écrit:
l = LinkedList() l.add(Node(3)) l.add(Node(2)) l.add(Node(1)) l.print()
Dans les deux cas, exécuter l'instruction l.print() affichera [ 1 2 3 ].
Lequel des deux fragments de code ci-dessus vous semble correct? Pourquoi l'autre imprime quand-même le même résulat?
Indice : qu'est-ce qui se passe si on exécute l'instruction print(Node(Node(Node(1))))? Pouvez-vous expliquer?
Dans le code donné dans l'annexe Appendix - Source code of linked lists la méthode print() de la classe LinkedList imprime les éléments d'une liste chaînée, séparés par une espace. Par exemple:
>>> l = LinkedList() >>> l.add(3) >>> l.add(2) >>> l.add(1) >>> l.print() [ 1 2 3 ]
Or, normalement la convention est de séparer les éléments d'une liste par des virgules. Ajoutez une nouvelle méthode print_avec_virgule() dans la classe LinkedList qui imprime la liste comme suite:
>>> l.print_avec_virgule() [ 1, 2, 3 ]
Attention: il n'y a pas de virgule après le dernier élément ou si on imprime une liste vide.
>>> l = LinkedList() >>> l.print_avec_virgule() [ ]
Généralisez la méthode print_avec_virgule de l'exercice précédente par une méthode print_avec_separateur qui peut prendre n'importe quelle séparateur. Par exemple:
>>> l = LinkedList() >>> l.add(3) >>> l.add(2) >>> l.add(1) >>> l.print_avec_separateur(", ") [ 1, 2, 3 ] >>> l.print_avec_separateur(" ") [ 1 2 3 ] >>> l.print_avec_separateur(" - ") [ 1 - 2 - 3 ]
Ajoutez à la classe LinkedList une méthode add_to_end(self, cargo) pour ajouter un noeud en queue de la liste. Ajoutez d'abord un nouvel attribut last dans la méthode d'initialisation pour garder une référence vers ce dernier élément. Changez ensuite la méthode add pour assigner cette référence lors de l'ajout du premier noeud, et la méthode remove pour remettre cette référence à None lorsque la liste est de nouveau vide. Implémentez ensuite la méthode add_to_end.
Étapes de l'ajout d'un noeud dans une liste chainée :
Pour plus de détails sur les étapes, allez voir le catalogue : Ajout d'un noeud dans une liste chainée .
Est-ce facile d'implémenter une méthode remove_from_end(self, cargo) pour supprimer un élément de la queue d'une LinkedList? Expliquez en mots comment vous feriez.
En Python il est possible d'imbriquer une classe dans une autre. Ceci peut être utile si une classe n'est utilise que par une seule autre classe. C'est le cas ici avec la classe Node. Ce n'est qu'une classe auxiliaire pour la classe LinkedList. Adaptez le code de votre classe LinkedList en imbriquant le code de la classe Node dedans. Il suffit d'indenter le code de la classe Node et de changer chaque référence à la classe Node à l'interieur de la classe LinkedList par self.Node.
Quels sont les avantages et désavantages d'imbriquer la classe Node dans la classe LinkedList?
Considérez qu'on désire utiliser la classe LinkedList pour stocker une liste de strings. On souhaite qu'elle soit en permanence en ordre alphabétique croissant.
Dessinez complètement une telle liste chainée contenant les trois strings "abc", "def" et "xyz".
Expliquez en français et dessinez les opérations à effectuer pour ajouter un Node contenant "aaa" dans cette liste.
Même question avec un Node contenant "ghi".
Même question avec un Node contenant "def".
Même question avec un Node contenant "zzz".
Lors de la mission précédente, les tests unitaires avec unittest ont été introduits. Créez un test unitaire pour tester la classe LinkedList. Ce test doit contenir des tests pour les différentes méthodes de la classe LinkedList comme size(), first() ou add(valeur) (code original), remove() (question 4.2.6), add_to_end(valeur) (question 4.2.7) ou insert(valeur) (question 4.2.10). Pour vous mettre sur le bon chemin, votre classe test aura la structure suivante:
import unittest class LinkedListTest(unittest.TestCase): """Classe de test utilisé pour tester la classe LinkedList""" def test_size(self): """Test de la methode size() de la classe LinkedList.""" # ... assert*(...) ... def test_first(self): """Test de la methode first() de la classe LinkedList.""" # ... assert*(...) ... def test_add(self): """Test de la methode add(valeur) de la classe LinkedList.""" # ... assert*(...) ... def test_remove(self): """Test de la methode remove() de la classe LinkedList.""" # ... assert*(...) ... def test_add_to_end(self): """Test de la methode add_to_end(valeur) de la classe LinkedList.""" # ... assert*(...) ... def test_insert(self): """Test de la methode insert(valeur) de la classe LinkedList.""" # ... assert*(...) ... if __name__ == '__main__': unittest.main()
Lors de cette dernière mission, vous allez revenir une dernière fois vers votre logiciel de lecture multimédia. Une des fonctionnalités les plus demandées est la capacité de trier ses listes de lecture selon différents critères, comme la durée, l'ordre alphabétique de l'auteur, du média ou de l'album, etc. Dans cette mission vous allez implémenter cette fonctionnalité en utilisant une liste chaînée ordonnée (ordered linked list).
Vous trouverez sur INGInious une grande partie du code nécessaire, sauf la classe OrderedLinkedList que vous devez produire, ainsi qu'une nouvelle implémentation de ListeLecture nommée ListeLectureOrdonnee. Vous devez également écrire des fichiers de test détaillées (TestOrderedLinkedList.py et TestListeLectureOrdonnee.py) en utilisant des assert pour vérifier votre implémentation de ces classes. Des fichiers de test TestMedia.py et TestListeLecture.py pour tester, respectivement, l'implémentation des classes Media et ListeLecture, vous sont fournis comme exemple.
En plus de ces fichiers de tests et le code des classes Duree, Media et ListeLecture, vous aurez également accès à une implémentation complète de la classe LinkedList et son fichier de test TestLinkedList.py. Vous pouvez vous baser sur cette implémentation de la classe LinkedList pour implémenter la classe OrderedLinkedList.
Voici les étapes à suivre.
Pour commencer, vous avez à votre disposition les fichiers suivants:
Lisez bien le code fourni dans ces fichiers pour bien comprendre sa structure et son comportement.
Dans un premier temps, adaptez la classe ListeLecture afin qu'elle utilise une liste chainée LinkedList comme structure de données plutôt qu'une simple liste Python. Cette étape vous facilitera le travail pour après implémenter une classe ListeLectureOrdonnee qui utilse une liste chainée ordonnée OrderedLinkedList comme structure de données.
Une liste chaînée ordonnée fonctionne exactement comme une liste chaînée ordinaire, sauf qu'au lieu d'insérer les éléments au début, il faut les insérer de sorte qu'un certain ordre soit respecté.
Pour l'implémentation de votre OrderedLinkedList, basez-vous sur la classe LinkedList qui vous est fournie, et étendez-la si nécessaire de sorte qu'elle puisse répondre à tous vos besoins dans l'implémentation de ListeLectureOrdonnee. Il faudra donc au minimum les méthodes suivantes :
class OrderedLinkedList: def __init__(self, lst=[]): """ Initialises a new linked list object, with a given list of elements lst. @pre: - @post: A new ordered linked list object has been initialised. Its length is equal to the number of elements in lst. The data elements stored in the linked list's nodes correspond to those of the list lst, and appear in the same order as in that list. If no list lst is passed as argument, the newly created linked list will have 0 length, contain no nodes and its head will point to None. """ pass def size(self): """ Accessor method which returns the number of nodes contained in this ordered linked list. @pre: - @post: Returns the number of nodes (possibly zero) contained in this ordered linked list. """ pass def add(self, cargo): """ Adds a new Node with given cargo to the ordered linked list such that the order is maintained @pre: self is a (possibly empty) LinkedList. @post: A new Node object is created with the given cargo. This new Node is added to the ordered linked list in an ordered manner. The length counter has been incremented by 1. """ pass def remove(self, cargo): """ Removes the first node with a given cargo from the list. Leaves the list intact if already empty or if a node with that cargo doesn't exist. """ pass
Ensuite, créez un fichier de tests TestOrderedLinkedList.py afin de tester le bon fonctionnement de votre implémentation.
Cette classe est déjà presque complète, mais afin de pouvoir mettre des médias "dans l'ordre", il faut définir les critères selon lequel on établit cet ordre.
Vous devez choisir un critère au choix (durée, taille, titre, artiste, etc.) et ensuite implémenter la méthode magique __lt__ (pour 'less than') ou __gt__ (pour 'greater than') dans la classe Media afin de pouvoir comparer différentes instances de celle-ci à l'aide de l'opérateur usuel < (ou >). Tout comme la méthode __eq__ permet d'utiliser l'opérateur == sur des objets des classes créée par vous, les méthodes magiques __lt__ et __gt__ vous permettent d'utiliser < et > respectivement. L'implémentation se fait suivant le pseudo code suivant :
def __lt__(self, other) : if not isinstance(other, Media) : return False return [self plus petit que other]
Bien évidemment, ce que signifie "self plus petit (/plus grand) que other" est à vous de déterminer.
Il peut être utile de compléter le tests dans le fichier TestMedia.py afin de vérifier que l'implémentation de l'ordre est correcte.
Cette classe doit implémenter les mêmes méthodes que ListeLecture, mais en utilisant une liste chaînée ordonnée à la place d'une liste python classique. À savoir:
class ListeLectureOrdonnee: """ @pre: name est un string id est un entier @post: une instance de 'ListeLecture' ayant un identifiant unique. """ def __init__(self, name, id) : pass """ Ajoute 'media' à la liste de lecture @pre: media est une instance de 'Media' @post: la liste de lecture comprend maintenant 'media' """ def ajouter(self, media) : pass """ Retire 'media' la première occurence de 'media' à la liste de lecture @pre: media est une instance de 'Media' @post: la première instance de 'media' dans la liste de lecture a été supprimé """ def retirer(self, media) : pass """Renvoie le nombre de medias dans liste""" def nombre_de_medias(self) : pass """ Renvoie un string avec une description de la liste et tous ses éléments. """ def __str__(self) : pass
Ensuite, créez un fichier de tests TestListeLectureOrdonnee.py comme précédémment.
Vous remarquerez sans doute que dans le code final, certaines classes ont des méthodes très similaires à d'autres. (Par exemple les classes OrderedLinkedList et LinkedList, ou les classes ListeLectureOrdonnee et ListeLecture.) Pour entrainer votre intuition pour l'examen, nous vous proposons d'essayer de faire hériter certaines classes d'autres classes existantes. Votre implémentation sera bien évidemment corrigée par votre tuteur·trice. En particulier réfléchissez si les classes ListeLectureOrdonnee ou OrderedLinkedList peuvent être implémentées en utilisant l'héritage.
Ajoutez également une méthode test correspondante à votre classe test pour vérifier le bon comportement de la méthode __str__.
Ajoutez également une méthode test correspondante à votre classe test pour vérifier le bon comportement de la méthode remove_from_end.
Ajoutez une méthode contains(e) dans la classe LinkedList qui retourne True si la valeur e se trouve dans la liste et False autrement.
Ajoutez également une méthode test correspondante à votre classe test pour vérifier le bon comportement de la méthode contains(e). N'oubliez pas de prendre en compte différents cas comme:
* la liste est vide; * la liste contient un seul élément égal à l'élément cherché; * la liste contient un seul élément différente de l'élément cherché; * la liste contient différents fois un même élément; * la liste contient différents fois l'élément cherché.
Adaptez le code de la classe LinkedList pour créer une classe DoubleLinkedList représentant une liste doublement chaînée. Pour chaque noeud, en plus d'avoir un lien vers le noeud suivant, on ajoute un lien vers le noeud précédent. Ceci permet de parcourir la liste dans les deux sens: avancer en suivant la référence vers le noeud suivant, ou reculer en suivant la référence vers le noeud précédent. Votre classe DoubleLinkedList doit au moins fournir les méthodes suivantes:
Ecrivez également une classe test unitaire TestDoubleLinkedList pour vérifier le bon comportement des différentes méthodes de la classe DoubleLinkedList. Cette classe test doit contenir des tests pour les différentes méthodes de la classe DoubleLinkedList.
Au code de la liste doublement chaînée de la question complémentaire précédente, ajoutez une méthode remove(e) pour supprimer la première occurrence de e de la liste doublement chainée.
Ajoutez également une méthode test correspondante à votre classe test pour vérifier le bon comportement de la méthode remove.