Aller au contenu

Exercices - Traitement de données en tables


Fichiers CSV et importation

Exercice 1 - Lecture d'un fichier CSV

On dispose du fichier villes.csv suivant :

nom,population,departement,altitude
Lyon,522250,69,175
Marseille,870018,13,12
Lille,236234,59,27
Toulouse,493465,31,141
Bordeaux,257804,33,6

a. Combien d'enregistrements contient cette table ? Combien d'attributs ?

b. Écrire un programme Python qui lit ce fichier et stocke les données dans un tableau de dictionnaires, en convertissant population et altitude en entiers.

c. Afficher le nom et la population de chaque ville.

Solution

a. 5 enregistrements (lignes de données) et 4 attributs (colonnes : nom, population, departement, altitude).

b.

import csv

table = []
with open("villes.csv", "r", encoding="utf-8") as fichier:
    lecteur = csv.DictReader(fichier)
    for ligne in lecteur:
        enregistrement = dict(ligne)
        enregistrement["population"] = int(enregistrement["population"])
        enregistrement["altitude"] = int(enregistrement["altitude"])
        table.append(enregistrement)

c.

for ville in table:
    print(f"{ville['nom']} : {ville['population']} habitants")


Exercice 2 - Écriture d'un fichier CSV

On dispose du tableau suivant :

films = [
    {"titre": "Le Fabuleux Destin d'Amélie Poulain", "annee": 2001, "realisateur": "Jean-Pierre Jeunet"},
    {"titre": "Intouchables", "annee": 2011, "realisateur": "Olivier Nakache"},
    {"titre": "La Haine", "annee": 1995, "realisateur": "Mathieu Kassovitz"}
]

a. Écrire un programme qui enregistre ces données dans un fichier films.csv.

b. Ouvrir le fichier produit avec un éditeur de texte et vérifier son contenu.

Solution

a.

import csv

films = [
    {"titre": "Le Fabuleux Destin d'Amélie Poulain", "annee": 2001, "realisateur": "Jean-Pierre Jeunet"},
    {"titre": "Intouchables", "annee": 2011, "realisateur": "Olivier Nakache"},
    {"titre": "La Haine", "annee": 1995, "realisateur": "Mathieu Kassovitz"}
]

with open("films.csv", "w", encoding="utf-8", newline="") as fichier:
    colonnes = ["titre", "annee", "realisateur"]
    ecrivain = csv.DictWriter(fichier, fieldnames=colonnes)
    ecrivain.writeheader()
    for film in films:
        ecrivain.writerow(film)

b. Le fichier contient :

titre,annee,realisateur
Le Fabuleux Destin d'Amélie Poulain,2001,Jean-Pierre Jeunet
Intouchables,2011,Olivier Nakache
La Haine,1995,Mathieu Kassovitz


Recherche et filtrage

Exercice 3 - Sélection de lignes

On travaille avec la table des villes de l'exercice 1.

a. Écrire une instruction qui renvoie la liste des villes de plus de 300 000 habitants.

b. Écrire une instruction qui renvoie la liste des villes dont l'altitude est inférieure à 50 mètres.

c. Écrire une instruction qui renvoie la liste des villes du département 13 ou 69.

Solution

a.

grandes = [v for v in table if v["population"] > 300000]
for v in grandes:
    print(v["nom"])    # Lyon, Marseille, Toulouse

b.

basses = [v for v in table if v["altitude"] < 50]
for v in basses:
    print(f"{v['nom']} ({v['altitude']} m)")
# Marseille (12 m), Lille (27 m), Bordeaux (6 m)

c.

selection = [v for v in table if v["departement"] in ["13", "69"]]
for v in selection:
    print(v["nom"])    # Lyon, Marseille


Exercice 4 - Projection et agrégation

a. À partir de la table des villes, créer un nouveau tableau ne contenant que les attributs nom et population.

b. Calculer la population totale des 5 villes.

c. Calculer l'altitude moyenne.

d. Trouver la ville la plus peuplée et la moins peuplée.

Solution

a.

extrait = [{"nom": v["nom"], "population": v["population"]} for v in table]

b.

total = sum(v["population"] for v in table)
print(f"Population totale : {total}")    # 2 379 771

c.

altitudes = [v["altitude"] for v in table]
moyenne = sum(altitudes) / len(altitudes)
print(f"Altitude moyenne : {moyenne} m")    # 72.2 m

d.

plus_peuplee = max(table, key=lambda v: v["population"])
moins_peuplee = min(table, key=lambda v: v["population"])
print(f"Plus peuplée : {plus_peuplee['nom']}")     # Marseille
print(f"Moins peuplée : {moins_peuplee['nom']}")   # Lille


Tri

Exercice 5 - Tri d'une table

a. Trier la table des villes par population croissante. Afficher le résultat.

b. Trier par nom alphabétique.

c. Trier par altitude décroissante.

d. On ajoute des villes ayant la même altitude. Le tri est-il stable ? Vérifier.

Solution

a.

par_pop = sorted(table, key=lambda v: v["population"])
for v in par_pop:
    print(f"{v['nom']} : {v['population']}")
# Lille, Toulouse, Bordeaux, Lyon, Marseille

b.

par_nom = sorted(table, key=lambda v: v["nom"])
for v in par_nom:
    print(v["nom"])
# Bordeaux, Lille, Lyon, Marseille, Toulouse

c.

par_alt_desc = sorted(table, key=lambda v: v["altitude"], reverse=True)
for v in par_alt_desc:
    print(f"{v['nom']} ({v['altitude']} m)")
# Lyon (175), Toulouse (141), Lille (27), Marseille (12), Bordeaux (6)

d. Oui, le tri de Python (Timsort) est stable : si deux villes ont la même altitude, elles restent dans leur ordre d'apparition d'origine.


Exercice 6 - Doublons et cohérence

On dispose de la table suivante :

notes = [
    {"nom": "Dupont", "matiere": "NSI", "note": 15},
    {"nom": "Martin", "matiere": "NSI", "note": 12},
    {"nom": "Dupont", "matiere": "Maths", "note": 14},
    {"nom": "Durand", "matiere": "NSI", "note": "18"},
    {"nom": "Martin", "matiere": "NSI", "note": 16},
    {"nom": "Petit",  "matiere": "NSI", "note": -2}
]

a. Identifier les doublons sur l'attribut nom dans la matière NSI.

b. Repérer les problèmes de cohérence dans cette table (types, valeurs aberrantes).

c. Écrire une fonction qui nettoie la table : convertir les notes en entiers et supprimer les notes hors de [0, 20].

Solution

a. En filtrant sur la matière NSI : - Dupont n'apparait qu'une fois en NSI (l'autre est en Maths). - Martin apparait deux fois en NSI (notes 12 et 16) : c'est un doublon.

b. Problèmes : - Ligne 3 (Durand) : la note "18" est une chaîne au lieu d'un entier. - Ligne 5 (Petit) : la note -2 est aberrante (hors de [0, 20]).

c.

def nettoyer_notes(table):
    resultat = []
    for ligne in table:
        copie = dict(ligne)
        try:
            copie["note"] = int(copie["note"])
        except (ValueError, TypeError):
            continue
        if 0 <= copie["note"] <= 20:
            resultat.append(copie)
    return resultat

propre = nettoyer_notes(notes)
for n in propre:
    print(n)


Fusion de tables

Exercice 7 - Jointure simple

On dispose de deux tables :

commandes = [
    {"id_commande": 1, "id_client": 10, "produit": "Livre", "prix": 15},
    {"id_commande": 2, "id_client": 20, "produit": "Stylo", "prix": 3},
    {"id_commande": 3, "id_client": 10, "produit": "Cahier", "prix": 5},
    {"id_commande": 4, "id_client": 30, "produit": "Gomme", "prix": 2}
]

clients = [
    {"id_client": 10, "nom": "Dupont", "ville": "Lyon"},
    {"id_client": 20, "nom": "Martin", "ville": "Paris"},
    {"id_client": 30, "nom": "Durand", "ville": "Lille"}
]

a. Quelle est la clé de jointure ? Identifier la clé primaire et la clé étrangère.

b. Effectuer la jointure et afficher, pour chaque commande, le nom du client, le produit et le prix.

c. Après la jointure, filtrer les commandes des clients de Lyon.

Solution

a. La clé de jointure est "id_client". C'est la clé primaire de la table clients et la clé étrangère de la table commandes.

b.

def jointure(table1, table2, cle):
    resultat = []
    for l1 in table1:
        for l2 in table2:
            if l1[cle] == l2[cle]:
                fusion = {}
                fusion.update(l1)
                fusion.update(l2)
                resultat.append(fusion)
    return resultat

complet = jointure(commandes, clients, "id_client")
for c in complet:
    print(f"{c['nom']} a commandé {c['produit']} ({c['prix']} euros)")
# Dupont a commandé Livre (15 euros)
# Martin a commandé Stylo (3 euros)
# Dupont a commandé Cahier (5 euros)
# Durand a commandé Gomme (2 euros)

c.

lyon = [c for c in complet if c["ville"] == "Lyon"]
for c in lyon:
    print(f"{c['produit']} - {c['prix']} euros")
# Livre - 15 euros
# Cahier - 5 euros


Exercice 8 - Jointure avec données manquantes

On ajoute un client sans commande et une commande avec un client inconnu :

clients.append({"id_client": 40, "nom": "Morel", "ville": "Bordeaux"})
commandes.append({"id_commande": 5, "id_client": 99, "produit": "Règle", "prix": 4})

a. Effectuer la jointure. Morel et la commande 5 apparaissent-ils dans le résultat ?

b. Expliquer pourquoi.

Solution

a. Non, ni Morel ni la commande 5 n'apparaissent dans le résultat.

b. La jointure ne conserve que les lignes ayant une correspondance dans les deux tables :

  • Morel (id_client = 40) n'a aucune commande → pas de correspondance dans commandes.
  • La commande 5 (id_client = 99) n'a aucun client correspondant → pas de correspondance dans clients.

Exercice 9 - Calculs après jointure

On reprend les tables commandes et clients de l'exercice 7.

a. Après jointure, calculer le montant total des commandes par client.

b. Trouver le client qui a dépensé le plus.

c. Calculer le montant total de toutes les commandes.

Solution

a.

complet = jointure(commandes, clients, "id_client")

totaux = {}
for c in complet:
    nom = c["nom"]
    if nom in totaux:
        totaux[nom] += c["prix"]
    else:
        totaux[nom] = c["prix"]

for nom, total in totaux.items():
    print(f"{nom} : {total} euros")
# Dupont : 20 euros
# Martin : 3 euros
# Durand : 2 euros

b.

meilleur_client = max(totaux, key=lambda n: totaux[n])
print(f"Meilleur client : {meilleur_client} ({totaux[meilleur_client]} euros)")
# Meilleur client : Dupont (20 euros)

c.

total_general = sum(c["prix"] for c in complet)
print(f"Total : {total_general} euros")    # 25 euros


Exercice 10 - Questions transversales

On dispose de la table suivante (déjà en mémoire) :

pays = [
    {"nom": "France", "capitale": "Paris", "population": 67, "continent": "Europe"},
    {"nom": "Japon", "capitale": "Tokyo", "population": 126, "continent": "Asie"},
    {"nom": "Brésil", "capitale": "Brasilia", "population": 213, "continent": "Amérique"},
    {"nom": "Allemagne", "capitale": "Berlin", "population": 83, "continent": "Europe"},
    {"nom": "Inde", "capitale": "New Delhi", "population": 1408, "continent": "Asie"},
    {"nom": "Canada", "capitale": "Ottawa", "population": 38, "continent": "Amérique"},
    {"nom": "Italie", "capitale": "Rome", "population": 60, "continent": "Europe"}
]

Les populations sont en millions.

a. Filtrer les pays d'Europe. Combien sont-ils ?

b. Trier tous les pays par population décroissante.

c. Calculer la population totale par continent.

d. Trouver le pays le plus peuplé de chaque continent.

Solution

a.

europe = [p for p in pays if p["continent"] == "Europe"]
print(f"Pays d'Europe : {len(europe)}")    # 3
for p in europe:
    print(p["nom"])    # France, Allemagne, Italie

b.

par_pop = sorted(pays, key=lambda p: p["population"], reverse=True)
for p in par_pop:
    print(f"{p['nom']} : {p['population']} M")
# Inde, Brésil, Japon, Allemagne, France, Italie, Canada

c.

pop_continent = {}
for p in pays:
    c = p["continent"]
    if c in pop_continent:
        pop_continent[c] += p["population"]
    else:
        pop_continent[c] = p["population"]

for c, pop in pop_continent.items():
    print(f"{c} : {pop} M")
# Europe : 210 M
# Asie : 1534 M
# Amérique : 251 M

d.

continents = set(p["continent"] for p in pays)
for c in continents:
    pays_du_continent = [p for p in pays if p["continent"] == c]
    plus_peuple = max(pays_du_continent, key=lambda p: p["population"])
    print(f"{c} : {plus_peuple['nom']} ({plus_peuple['population']} M)")
# Europe : Allemagne (83 M)
# Asie : Inde (1408 M)
# Amérique : Brésil (213 M)



Activités


🧪 Activité 1 - Explorer un jeu de données réel (open data)

Objectif

Importer un fichier CSV de données ouvertes, l'explorer et en extraire des informations pertinentes.

Données

Créer le fichier communes.csv suivant (extrait simplifié) :

nom,code_postal,population,superficie,region
Paris,75000,2161000,105,Ile-de-France
Marseille,13000,870731,241,Provence-Alpes-Cote d'Azur
Lyon,69000,522969,48,Auvergne-Rhone-Alpes
Toulouse,31000,493465,118,Occitanie
Nice,06000,342669,72,Provence-Alpes-Cote d'Azur
Nantes,44000,318808,65,Pays de la Loire
Montpellier,34000,290053,57,Occitanie
Strasbourg,67000,287228,78,Grand Est
Bordeaux,33000,257804,50,Nouvelle-Aquitaine
Lille,59000,236234,35,Hauts-de-France

Questions

a. Importer les données et convertir les types numériques.

b. Calculer la densité de population (habitants par km²) pour chaque commune et l'ajouter comme nouvel attribut.

c. Afficher les 5 communes les plus denses, triées par densité décroissante.

d. Calculer la population totale et la population moyenne des communes du fichier.

e. Regrouper les communes par région et calculer la population totale de chaque région.

f. Exporter les résultats (avec la densité) dans un nouveau fichier communes_enrichi.csv.

Correction

a.

import csv

table = []
with open("communes.csv", "r", encoding="utf-8") as f:
    lecteur = csv.DictReader(f)
    for ligne in lecteur:
        e = dict(ligne)
        e["population"] = int(e["population"])
        e["superficie"] = int(e["superficie"])
        table.append(e)

b.

for commune in table:
    commune["densite"] = round(commune["population"] / commune["superficie"])

c.

par_densite = sorted(table, key=lambda c: c["densite"], reverse=True)
print("Top 5 des communes les plus denses :")
for c in par_densite[:5]:
    print(f"  {c['nom']} : {c['densite']} hab/km2")
# Paris : 20581, Lyon : 10895, Lille : 6749, Montpellier : 5088, Nantes : 4905

d.

pop_totale = sum(c["population"] for c in table)
pop_moyenne = pop_totale / len(table)
print(f"Population totale : {pop_totale}")      # 5 780 961
print(f"Population moyenne : {pop_moyenne:.0f}")  # 578 096

e.

par_region = {}
for c in table:
    r = c["region"]
    if r in par_region:
        par_region[r] += c["population"]
    else:
        par_region[r] = c["population"]

for region, pop in sorted(par_region.items(), key=lambda x: x[1], reverse=True):
    print(f"  {region} : {pop}")

f.

with open("communes_enrichi.csv", "w", encoding="utf-8", newline="") as f:
    colonnes = ["nom", "code_postal", "population", "superficie", "region", "densite"]
    ecrivain = csv.DictWriter(f, fieldnames=colonnes)
    ecrivain.writeheader()
    for c in table:
        ecrivain.writerow(c)


🧪 Activité 2 - Le bulletin de notes (fusion de tables)

Objectif

Fusionner plusieurs fichiers CSV pour produire un bulletin complet.

Données

Créer les trois fichiers suivants :

eleves.csv :

id_eleve,nom,prenom,classe
1,Dupont,Marie,1NSI-A
2,Martin,Léo,1NSI-A
3,Durand,Emma,1NSI-B
4,Petit,Hugo,1NSI-B
5,Leroy,Alice,1NSI-A

notes.csv :

id_eleve,matiere,note
1,NSI,15
1,Maths,14
1,Anglais,12
2,NSI,18
2,Maths,10
2,Anglais,16
3,NSI,12
3,Maths,17
3,Anglais,14
4,NSI,8
4,Maths,9
4,Anglais,11
5,NSI,16
5,Maths,15
5,Anglais,17

matieres.csv :

matiere,coefficient
NSI,4
Maths,4
Anglais,3

Questions

a. Importer les trois fichiers.

b. Fusionner notes et eleves sur id_eleve, puis fusionner le résultat avec matieres sur matiere.

c. Calculer la moyenne pondérée de chaque élève.

d. Afficher le bulletin complet sous forme de tableau lisible.

e. Déterminer le premier et le dernier de la classe (par moyenne pondérée).

Correction
import csv

def charger_csv(nom_fichier):
    table = []
    with open(nom_fichier, "r", encoding="utf-8") as f:
        for ligne in csv.DictReader(f):
            table.append(dict(ligne))
    return table

def jointure(t1, t2, cle):
    resultat = []
    for l1 in t1:
        for l2 in t2:
            if l1[cle] == l2[cle]:
                fusion = {}
                fusion.update(l1)
                fusion.update(l2)
                resultat.append(fusion)
    return resultat

# a. Importer
eleves = charger_csv("eleves.csv")
notes = charger_csv("notes.csv")
matieres = charger_csv("matieres.csv")

# Conversions
for n in notes:
    n["id_eleve"] = int(n["id_eleve"])
    n["note"] = float(n["note"])
for e in eleves:
    e["id_eleve"] = int(e["id_eleve"])
for m in matieres:
    m["coefficient"] = int(m["coefficient"])

# b. Fusions
etape1 = jointure(notes, eleves, "id_eleve")
complet = jointure(etape1, matieres, "matiere")

# c. Moyenne pondérée par élève
moyennes = {}
for ligne in complet:
    nom = f"{ligne['prenom']} {ligne['nom']}"
    if nom not in moyennes:
        moyennes[nom] = {"somme_ponderee": 0, "somme_coefs": 0}
    moyennes[nom]["somme_ponderee"] += ligne["note"] * ligne["coefficient"]
    moyennes[nom]["somme_coefs"] += ligne["coefficient"]

# d. Affichage
print(f"{'Élève':<20} {'Moyenne':>8}")
print("-" * 30)
for nom, data in moyennes.items():
    moy = data["somme_ponderee"] / data["somme_coefs"]
    print(f"{nom:<20} {moy:>8.2f}")

# e. Premier et dernier
classement = sorted(moyennes.items(),
                    key=lambda x: x[1]["somme_ponderee"] / x[1]["somme_coefs"],
                    reverse=True)
premier = classement[0]
dernier = classement[-1]
moy_p = premier[1]["somme_ponderee"] / premier[1]["somme_coefs"]
moy_d = dernier[1]["somme_ponderee"] / dernier[1]["somme_coefs"]
print(f"\nPremier : {premier[0]} ({moy_p:.2f})")
print(f"Dernier : {dernier[0]} ({moy_d:.2f})")

🧪 Activité 3 - Détective de données (cohérence et nettoyage)

Objectif

Repérer et corriger les erreurs dans un jeu de données « sale ».

Données

Créer le fichier inscriptions.csv suivant (volontairement plein d'erreurs) :

nom,prenom,age,email,classe
Dupont,Marie,16,[email protected],1NSI-A
Martin,Léo,17,[email protected],1NSI-A
Durand,Emma,seize,[email protected],1NSI-B
,Hugo,15,[email protected],1NSI-B
Petit,Hugo,15,[email protected],1NSI-B
Martin,Léo,17,[email protected],1NSI-A
Leroy,Alice,-3,[email protected],1NSI-A
Bernard,Tom,16,,1NSI-B

Questions

a. Importer le fichier. Combien de lignes contient-il ?

b. Identifier tous les problèmes :

  • Valeurs manquantes (nom vide, email vide)
  • Types incorrects (age = "seize")
  • Valeurs aberrantes (age = -3)
  • Doublons (lignes identiques)

c. Écrire une fonction nettoyer(table) qui :

  1. Supprime les doublons exacts
  2. Signale les lignes avec des valeurs manquantes
  3. Signale les lignes avec un age non numérique ou hors [10, 20]

d. Exporter la table nettoyée dans inscriptions_propres.csv.

Correction
import csv

# a. Import
table = []
with open("inscriptions.csv", "r", encoding="utf-8") as f:
    for ligne in csv.DictReader(f):
        table.append(dict(ligne))
print(f"Nombre de lignes : {len(table)}")    # 8

# b. Identification des problèmes
print("\n--- Analyse ---")
for i, ligne in enumerate(table):
    problemes = []
    # Valeurs manquantes
    for cle, val in ligne.items():
        if val.strip() == "":
            problemes.append(f"{cle} manquant")
    # Type de age
    try:
        age = int(ligne["age"])
        if age < 10 or age > 20:
            problemes.append(f"age aberrant ({age})")
    except ValueError:
        problemes.append(f"age non numerique ('{ligne['age']}')")
    if problemes:
        print(f"Ligne {i} ({ligne.get('nom', '?')}) : {', '.join(problemes)}")

# Doublons
for i in range(len(table)):
    for j in range(i + 1, len(table)):
        if table[i] == table[j]:
            print(f"Doublon : lignes {i} et {j}")

# c. Nettoyage
def nettoyer(table):
    propre = []
    vus = []
    for ligne in table:
        # Suppression des doublons
        cle_unique = (ligne["nom"], ligne["prenom"], ligne["email"])
        if cle_unique in vus:
            continue
        vus.append(cle_unique)
        # Valeurs manquantes
        if any(v.strip() == "" for v in ligne.values()):
            print(f"  Ignoree (valeur manquante) : {ligne}")
            continue
        # Conversion et validation de l'age
        try:
            ligne["age"] = int(ligne["age"])
        except ValueError:
            print(f"  Ignoree (age invalide) : {ligne}")
            continue
        if ligne["age"] < 10 or ligne["age"] > 20:
            print(f"  Ignoree (age aberrant) : {ligne}")
            continue
        propre.append(ligne)
    return propre

print("\n--- Nettoyage ---")
propre = nettoyer(table)
print(f"\nLignes conservees : {len(propre)}")

# d. Export
with open("inscriptions_propres.csv", "w", encoding="utf-8", newline="") as f:
    colonnes = ["nom", "prenom", "age", "email", "classe"]
    ecrivain = csv.DictWriter(f, fieldnames=colonnes)
    ecrivain.writeheader()
    for ligne in propre:
        ecrivain.writerow(ligne)

🎯 Projet final - Analyse d'un championnat sportif

Objectif

Créer et exploiter plusieurs tables de données reliées entre elles pour analyser les résultats d'un championnat.

Partie A - Créer les données

a. Créer un fichier equipes.csv contenant 6 équipes avec les colonnes : id_equipe, nom, ville, stade.

b. Créer un fichier matchs.csv contenant 10 matchs avec les colonnes : id_match, id_equipe_dom, id_equipe_ext, buts_dom, buts_ext.

Partie B - Importation et fusion

c. Importer les deux fichiers.

d. Fusionner pour afficher, pour chaque match, les noms des équipes et le score (au lieu des identifiants).

Partie C - Classement

e. Calculer pour chaque équipe : nombre de matchs joués, victoires, nuls, défaites, buts marqués, buts encaissés, différence de buts, points (3 pour une victoire, 1 pour un nul, 0 pour une défaite).

f. Afficher le classement trié par points décroissants (puis par différence de buts en cas d'égalité).

Partie D - Statistiques

g. Calculer le nombre total de buts marqués dans le championnat.

h. Trouver le match avec le plus de buts et la plus grosse victoire.

i. Exporter le classement final dans un fichier classement.csv.

Correction
import csv

# Exemple de données
equipes = [
    {"id_equipe": 1, "nom": "Lyon", "ville": "Lyon", "stade": "Groupama"},
    {"id_equipe": 2, "nom": "Marseille", "ville": "Marseille", "stade": "Velodrome"},
    {"id_equipe": 3, "nom": "Paris", "ville": "Paris", "stade": "Parc des Princes"},
    {"id_equipe": 4, "nom": "Lille", "ville": "Lille", "stade": "Pierre Mauroy"},
    {"id_equipe": 5, "nom": "Nantes", "ville": "Nantes", "stade": "Beaujoire"},
    {"id_equipe": 6, "nom": "Toulouse", "ville": "Toulouse", "stade": "Stadium"}
]

matchs = [
    {"id_match": 1, "id_dom": 1, "id_ext": 2, "buts_dom": 2, "buts_ext": 1},
    {"id_match": 2, "id_dom": 3, "id_ext": 4, "buts_dom": 3, "buts_ext": 0},
    {"id_match": 3, "id_dom": 5, "id_ext": 6, "buts_dom": 1, "buts_ext": 1},
    {"id_match": 4, "id_dom": 2, "id_ext": 3, "buts_dom": 0, "buts_ext": 2},
    {"id_match": 5, "id_dom": 4, "id_ext": 1, "buts_dom": 1, "buts_ext": 3},
    {"id_match": 6, "id_dom": 6, "id_ext": 5, "buts_dom": 2, "buts_ext": 0},
    {"id_match": 7, "id_dom": 1, "id_ext": 3, "buts_dom": 1, "buts_ext": 1},
    {"id_match": 8, "id_dom": 2, "id_ext": 4, "buts_dom": 2, "buts_ext": 2},
    {"id_match": 9, "id_dom": 5, "id_ext": 1, "buts_dom": 0, "buts_ext": 1},
    {"id_match": 10, "id_dom": 6, "id_ext": 3, "buts_dom": 1, "buts_ext": 4}
]

# Dictionnaire id -> nom pour accès rapide
noms = {e["id_equipe"]: e["nom"] for e in equipes}

# Classement
stats = {}
for e in equipes:
    stats[e["id_equipe"]] = {
        "nom": e["nom"], "mj": 0, "v": 0, "n": 0, "d": 0,
        "bp": 0, "bc": 0, "diff": 0, "pts": 0
    }

for m in matchs:
    dom = m["id_dom"]
    ext = m["id_ext"]
    bd = m["buts_dom"]
    be = m["buts_ext"]

    stats[dom]["mj"] += 1
    stats[ext]["mj"] += 1
    stats[dom]["bp"] += bd
    stats[dom]["bc"] += be
    stats[ext]["bp"] += be
    stats[ext]["bc"] += bd

    if bd > be:
        stats[dom]["v"] += 1
        stats[dom]["pts"] += 3
        stats[ext]["d"] += 1
    elif bd < be:
        stats[ext]["v"] += 1
        stats[ext]["pts"] += 3
        stats[dom]["d"] += 1
    else:
        stats[dom]["n"] += 1
        stats[ext]["n"] += 1
        stats[dom]["pts"] += 1
        stats[ext]["pts"] += 1

for s in stats.values():
    s["diff"] = s["bp"] - s["bc"]

# Affichage du classement
classement = sorted(stats.values(),
                    key=lambda s: (s["pts"], s["diff"]),
                    reverse=True)

print(f"{'Pos':<4} {'Equipe':<12} {'MJ':<4} {'V':<4} {'N':<4} {'D':<4} "
      f"{'BP':<4} {'BC':<4} {'Diff':<5} {'Pts':<4}")
print("-" * 55)
for i, s in enumerate(classement):
    diff = f"+{s['diff']}" if s['diff'] > 0 else str(s['diff'])
    print(f"{i+1:<4} {s['nom']:<12} {s['mj']:<4} {s['v']:<4} {s['n']:<4} "
          f"{s['d']:<4} {s['bp']:<4} {s['bc']:<4} {diff:<5} {s['pts']:<4}")

# Statistiques
total_buts = sum(m["buts_dom"] + m["buts_ext"] for m in matchs)
print(f"\nTotal buts : {total_buts}")

plus_de_buts = max(matchs, key=lambda m: m["buts_dom"] + m["buts_ext"])
print(f"Match avec le plus de buts : {noms[plus_de_buts['id_dom']]} "
      f"{plus_de_buts['buts_dom']}-{plus_de_buts['buts_ext']} "
      f"{noms[plus_de_buts['id_ext']]}")