TD16. Lancer de dés
Le sujet de ce TD est disponible au format pdf ici.
L'objectif de ce TD est de continuer à s'entraîner à écrire des fonctions récursives. Cette fois-ci, nous allons utiliser notre ordinateur pour explorer de façon systématique toutes les possibilités offertes par des lancers de dés à six faces.
Dans ce TD, toutes les fonctions demandées doivent être récursives.
Exercice 1 : énumération des lancers possibles
Question 1
En guise d'échauffement, implémenter une fonction lance(nb_des)
renvoyant la somme des valeurs du nombre de dés demandé lancés aléatoirement (une seule fois).
Correction question 1
Cliquez ici pour révéler la correction.
Cette première fonction devrait normalement être est assez simple à réaliser :
1 2 3 4 5 |
|
Question 2
Sans lien avec la question précédente, on souhaite maintenant énumérer tous les lancers possibles pour un nombre de dés donné. Il n'est donc plus question d'aléatoire ici. Par énumération nous entendons ici afficher sur la sortie standard tous les lancés de dés possibles.
Par exemple pour 2 dés on cherche à afficher les lancers suivants. La première colonne représente le premier dé, et la seconde le deuxième.
1 2 3 4 5 6 7 8 9 |
|
Pour ce faire, on utilise un tableau dynamique (donc une list
Python) des
dont chaque case correspond à un dé.
Initialement, le tableau dynamique est rempli de 0
pour indiquer qu'aucun dé n'a encore été tiré.
Il sera ensuite rempli avec des valeurs entre 1 et 6 au cours de l’énumération.
On effectue une récurrence sur le nombre de dés non encore lancés.
Implémenter une fonction récursive enumere_lancers_rec(des, des_restants)
, où des
contient un lancer partiel et des_restants
le nombre de dés restant à lancer, qui affiche sur la sortie standard tous les lancers possibles.
Implémenter une fonction enumere_lancers(nb_des)
qui appelle enumere_lancers_rec(des, des_restants)
avec les paramètres initiaux permettant d’énumérer tous les lancers possibles pour le nombre de dés spécifié.
Correction question 2
Cliquez ici pour révéler la correction.
Le paramètre optionnel somme
dans la fonction suivante sera ajouté pour répondre à la question 3, on l'ignore pour le moment.
La complexité de enumere_lancers(nb_des)
est 6^nb_des
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Correction vidéo proposant une autre façon de faire :
Question 3
Implémenter une fonction enumere_lancers_somme(nb_des, somme)
affichant tous les lancers comme enumere_lancers(nb_des)
mais qui "encadre" l'affichage des lancers dont la somme vaut somme
par "***".
Afin de factoriser notre code, on utilisera la fonction enumere_lancers_rec
de la question précédente.
Nous ajouterons le nécessaire à cette fonction pour pouvoir répondre à cette question tout en préservant le comportement de la question précédente.
Correction question 3
Cliquez ici pour révéler la correction.
Pour ne pas dupliquer de code, on ajoute donc un paramètre optionnel somme
à la fonction enumere_lancers_rec(des, des_restants)
comme c'est le cas dans le code ci-dessus.
Et ensuite on appelle la fonction avec une valeur différente de None
pour ce paramètre somme
comme ci-dessous.
1 2 3 4 5 6 |
|
Exercice 2 : compter le nombre de lancers
Question 1
Implémenter une fonction compte_occurence_somme(nb_des, somme)
renvoyant le nombre de lancers du nombre de dés donné atteignant la somme demandée.
On essaiera d’éviter d’énumérer toutes les combinaisons possibles.
Correction question 1
Cliquez ici pour révéler la correction.
On va élaguer le plus possible de branches dans l'arbre d'appels. Néanmoins, cette solution reste exponentielle en le nombre de dés dans le pire cas. Pour se sortir de ça, il faudrait faire de la programmation dynamique (abordée plus tard dans les cursus Ensimag).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Correction vidéo :
Question 2
On suppose disposer d’une fonction fonction_validation
prenant un tableau dynamique de dés en argument.
Cette fonction classe les lancers de dés en valides ou invalides selon un certain critère arbitraire : elle renvoie True
lorsqu’un lancer doit être considéré comme valide et
False
dans le cas contraire.
Implémenter une fonction compte_lancers_valides(nb_des, fonction_validation)
renvoyant pour le nombre de dés donné le nombre de lancers valides selon la fonction de validation donnée.
Correction question 2
Cliquez ici pour révéler la correction.
Ici on ne peut plus élaguer, il faut donc explorer l'espace des possibles complètement.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Exercice 3 : est-ce que 1, 2, 4
est différent de 4, 1, 2
? (pour aller plus loin)
Quand on joue aux dés, et qu'on les lance tous d'un coup, "l'ordre" n'a aucune importance.
Autrement dit, avec 3 dés par exemple, le lancer 1, 2, 4
est équivalent au lancer 4, 1, 2
.
Question 1
Reprendre la question 2 de l'exercice 1 pour énumérer seulement les lancers distincts (relativement à la permutation des dés).
Correction question 1
Cliquez ici pour révéler la correction.
L'idée est de ne considérer que les lancers où les valeurs des dés sont triées par ordre croissant par exemple.
Pour ce faire, une solution consiste à ajouter un paramètre vmin
à notre fonction récursive.
Ce paramètre indique la valeur minimum des prochains dés à tirer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
|
Question 2
Combien de lancers distincts existent avec nbdes
dés ?
Correction question 2
Cliquez ici pour révéler la correction.
Le nombre de lancers distincts est le nombre de combinaisons de taille nbdes
avec répétition parmi un ensemble de taille 6
qui vaut :
Sinon pour information, itertools.combinations_with_replacement(range(1, 7), 3)
fait exactement ce qu'on veut :)