09 - Tuples

Exercice 1 : QCM – vérification des prérequis

Pour chaque question, une seule réponse est correcte.

1. Quelle est la différence fondamentale entre une liste et un tuple en Python ?

  • A. Un tuple ne peut contenir que des entiers
  • B. Un tuple est immutable (non modifiable après création)
  • C. Un tuple est plus lent qu’une liste
  • D. Un tuple ne peut pas contenir de doublons
Correction

Réponse : B. Un tuple, une fois créé, ne peut plus être modifié : on ne peut ni ajouter, ni supprimer, ni remplacer un élément. C’est la propriété d’immutabilité.

  • A est faux : un tuple peut contenir n’importe quel type (int, str, float, d’autres tuples, etc.).
  • C est faux : c’est l’inverse, un tuple est légèrement plus rapide qu’une liste.
  • D est faux : un tuple peut contenir des doublons, comme (1, 2, 1).

2. Parmi les expressions suivantes, laquelle crée un tuple contenant un seul élément ?

  • A. (42)
  • B. (42,)
  • C. [42]
  • D. {42}
Correction

Réponse : B. Pour créer un tuple à un seul élément, il faut ajouter une virgule après l’élément : (42,).

  • A est faux : (42) est simplement l’entier 42 entre parenthèses (les parenthèses servent ici au regroupement, pas à la création d’un tuple). type((42)) renvoie int.
  • C est faux : [42] crée une liste à un élément.
  • D est faux : {42} crée un ensemble (set) à un élément.

3. Que se passe-t-il si on exécute T[0] = 5 avec T = (1, 2, 3) ?

  • A. Le tuple devient (5, 2, 3)
  • B. Python crée un nouveau tuple (5, 2, 3)
  • C. Python lève une erreur TypeError
  • D. Python lève une erreur IndexError
Correction

Réponse : C. L’affectation par indice est interdite sur un tuple car il est immutable. Python lève l’erreur TypeError: 'tuple' object does not support item assignment.

  • A est faux : le tuple ne peut pas être modifié.
  • B est faux : Python ne crée pas automatiquement un nouveau tuple.
  • D est faux : l’indice 0 existe bien ; c’est l’opération d’écriture qui est interdite, pas la lecture.

4. Que renvoie la fonction suivante ?

def f():
    return 3, 7
  • A. L’entier 3 puis l’entier 7
  • B. La chaîne "3, 7"
  • C. Le tuple (3, 7)
  • D. Une erreur car return ne peut renvoyer qu’une valeur
Correction

Réponse : C. L’expression 3, 7 crée un tuple (3, 7). Une fonction peut renvoyer un tuple, ce qui permet de renvoyer plusieurs valeurs en une seule instruction return.

  • A est faux : Python ne renvoie pas les valeurs « l’une après l’autre ».
  • B est faux : il n’y a aucune conversion en chaîne.
  • D est faux : return renvoie un seul objet, ici le tuple (3, 7).

Exercice 2 : exemple travaillé – déballage et fonctions à valeurs multiples

Problème : écrire une fonction qui renvoie le quotient et le reste de la division euclidienne, puis utiliser le déballage pour récupérer les deux résultats.

Étape 1. Écriture de la fonction :

def division(a, b):
    """Renvoie le quotient et le reste de la division de a par b.

    Paramètres :
        a (int) : dividende (>= 0)
        b (int) : diviseur (> 0)

    Retour :
        tuple : (quotient, reste)
    """
    return (a // b, a % b)

Étape 2. Appel avec déballage :

q, r = division(17, 5)
print(f"17 = 5 × {q} + {r}")  # 17 = 5 × 3 + 2

Étape 3. Vérification :

assert division(17, 5) == (3, 2)
assert division(20, 4) == (5, 0)
assert division(7, 10) == (0, 7)

Points clés :

  • return (a // b, a % b) renvoie un tuple de deux éléments.
  • q, r = division(17, 5) utilise le déballage : chaque variable reçoit un élément du tuple.
  • Le nombre de variables à gauche doit correspondre exactement au nombre d’éléments du tuple.

Exercice 3 : manipulations guidées

3.1 Création et accès

Prévoir le résultat de chaque instruction, puis vérifier en Python :

a = (1, 2, 3)
b = 1, 2, 3
c = (1)
d = (1,)
e = tuple([1, 2])
f = ()
  1. Quel est le type de chacune de ces variables ?
  2. Que vaut a == b ?
  3. Que vaut a[1] ? Et a[-1] ?
Correction
a = (1, 2, 3)     # tuple
b = 1, 2, 3       # tuple (les parenthèses sont facultatives)
c = (1)            # int ! Les parenthèses ne suffisent pas.
d = (1,)           # tuple (la virgule fait le tuple)
e = tuple([1, 2])  # tuple (conversion depuis une liste)
f = ()             # tuple vide

print(type(a))  # <class 'tuple'>
print(type(b))  # <class 'tuple'>
print(type(c))  # <class 'int'>
print(type(d))  # <class 'tuple'>
print(type(e))  # <class 'tuple'>
print(type(f))  # <class 'tuple'>

print(a == b)   # True
print(a[1])     # 2
print(a[-1])    # 3

Piège classique : c = (1) ne crée pas un tuple mais un entier. Il faut écrire c = (1,) pour obtenir un tuple à un élément.

3.2 Déballage

Compléter le code suivant pour afficher les coordonnées d’un point :

point = (4.5, -2.3)
___, ___ = point
print(f"x = {___}, y = {___}")
Correction
point = (4.5, -2.3)
x, y = point
print(f"x = {x}, y = {y}")
# x = 4.5, y = -2.3

Exercice 4 : tuples et géométrie (mathématiques)

On représente un point du plan par un tuple (x, y).

  1. Écrire une fonction milieu(A, B) qui prend deux points et renvoie le tuple des coordonnées du milieu du segment $[AB]$.

    Rappel : $M = \left(\dfrac{x_A + x_B}{2},;, \dfrac{y_A + y_B}{2}\right)$

  2. Écrire une fonction distance(A, B) qui calcule la distance entre deux points.

    Rappel : $AB = \sqrt{(x_B - x_A)^2 + (y_B - y_A)^2}$

  3. Tester avec $A(1,;, 2)$ et $B(5,;, 8)$.

Correction
import math

def milieu(A, B):
    """Renvoie le milieu du segment [AB]."""
    xA, yA = A
    xB, yB = B
    return ((xA + xB) / 2, (yA + yB) / 2)

def distance(A, B):
    """Calcule la distance entre A et B."""
    xA, yA = A
    xB, yB = B
    return round(math.sqrt((xB - xA) ** 2 + (yB - yA) ** 2), 2)

A = (1, 2)
B = (5, 8)
print(f"Milieu : {milieu(A, B)}")    # (3.0, 5.0)
print(f"Distance : {distance(A, B)}") # 7.21

assert milieu((1, 2), (5, 8)) == (3.0, 5.0)
assert milieu((0, 0), (4, 6)) == (2.0, 3.0)
assert distance((0, 0), (3, 4)) == 5.0
print("Tests OK")

Point clé : le déballage xA, yA = A permet d’accéder directement aux coordonnées sans écrire A[0] et A[1], ce qui rend le code plus lisible.


Exercice 5 : tuples et données structurées (sport)

On représente les résultats d’une compétition de natation par une liste de tuples (nom, temps_en_secondes) :

resultats = [
    ("Alice", 28.4),
    ("Bob", 25.1),
    ("Clara", 27.8),
    ("David", 24.9),
    ("Eva", 26.3),
]
  1. Écrire une fonction gagnant(resultats) qui renvoie le nom du nageur ayant le meilleur temps (le plus petit).
  2. Écrire une fonction podium(resultats) qui renvoie un tuple de trois noms (premier, deuxième, troisième) correspondant aux trois meilleurs temps. Indication : trier la liste par temps croissant, puis extraire les trois premiers noms.
  3. Écrire une fonction ecart(resultats) qui renvoie l’écart en secondes entre le meilleur et le pire temps.
Correction
def gagnant(resultats):
    """Renvoie le nom du nageur le plus rapide."""
    meilleur = resultats[0]
    for nom, temps in resultats:
        if temps < meilleur[1]:
            meilleur = (nom, temps)
    return meilleur[0]

def podium(resultats):
    """Renvoie le tuple (1er, 2e, 3e) par temps croissant."""
    # Tri par insertion sur le temps (2e élément du tuple)
    tri = list(resultats)  # copie pour ne pas modifier l'original
    for i in range(1, len(tri)):
        en_cours = tri[i]
        j = i
        while j > 0 and tri[j - 1][1] > en_cours[1]:
            tri[j] = tri[j - 1]
            j -= 1
        tri[j] = en_cours
    return (tri[0][0], tri[1][0], tri[2][0])

def ecart(resultats):
    """Renvoie l'écart entre le meilleur et le pire temps."""
    temps_min = resultats[0][1]
    temps_max = resultats[0][1]
    for _, temps in resultats:
        if temps < temps_min:
            temps_min = temps
        if temps > temps_max:
            temps_max = temps
    return round(temps_max - temps_min, 1)

resultats = [
    ("Alice", 28.4),
    ("Bob", 25.1),
    ("Clara", 27.8),
    ("David", 24.9),
    ("Eva", 26.3),
]

print(f"Gagnant : {gagnant(resultats)}")   # David
print(f"Podium : {podium(resultats)}")      # ('David', 'Bob', 'Eva')
print(f"Écart : {ecart(resultats)} s")      # 3.5 s

Point clé : les tuples permettent de structurer des données hétérogènes (ici un nom et un temps). On accède au nom avec t[0] et au temps avec t[1], ou par déballage nom, temps = t.


Exercice 6 : tuples comme clés de dictionnaire (géographie)

Les tuples, étant immutables, peuvent servir de clés de dictionnaire. On utilise cette propriété pour associer des données à des coordonnées GPS :

villes = {
    (48.86, 2.35): "Paris",
    (43.30, 5.37): "Marseille",
    (45.76, 4.84): "Lyon",
    (43.60, 1.44): "Toulouse",
}
  1. Comment accéder au nom de la ville située aux coordonnées (45.76, 4.84) ?
  2. Écrire une fonction ville_plus_proche(lat, lon, villes) qui, étant donné une latitude et une longitude, renvoie le nom de la ville la plus proche (en utilisant la distance euclidienne comme approximation).
  3. Tester avec les coordonnées (44.0, 3.0). Quelle est la ville la plus proche ?

Correction
import math

villes = {
    (48.86, 2.35): "Paris",
    (43.30, 5.37): "Marseille",
    (45.76, 4.84): "Lyon",
    (43.60, 1.44): "Toulouse",
}

# 1.
print(villes[(45.76, 4.84)])  # Lyon

# 2.
def ville_plus_proche(lat, lon, villes):
    """Renvoie le nom de la ville la plus proche."""
    plus_proche = None
    dist_min = float('inf')
    for (lat_v, lon_v), nom in villes.items():
        d = math.sqrt((lat - lat_v) ** 2 + (lon - lon_v) ** 2)
        if d < dist_min:
            dist_min = d
            plus_proche = nom
    return plus_proche

# 3.
print(ville_plus_proche(44.0, 3.0, villes))  # Toulouse

Remarque : la distance euclidienne sur les coordonnées GPS n’est qu’une approximation (la Terre n’est pas plate). Pour un calcul exact, il faudrait utiliser la formule de Haversine (hors programme).


Exercice 7 : synthèse – statistiques sportives

On enregistre les performances de plusieurs athlètes lors d’un championnat d’athlétisme sous forme d’une liste de tuples (nom, épreuve, performance). La performance est en secondes pour les courses et en mètres pour les lancers.

courses = [
    ("Léa", "100m", 12.4),
    ("Karim", "100m", 11.8),
    ("Inès", "100m", 12.1),
    ("Théo", "100m", 11.5),
    ("Salma", "100m", 12.7),
]
  1. Écrire une fonction classement(resultats) qui prend cette liste et renvoie une nouvelle liste de tuples (rang, nom, performance) triée par performance croissante (le meilleur temps en premier). Utiliser un tri par insertion.

  2. Écrire une fonction ecart_premier(resultats) qui renvoie un tuple (nom_dernier, ecart) contenant le nom du dernier classé et son écart en secondes avec le premier.

  3. Écrire une fonction resume(resultats) qui affiche un tableau récapitulatif formaté avec le rang, le nom et le temps de chaque athlète, ainsi que l’écart avec le premier.

Correction
def classement(resultats):
    """Renvoie la liste triée par performance croissante avec le rang."""
    # Copie pour ne pas modifier l'original
    tri = list(resultats)
    # Tri par insertion sur la performance (3e élément)
    for i in range(1, len(tri)):
        en_cours = tri[i]
        j = i
        while j > 0 and tri[j - 1][2] > en_cours[2]:
            tri[j] = tri[j - 1]
            j -= 1
        tri[j] = en_cours
    # Construction de la liste avec rang
    return [(rang + 1, nom, perf) for rang, (nom, _, perf) in enumerate(tri)]

def ecart_premier(resultats):
    """Renvoie (nom_dernier, écart avec le premier)."""
    cl = classement(resultats)
    premier = cl[0]
    dernier = cl[-1]
    ecart = round(dernier[2] - premier[2], 1)
    return (dernier[1], ecart)

def resume(resultats):
    """Affiche le tableau récapitulatif de la course."""
    cl = classement(resultats)
    meilleur_temps = cl[0][2]
    print(f"{'Rang':<6} {'Nom':<10} {'Temps':>7} {'Écart':>7}")
    print("-" * 32)
    for rang, nom, perf in cl:
        ecart = round(perf - meilleur_temps, 1)
        ecart_str = f"+{ecart}s" if ecart > 0 else "—"
        print(f"{rang:<6} {nom:<10} {perf:>6.1f}s {ecart_str:>7}")

courses = [
    ("Léa", "100m", 12.4),
    ("Karim", "100m", 11.8),
    ("Inès", "100m", 12.1),
    ("Théo", "100m", 11.5),
    ("Salma", "100m", 12.7),
]

resume(courses)
nom, ecart = ecart_premier(courses)
print(f"\nÉcart premier-dernier : {nom} à {ecart}s du premier")

Résultat :

Rang   Nom         Temps   Écart
--------------------------------
1      Théo        11.5s       —
2      Karim       11.8s  +0.3s
3      Inès        12.1s  +0.6s
4      Léa         12.4s  +0.9s
5      Salma       12.7s  +1.2s

Écart premier-dernier : Salma à 1.2s du premier

Points importants :

  • Le déballage rang, nom, perf = ... rend le code lisible dans la boucle for.
  • ecart_premier renvoie un tuple qu’on déballe avec nom, ecart = ecart_premier(courses).
  • resume réutilise classement : c’est le principe de modularité.
  • Le tri par insertion modifie une copie de la liste (list(resultats)) pour ne pas altérer les données d’origine.