Exercice Python : Secret Santa

Retour à la liste des articles

04 décembre 2019

Une fois n'est pas coutume, regardons ce que dit Wikipédia à propos de Secret Santa :

Secret Santa, ou Noël canadien au Québec et dans les pays francophones, est une tradition de Noël, surtout dans les pays anglo-saxons, lors de laquelle les membres d'un groupe ou d'une communauté s'offrent au hasard des cadeaux. Le tirage au sort est anonyme (il peut y avoir des variantes, mais souvent le nom de l'offrant est révélé seulement le jour de l'échange des cadeaux).

Chaque participant-e va donc offrir un cadeau à une autre personne au hasard, pour qu'au final chacun-e offre un cadeau et en reçoive un autre.

Prérequis pour cet article

Assure-toi d'avoir les connaissances nécessaires pour cet article en terminant la lecture du chapitre 6 « Un programme dynamique avec Python ».

Tu ne possèdes pas encore Génies du code ? Il s'agit d'une méthode illustrée, adaptée à tous les niveaux, qui te fera découvrir la programmation à travers la réalisation de ton propre site web de A à Z. Les deux premiers chapitres sont disponibles gratuitement dans leur intégralité !

Découvrir Génies du code

Le but de cet exercice est de demander une liste de noms, puis de créer des paires personne qui donne - personne qui reçoit. Examinons les étapes nécessaires pour ce programme :

  1. Demander les noms à l'utilisateur-trice ;
  2. Créer une liste à partir de ces noms ;
  3. Mélanger la liste ;
  4. Créer des paires en associant chaque élément de la liste avec l'élément suivant.

Concrètement, en termes de types de données Python, on aimerait donc partir d'une chaîne de caractères, où chaque nom est séparé par une virgule, pour arriver à une liste de paires personne qui donne - personne qui reçoit :

# 1. Chaîne de caractères entrée par l'utilisateur-trice
'Cyril, Julie, Caipi'

# 2. Création d'une liste à partir de ces noms séparés par des virgules
['Cyril', 'Julie', 'Caipi']

# 3. Mélange de la liste
['Julie', 'Caipi', 'Cyril']

# 4. Transformation en liste de paires. Chaque élément est
# associé à l'élément qui le suit. Le dernier élément
# est associé au premier
[('Julie', 'Caipi'), ('Caipi', 'Cyril'), ('Cyril', 'Julie')]

Dans cet exemple, Julie va offrir un cadeau à Caipi, Caipi à Cyril, et Cyril à Julie.

Allez zou, retrousse tes manches, on va maintenant écrire le code pour chacune de ces étapes !

Création de la liste de noms

La première étape consiste à transformer une chaîne de caractères, par exemple "Cyril,Julie,Caipi" en liste Python, par exemple ['Cyril', 'Julie', 'Caipi']. Complète la fonction split_names ci-dessous pour qu'elle transforme une chaîne de caractères, avec des noms séparés par des virgules, en une liste :

Le résultat des instructions print() s'affichera ici.

Solution

def split_names(names):
    return names.split(",")

print(split_names("Julie,Caipi,Cyril"))

Indice

Utilise la méthode split pour créer une liste à partir d'une chaîne de caractères.

Tu remarqueras que si la personne met des espaces entre les virgules, par exemple en entrant "Julie, Caipi, Cyril", les éléments dans la liste auront eux aussi des espaces (['Julie', ' Caipi', ' Cyril']), ce qui est peu élégant. La méthode strip permet d'éliminer les espaces au début et à la fin d'une chaîne de caractères :

>>> ' Caipi'.strip()
'Caipi'

On pourrait l'utiliser sur chaque élément de la liste, par exemple de la façon suivante :

def split_names(names):
    names_list = names.split(",")
    names_list_stripped = []

    for name in names_list:
        names_list_stripped.append(name.strip())

    return names_list_stripped

print(split_names("Julie, Cyril, Caipi"))

Ce serait correct, mais il existe une façon plus concise de l'écrire, qui évite au passage de passer par une nouvelle variable intermédiaire (names_list_stripped). Il s'agit des compréhensions de listes.

Les compréhensions de listes

Les compréhensions de listes permettent de créer une nouvelle liste à partir d'une liste existante, la plupart du temps en appliquant une fonction sur chaque élément. Prenons par exemple le code suivant, qui crée une variable double et y met chaque élément de la liste my_list multiplié par 2 :

Le résultat des instructions print() s'affichera ici.

On a d'abord dû créer une liste double vide, puis parcourir la liste my_list avec une boucle, et enfin ajouter chaque élément dans la nouvelle liste double.

Une compréhension de liste permet de réaliser ces trois étapes en une seule instruction, ce qui rend le code nettement plus concis. La liste est directement créée avec les bons éléments dedans :

Le résultat des instructions print() s'affichera ici.

Une compréhension de liste utilise donc la même syntaxe for...in, que tu as déjà utilisée pour les boucles, mais à l'intérieur de crochets, pour indiquer que cette instruction va initialiser une nouvelle liste.

Les compréhensions de listes peuvent aussi être utilisées pour filtrer les éléments d'une liste en y ajoutant une instruction if. Par exemple, la compréhension de liste suivante crée une nouvelle liste qui ne contient que les nombres supérieurs à 1 :

Le résultat des instructions print() s'affichera ici.

Les compréhensions de listes sont un peu difficiles à lire au début, mais une fois qu'on y est habitué-e, on a envie de les utiliser partout ! Je t'encourage à les utiliser autant que possible, cela rendra ton code plus concis, et plus facile à lire.

Exercice : compréhension de liste

Réécris la fonction split_names pour utiliser une compréhension de liste pour retirer les espaces au début et à la fin des noms. Profites-en pour ajouter de l'interactivité en demandant la liste des noms à l'utilisateur-trice à l'aide de la fonction input.

Le résultat des instructions print() s'affichera ici.

Solution

def split_names(names):
    return [name.strip() for name in names.split(",")]

print(split_names(input("Entre les noms des participant-e-s, séparés par des virgules: ")))

Mélanger la liste

On a maintenant une liste de noms, que l'on va devoir mélanger pour que le résultat ne soit pas prévisible. Puisque l'on sait qu'on va devoir utiliser des éléments aléatoires dans notre programme, regardons dans la documentation de Python s'il n'y aurait pas à tout hasard un module qui pourrait nous aider. Quel hasard ! Il se trouve qu'il existe justement un module random, qui met à disposition une panoplie de fonctions en lien avec l'aléatoire. Ce module dispose en particulier d'une fonction shuffle, qui permet justement de mélanger une liste :

random.shuffle(x[, random])
Shuffle the sequence x in place. [...]

La description de la fonction mentionne in place, cela signifie qu'au lieu de renvoyer une liste modifiée, elle va modifier directement la liste passée en paramètre, et qu'on a donc pas besoin de passer par une valeur de retour.

Essaie d'exécuter le programme suivant plusieurs fois pour voir l'ordre des éléments dans la liste changer.

Le résultat des instructions print() s'affichera ici.

Exercice : mélanger la liste

Modifie la ligne 9 du code ci-dessous pour mélanger les éléments de la liste avant de les afficher.

Le résultat des instructions print() s'affichera ici.

Solution

import random

def split_names(names):
    return [name.strip() for name in names.split(",")]

names = split_names(
    input("Entre les noms des participant-e-s, séparés par des virgules: ")
)
random.shuffle(names)
print(names)

Maintenant que notre liste est mélangée, il ne reste plus qu'à créer des paires. L'échange de cadeaux se rapprochent dangereusement!

Créer des paires

Le but de cette dernière étape est de créer une fonction make_pairs, qui prend en paramètre une liste et qui crée des paires avec chaque élément de la liste et son élément suivant (et le dernier élément pointe sur le premier). Puisque cette phrase n'est pas du tout claire, je te propose de regarder plutôt par toi-même ce que je veux dire :

>>> make_pairs(['Cyril', 'Julie', 'Caipi'])
[('Cyril', 'Julie'), ('Julie', 'Caipi'), ('Caipi', 'Cyril')]

Le résultat des instructions print() s'affichera ici.

Solution

def make_pairs(names):
    names_pairs = []
    for i, name in enumerate(names):
        # name est l'élément courant de la liste
        # names[i + 1] est l'élément suivant
        # L'opérateur % permet de recommencer au début de la liste
        # lorsqu'on se trouve sur le dernier élément
        pair = (name, names[(i + 1) % len(names)])
        names_pairs.append(pair)

    return names_pairs


## La même chose, avec une compréhension de liste
#def make_pairs(names):
#    return [(name, names[(i + 1) % len(names)]) for i, name in enumerate(names)]


print(make_pairs(['Cyril', 'Julie', 'Caipi']))

Indice

Utilise la fonction enumerate pour obtenir des paires (indice, élément) à partir de la liste names. Tu pourras alors accéder à l'élément suivant dans la liste en accédant à names[indice + 1]. Pour créer une paire composée du dernier élément de la liste et du premier, utilise l'opérateur % (modulo).

Tu trouveras plus de détails sur l'opérateur modulo dans l'article Exercice Python : générateur de messages codés .

Créer le programme final

Regardons à nouveau les étapes nécessaires que l'on avait définies :

  1. Demander les noms à l'utilisateur-trice
    • Grâce à la fonction input ;
  2. Créer une liste à partir de ces noms
    • Grâce à la fonction split_names ;
  3. Mélanger la liste
    • Grâce à la fonction random.shuffle ;
  4. Créer des paires en associant chaque élément de la liste avec l'élément suivant
    • Grâce à la fonction make_pairs.

On a maintenant toutes les fonctions nécessaires pour créer notre programme ! Complète le code suivant pour afficher "X va offrir un cadeau à Y" pour chaque paire dans la liste renvoyée par make_pairs.

Le résultat des instructions print() s'affichera ici.

Solution

import random

def make_pairs(names):
    return [
        (name, names[(i + 1) % len(names)])
        for i, name in enumerate(names)
    ]

def split_names(names):
    return [name.strip() for name in names.split(",")]

names = split_names(
    input("Entre les noms des participant-e-s, séparés par des virgules: ")
)
random.shuffle(names)
pairs = make_pairs(names)

for giver, receiver in pairs:
    print("{} va offrir un cadeau à {}".format(giver, receiver))

Indice

La variable pairs est une liste qui contient des paires, par exemple [('Julie', 'Caipi'), ('Caipi', 'Julie')]. La syntaxe for x, y in pairs: permet d'extraire les éléments de chaque paire. Ainsi, à chaque passage dans la boucle, x correspondra au premier élément de la paire, et y au second élément.

Conclusion

Pour résoudre un problème, la meilleure approche est toujours de le diviser en problèmes plus petits et plus faciles à résoudre. Enfin en tout cas pour la programmation, dans la vie c’est moins sûr. Dans notre cas, cela consiste à créer des fonctions qui font des opérations très spécifiques, et qui, une fois mises ensemble, permettent de résoudre le problème initial.

Tu sais maintenant comment fonctionnent les compréhensions de liste. Utilise-les le plus possible pour t'habituer à leur syntaxe, je suis sûr que tu finiras par les aimer !

Tu trouveras ci-dessous la version complète du programme pour référence. N'hésite pas à l'utiliser pour tirer au sort les distributions de cadeaux, et à la partager avec tes ami-e-s.

Le résultat des instructions print() s'affichera ici.

P.S.

Si tu ne fêtes pas Noël, ou si tu as la chance d'avoir aboli l'échange de cadeaux dans ton foyer, ce programme peut s'avérer tout aussi utile pour surprendre ses potes en faisant une chaîne d'envoi de cartes postales. Tu peux aussi l'adapter pour, par exemple, tirer au sort la personne qui devra faire la vaisselle.

Sur ce, bonnes fêtes de fin d'année !

Tu veux en savoir plus ?

Génies du code est une méthode illustrée, adaptée à tous les niveaux, qui t'initiera à la programmation à travers la réalisation de ton propre site web de A à Z. Les deux premiers chapitres sont disponibles gratuitement dans leur intégralité !

Découvrir Génies du code

Et aussi, fais un tour sur les autres articles, tous plus intéressants les uns que les autres, en toute modestie.