diff --git a/readme.md b/readme.md index 2569f4b0b10afc9e08b937a7f190ce0d8632f432..5ea70c5ad3bbbb70adb8f124573f127cbebd21df 100644 --- a/readme.md +++ b/readme.md @@ -5,7 +5,7 @@ ce _repo_ contient l'ensemble des sujets de BE pour l'enseignement `INF-TC2` de **Remarques** - Pour rappel (vu en cours), les BE #3 et #5 seront évalués par votre encadrant. Les consignes pour le rendu sont précisées dans les répertoires respectifs. - - Les slides du cours sont disponibles à l'adresse https://pedagogie1.ec-lyon.fr/course/view.php?id=969. - - Dans le répertoire _tuto-git-gitlab_, vous trouverez les scénarios des 2 tutos joués en direct dans le cours #3. C'est le moment de vous exercer... + - Les slides du cours sont disponibles sur [Moodle](https://pedagogie1.ec-lyon.fr/course/view.php?id=1024). + - Dans le répertoire _tuto-git-gitlab_, vous trouverez les scénarios des 2 tutos joués en direct dans le cours #4. C'est le moment de vous exercer... Stéphane Derrode et Thibault Rafaillac \ No newline at end of file diff --git a/seance1_4h/seance1_4h.md b/seance1_4h/seance1_4h.md index 7747df232d91069290962943280a807d85a3ecfe..b54daaf7241ea7ce5b7316d7e312135ff538c19a 100644 --- a/seance1_4h/seance1_4h.md +++ b/seance1_4h/seance1_4h.md @@ -2,43 +2,38 @@ [[_TOC_]] -# TD1 : Bibliothèque - -_Remarque introductive_ : Les sujets de BE sont rédigés dans le format _Markdown_. Si ce format vous intéresse, vous trouverez des pointeurs vers des logiciels open source et des tutoriels sur le site du cours, sur https://pedagogie1.ec-lyon.fr/course/view.php?id=969. - -> _Markdown est un langage de balisage léger créé en 2004 par John Gruber avec l'aide d'Aaron Swartz. Son but est d'offrir une syntaxe facile à lire et à écrire. Un document balisé par Markdown peut être lu en l'état sans donner l'impression d'avoir été balisé ou formaté par des instructions particulières. Il peut être converti en HTML, en PDF ou en d'autres formats._ +# BE #1 : Bibliothèque +--- ## Objectif du sujet -Dans cet énoncé, nous abordons deux concepts de base de la programmation orientée objet, __l'encapsulation__ et la __composition__, qui ont été vus lors du premier cours. Nous nous exercerons également aux diagrammes de classe du langage graphique UML. Pour vos propres diagrammes, vous pouvez utiliser https://app.diagrams.net pour dessiner en ligne (et sauvegarder localement vos diagrammes sur votre machine). +Dans cet énoncé, nous abordons deux concepts fondamentaux de la programmation orientée objet (POO), __l'encapsulation__ et la __composition__, qui ont été vus lors du premier cours. Nous nous exercerons également aux [diagrammes de classe](https://fr.wikipedia.org/wiki/Diagramme_de_classes) du langage graphique [UML](https://fr.wikipedia.org/wiki/UML_(informatique)). Pour vos propres diagrammes, vous pouvez utiliser [diagrams](https://app.diagrams.net). Dans ce BE, il s'agit de concevoir et de réaliser un programme _simple_ de gestion d'une bibliothèque, intégrant des lecteurs, des livres et des emprunts. -_Remarque_ : Cet énoncé part d'un problème simple et connu qui permet d'en faire la conception et la réalisation dans le temps qui nous est imparti par les contraintes scolaires. Les choix de conception et de réalisation sont donc orientés par ces contraintes et par les objectifs pédagogiques, à savoir : apprendre la programmation orientée objet en Python. Il est clair que le même problème dans un cadre professionnel serait traité d’une autre manière et une solution basée sur des bases de données émergerait naturellement, solution que nous écartons a priori car en dehors du périmètre de ce cours. - +_Remarque_ : Cet énoncé part d'un problème simple et connu qui permet d'en faire la conception et la réalisation dans le temps qui nous est imparti. Les choix de conception et de réalisation sont donc orientés par ces contraintes et par les objectifs pédagogiques, à savoir : apprendre la POO en Python. Il est clair que le même problème dans un cadre professionnel serait traité d’une autre manière et une solution basée sur des bases de données émergerait naturellement, solution que nous écartons a priori car en dehors du périmètre de ce cours. +--- ## Cahier des charges Le cahier des charges de notre application est décrit ci-dessous. Il est volontairement donné de manière informelle. On doit pouvoir gérer le fond documentaire d'une bibliothèque identifiée par son nom. -1. Pour simplifier, on considérera que tous les ouvrages sont des livres caractérisés par : +1. Notre application doit être capable de gérer des lecteurs. Chacun d’eux est caractérisé par : + + - Son nom complet (nom et prénom) + - Son adresse, + - Un numéro (entier positif attribué de manière unique par les bibliothécaires). + +1. Pour simplifier, on considérera que tous les ouvrages sont des livres, caractérisés par : - Le nom de l’auteur, - Le titre de l’ouvrage, - Un numéro de livre (attribué de manière unique par les bibliothécaires), - - Le nombre d’exemplaires présents. + - Le nombre d’exemplaires disponibles. -1. Notre application doit être capable de gérer également des lecteurs. Chacun d’eux est caractérisé par : - - - Son nom, - - Son prénom, - - Son adresse, - - Un numéro (entier positif attribué de manière unique par les bibliothécaires). - -1. On doit pouvoir ajouter un lecteur à tout moment et rechercher un lecteur (par son numéro de lecteur). -On doit également pouvoir ajouter des ouvrages (acquisition de livres), et rechercher un ouvrage (par numéro). +1. On doit pouvoir ajouter un lecteur à une bibliothèque à tout moment et rechercher un lecteur (par son numéro de lecteur). On doit également pouvoir ajouter des ouvrages (acquisition de livres), et rechercher un ouvrage (par son numéro de livre). 1. Tout lecteur peut emprunter des livres dans une ou plusieurs bibliothèques. Un lecteur peut emprunter plusieurs livres différents simultanément ou à des dates différentes et un même livre peut être emprunté par plusieurs lecteurs (s’il existe plusieurs exemplaires). Au moment de l’emprunt, il faut donc vérifier qu’un exemplaire de l'ouvrage est bien disponible et qu'il n'a pas été emprunté par le même lecteur. De manière symétrique, il faut également gérer le retour des livres. 1. On désire également pouvoir éditer des états détaillés : @@ -47,46 +42,46 @@ On doit également pouvoir ajouter des ouvrages (acquisition de livres), et rech - Liste de tous les livres d'une bibliothèque - Liste de tous les emprunts d'une bibliothèque -1. Da manière facultative, on souhaite pouvoir retirer un lecteur s'il n'a plus d'emprunt en cours, et retirer un exemplaire non emprunté d'un livre (désherbage ou vol). Un livre qui n'aurait plus d’exemplaire ne doit plus apparaître dans la liste des livres à disposition de la bibliothèque. +1. Da manière facultative, on souhaite pouvoir retirer un lecteur (s'il n'a plus d'emprunt en cours), et retirer un exemplaire non emprunté d'un livre (désherbage ou vol). Un livre qui n'aurait plus d’exemplaire ne doit plus apparaître dans la liste des livres à disposition de la bibliothèque. Cet énoncé a pour objectif de vous accompagner pour répondre au cahier des charges, __suivez le guide...__ -_Remarque_ : Dans chaque titre de chapitre, vous verrez figurer un temps entre parenthèses : celui-ci correspond au temps approximatif à passer pour répondre aux questions du chapitre. Si vous prenez trop retard par rapport à ces _milestones_, demandez de l'aide à votre encadrant! +_Remarque_ : Dans chaque titre de section, vous verrez figurer une durée entre parenthèses : celui-ci correspond au temps approximatif à passer pour répondre aux questions du chapitre. Si vous prenez trop de retard par rapport à ces _milestones_, demandez de l'aide à votre encadrant ! _Remarque_ : Même si ce n'est pas pas obligatoire, il vous est demandé de développer chaque classe dans un fichier python séparé (pensez à enregistrer tous les fichiers dans le même répertoire). --- - ## Classe Lecteur et classe Livre (75 minutes) **Point 1. du cahier des charges** 1. Dessinez la boîte UML de la classe __Lecteur__, en respectant le cahier des charges ci-dessus et les conventions syntaxiques présentées en cours. - * Quels attributs devront être publiques, lesquels devront être privés? Pour rappel: selon les principes de l'encapsulation, un attribut devra être privé si on veut/peut exercer un contrôle dessus. - * Prévoyez une méthode qui affiche l'état d'un lecteur (pour rappel: l'état d'un objet est l'ensemble des valeurs de ses attributs à un instant donné). + * Quels attributs devront être publiques, lesquels devront être privés ? Pour rappel : selon les principes de l'encapsulation, un attribut devra être privé si on veut/peut exercer un contrôle dessus. + * Prévoyez une méthode qui affiche l'état d'un lecteur. Pour rappel : l'état d'un objet est l'ensemble des valeurs de ses attributs à un instant donné. -1. Dans un fichier appelé _Lecteur.py_, implémentez votre classe progressivement, et développez simultanément le programme principal qui permet de vérifier le comportement attendu de votre classe (vu en cours: un programme principal commence par la commande `if __name__ == '__main__')`. Pour être plus précis: +1. Dans un fichier appelé _Lecteur.py_, implémentez votre classe progressivement, et développez simultanément le programme principal qui permet de vérifier le comportement attendu de votre classe (vu en cours : un programme principal commence par la commande `if __name__ == '__main__':`. +Pour être plus précis: - * Écrivez en premier lieu le constructeur de votre classe et créez un ou deux lecteurs dans votre programme principal. L'exécution du programme ne produit aucun résultat visible mais elle permet de vérifier que le programme est syntaxiquement correct. Ne passez pas à la méthode suivante tant que l'exécution produit des erreurs. + * Écrivez en premier lieu le constructeur de votre classe et créez deux lecteurs dans votre programme principal. L'exécution du programme ne produit aucun résultat visible mais elle permet de vérifier que le programme est syntaxiquement correct. Ne passez pas à la méthode suivante tant que l'exécution produit des erreurs. * Passez ensuite au codage de la méthode d'affichage (méthode dont le nom est `__str__`). Testez cette méthode en modifiant votre programme principal pour qu'il affiche les deux lecteurs précédemment créés. L'exécution du programme vous donne-t-elle satisfaction ? -1. Continuez ainsi pour les méthodes que vous avez prévues. Il est fort probable que nous soyons amenés à revenir sur cette classe par la suite, pour la compléter avec de nouvelles méthodes. -Un programme principal typique aura l'allure suivante: +1. Continuez de la même manière pour toutes les méthodes que vous avez prévues. Il est fort probable que nous soyons amenés à revenir sur les fonctionnalités de cette classe par la suite, pour la compléter avec de nouvelles méthodes et attributs. +Un programme principal typique aura l'allure suivante : ```python if __name__ == '__main__': # Des lecteurs - L1 = Lecteur('Mzai', 'Ahmed', 'rue de la Paix',1) - L2 = Lecteur('Dupond', 'John', 'rue de la Gare',2) - L3 = Lecteur('Levgueni', 'Dimitri','rue La Fayette',3) - L4 = Lecteur('Rodriguez','Alfonso','rue du Stade', 4) - - print('L1 -->', L1) - print('L2 -->', L2) - print('L3 -->', L3) - print('L4 -->', L4) + L1 = Lecteur('Mzai Ahmed', 'Boulevard de la Paix', 1) + L2 = Lecteur('Xu John', 'Rue de la Gare', 2) + L3 = Lecteur('Levgueni Dimitri', 'Impasse La Fayette', 3) + L4 = Lecteur('Rodriguez Alfonso', 'Rue du Stade', 4) + + print('Lecteur 1 -->', L1) + print('Lecteur 2 -->', L2) + print('Lecteur 3 -->', L3) + print('Lecteur 4 -->', L4) ``` **Point 2. du cahier des charges** @@ -97,64 +92,68 @@ Un programme principal typique aura l'allure suivante: if __name__ == '__main__': # Des livres - B1 = Livre('Le Père Goriot', 'Honoré de Balzac', 2,101) - B2 = Livre("Léon l'Africain", 'Amin Maalouf', 2,102) - B3 = Livre('Le Petit Prince', 'Antoine de Saint-Éxupery',2,103) - B4 = Livre("L'Étranger", 'Albert Camus', 2,104) - - print('B1 -->', B1) - print('B2 -->', B2) - print('B3 -->', B3) - print('B4 -->', B4) + B1 = Livre('Le Père Goriot', 'Honoré de Balzac', 2, 101) + B2 = Livre("Léon l'Africain", 'Amin Maalouf', 2, 102) + B3 = Livre('Le Petit Prince', 'Antoine de Saint-Éxupery', 2, 103) + B4 = Livre("L'Étranger", 'Albert Camus', 2, 104) + + print('Livre 1 -->', B1) + print('Livre 2 -->', B2) + print('Livre 3 -->', B3) + print('Livre 4 -->', B4) ``` +--- ## Classe Bibliothèque (75 minutes) -**Points 3. et 5. du cahier des charges** +**Points 3. et 5. (partiel) du cahier des charges** -1. Un bibliothèque gère une collection de livres et un répertoire de lecteurs (et des emprunts, mais nous verrons cela plus tard). Quels liens entre les classes __Bibliotheque__ et __Livre__ d'une part, et __Bibliotheque__ et __Lecteur__ vous semblent les plus adaptés pour exprimer ces relations ? *Tip* Si un Livre appartient à une bibliothèque, un lecteur peut s'inscrire dans plusieurs bibliothèques (vous voyez la différence?)! Dessinez alors le schéma UML entre ces 3 classes. N'oubliez pas d'ajouter les cardinalités de part et d'autre des liens. +1. Un bibliothèque gère une collection de livres et un répertoire de lecteurs (et des emprunts, mais nous verrons cela plus tard). Quels types de lien entre les classes __Bibliotheque__ et __Livre__ d'une part, et entre les classes __Bibliotheque__ et __Lecteur__ d'autre part, vous semblent les plus adaptés pour exprimer ces relations ? *Tip* Si un livre appartient à une bibliothèque, un lecteur peut s'inscrire dans plusieurs bibliothèques (vous voyez la différence ?) ! Dessinez alors le schéma UML entre ces 3 classes. N'oubliez pas d'ajouter les cardinalités de part et d'autre des liens. Dessinez ensuite le détail de la boîte UML de la classe __Bibliothèque__, de manière à répondre au point 3. du cahier des charges. 1. Dans un nouveau fichier appelé _Bibliotheque.py_, commencez par coder le constructeur de la classe __Bibliotheque__, en choisissant la structure de données adéquate pour gérer les lecteurs et les livres. Créez un programme principal, dans lequel vous créerez une bibliothèque (de nom _Michel Serre_ ?). Codez ensuite la méthode `__str__` qui affiche simplement le nom de la bibliothèque. Testez dans votre programme principal. 1. On se concentre sur les lecteurs. Codez successivement l'implémentation des méthodes permettant - * d'ajouter un lecteur (on fera l'hypothèse qu'on n'essaye pas d'ajouter un lecteur déjà présent), + * d'ajouter un lecteur (on fera l'hypothèse que le numéro du lecteur est unique, sans vérification), * d'afficher la liste des lecteurs de la bibliothèque, * de chercher un lecteur par son numéro, * de chercher un lecteur par son nom, - N'oubliez pas de tester chacune des méthodes avant de passer à la suivante! N'hésitez pas à compléter la classe __Lecteur__ s'il vous manque des méthodes... + N'oubliez pas de tester chacune des méthodes avant de passer à la suivante ! N'hésitez pas à compléter la classe __Lecteur__ s'il vous manque des méthodes... 1. Faites de même avec les livres. On ne vérifiera pas si le livre est déjà présent dans la collection avant de l'ajouter. +--- ## Les emprunts (90 minutes) -**Points 4. et 5. du cahier des charges** +**Points 4. et 5. (partiel) du cahier des charges** -Un emprunt sera modélisé par un objet qui associe un lecteur (connu par son identifiant) avec un livre (connu par son identifiant) à une date donnée. +Un emprunt sera modélisé par un objet qui associe un lecteur (connu par son identifiant) avec un livre (lui aussi connu par son identifiant) à une date donnée. -1. Dessinez la boite UML de la classe __Emprunt__. Celle-ci doit permettre de créer un nouvel emprunt, et d'afficher son état. Prévoyez des getter pour les attributs privés. Comment modéliser en UML les relations de la classe __Emprunt__ avec les classes __Lecteur__ et __Livre__? +1. Dessinez la boite UML de la classe __Emprunt__. Celle-ci doit permettre de créer un nouvel emprunt, et d'afficher son état. Prévoyez des _getter_ (_i.e._ une méthode qui renvoie la valeur d'un attribut privé). Comment modéliser en UML les relations de la classe __Emprunt__ avec les classes __Lecteur__ et __Livre__ ? 1. Implémentez votre classe dans un fichier appelé _Emprunt.py_. Complétez, en parallèle de l'implémentation des méthodes, un programme principal qui pourra finalement avoir l'allure suivante: ```python if __name__ == '__main__': - # Creation d'une Emprunt - E1 = Emprunt(1, 2) - print('E1 --> ', E1) - print("Num lecteur de l'emprunt E1: ", E1.get_numero_lecteur()) + ... + + # Création d'un emprunt entre un lecteur et un livre + E1 = Emprunt(1, 100) + print('Emprunt --> ', E1) + print("Num lecteur de l'emprunt : ", E1.get_numero_lecteur()) ``` Résultat: ```console - E1 --> Emprunt - Numero lecteur : 1, Numero livre: 2, Date : 2020-08-23 - Num lecteur de l'emprunt E1: 1 + E1 --> Emprunt - Numero lecteur : 1, Numero livre: 100, Date : 2020-08-23 + Num lecteur de l'emprunt E1: 1 ``` - _Remarque_ : Pour la date, on pourra utiliser l’instruction `date.isoformat(date.today())`, en ayant pris soin d'importer la librairie, à l'aide de la commande `from datetime import date` (à positionner tout en haut de la classe). + _Remarque_ : Pour la date, on pourra utiliser l’instruction `date.isoformat(date.today())`, en ayant pris soin d'importer la librairie : `from datetime import date` (à positionner tout en haut de la classe). 1. À ce stade du développement, les emprunts sont indépendants de la bibliothèque. Compléter votre schéma UML précédent pour modéliser le fait que c'est la bibliothèque qui gère tous les emprunts. Quel nouvel attribut de la classe __Bibliotheque__ prévoyez-vous pour stocker la collection des emprunts ? @@ -167,11 +166,10 @@ Un emprunt sera modélisé par un objet qui associe un lecteur (connu par son id _Tip_ : Pensez à 1. modifier la classe __Lecteur__ de manière à lui ajouter un compteur de livres empruntés (à sa création, un lecteur n'a pas d'emprunt). Prévoyez une méthode publique appelée `incremente_nb_emprunts()`. - 1. modifier la classe __Livre__ de manière à lui ajouter un compteur qui tient à jour le nombre d'exemplaires disponibles (à sa création, un livre à autant d'exemplaires disponibles qu'il a de nombre d'exemplaires). Prévoyez une méthode publique appelée `decremente__dispo()` qui décrémentera le nombre d'exemplaires disponibles et renverra `True` s'il reste au moins un exemplaire, sinon `False`. + 1. modifier la classe __Livre__ de manière à lui ajouter un compteur qui tient à jour le nombre d'exemplaires disponibles (à sa création, un livre à autant d'exemplaires disponibles qu'il a de nombre d'exemplaires). Prévoyez une méthode publique appelée `decremente_dispo()` qui décrémentera le nombre d'exemplaires disponibles et renverra `True` s'il reste au moins un exemplaire, sinon `False`. + 1. modifier les méthodes `__str__` des 2 classes pour y inclure l'affichage de leur nouvel attribut. - Pensez à modifier les méthodes `__str__` des 2 classes pour y inclure l'affichage de leur nouvel attribut. - - Vous ajouterez également une méthode `affiche_emprunts()` pour visualiser la liste des emprunts et testerez vos nouvelles méthodes avant de passer à la suite. Pour tester votre méthode, ajouter ces quelques lignes à votre programme principal: + Vous ajouterez également une méthode `affiche_emprunts()` pour visualiser la liste des emprunts et testerez vos nouvelles méthodes avant de passer à la suite. Pour tester votre méthode, ajouter ces quelques lignes à votre programme principal : ```python if __name__ == '__main__': @@ -180,10 +178,10 @@ Un emprunt sera modélisé par un objet qui associe un lecteur (connu par son id # Quelques emprunts print('\n--- Quelques emprunts :') - b.emprunt_livre(1,101) - b.emprunt_livre(1,104) - b.emprunt_livre(2,101) - b.emprunt_livre(3,101) + b.emprunt_livre(1, 101) + b.emprunt_livre(1, 104) + b.emprunt_livre(2, 101) + b.emprunt_livre(3, 101) print(b.chercher_livre_numero(101)) # Affichage des emprunts, des lecteurs et des livres @@ -204,7 +202,7 @@ Un emprunt sera modélisé par un objet qui associe un lecteur (connu par son id Emprunt - Numero lecteur : 2, Numero livre: 101, Date : 2020-08-23 ``` -1. Pour simuler le rendu d'un livre par un lecteur à la bibliothèque, implémentez une méthode _retour\_livre(self, numero_lecteur, numero_livre)_. Pour cela, on pourra faire appel à une __méthode privée__ appelée _\_\_chercher_emprunt(self, numero_lecteur, numero_livre)_ qui renverra l'instance de l'emprunt s'il fait partie de la liste des emprunts, ou _None_ dans le cas contraire. La méthode _retour\_livre_ affichera un message d'erreur si l'emprunt n'existe pas. Dans le cas contraire, détruisez l'emprunt et pensez à mettre à jour le nombre d’exemplaires du livre ainsi que le nombre d’emprunts du lecteur. +1. Pour simuler le rendu d'un livre par un lecteur à la bibliothèque, implémentez une méthode _retour\_livre(self, numero_lecteur, numero_livre)_. Pour coder cette méthode, on pourra faire appel à une __méthode privée__ appelée _\_\_chercher_emprunt(self, numero_lecteur, numero_livre)_ qui renverra l'instance de l'emprunt s'il fait partie de la liste des emprunts, ou _None_ dans le cas contraire. La méthode _retour\_livre_ affichera un message d'erreur si l'emprunt n'existe pas. Dans le cas contraire, détruisez l'emprunt et pensez à mettre à jour le nombre d’exemplaires du livre ainsi que le nombre d’emprunts du lecteur. Tester le retour dans votre programme principal grâce au code suivant : @@ -215,9 +213,9 @@ Un emprunt sera modélisé par un objet qui associe un lecteur (connu par son id # Quelques retours de livres print('\n--- Quelques retours de livres :') - b.retour_livre(1,101) - b.retour_livre(1,102) - b.retour_livre(10,108) + b.retour_livre(1, 101) + b.retour_livre(1, 102) + b.retour_livre(10, 108) ``` qui devra afficher quelque chose de similaire à : @@ -228,103 +226,21 @@ Un emprunt sera modélisé par un objet qui associe un lecteur (connu par son id ``` +--- +## Crash-test -## Questions ouvertes supplémentaires - -**Point 6. (facultatif) du cahier des charges** - -1. Implémentez une méthode permettant de supprimer un livre (si tous les exemplaires sont rendus). - - ```python - def retrait_livre(self,numero): - # On cherche le livre - livre = self.chercher_livre_numero(numero) - if livre == None: - return False - # On verifie que le livre n'est pas en cours d'emprunt - for e in self.__emprunts: - if e.get_numero_livre()==numero: - return False - # On peut ici retirer le livre de la liste - self.__livres.remove(livre) - return True - ``` - Les commandes - - ```python - if __name__ == '__main__': - ... - - # Suppression de quelques livres - print('\n--- Suppression de quelques livres :') - rep = b.retrait_livre(101) - if not rep: - print('Retrait du livre impossible') - else: - print('Retrait du livre effectue') - - b.retour_livre(2,101) - - rep = b.retrait_livre(101) - if not rep: - print('Retrait du livre impossible') - else: - print('Retrait du livre effectue') - ``` - donnent - - ```console - --- Suppression de quelques livres : - Retrait du livre impossible - Retrait du livre effectue - ``` +À ce stade, votre programme doit être fonctionnel, mais êtes-vous bien certains que l'ensemble de vos classes sont robustes ? Nous vous proposons de le vérifier à l'aide de ce [crash-test](crash_test_biblio.py), à télécharger à côté de vos fichiers. -1. Implémentez une méthode permettant de supprimer un lecteur (si il n'est redevable d'aucun emprunt). +Si, en lançant ce programme, vous obtenez des erreurs d'exécution, chercher à corriger vos méthodes. Les réponses attendues à chaque ligne de code sont données sous forme de commentaire dans le fichier. A vous de vérifier que tout est OK ! - ```python - def retrait_lecteur(self,numero): - # On cherche le lecteur - lecteur = self.chercher_lecteur_numero(numero) - if lecteur == None: - return False - # On verifie qu'il n'a pas d'emprunt en cours - for e in self.__emprunts: - if e.get_numero_lecteur()==numero: - return False - # On peut ici retirer le lecteur de la liste - self.__lecteurs.remove(lecteur) - return True - ``` - Les commandes +_Remarque_ : Pour établir la robustesse de ses programmes, on utilise la programmation par tests et les [tests unitaires](https://fr.wikipedia.org/wiki/Test_unitaire)... - ```python - if __name__ == '__main__': - ... - - # Suppression de quelques lecteurs - print('\n--- Suppression de quelques lecteurs :') - rep = b.retrait_lecteur(1) - if not rep: - print('Retrait du lecteur impossible') - else: - print('Retrait du lecteur effectue') - - b.retour_livre(1,104) - - rep = b.retrait_lecteur(1) - if not rep: - print('Retrait du lecteur impossible') - else: - print('Retrait du lecteur effectue') - ``` - donnent - - ``` console - --- Suppression de quelques lecteurs : - Retrait du lecteur impossible - Retrait du lecteur effectue - ``` +--- +## Questions ouvertes supplémentaires -1. Un livre qui n'aurait plus d’exemplaire ne doit plus apparaître dans la liste des livres à disposition de la bibliothèque. +**Point 6. (facultatif) du cahier des charges** +1. Implémentez une méthode permettant de supprimer un livre (si tous les exemplaires sont rendus). +1. Implémentez une méthode permettant de supprimer un lecteur (si il n'est redevable d'aucun emprunt). +1. Un livre qui n'aurait plus d’exemplaire ne doit plus apparaître dans la liste des livres à disposition de la bibliothèque. 1. Comment implémenter un mécanisme de vérification de l'unicité de l'identifiant d'un lecteur et de celui d'un livre ? \ No newline at end of file diff --git a/seance3_4h/consignes_BE#3.md b/seance3_4h/consignes_BE#3.md index 04cb6fb7a1a1c74fa424fcc0f37444f3da6ecba2..49a811ee71ddf3c4ce2f402b6ec9aed561ab90f9 100644 --- a/seance3_4h/consignes_BE#3.md +++ b/seance3_4h/consignes_BE#3.md @@ -1,17 +1,17 @@ -**Consignes pour le rendu** +### Consignes pour le rendu (BE #3 - INF-TC2) -Ce BE constitue le premier devoir à rendre concernant INF-TC2. Le compte-rendu (CR) de ce travail devra être déposé sur ``Pedagogie1``, sur l'espace de dépôt spécifique à votre groupe. Et cela dans un **délai de deux semaines après la séance** (délai de rigueur, aucun travail accepté au delà de cette date). +Ce BE est le premier devoir à rendre concernant INF-TC2. Le compte-rendu (CR) de ce travail devra être déposé sur ``Pedagogie1``, sur l'espace de dépôt spécifique à votre groupe. Et cela dans un **délai de deux semaines après la séance** (délai de rigueur, aucun travail accepté au delà de cette date). -*Commentaires:* +**Consignes:** - - Le travail peut être individuel ou en binôme. - - Le dépôt consistera en une unique archive (zip, rar) contenant l'ensemble des fichier : - - la base de données *Hotellerie.db* après exécution de vos requêtes. - - Le fichier Python, appelé *HotelDB.py*, contenant la classe **HotelDB** et un programme principal permettant de rejouer l'ensemble des requêtes de cet énoncé. - - la dernière partie de ce BE propose de travailler éventuellement sur une seconde bdd de votre choix : si vous choisissez cette option, n'oubliez pas d'inclure la nouvelle bdd (si elle est très volumineuse, donnez uniquement le chemin de téléchargement dans votre rapport) et le fichier Python qui réalise les requêtes que vous aurez imaginées. - - un rapport (format markdown ou pdf) contenant - - une en-tête où devront figurer de manière claire le nom des élèves, leur numéro de groupe, le nom de l'encadrant ainsi que le titre du BE. + - Le travail peut être individuel ou en binôme. Si vous travaillez en binôme, **un seul dépôt suffit !**. + - Le dépôt consistera en une unique archive (zip, rar) contenant l'ensemble des fichiers suivant : + - La base de données *Hotellerie.db*, après exécution de vos requêtes. + - Le fichier _HotelDB.py_, contenant la classe **HotelDB** et un programme principal permettant de rejouer l'ensemble des requêtes de cet énoncé. + - La dernière base de données de votre choix : si vous choisissez cette option, n'oubliez pas d'inclure la nouvelle bdd dans votre archive (si elle est très volumineuse, donnez uniquement le chemin de téléchargement dans votre rapport), ainsi que le fichier _Python_ qui réalise les requêtes que vous aurez imaginées. + - un rapport (format _word_, _pdf_, ou mieux encore _markdown_ !) contenant + - une en-tête où devront figurer le nom des élèves, leur numéro de groupe, le nom de l'encadrant ainsi que le titre du BE. - des commentaires sur la programmation de chacune des requêtes et les résultats obtenus. - - Vous pouvez insérer tout diagramme, toute figure ou toute explication que vous souhaitez, mais dans un **nombre de pages limité à 10** (il n'est pas demandé de rédiger 10 pages, c'est juste une limite à ne pas dépasser!). + - tout diagramme, toute figure ou toute explication que vous jugerez utile, mais dans un **nombre de pages limité à 10** (il n'est pas demandé de rédiger 10 pages, c'est une limite à ne pas dépasser !). - L'archive devra nécessairement porter le nom suivant : *nom1-devoir1.zip* ou *nom1-nom2-devoir1.zip* (pour les étourdis, pensez à remplacer *nom1* et *nom2* par vos propres noms :-) ) diff --git a/seance3_4h/seance3_4h.md b/seance3_4h/seance3_4h.md index 079a5c967996b32086b3eb7fec7c3e77b6725c75..abfe1f5ae85e75e1ad420dd5cf29d6e2e6bf42c7 100644 --- a/seance3_4h/seance3_4h.md +++ b/seance3_4h/seance3_4h.md @@ -4,22 +4,22 @@ # BE #3 : Base de données SQL -L'objectif de ce BE est d'expérimenter la manipulation (créer, lire, interroger, modifier) de bases de données relationnelles avec le langage SQL (*Structured Query Language*), à partir de Python. Le BE est décomposé en trois parties: - 1. **La première partie** (durée: 45 min.) présente quelques commandes élémentaires pour interroger une base SQL à partir de Python; - 1. **La seconde partie** (durée: 75 min.) permet de découvrir comment manipuler une base de données SQL avec Python. L'énoncé est rédigé sous une forme tutoriel; - 1. **La troisième partie** (durée: 120 min. et +) constitue un travail plus personnalisé qui vous permettra de mettre à profit vos connaissances sur la programmation par tests, la gestion des exceptions, la librairie graphique *matplotlib* et bien sûr les bases de données SQL. -Ce BE fera l'objet d'un compte-rendu (CR), seul ou en binôme. Avant de commencer, veuillez prendre connaissance des consignes concernant le rendu du travail (à respecter scrupuleusement) qui se trouvent dans le fichier [consignes_BE#3.md](./consignes_BE#3.md) (dans le même répertoire que cet énoncé). +L'objectif de ce BE est d'expérimenter la manipulation (créer, lire, interroger, modifier) de bases de données relationnelles avec le langage SQL (*Structured Query Language*), à partir de _Python_. Ce BE est décomposé en trois parties: + 1. **La première partie** (durée: 45 min.) présente quelques commandes élémentaires pour interroger une base _SQL_ à partir de _Python_; + 1. **La seconde partie** (durée: 75 min.) permet de découvrir comment manipuler une base de données _SQL_ en _Python_ orienté objet. L'énoncé est rédigé sous une forme tutoriel; + 1. **La troisième partie** (durée: 120 min. et +) vous amène à un travail plus personnalisé, pour mettre à profit vos connaissances sur la gestion des exceptions, la librairie graphique *matplotlib* et bien sûr les bases de données _SQL_. -## 1. Mini-tutoriel sur la base Hotellerie.db (45 min.) +Ce BE fera l'objet d'un compte-rendu (CR), seul ou en binôme. Avant de commencer, veuillez prendre connaissance des consignes concernant le rendu du travail (à respecter scrupuleusement) qui se trouvent dans le fichier [consignes_BE#3.md](./consignes_BE#3.md) (dans le même répertoire que cet énoncé). -Le système de gestion de base de données qui sera utilisé pour la suite de ce BE est ``SQLite``. Ce système très simple fonctionne en stockant une base de données dans un fichier unique au format ``.sqlite``. La base de tests utilisée dans cette partie s'appelle *hotellerie.db* ; elle est disponible au même endroit que cet énoncé. +--- +## 1. Mini-tutoriel sur la base Hotellerie.db (45 min.) -Le schéma de la base de données est le suivant: +Le système de gestion de base de données qui sera utilisé durant ce BE est _SQLite_. Ce système très simple fonctionne en stockant une base de données dans un fichier d'extension _.sqlite_. La base de tests utilisée dans cette partie (_hotellerie.db_) est disponible au même endroit que cet énoncé. Son schéma est le suivant : -<center><img src="figures/schema_bdd_hotellerie.png" style="width:50%"/></center> +<center><img src="figures/schema_bdd_hotellerie.png" style="width:60%"/></center> La base est composée de 5 tables, ces tables étant composées d'un nombre variable de champs. Les champs soulignés représentent les clés primaires (ou *primary key (PK)* en anglais). En particulier, la clé primaire de la table **chambre** est composée des attributs *numchambre* et *numhotel* (cette dernière étant la clé primaire de la table **hotel**). @@ -29,10 +29,10 @@ La base est composée de 5 tables, ces tables étant composées d'un nombre vari ### 1.1 DB browser for SQLite (30 min.) -Toutes les opérations sur une base de données de ce type peuvent être effectuées en Python via les classes et les méthodes présentes au sein du module ``sqlite3``. Pour manipuler de manière interactive le contenu de la base (créer, supprimer ou modifier des tables et des enregistrements, effectuer des requêtes SQL...), il existe des outils adaptés. L'outil retenu dans le cadre de ce cours s'appelle ``DB Browser for SQLite``. C'est un logiciel libre qui existe pour toutes les plate-formes : Windows, MacOs, nombreuses distributions Linux et Unix... +Toutes les opérations sur une base de données de ce type peuvent être effectuées en _Python_ via les classes et les méthodes présentes au sein du module _sqlite3_. Pour manipuler de manière interactive le contenu de la base (créer, supprimer ou modifier des tables et des enregistrements, effectuer des requêtes _SQL_...), il existe des outils adaptés. L'outil retenu dans le cadre de ce BE s'appelle ``DB Browser for SQLite``. C'est un logiciel libre qui existe pour toutes les plate-formes : Windows, MacOs, nombreuses distributions Linux et Unix... - Téléchargez et installez [DB Browser for SQLite](https://sqlitebrowser.org/) en suivant les instructions d'installation en fonction de votre système d'exploitation. - - Ouvrez la base *hotellerie.db* et naviguez dans les tables (onglet ``Structure de la Base de Données``) et les enregistrements (onglet ``Parcourir les données``) pour prendre connaissance de la base (telle qu'elle est schématisée dans la figure ci-dessus). + - Ouvrez la base *hotellerie.db* et naviguez dans les tables (onglet ``Structure de la Base de Données``) et les enregistrements (onglet ``Parcourir les données``) pour prendre connaissance de la base (telle qu'elle est schématisée ci-dessus). <center><img src="figures/DBBrowser.png" style="width:75%"/></center> @@ -41,19 +41,20 @@ Toutes les opérations sur une base de données de ce type peuvent être effectu SELECT nom, ville FROM hotel; ``` -La réponse apparaît sous forme de 12 lignes. Ça vous rappelle des choses ? Si non, alors voici quelques pointeurs pour vous rafraîchir la mémoire: +La réponse apparaît sous forme de 12 lignes. Ça vous rappelle des choses ? Si non, alors voici quelques pointeurs pour vous rafraîchir la mémoire : - [cours tutoriel sur SQL](https://www.1keydata.com/fr/sql/) - [SQL : sélection, jointure, regroupement, filtre](http://cerig.pagora.grenoble-inp.fr/tutoriel/bases-de-donnees/chap20.htm) - et tant d'autres... -### 1.2 Quelques requêtes Python (15 min.) -**Attention** Avant de lancer un requête sur la bdd avec python, il est fortement conseillé de fermer ``DB Browser for SQLite``, sinon vous pourriez soit avoir un plantage de votre programme python, soit détruire la bdd (auquel cas il vous suffirait de la télécharger à nouveau). +### 1.2 Quelques requêtes en _Python_ (15 min.) -Nous allons maintenant chercher à reproduire la requête en utilisant Python et le package ``sqlite3``. C'est une librairie objet dont la [documentation](https://docs.python.org/3/library/sqlite3.html#module-sqlite3) fournit une description des classes et des méthodes disponibles. Suivez le guide... +**Attention** Avant de lancer un requête sur la bdd avec _Python_, il est fortement conseillé de fermer ``DB Browser for SQLite``, sinon vous pourriez soit avoir un plantage de votre programme, soit détruire la bdd (auquel cas il vous suffirait de la télécharger à nouveau). -Le squelette typique d'un tel programme s'écrit: +Nous allons à présent chercher à reproduire la requête ci-dessus en utilisant _Python_ et le package _sqlite3_. C'est une librairie objet dont la [documentation](https://docs.python.org/3/library/sqlite3.html#module-sqlite3) fournit une description des classes et des méthodes disponibles. Suivez le guide... + +Le squelette typique d'un tel programme s'écrit : ```python import sqlite3 if __name__ == '__main__': @@ -63,7 +64,7 @@ if __name__ == '__main__': conn.close() # pour fermer proprement l'accès à la base ``` -Ainsi, pour obtenir la réponse à la requête précédente: +Ainsi, pour obtenir la réponse à la requête précédente : ```python import sqlite3 if __name__ == '__main__': @@ -79,13 +80,14 @@ if __name__ == '__main__': conn.close() ``` -Copiez et exécutez ce programme ; le résultat se présente sous forme d'un tuple, ou sous forme d'une liste de tuples. Ainsi la commande +_Remarque_ La commande ```conn.commit()``` n'est pas nécessaire ici puisque le script ne modifie pas la base. + +Copiez et exécutez ce programme ; le résultat se présente sous forme d'un tuple, ou sous forme d'une liste de tuples. Ainsi la commande suivante imprime le nom du premier hôtel qui apparaît dans la liste des résultats de la requête : ```python print(ligneAll[0][0]) ``` -imprime le nom du premier hôtel qui apparaît dans la liste des résultats de la requête. -Voici un usage intéressant à étudier: +Voici un usage intéressant à étudier : ```python import sqlite3 if __name__ == '__main__': @@ -98,51 +100,72 @@ if __name__ == '__main__': conn.close() ``` ------ - +--- ## 2. Classe HotelDB (75 min.) Il s'agit de créer une classe **HotelDB** permettant de réaliser un certain nombre de requêtes et de mises à jour de la base *Hotellerie.db*. ### 2.1 Requête en lecture (30 min.) -Dans un fichier *HotelDB.py*, commencez à développer la classe permettant de répondre à ce programme principal dont l'objectif est d'afficher le nom des hôtels 2 étoiles (noter que le nombre d'étoiles est passé en argument): +Dans un fichier *HotelDB.py*, commencez à développer la classe permettant de répondre au programme principal suivant, dont l'objectif est d'afficher le nom des hôtels 2 étoiles (notez que le nombre d'étoiles est passé en argument) : ```python if __name__ == '__main__': aHotelDB = HotelDB('hotellerie.db') - print(aHotelDB.get_name_hotel_etoile(2)) - ``` -*Remarque* : Pour fermer correctement l'accès à la base de donnée, pensez à implémenter la méthode ``__del__(self)`` (vue en cours), qui est appelée automatiquement par Python lorsque l'objet est détruit. + resultat = aHotelDB.get_name_hotel_etoile(2) + print("Liste des noms d'hotel 2 étoiles : ", resultat) + ``` + +Il s'agit ici de développer le constructeur (qui stockera le connecteur en tant qu'attribut) et la méthode _get_name_hotel_etoile(...)_. + +*Remarque* : Pour fermer correctement l'accès à la base de donnée, pensez à implémenter la méthode ```__del__(self)``` (vue en cours), qui est appelée automatiquement par _Python_ lors de la destruction des objets de la classe. Typiquement : +```python +def __del__ (self): + self.__conn.close() +``` + +__Améliorations à implémenter__ : Comment se comporte votre programme si on insère la commande `aHotelDB.get_name_hotel_etoile(-1)` ? Et la commande `aHotelDB.get_name_hotel_etoile("Hello")`. Comment éviter que cet usage ne produise la fin brutale du programme et qu'il renvoie une liste vide tout simplement ? -*Améliorations* : Comment se comporte votre programme si on insère dans le programme ci-dessus la commande ``print(aHotelDB.get_name_hotel_etoile(-1))``? Et la commande ``print(aHotelDB.get_name_hotel_etoile("Hello"))``. Comment éviter que cet usage ne produise la fin brutale du programme et renvoie une liste vide tout simplement? ### 2.2 Requête en écriture (45 min.) -Créer une requête permettant d'ajouter un nouveau client. Si le client existe déjà (même nom ET même prénom), la méthode renverra le numéro de ce client. Si le client n'existe pas, alors la méthode créera un nouveau client, en lui adjoignant le premier numéro non encore utilisé (c'est une clé primaire !). Pour cela, renseignez-vous sur la commande ``INSERT INTO``. Vérifier que le nouveau client a bien été sauvegardé dans le fichier *Hotellerie.db*: +Créer une requête permettant d'ajouter un nouveau client. Si le client existe déjà (même nom ET même prénom), la méthode renverra le numéro de ce client. Si le client n'existe pas, alors la méthode créera un nouveau client, en lui adjoignant le premier numéro non encore utilisé (c'est une clé primaire !). Pour cela, renseignez-vous sur la commande ``INSERT INTO``. Vérifier que le nouveau client a bien été sauvegardé dans le fichier *Hotellerie.db* : - - soit en consultant la base avec ``DB Browser for SQLite``; + - soit en consultant la base avec `DB Browser for SQLite`; - soit en exécutant par 2 fois le même programme, vous devriez retrouver le même numéro de client. ------ +--- +## 3. Requêtes libres (120 min. et +) +Dans cette dernière partie, nous vous invitons à imaginer et implémenter **DEUX (2)** requêtes originales à partir de la bdd *Hotellerie.db*, ou de tout autre bdd que vous aurez trouvée sur Internet. Voici quelques exemples de sites proposant des bdd _SQLite_ gratuites : -## 3. Requêtes libres (120 min. et +) + - Le site [SQLite tutorial](https://www.sqlitetutorial.net/sqlite-sample-database/) propose un base de données appelée _chinook_ (_digital media store_), composée de 11 tables. + - Dans le même genre, une abse de données très célèbre [Northwind](https://cs.ulb.ac.be/public/_media/teaching/infoh303/northwind_sqlite.db.zip) (8 tables) + - Si vous êtes fan des _Pokémon_, vous pouvez décompresser la base [veekun's Pokédex](http://veekun.com/static/pokedex/downloads/veekun-pokedex.sqlite.gz) (172 tables). + - Si vous êtes fan de musique, vous pouvez décompresser et utiliser la base [musicBrainz](https://matthieu-moy.fr/cours/infocpp-S3/TPL/musicBrainz.zip) (4 tables). + - Une petite base concernant la peinture : [peinture.db](https://carnot.cpge.info/wp-content/uploads/2020/02/peinture.db) + - [murder-mystery](https://forge.univ-lyon1.fr/diu-eil/bloc4/-/raw/master/3_bases_de_donnees_introduction/TP/base-sql-murder-mystery.db) (9 tables). Lire le [site original](https://github.com/NUKnightLab/sql-mysteries). + - [postuler à Stanford](https://forge.univ-lyon1.fr/diu-eil/bloc4/-/raw/master/3_bases_de_donnees_introduction/TP/base-stanford.db) (3 tables). + +Vous pouvez également transformer des données du format `.csv` (_Comma Separated Value_) vers le format `.sqlite3`, en suivant ce [tutoriel video](https://tube.ac-lyon.fr/videos/watch/85399ea5-bba0-428b-9470-2d3bb41b7de1). Toutes les données de [data.gouv.fr](https://www.data.gouv.fr/fr/), par exemple, deviennent alors exploitables pour votre CR... -Dans cette dernière partie, nous vous invitons à imaginer et implémenter **DEUX (2)** requêtes originales à partir de la bdd *Hotellerie.db*, ou de tout autre bdd que vous aurez trouvée sur Internet. Voici un exemple de site proposant des bdd SQL gratuites (il y en a beaucoup d'autres) : [bases-donnees-gratuites](https://sql.sh/categorie/bases-donnees-gratuites). Dans le second cas, n'oubliez pas d'inclure cette base dans votre archive, et de préciser dans votre rapport le chemin pour la télécharger. +Au cas où vous opteriez pour une bdd originale, n'oubliez pas d'inclure cette base dans votre archive (si elle n'est pas trop volumineuse), et de préciser dans votre rapport le chemin pour la télécharger. -*Quelques conseils*: +**Quelques conseils**: -* Imaginez deux requêtes originales à partir de la base que vous avez sélectionnée. N'hésitez pas à visiter un site de réservation d'hôtels pour trouver des idées de requêtes intéressantes. -* Concevez des exemples de requêtes ainsi que les résultats qu'elles doivent renvoyer (il est recommandé de les rédiger sous forme de tests unitaires). -* Implémentez les méthodes gérant vos deux requêtes (éventuellement dans une nouvelle classe calquée sur le modèle de la classe **HotleDB** si vous décidez d'utiliser une seconde bdd). +* Si vous optez pour la bdd `HotelDB`, n'hésitez pas à visiter un site de réservation d'hôtels pour trouver des idées de requêtes intéressantes. _Attention_ : le nom d'un hôtel n'est pas une clé primaire ! Plusieurs hôtels portent le même nom. Par contre, il n'existe pas 2 hôtels de même nom dans la même ville. Pensez-y ! +* Si vous optez pour une autre base, développer une seconde classe dans un second fichier (sur le modèle de la classe **HotelDB**). -Les deux requêtes doivent être relativement sophistiquées (pas de simples ``select ... from``), l'évaluation de cette partie dépendra : +Les deux requêtes attendues doivent être relativement sophistiquées (pas de simples ``select ... from``), l'évaluation de cette partie dépendra : - - de l'originalité de vos requêtes, et - - de la valorisation graphique des résultats de vos requêtes à l'aide de la librairie **matplotlib**. - - de leur présentation dans le rapport. + 1. de l'originalité de vos requêtes, + 1. de la valorisation graphique des résultats de vos requêtes à l'aide de la librairie **matplotlib**, + 1. de la robustesse de vos requêtes à des usages erronés ou inattendus (usage des exceptions). _Par exemple_ : comment se comporte la requête si le nom de l'hôtel passé en argument n'existe pas dans la base ? Vous pouvez "prouver" la robustesse de vos requêtes en proposant une série de _crash-tests_. + 1. de leur présentation dans le rapport. -À titre d'exemples, vous trouverez, à côté de cet énoncé, un fichier nommé ``ex_matplotlib.py``. L'exécution de ce script génère 4 figures dans le répertoire *figures*. Inspirez-vous largement de ce programme pour vos propres figures. *Conseil*: Évitez de vous lancer dans des requêtes avec des données géographiques, genre ```trouver tous les hôtels à moins de 5 kilomètres``` car l'usage de cartes géographiques dépasse les attentes de ce qui est demandé ici. +Votre programme principal doit contenir plusieurs appels à chaque requête (en changeant les arguments), de manière à illustrer leur robustesse dans des cas de figure différents (_p. ex._ hôtel ou ville inconnue). +**Remarques** : +- Vous programmerez les représentations graphiques dans le programme principal (et non pas dans la méthode qui traite la requête). En effet, quand on fait une requête sur une base de données, l'affichage graphique ne doit pas être obligatoire. C'est pour cela qu'on sépare la requête de l'affichage de son résultat (qu'il soit au format texte ou au format graphique). +- Usage de __Matplotlib__ : À titre d'exemples, vous trouverez, à côté de cet énoncé, un fichier nommé [ex_matplotlib.py](./ex_matplotlib.py). L'exécution de ce script génère 4 figures dans le sous-répertoire *figures*. Inspirez-vous largement de ce programme pour vos propres figures. *Conseil*: Évitez de vous lancer dans des requêtes avec des données géographiques, genre ```trouver tous les hôtels à moins de 5 kilomètres``` car l'usage de cartes géographiques dépasse les attentes de ce qui est demandé ici.