Aller au contenu principal

Ch. 00 · Leçon 2

Mini-projet 1 — Gestion d'une bibliothèque scolaire

120 minanalyse · python
Affichage du code

Ce que vous saurez faire

  • Analyser un cahier des charges réel en sous-problèmes
  • Combiner tableaux, recherche et tri dans un programme complet
  • Implémenter un menu interactif avec validation de saisie
  • Justifier les choix de structures à chaque étape

Une bibliothèque, 50 livres, 4 chapitres mobilisés

Ce mini-projet est un sujet bac type qui mobilise les notions des 4 chapitres officiels. Donnez-vous 2 heures, traitez-le dans les conditions de l'examen, puis comparez votre solution au corrigé.


Énoncé

Une bibliothèque scolaire gère ses livres avec un programme. Chaque livre est caractérisé par :

  • son titre (chaîne de caractères),
  • son nombre d'emprunts depuis le début de l'année (entier).

La bibliothèque contient au maximum 50 livres. Le programme propose un menu :

=== Gestion bibliothèque ===
1. Ajouter un livre
2. Enregistrer un emprunt
3. Afficher les N livres les plus empruntés
4. Rechercher un livre par titre (recherche exacte)
0. Quitter

Travail demandé

  1. Analyse modulaire : proposer un arbre d'analyse avec les modules nécessaires.
  2. T.D.O complet du programme principal.
  3. Algorithme détaillé de chaque sous-programme.
  4. Traduction Python 3 de l'ensemble.
  5. Justifications : structure de stockage, choix des boucles, choix de l'algorithme de tri.

Pistes (à ne consulter qu'après une vraie tentative)

💡 Indice 1 — Comment stocker 50 livres ?

Comme on n'a pas étudié les structures composées (records / classes), on utilise deux tableaux parallèles :

titres[50]    : tableau de chaînes
emprunts[50]  : tableau d'entiers
nb_livres     : entier (nombre actuel de livres, ≤ 50)

Le livre d'indice i a comme titre titres[i] et comme emprunts emprunts[i].

💡 Indice 2 — Quel algorithme pour le top N le plus emprunté ?

Triez les deux tableaux en parallèle (par emprunts décroissants), puis affichez les N premiers titres. Le tri par sélection est le plus simple : à chaque passe, trouvez l'indice du max restant, échangez T[i] avec T[indice_max] dans les deux tableaux simultanément.

💡 Indice 3 — Quelle recherche pour un livre par titre ?

Les titres ne sont pas triés par défaut (ils sont triés par emprunts après le tri du point précédent). Donc : recherche séquentielle (O(N)). La dichotomie nécessiterait de re-trier par titre.


Corrigé complet

📘 Voir le corrigé (à n'ouvrir qu'après votre tentative)

1. Analyse modulaire

                 ProgrammePrincipal
                          │
   ┌────────┬─────────┬───┴──────┬──────────┬──────────┐
   ▼        ▼         ▼          ▼          ▼          ▼
afficher  lire_  ajouter_  enregistrer_  top_n_    rechercher_
_menu     choix  livre     emprunt      empruntes  par_titre
                              │              │
                              ▼              ▼
                       chercher_idx   trier_par_emprunts
                       _du_titre      _decroissants

2. T.D.O programme principal

T.D.O
  Objet       | Type / Nature                 | Rôle
  ------------|-------------------------------|----------------------------------
  titres      | Tableau de 50 chaînes         | Titres des livres
  emprunts    | Tableau de 50 entiers         | Nombre d'emprunts par livre
  nb_livres   | Variable / entier             | Nombre actuel de livres (0..50)
  choix       | Variable / entier             | Choix de l'utilisateur dans le menu
  MAX_LIVRES  | Constante / entier = 50       | Capacité maximale

3. Code Python complet

MAX_LIVRES = 50

def afficher_menu():
    print("=== Gestion bibliothèque ===")
    print("1. Ajouter un livre")
    print("2. Enregistrer un emprunt")
    print("3. Afficher les N livres les plus empruntés")
    print("4. Rechercher un livre par titre")
    print("0. Quitter")

def lire_choix() -> int:
    """Lit un choix valide (0-4)."""
    while True:
        try:
            c = int(input("Votre choix : "))
            if 0 <= c <= 4:
                return c
        except ValueError:
            pass
        print("Choix invalide, recommencer.")

def ajouter_livre(titres, emprunts, nb_livres) -> int:
    """Retourne le nouveau nb_livres."""
    if nb_livres >= MAX_LIVRES:
        print("Bibliothèque pleine.")
        return nb_livres
    titre = input("Titre du nouveau livre : ").strip()
    titres[nb_livres] = titre
    emprunts[nb_livres] = 0
    return nb_livres + 1

def chercher_idx_du_titre(titres, nb_livres, cible) -> int:
    """Recherche séquentielle : retourne l'indice ou -1."""
    cible_lower = cible.lower()
    for i in range(nb_livres):
        if titres[i].lower() == cible_lower:
            return i
    return -1

def enregistrer_emprunt(titres, emprunts, nb_livres) -> None:
    titre = input("Titre du livre emprunté : ").strip()
    idx = chercher_idx_du_titre(titres, nb_livres, titre)
    if idx == -1:
        print(f"Livre « {titre} » introuvable.")
        return
    emprunts[idx] += 1
    print(f"Emprunt enregistré. Total : {emprunts[idx]}.")

def trier_par_emprunts_decroissants(titres, emprunts, nb_livres) -> None:
    """Tri par sélection des deux tableaux en parallèle."""
    for i in range(nb_livres - 1):
        idx_max = i
        for j in range(i + 1, nb_livres):
            if emprunts[j] > emprunts[idx_max]:
                idx_max = j
        # Échange dans les deux tableaux
        titres[i], titres[idx_max] = titres[idx_max], titres[i]
        emprunts[i], emprunts[idx_max] = emprunts[idx_max], emprunts[i]

def top_n_empruntes(titres, emprunts, nb_livres) -> None:
    n = int(input(f"Combien de livres afficher (1..{nb_livres}) ? "))
    n = min(max(1, n), nb_livres)
    trier_par_emprunts_decroissants(titres, emprunts, nb_livres)
    print(f"\n--- Top {n} ---")
    for i in range(n):
        print(f"{i + 1}. {titres[i]} ({emprunts[i]} emprunts)")

def rechercher_par_titre(titres, nb_livres) -> None:
    cible = input("Titre à rechercher : ").strip()
    idx = chercher_idx_du_titre(titres, nb_livres, cible)
    if idx == -1:
        print("Livre introuvable.")
    else:
        print(f"Trouvé à l'indice {idx}.")

# === Programme principal ===
titres = [""] * MAX_LIVRES
emprunts = [0] * MAX_LIVRES
nb_livres = 0

while True:
    afficher_menu()
    choix = lire_choix()
    if choix == 0:
        break
    elif choix == 1:
        nb_livres = ajouter_livre(titres, emprunts, nb_livres)
    elif choix == 2:
        enregistrer_emprunt(titres, emprunts, nb_livres)
    elif choix == 3:
        top_n_empruntes(titres, emprunts, nb_livres)
    elif choix == 4:
        rechercher_par_titre(titres, nb_livres)

print("Au revoir.")

4. Justifications

  • Tableaux parallèles : choix forcé car pas de type composé au programme. titres[i]emprunts[i].
  • Recherche séquentielle : les titres ne sont pas triés alphabétiquement, donc dichotomie impossible.
  • Tri par sélection : algorithme simple, O(n²) acceptable pour n = 50 (2500 opérations max → instantané).
  • Tri parallèle : on permute simultanément dans les deux tableaux pour préserver l'association.
  • Menu — boucle while True / break : équivaut à Répéter … Jusqu'à choix = 0. On exécute au moins une fois.

5. Cas limites traités

  • Bibliothèque vide (nb_livres = 0) → menu marche, recherche retourne -1, top N affiche 0 livres.
  • Bibliothèque pleine (nb_livres = 50) → ajout refusé proprement.
  • Titre déjà existant → on permet le doublon (peut être un point d'amélioration).
  • Comparaison de titres insensible à la casse (.lower()).

Auto-évaluation

Cochez ce que vous avez réussi :

  • Arbre d'analyse complet (au moins 6 modules)
  • T.D.O exhaustif
  • Algorithme de tri par sélection en parallèle sur 2 tableaux
  • Recherche séquentielle correcte
  • Boucle Répéter…Jusqu'à (ou while True/break) pour le menu
  • Justifications écrites pour chaque choix

Barème indicatif : 4 pts par item coché ≥ 20/20.

Bravo d'être arrivé jusqu'ici. Marquez la leçon terminée pour ancrer le progrès.