Gloutons : activité draft d'Arène (exercice débranché + TP Python)

This commit is contained in:
2026-08-24 16:51:53 +02:00
parent 9f84d1c3f2
commit cc418e4732
3 changed files with 1161 additions and 6 deletions
+118 -6
View File
@@ -1,10 +1,10 @@
### Exercices - Algorithmes Gloutons
**Niveaux de difficulté :** Facile | ⭐⭐ Moyen | ⭐⭐⭐ Difficile
**Niveaux de difficulté :** Facile | Moyen | Difficile
---
## Exercice 1 ⭐⭐ : Achat de V-Bucks (Fortnite)
## Exercice 1 (Moyen) : Achat de V-Bucks (Fortnite)
Dans Fortnite, on peut acheter des V-Bucks avec les packs suivants : `{500, 1000, 2800, 5000, 13500}` V-Bucks.
@@ -37,7 +37,7 @@ Non, ce système n'est pas canonique. L'algorithme glouton ne fonctionne pas tou
---
## Exercice 2 : Playlist Spotify optimale
## Exercice 2 (Facile) : Playlist Spotify optimale
Tu prépares une playlist pour un trajet de **60 minutes**. Tu veux y mettre le maximum de sons parmi tes favoris :
@@ -90,7 +90,7 @@ Durée totale : **33 minutes**
---
## Exercice 3 ⭐⭐ : Inventaire Minecraft
## Exercice 3 (Moyen) : Inventaire Minecraft
Tu joues à Minecraft et ton inventaire peut contenir **64 objets** maximum. Tu veux maximiser la valeur totale des objets pour le commerce avec les villageois.
@@ -135,7 +135,7 @@ Tri par ratio décroissant, on prend dans l'ordre :
---
## Exercice 4 ⭐⭐⭐ : Planning de stream Twitch
## Exercice 4 (Difficile) : Planning de stream Twitch
Tu es streamer et tu dois planifier tes lives pour demain. Plusieurs marques te proposent des partenariats avec des créneaux imposés :
@@ -217,7 +217,119 @@ print("Rémunération totale :", total, "€")
---
## Exercice 5 ⭐⭐ : Réflexion
## Exercice 5 (Difficile) : Draft d'Arène (jeu de cartes)
Dans le mode **Arène** d'un jeu de cartes, on ne joue pas avec sa collection : on **drafte** son deck. On t'ouvre un booster de 3 cartes, tu en gardes **une seule**, et on passe au booster suivant. **Une carte écartée est perdue pour toujours : on ne revient jamais en arrière.**
Chaque carte a une **puissance** (plus c'est haut, mieux c'est) et un **coût en mana** (le prix à payer pour la poser sur le plateau).
> **Règle de jouabilité.** Un deck de 5 cartes n'est jouable que s'il contient **au plus 2 cartes coûtant 5 manas ou plus**. Sinon, tu n'as rien à jouer pendant tes premiers tours, tu perds la partie avant d'avoir pu poser tes grosses cartes : le deck est déclaré **injouable**.
**Objectif** : obtenir le deck jouable de puissance totale maximale.
**Booster 1**
| Carte | Puissance | Coût |
|-------|-----------|------|
| Moineau vif | 2 | 1 |
| Druide chétif | 4 | 3 |
| Gardien de pierre | 5 | 5 |
**Booster 2**
| Carte | Puissance | Coût |
|-------|-----------|------|
| Acolyte du givre | 3 | 2 |
| Chevaucheur de loup | 4 | 3 |
| Abomination | 6 | 6 |
**Booster 3**
| Carte | Puissance | Coût |
|-------|-----------|------|
| Voleuse de poches | 2 | 1 |
| Sentinelle elfe | 4 | 4 |
| Ver de foudre | 8 | 7 |
**Booster 4**
| Carte | Puissance | Coût |
|-------|-----------|------|
| Mage novice | 1 | 1 |
| Alchimiste fou | 3 | 2 |
| Dragon de givre | 9 | 8 |
**Booster 5**
| Carte | Puissance | Coût |
|-------|-----------|------|
| Bouffon du roi | 3 | 2 |
| Capitaine pirate | 5 | 4 |
| Kraken des profondeurs | 6 | 6 |
1. Applique la stratégie gloutonne la plus naturelle : **à chaque booster, prendre la carte la plus puissante**. Quel deck obtiens-tu ? Quelle est sa puissance totale ? Est-il jouable ?
2. Corrige la stratégie pour qu'elle respecte la règle : **à chaque booster, prendre la carte la plus puissante parmi celles qu'on a encore le droit de prendre**. Quel deck obtiens-tu ? Quelle est sa puissance ?
3. Trouve un deck jouable **strictement meilleur** que celui de la question 2. Où l'algorithme glouton s'est-il fait piéger ?
4. Le problème vient d'un choix fait au booster 1. Pourquoi l'algorithme glouton ne peut-il pas le corriger une fois arrivé au booster 4 ?
<details>
<summary><strong>Voir la correction</strong></summary>
**Question 1 : glouton naïf**
| Booster | Carte prise | Puissance | Coût |
|---------|-------------|-----------|------|
| 1 | Gardien de pierre | 5 | 5 |
| 2 | Abomination | 6 | 6 |
| 3 | Ver de foudre | 8 | 7 |
| 4 | Dragon de givre | 9 | 8 |
| 5 | Kraken des profondeurs | 6 | 6 |
Puissance totale : **34**... mais le deck contient **5 cartes à 5 manas ou plus**, alors que la limite est de 2.
Le deck est **injouable**. L'algorithme glouton n'a pas donné un résultat « un peu moins bon » : il a donné un résultat **inutilisable**. En ne regardant que la puissance, il a complètement ignoré la contrainte de coût.
**Question 2 : glouton contraint**
On compte au fur et à mesure les cartes chères déjà prises.
| Booster | Cartes autorisées | Carte prise | Puissance | Cartes chères |
|---------|-------------------|-------------|-----------|---------------|
| 1 | toutes (0 chère prise) | Gardien de pierre (5) | 5 | 1 / 2 |
| 2 | toutes (1 chère prise) | Abomination (6) | 6 | 2 / 2 |
| 3 | quota plein → seulement les cartes < 5 manas | Sentinelle elfe (4) | 4 | 2 / 2 |
| 4 | quota plein | Alchimiste fou (3) | 3 | 2 / 2 |
| 5 | quota plein | Capitaine pirate (5) | 5 | 2 / 2 |
Deck jouable, puissance totale : **23**.
**Question 3 : mieux**
Deck optimal : Druide chétif (4) + Chevaucheur de loup (4) + **Ver de foudre (8)** + **Dragon de givre (9)** + Capitaine pirate (5) = **30**.
Il ne contient bien que 2 cartes chères (Ver de foudre et Dragon de givre), il est donc jouable.
**Le piège** : le glouton a dépensé ses 2 places de cartes chères dès les boosters 1 et 2, pour des gains ridicules.
- Au booster 1, prendre le Gardien de pierre (5) au lieu du Druide chétif (4) rapporte **+1**.
- Au booster 2, prendre l'Abomination (6) au lieu du Chevaucheur de loup (4) rapporte **+2**.
- Mais au booster 3, le Ver de foudre (8) au lieu de la Sentinelle elfe (4) rapportait **+4**.
- Et au booster 4, le Dragon de givre (9) au lieu de l'Alchimiste fou (3) rapportait **+6**.
Le glouton a payé +1 et +2 des places qui valaient +4 et +6. Il a fait deux fois le meilleur choix **du moment**, et c'est précisément ce qui l'a ruiné.
**Question 4 :**
Parce que c'est la définition même d'un algorithme glouton : **il ne revient jamais sur un choix déjà fait**. Arrivé au booster 4, il « voit » bien que le Dragon de givre serait excellent, mais son quota est plein et le Gardien de pierre est déjà dans le deck — il ne peut plus l'échanger.
Ici la règle du jeu et la règle de l'algorithme coïncident : dans un draft, une carte écartée est réellement perdue.
Plus profondément, au booster 1 l'algorithme **n'avait aucun moyen de savoir** qu'un Dragon de givre l'attendait au booster 4 : il décide avec une vision purement locale. C'est pour cela qu'un algorithme glouton est une **heuristique** — il donne vite une solution correcte, pas forcément la meilleure.
</details>
---
## Exercice 6 (Moyen) : Réflexion
1. Dans l'exercice 4, on a maximisé le **nombre** de streams. Si on voulait maximiser la **rémunération totale**, l'algorithme glouton donnerait-il le même résultat ?
+494
View File
@@ -0,0 +1,494 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# TP — Draft d'Arène : un algorithme glouton en action\n",
"\n",
"Dans le mode **Arène** d'un jeu de cartes, on ne joue pas avec sa collection : on **drafte** son deck.\n",
"On t'ouvre un booster de 3 cartes, tu en gardes **une seule**, et on passe au booster suivant.\n",
"\n",
"**Une carte écartée est perdue pour toujours. On ne revient jamais en arrière.**\n",
"\n",
"Cette règle du jeu est *exactement* la définition d'un algorithme glouton :\n",
"\n",
"- on procède par étapes (un booster à la fois) ;\n",
"- à chaque étape on fait le meilleur choix possible ;\n",
"- on ne revient jamais sur un choix déjà fait.\n",
"\n",
"Ce TP a un seul but : voir de nos propres yeux **ce que le glouton fait bien, et ce qu'il rate**.\n",
"\n",
"---\n",
"\n",
"## Les données\n",
"\n",
"Une carte est un tuple `(nom, puissance, cout)` :\n",
"\n",
"- la **puissance** mesure la force de la carte (plus c'est haut, mieux c'est) ;\n",
"- le **coût** est le nombre de manas nécessaires pour la poser sur le plateau.\n",
"\n",
"> **Règle de jouabilité.** Une carte est dite **chère** si elle coûte **5 manas ou plus**.\n",
"> Un deck n'est **jouable** que s'il contient **au plus 3 cartes chères**.\n",
"> Sinon, on n'a rien à jouer pendant les premiers tours : on perd la partie avant même\n",
"> d'avoir pu poser ses grosses cartes.\n",
"\n",
"**Objectif : obtenir le deck jouable de puissance totale maximale.**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ---------------------------------------------------------------\n",
"# Données du TP — ne pas modifier\n",
"# ---------------------------------------------------------------\n",
"\n",
"# Une carte = (nom, puissance, cout)\n",
"BOOSTERS = [\n",
" [(\"Gnome bricoleur\", 3, 1), (\"Yéti des roches\", 5, 4), (\"Golem de siège\", 6, 5)],\n",
" [(\"Écuyer fidèle\", 2, 1), (\"Loup argenté\", 4, 3), (\"Chevalier du néant\", 5, 5)],\n",
" [(\"Apprenti mage\", 3, 2), (\"Sorcière du marais\", 4, 4), (\"Dragon de bronze\", 5, 5)],\n",
" [(\"Recrue de la garde\", 2, 1), (\"Berserker orc\", 4, 3), (\"Titan de pierre\", 9, 7)],\n",
" [(\"Novice de l'ombre\", 1, 1), (\"Archer elfe\", 3, 2), (\"Léviathan des abysses\", 10, 8)],\n",
" [(\"Sanglier tenace\", 2, 2), (\"Garde royal\", 5, 4), (\"Ogre lourdaud\", 4, 6)],\n",
" [(\"Voleur agile\", 3, 2), (\"Prêtresse de la lune\", 4, 3), (\"Colosse runique\", 7, 6)],\n",
" [(\"Gobelin ferrailleur\", 2, 1), (\"Chaman du totem\", 4, 4), (\"Hydre des marais\", 6, 6)],\n",
" [(\"Éclaireur nain\", 3, 2), (\"Paladin d'argent\", 5, 4), (\"Behemoth ancien\", 6, 7)],\n",
" [(\"Rat des égouts\", 1, 1), (\"Duelliste vétéran\", 5, 3), (\"Ver des sables\", 6, 5)],\n",
"]\n",
"\n",
"COUT_CHER = 5 # une carte est \"chère\" à partir de 5 manas\n",
"MAX_CHERES = 3 # au plus 3 cartes chères dans un deck jouable\n",
"\n",
"\n",
"def afficher_deck(deck):\n",
" \"\"\"Affiche un deck sous forme de tableau.\"\"\"\n",
" for nom, puissance, cout in deck:\n",
" marque = \" (chère)\" if cout >= COUT_CHER else \"\"\n",
" print(f\" {nom:<24} puissance {puissance:>2} coût {cout}{marque}\")\n",
"\n",
"\n",
"print(f\"{len(BOOSTERS)} boosters de 3 cartes.\")\n",
"print(\"Booster 1 :\")\n",
"afficher_deck(BOOSTERS[0])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Partie 1 — Savoir juger un deck\n",
"\n",
"Avant de drafter, il faut savoir répondre à deux questions sur un deck :\n",
"**combien vaut-il ?** et **est-il seulement jouable ?**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Exercice 1\n",
"# Écrire une fonction qui renvoie la somme des puissances des cartes d'un deck.\n",
"\n",
"def puissance_totale(deck):\n",
" \"\"\"Renvoie la somme des puissances des cartes du deck.\"\"\"\n",
" # À COMPLÉTER\n",
" ...\n",
"\n",
"\n",
"# Vérification\n",
"test = [(\"A\", 3, 1), (\"B\", 5, 6), (\"C\", 2, 2)]\n",
"assert puissance_totale(test) == 10\n",
"assert puissance_totale([]) == 0\n",
"print(\"Exercice 1 : OK\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Exercice 2\n",
"# a) Compter les cartes chères (coût >= COUT_CHER) d'un deck.\n",
"# b) En déduire si le deck est jouable (au plus MAX_CHERES cartes chères).\n",
"\n",
"def nb_cartes_cheres(deck):\n",
" \"\"\"Renvoie le nombre de cartes coûtant COUT_CHER manas ou plus.\"\"\"\n",
" # À COMPLÉTER\n",
" ...\n",
"\n",
"\n",
"def deck_jouable(deck):\n",
" \"\"\"Renvoie True si le deck respecte la règle de jouabilité.\"\"\"\n",
" # À COMPLÉTER\n",
" ...\n",
"\n",
"\n",
"# Vérification\n",
"test = [(\"A\", 3, 1), (\"B\", 5, 6), (\"C\", 2, 7), (\"D\", 4, 5), (\"E\", 1, 8)]\n",
"assert nb_cartes_cheres(test) == 4\n",
"assert deck_jouable(test) is False\n",
"assert deck_jouable(test[:3]) is True\n",
"print(\"Exercice 2 : OK\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Partie 2 — Le glouton naïf\n",
"\n",
"La stratégie qui vient spontanément à l'esprit :\n",
"\n",
"> **À chaque booster, je prends la carte la plus puissante.**\n",
"\n",
"C'est bien un algorithme glouton : un choix par étape, le meilleur sur le moment,\n",
"jamais de retour en arrière. Codons-la."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Exercice 3\n",
"# Renvoyer la carte de plus grande puissance d'un booster.\n",
"# (Ne pas utiliser max() : on veut voir la boucle de recherche du maximum.)\n",
"\n",
"def carte_la_plus_puissante(booster):\n",
" \"\"\"Renvoie la carte de plus grande puissance du booster.\"\"\"\n",
" # À COMPLÉTER\n",
" ...\n",
"\n",
"\n",
"# Vérification\n",
"assert carte_la_plus_puissante(BOOSTERS[0]) == (\"Golem de siège\", 6, 5)\n",
"assert carte_la_plus_puissante(BOOSTERS[4]) == (\"Léviathan des abysses\", 10, 8)\n",
"print(\"Exercice 3 : OK\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Exercice 4\n",
"# Drafter un deck complet : pour chaque booster, prendre la carte la plus puissante.\n",
"\n",
"def draft_naif(boosters):\n",
" \"\"\"Renvoie le deck obtenu en prenant à chaque fois la carte la plus puissante.\"\"\"\n",
" # À COMPLÉTER\n",
" ...\n",
"\n",
"\n",
"deck_naif = draft_naif(BOOSTERS)\n",
"afficher_deck(deck_naif)\n",
"print()\n",
"print(\"Puissance totale :\", puissance_totale(deck_naif))\n",
"print(\"Cartes chères :\", nb_cartes_cheres(deck_naif), \"( maximum autorisé :\", MAX_CHERES, \")\")\n",
"print(\"Deck jouable ? :\", deck_jouable(deck_naif))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Question 1\n",
"\n",
"Le deck obtenu a une puissance totale de **65**, la plus haute qu'on puisse imaginer...\n",
"et il est **injouable** : 9 cartes chères pour 3 autorisées.\n",
"\n",
"Remarque bien ce qui vient de se passer : le glouton n'a pas donné un résultat\n",
"« un peu moins bon que l'optimal ». Il a donné un résultat **inutilisable**.\n",
"\n",
"**À toi.** Explique en une phrase pourquoi cet algorithme produit un deck injouable.\n",
"Qu'a-t-il regardé, et surtout qu'a-t-il *ignoré* ?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Partie 3 — Le glouton contraint\n",
"\n",
"Réparons l'algorithme sans changer sa nature. Il reste glouton — un choix par étape,\n",
"pas de retour en arrière — mais il compte au fur et à mesure les cartes chères déjà prises :\n",
"\n",
"> **À chaque booster, je prends la carte la plus puissante _parmi celles que j'ai encore le droit de prendre_.**\n",
"\n",
"Tant que le quota de cartes chères n'est pas atteint, toutes les cartes sont autorisées.\n",
"Une fois le quota plein, seules les cartes à moins de `COUT_CHER` manas le sont.\n",
"\n",
"*(Les boosters sont construits pour qu'il y ait toujours au moins une carte à moins de 5 manas :\n",
"l'algorithme ne peut donc jamais se retrouver bloqué.)*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Exercice 5\n",
"# Drafter en respectant le quota de cartes chères.\n",
"#\n",
"# Indication : garder un compteur du nombre de cartes chères déjà prises.\n",
"# Pour chaque booster, construire la liste des cartes autorisées, puis prendre\n",
"# la plus puissante de cette liste.\n",
"\n",
"def draft_contraint(boosters):\n",
" \"\"\"Renvoie le deck obtenu par un glouton qui respecte le quota de cartes chères.\"\"\"\n",
" # À COMPLÉTER\n",
" ...\n",
"\n",
"\n",
"deck_glouton = draft_contraint(BOOSTERS)\n",
"afficher_deck(deck_glouton)\n",
"print()\n",
"print(\"Puissance totale :\", puissance_totale(deck_glouton))\n",
"print(\"Deck jouable ? :\", deck_jouable(deck_glouton))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Question 2\n",
"\n",
"Cette fois le deck est jouable, pour une puissance de **46**.\n",
"\n",
"Notre algorithme fonctionne. Mais une question reste ouverte, et c'est *la* question\n",
"du chapitre : **46, est-ce le maximum ?**\n",
"\n",
"Le glouton, lui, en est incapable de le dire — il n'a jamais comparé son deck à un autre."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Partie 4 — La force brute : quel était le vrai maximum ?\n",
"\n",
"Pour le savoir, il n'y a qu'une méthode certaine : **essayer tous les decks possibles**\n",
"et garder le meilleur deck jouable.\n",
"\n",
"À chaque booster on a 3 choix, et il y a 10 boosters : cela fait\n",
"\n",
"$$3^{10} = 59\\,049 \\text{ decks possibles.}$$\n",
"\n",
"C'est beaucoup pour un humain, très peu pour une machine. Le code ci-dessous est **fourni** :\n",
"`product(*boosters)` fabrique toutes les combinaisons possibles, une par une.\n",
"\n",
"Lis-le, exécute-le, mais ne cherche pas à le réécrire : ce n'est pas l'objet du TP."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ---------------------------------------------------------------\n",
"# Force brute — code fourni\n",
"# ---------------------------------------------------------------\n",
"from itertools import product\n",
"from time import perf_counter\n",
"\n",
"\n",
"def meilleur_deck_force_brute(boosters):\n",
" \"\"\"Essaie TOUS les decks possibles et renvoie le meilleur deck jouable.\"\"\"\n",
" meilleur = None\n",
" meilleure_puissance = -1\n",
" for deck in product(*boosters):\n",
" if deck_jouable(deck) and puissance_totale(deck) > meilleure_puissance:\n",
" meilleur = list(deck)\n",
" meilleure_puissance = puissance_totale(deck)\n",
" return meilleur, meilleure_puissance\n",
"\n",
"\n",
"debut = perf_counter()\n",
"deck_optimal, puissance_optimale = meilleur_deck_force_brute(BOOSTERS)\n",
"duree = perf_counter() - debut\n",
"\n",
"afficher_deck(deck_optimal)\n",
"print()\n",
"print(\"Puissance optimale :\", puissance_optimale)\n",
"print(f\"Calculé en {duree:.2f} s après avoir testé {3 ** len(BOOSTERS)} decks.\")\n",
"print()\n",
"print(\"Glouton :\", puissance_totale(deck_glouton), \" | Optimal :\", puissance_optimale)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Question 3\n",
"\n",
"Le glouton obtient **46**, l'optimum est **58**. Il s'est fait piéger — reste à comprendre où.\n",
"\n",
"Regarde les 3 cartes chères que chacun a retenues :\n",
"\n",
"- **glouton** : Golem de siège, Chevalier du néant, Dragon de bronze (les boosters 1, 2 et 3) ;\n",
"- **optimum** : Titan de pierre, Léviathan des abysses, Colosse runique (les boosters 4, 5 et 7).\n",
"\n",
"Le tableau ci-dessous calcule, pour chaque booster, ce que rapporte le fait d'y « dépenser »\n",
"une place de carte chère plutôt que de prendre la meilleure carte bon marché.\n",
"\n",
"Exécute-le, puis explique en quelques lignes l'erreur du glouton."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Ce que rapporte réellement chaque carte chère, comparée à la meilleure carte pas chère du même booster\n",
"print(f\"{'Booster':<9}{'meilleure pas chère':<27}{'meilleure chère':<29}{'gain'}\")\n",
"for i, booster in enumerate(BOOSTERS):\n",
" pas_cheres = [c for c in booster if c[2] < COUT_CHER]\n",
" cheres = [c for c in booster if c[2] >= COUT_CHER]\n",
" if cheres == []:\n",
" continue\n",
" meilleure_pas_chere = carte_la_plus_puissante(pas_cheres)\n",
" meilleure_chere = carte_la_plus_puissante(cheres)\n",
" gain = meilleure_chere[1] - meilleure_pas_chere[1]\n",
" print(f\"{i + 1:<9}{meilleure_pas_chere[0] + f' ({meilleure_pas_chere[1]})':<27}\"\n",
" f\"{meilleure_chere[0] + f' ({meilleure_chere[1]})':<29}{gain:+d}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Partie 5 — Alors pourquoi ne pas toujours faire de la force brute ?\n",
"\n",
"Puisque la force brute donne toujours le bon résultat, autant l'utiliser partout, non ?\n",
"\n",
"La cellule suivante mesure le temps de calcul de la force brute pour un nombre croissant\n",
"de boosters. Observe la colonne « temps »."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(f\"{'boosters':<11}{'decks testés':<16}{'temps (s)'}\")\n",
"for n in range(4, len(BOOSTERS) + 1):\n",
" debut = perf_counter()\n",
" meilleur_deck_force_brute(BOOSTERS[:n])\n",
" duree_n = perf_counter() - debut\n",
" print(f\"{n:<11}{3 ** n:<16}{duree_n:.3f}\")\n",
"\n",
"# Extrapolation à un vrai draft d'Arène (30 boosters), à partir du temps mesuré ci-dessus\n",
"vitesse = 3 ** len(BOOSTERS) / duree_n # decks testés par seconde\n",
"secondes = 3 ** 30 / vitesse\n",
"print()\n",
"print(f\"Vitesse mesurée : environ {vitesse:.0f} decks testés par seconde.\")\n",
"print(f\"Pour 30 boosters : {3 ** 30} decks à tester,\")\n",
"print(f\"soit environ {secondes / (365 * 24 * 3600):.0f} années de calcul.\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Question 4\n",
"\n",
"1. Quand on ajoute **un seul** booster, par combien le nombre de decks à tester est-il multiplié ?\n",
" Et le temps de calcul ?\n",
"2. Un vrai draft d'Arène compte **30 boosters**. Combien de decks faudrait-il tester ?\n",
" En te servant du temps mesuré pour 10 boosters, estime la durée du calcul.\n",
"3. Conclusion : dans quelle situation l'algorithme glouton est-il **le bon choix**,\n",
" même s'il ne garantit pas l'optimum ?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Ce qu'il faut retenir\n",
"\n",
"| | Glouton | Force brute |\n",
"|---|---|---|\n",
"| Résultat | 46 (pas l'optimum) | 58 (l'optimum) |\n",
"| Nombre d'opérations | 10 (une par booster) | 59 049 |\n",
"| Avec 30 boosters | 30 opérations | plusieurs années |\n",
"| Garantie | aucune | optimum certain |\n",
"\n",
"Un **algorithme glouton** construit une solution par étapes, en faisant à chaque étape\n",
"le meilleur choix selon un critère local, et **sans jamais revenir en arrière**.\n",
"\n",
"Il est rapide. Il n'est pas toujours optimal. Quand ce n'est pas le cas, on parle\n",
"d'**heuristique**.\n",
"\n",
"---\n",
"\n",
"## Pour aller plus loin\n",
"\n",
"Reprends le tableau des gains de la partie 4. Un autre algorithme glouton est possible :\n",
"calculer le gain de chaque booster, puis dépenser ses 3 places de cartes chères sur les\n",
"**3 plus gros gains**.\n",
"\n",
"1. Vérifie sur les données du TP que cette stratégie donne bien 58, c'est-à-dire l'optimum.\n",
"2. Pourtant, cette stratégie est **impossible à appliquer dans un vrai draft**. Pourquoi ?\n",
"\n",
"<details>\n",
"<summary><strong>Indice</strong></summary>\n",
"\n",
"De quelle information a-t-on besoin pour calculer le tableau des gains ? Et à quel moment\n",
"un joueur en Arène dispose-t-il de cette information ?\n",
"\n",
"</details>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"Auteur : Florian Mathieu\n",
"\n",
"Licence CC BY-SA\n",
"\n",
"<a rel=\"license\" href=\"http://creativecommons.org/licenses/by-sa/4.0/\"><img alt=\"Licence Creative Commons\" style=\"border-width:0\" src=\"https://i.creativecommons.org/l/by-sa/4.0/88x31.png\" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel=\"license\" href=\"http://creativecommons.org/licenses/by-sa/4.0/\">Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International</a>"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
+549
View File
@@ -0,0 +1,549 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# TP — Draft d'Arène : **corrigé**\n",
"\n",
"Dans le mode **Arène** d'un jeu de cartes, on ne joue pas avec sa collection : on **drafte** son deck.\n",
"On t'ouvre un booster de 3 cartes, tu en gardes **une seule**, et on passe au booster suivant.\n",
"\n",
"**Une carte écartée est perdue pour toujours. On ne revient jamais en arrière.**\n",
"\n",
"Cette règle du jeu est *exactement* la définition d'un algorithme glouton :\n",
"\n",
"- on procède par étapes (un booster à la fois) ;\n",
"- à chaque étape on fait le meilleur choix possible ;\n",
"- on ne revient jamais sur un choix déjà fait.\n",
"\n",
"Ce TP a un seul but : voir de nos propres yeux **ce que le glouton fait bien, et ce qu'il rate**.\n",
"\n",
"---\n",
"\n",
"## Les données\n",
"\n",
"Une carte est un tuple `(nom, puissance, cout)` :\n",
"\n",
"- la **puissance** mesure la force de la carte (plus c'est haut, mieux c'est) ;\n",
"- le **coût** est le nombre de manas nécessaires pour la poser sur le plateau.\n",
"\n",
"> **Règle de jouabilité.** Une carte est dite **chère** si elle coûte **5 manas ou plus**.\n",
"> Un deck n'est **jouable** que s'il contient **au plus 3 cartes chères**.\n",
"> Sinon, on n'a rien à jouer pendant les premiers tours : on perd la partie avant même\n",
"> d'avoir pu poser ses grosses cartes.\n",
"\n",
"**Objectif : obtenir le deck jouable de puissance totale maximale.**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ---------------------------------------------------------------\n",
"# Données du TP — ne pas modifier\n",
"# ---------------------------------------------------------------\n",
"\n",
"# Une carte = (nom, puissance, cout)\n",
"BOOSTERS = [\n",
" [(\"Gnome bricoleur\", 3, 1), (\"Yéti des roches\", 5, 4), (\"Golem de siège\", 6, 5)],\n",
" [(\"Écuyer fidèle\", 2, 1), (\"Loup argenté\", 4, 3), (\"Chevalier du néant\", 5, 5)],\n",
" [(\"Apprenti mage\", 3, 2), (\"Sorcière du marais\", 4, 4), (\"Dragon de bronze\", 5, 5)],\n",
" [(\"Recrue de la garde\", 2, 1), (\"Berserker orc\", 4, 3), (\"Titan de pierre\", 9, 7)],\n",
" [(\"Novice de l'ombre\", 1, 1), (\"Archer elfe\", 3, 2), (\"Léviathan des abysses\", 10, 8)],\n",
" [(\"Sanglier tenace\", 2, 2), (\"Garde royal\", 5, 4), (\"Ogre lourdaud\", 4, 6)],\n",
" [(\"Voleur agile\", 3, 2), (\"Prêtresse de la lune\", 4, 3), (\"Colosse runique\", 7, 6)],\n",
" [(\"Gobelin ferrailleur\", 2, 1), (\"Chaman du totem\", 4, 4), (\"Hydre des marais\", 6, 6)],\n",
" [(\"Éclaireur nain\", 3, 2), (\"Paladin d'argent\", 5, 4), (\"Behemoth ancien\", 6, 7)],\n",
" [(\"Rat des égouts\", 1, 1), (\"Duelliste vétéran\", 5, 3), (\"Ver des sables\", 6, 5)],\n",
"]\n",
"\n",
"COUT_CHER = 5 # une carte est \"chère\" à partir de 5 manas\n",
"MAX_CHERES = 3 # au plus 3 cartes chères dans un deck jouable\n",
"\n",
"\n",
"def afficher_deck(deck):\n",
" \"\"\"Affiche un deck sous forme de tableau.\"\"\"\n",
" for nom, puissance, cout in deck:\n",
" marque = \" (chère)\" if cout >= COUT_CHER else \"\"\n",
" print(f\" {nom:<24} puissance {puissance:>2} coût {cout}{marque}\")\n",
"\n",
"\n",
"print(f\"{len(BOOSTERS)} boosters de 3 cartes.\")\n",
"print(\"Booster 1 :\")\n",
"afficher_deck(BOOSTERS[0])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Partie 1 — Savoir juger un deck\n",
"\n",
"Avant de drafter, il faut savoir répondre à deux questions sur un deck :\n",
"**combien vaut-il ?** et **est-il seulement jouable ?**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def puissance_totale(deck):\n",
" \"\"\"Renvoie la somme des puissances des cartes du deck.\"\"\"\n",
" total = 0\n",
" for nom, puissance, cout in deck:\n",
" total = total + puissance\n",
" return total\n",
"\n",
"\n",
"# Vérification\n",
"test = [(\"A\", 3, 1), (\"B\", 5, 6), (\"C\", 2, 2)]\n",
"assert puissance_totale(test) == 10\n",
"assert puissance_totale([]) == 0\n",
"print(\"Exercice 1 : OK\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def nb_cartes_cheres(deck):\n",
" \"\"\"Renvoie le nombre de cartes coûtant COUT_CHER manas ou plus.\"\"\"\n",
" n = 0\n",
" for nom, puissance, cout in deck:\n",
" if cout >= COUT_CHER:\n",
" n = n + 1\n",
" return n\n",
"\n",
"\n",
"def deck_jouable(deck):\n",
" \"\"\"Renvoie True si le deck respecte la règle de jouabilité.\"\"\"\n",
" return nb_cartes_cheres(deck) <= MAX_CHERES\n",
"\n",
"\n",
"# Vérification\n",
"test = [(\"A\", 3, 1), (\"B\", 5, 6), (\"C\", 2, 7), (\"D\", 4, 5), (\"E\", 1, 8)]\n",
"assert nb_cartes_cheres(test) == 4\n",
"assert deck_jouable(test) is False\n",
"assert deck_jouable(test[:3]) is True\n",
"print(\"Exercice 2 : OK\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Partie 2 — Le glouton naïf\n",
"\n",
"La stratégie qui vient spontanément à l'esprit :\n",
"\n",
"> **À chaque booster, je prends la carte la plus puissante.**\n",
"\n",
"C'est bien un algorithme glouton : un choix par étape, le meilleur sur le moment,\n",
"jamais de retour en arrière. Codons-la."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def carte_la_plus_puissante(booster):\n",
" \"\"\"Renvoie la carte de plus grande puissance du booster.\"\"\"\n",
" meilleure = booster[0]\n",
" for carte in booster:\n",
" if carte[1] > meilleure[1]:\n",
" meilleure = carte\n",
" return meilleure\n",
"\n",
"\n",
"# Vérification\n",
"assert carte_la_plus_puissante(BOOSTERS[0]) == (\"Golem de siège\", 6, 5)\n",
"assert carte_la_plus_puissante(BOOSTERS[4]) == (\"Léviathan des abysses\", 10, 8)\n",
"print(\"Exercice 3 : OK\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def draft_naif(boosters):\n",
" \"\"\"Renvoie le deck obtenu en prenant à chaque fois la carte la plus puissante.\"\"\"\n",
" deck = []\n",
" for booster in boosters:\n",
" deck.append(carte_la_plus_puissante(booster))\n",
" return deck\n",
"\n",
"\n",
"deck_naif = draft_naif(BOOSTERS)\n",
"afficher_deck(deck_naif)\n",
"print()\n",
"print(\"Puissance totale :\", puissance_totale(deck_naif))\n",
"print(\"Cartes chères :\", nb_cartes_cheres(deck_naif), \"( maximum autorisé :\", MAX_CHERES, \")\")\n",
"print(\"Deck jouable ? :\", deck_jouable(deck_naif))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Question 1 — correction\n",
"\n",
"L'algorithme n'a regardé qu'**un seul critère, la puissance**, et il l'a optimisé\n",
"booster par booster. Or les cartes les plus puissantes sont justement les plus chères :\n",
"en maximisant la puissance à chaque étape, il accumule mécaniquement des cartes chères.\n",
"\n",
"La contrainte de jouabilité, elle, est **globale** : elle porte sur le deck entier, pas sur\n",
"un choix isolé. Un algorithme qui décide en regardant uniquement le booster courant n'a\n",
"aucun moyen de la voir venir.\n",
"\n",
"C'est le premier enseignement du TP : un glouton optimise le critère qu'on lui donne,\n",
"**rien d'autre**."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Partie 3 — Le glouton contraint\n",
"\n",
"Réparons l'algorithme sans changer sa nature. Il reste glouton — un choix par étape,\n",
"pas de retour en arrière — mais il compte au fur et à mesure les cartes chères déjà prises :\n",
"\n",
"> **À chaque booster, je prends la carte la plus puissante _parmi celles que j'ai encore le droit de prendre_.**\n",
"\n",
"Tant que le quota de cartes chères n'est pas atteint, toutes les cartes sont autorisées.\n",
"Une fois le quota plein, seules les cartes à moins de `COUT_CHER` manas le sont.\n",
"\n",
"*(Les boosters sont construits pour qu'il y ait toujours au moins une carte à moins de 5 manas :\n",
"l'algorithme ne peut donc jamais se retrouver bloqué.)*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def draft_contraint(boosters):\n",
" \"\"\"Renvoie le deck obtenu par un glouton qui respecte le quota de cartes chères.\"\"\"\n",
" deck = []\n",
" cheres_prises = 0\n",
" for booster in boosters:\n",
" # Cartes que l'on a encore le droit de prendre\n",
" autorisees = []\n",
" for carte in booster:\n",
" if carte[2] < COUT_CHER or cheres_prises < MAX_CHERES:\n",
" autorisees.append(carte)\n",
" # Meilleur choix parmi elles\n",
" choisie = carte_la_plus_puissante(autorisees)\n",
" if choisie[2] >= COUT_CHER:\n",
" cheres_prises = cheres_prises + 1\n",
" deck.append(choisie)\n",
" return deck\n",
"\n",
"\n",
"deck_glouton = draft_contraint(BOOSTERS)\n",
"afficher_deck(deck_glouton)\n",
"print()\n",
"print(\"Puissance totale :\", puissance_totale(deck_glouton))\n",
"print(\"Deck jouable ? :\", deck_jouable(deck_glouton))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Question 2\n",
"\n",
"Cette fois le deck est jouable, pour une puissance de **46**.\n",
"\n",
"Notre algorithme fonctionne. Mais une question reste ouverte, et c'est *la* question\n",
"du chapitre : **46, est-ce le maximum ?**\n",
"\n",
"Le glouton, lui, en est incapable de le dire — il n'a jamais comparé son deck à un autre."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Partie 4 — La force brute : quel était le vrai maximum ?\n",
"\n",
"Pour le savoir, il n'y a qu'une méthode certaine : **essayer tous les decks possibles**\n",
"et garder le meilleur deck jouable.\n",
"\n",
"À chaque booster on a 3 choix, et il y a 10 boosters : cela fait\n",
"\n",
"$$3^{10} = 59\\,049 \\text{ decks possibles.}$$\n",
"\n",
"C'est beaucoup pour un humain, très peu pour une machine. Le code ci-dessous est **fourni** :\n",
"`product(*boosters)` fabrique toutes les combinaisons possibles, une par une.\n",
"\n",
"Lis-le, exécute-le, mais ne cherche pas à le réécrire : ce n'est pas l'objet du TP."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ---------------------------------------------------------------\n",
"# Force brute — code fourni\n",
"# ---------------------------------------------------------------\n",
"from itertools import product\n",
"from time import perf_counter\n",
"\n",
"\n",
"def meilleur_deck_force_brute(boosters):\n",
" \"\"\"Essaie TOUS les decks possibles et renvoie le meilleur deck jouable.\"\"\"\n",
" meilleur = None\n",
" meilleure_puissance = -1\n",
" for deck in product(*boosters):\n",
" if deck_jouable(deck) and puissance_totale(deck) > meilleure_puissance:\n",
" meilleur = list(deck)\n",
" meilleure_puissance = puissance_totale(deck)\n",
" return meilleur, meilleure_puissance\n",
"\n",
"\n",
"debut = perf_counter()\n",
"deck_optimal, puissance_optimale = meilleur_deck_force_brute(BOOSTERS)\n",
"duree = perf_counter() - debut\n",
"\n",
"afficher_deck(deck_optimal)\n",
"print()\n",
"print(\"Puissance optimale :\", puissance_optimale)\n",
"print(f\"Calculé en {duree:.2f} s après avoir testé {3 ** len(BOOSTERS)} decks.\")\n",
"print()\n",
"print(\"Glouton :\", puissance_totale(deck_glouton), \" | Optimal :\", puissance_optimale)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Question 3\n",
"\n",
"Le glouton obtient **46**, l'optimum est **58**. Il s'est fait piéger — reste à comprendre où.\n",
"\n",
"Regarde les 3 cartes chères que chacun a retenues :\n",
"\n",
"- **glouton** : Golem de siège, Chevalier du néant, Dragon de bronze (les boosters 1, 2 et 3) ;\n",
"- **optimum** : Titan de pierre, Léviathan des abysses, Colosse runique (les boosters 4, 5 et 7).\n",
"\n",
"Le tableau ci-dessous calcule, pour chaque booster, ce que rapporte le fait d'y « dépenser »\n",
"une place de carte chère plutôt que de prendre la meilleure carte bon marché.\n",
"\n",
"Exécute-le, puis explique en quelques lignes l'erreur du glouton."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Ce que rapporte réellement chaque carte chère, comparée à la meilleure carte pas chère du même booster\n",
"print(f\"{'Booster':<9}{'meilleure pas chère':<27}{'meilleure chère':<29}{'gain'}\")\n",
"for i, booster in enumerate(BOOSTERS):\n",
" pas_cheres = [c for c in booster if c[2] < COUT_CHER]\n",
" cheres = [c for c in booster if c[2] >= COUT_CHER]\n",
" if cheres == []:\n",
" continue\n",
" meilleure_pas_chere = carte_la_plus_puissante(pas_cheres)\n",
" meilleure_chere = carte_la_plus_puissante(cheres)\n",
" gain = meilleure_chere[1] - meilleure_pas_chere[1]\n",
" print(f\"{i + 1:<9}{meilleure_pas_chere[0] + f' ({meilleure_pas_chere[1]})':<27}\"\n",
" f\"{meilleure_chere[0] + f' ({meilleure_chere[1]})':<29}{gain:+d}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Question 3 — correction\n",
"\n",
"Les gains sont : **+1, +1, +1**, +5, +7, 1, +3, +2, +1, +1.\n",
"\n",
"Le glouton a dépensé ses trois places de cartes chères dans les boosters 1, 2 et 3, pour\n",
"un gain de **+1 à chaque fois**. Quand il arrive au booster 4 (Titan de pierre, **+5**) puis\n",
"au booster 5 (Léviathan des abysses, **+7**), son quota est plein : il doit se rabattre sur\n",
"le Berserker orc et l'Archer elfe.\n",
"\n",
"L'optimum fait exactement l'inverse : il **renonce** aux trois petits gains du début pour\n",
"garder ses places aux boosters 4, 5 et 7 (+7, +5, +3).\n",
"\n",
"$$46 + 7 + 5 + 3 - 1 - 1 - 1 = 58$$\n",
"\n",
"Et c'est là tout le problème : au booster 1, l'algorithme **n'a aucun moyen de savoir** qu'un\n",
"Léviathan des abysses l'attend au booster 5. Il décide avec une vision purement locale, et\n",
"la règle du draft lui interdit de revenir corriger son choix quand l'information arrive.\n",
"\n",
"Le glouton n'a pas fait d'erreur de calcul. Il a fait, trois fois de suite, le meilleur choix\n",
"**du moment** — et c'est précisément ce qui l'a ruiné."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Partie 5 — Alors pourquoi ne pas toujours faire de la force brute ?\n",
"\n",
"Puisque la force brute donne toujours le bon résultat, autant l'utiliser partout, non ?\n",
"\n",
"La cellule suivante mesure le temps de calcul de la force brute pour un nombre croissant\n",
"de boosters. Observe la colonne « temps »."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(f\"{'boosters':<11}{'decks testés':<16}{'temps (s)'}\")\n",
"for n in range(4, len(BOOSTERS) + 1):\n",
" debut = perf_counter()\n",
" meilleur_deck_force_brute(BOOSTERS[:n])\n",
" duree_n = perf_counter() - debut\n",
" print(f\"{n:<11}{3 ** n:<16}{duree_n:.3f}\")\n",
"\n",
"# Extrapolation à un vrai draft d'Arène (30 boosters), à partir du temps mesuré ci-dessus\n",
"vitesse = 3 ** len(BOOSTERS) / duree_n # decks testés par seconde\n",
"secondes = 3 ** 30 / vitesse\n",
"print()\n",
"print(f\"Vitesse mesurée : environ {vitesse:.0f} decks testés par seconde.\")\n",
"print(f\"Pour 30 boosters : {3 ** 30} decks à tester,\")\n",
"print(f\"soit environ {secondes / (365 * 24 * 3600):.0f} années de calcul.\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Question 4 — correction\n",
"\n",
"**1.** Chaque booster ajouté multiplie le nombre de decks par **3** — et le temps de calcul\n",
"également. On parle de coût **exponentiel**.\n",
"\n",
"**2.** Pour 30 boosters : $3^{30} \\approx 2 \\times 10^{14}$ decks, soit environ\n",
"**200 000 milliards**.\n",
"\n",
"Le calcul se fait sans connaître la machine : passer de 10 à 30 boosters, c'est multiplier\n",
"le travail par $3^{20} \\approx 3,5$ milliards. Le temps mesuré pour 10 boosters (quelques\n",
"centièmes de seconde) devient donc **plusieurs années** de calcul — la cellule précédente\n",
"affiche l'estimation exacte pour ta machine.\n",
"\n",
"Et gagner un facteur 100 sur la vitesse de la machine ne ferait pas gagner grand-chose :\n",
"il suffirait d'ajouter 5 boosters pour tout reperdre. La force brute n'est pas « lente »,\n",
"elle est **impraticable** : aucune optimisation de code ne rattrape une croissance\n",
"exponentielle.\n",
"\n",
"**3.** Le glouton est le bon choix dès que le nombre de possibilités explose, ou qu'il faut\n",
"une réponse immédiate. Il donne une solution correcte en un temps proportionnel au nombre\n",
"d'étapes (ici : 10 boosters → 10 tours de boucle) sans jamais garantir l'optimum.\n",
"\n",
"Un algorithme de ce type — rapide, raisonnable, sans garantie d'optimalité — s'appelle une\n",
"**heuristique**."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Ce qu'il faut retenir\n",
"\n",
"| | Glouton | Force brute |\n",
"|---|---|---|\n",
"| Résultat | 46 (pas l'optimum) | 58 (l'optimum) |\n",
"| Nombre d'opérations | 10 (une par booster) | 59 049 |\n",
"| Avec 30 boosters | 30 opérations | plusieurs années |\n",
"| Garantie | aucune | optimum certain |\n",
"\n",
"Un **algorithme glouton** construit une solution par étapes, en faisant à chaque étape\n",
"le meilleur choix selon un critère local, et **sans jamais revenir en arrière**.\n",
"\n",
"Il est rapide. Il n'est pas toujours optimal. Quand ce n'est pas le cas, on parle\n",
"d'**heuristique**.\n",
"\n",
"---"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Pour aller plus loin — correction\n",
"\n",
"**1.** Les gains sont +1, +1, +1, +5, +7, 1, +3, +2, +1, +1. Les trois plus gros sont\n",
"+7 (booster 5), +5 (booster 4) et +3 (booster 7) : exactement les cartes chères du deck\n",
"optimal. Cette stratégie donne bien **58**.\n",
"\n",
"Ce n'est pas un hasard : le choix fait dans un booster n'influence les autres que par le\n",
"quota de cartes chères. Le problème revient donc à choisir les 3 boosters où dépenser une\n",
"place, et prendre les 3 meilleurs gains est ici optimal.\n",
"\n",
"**Un glouton peut donc être optimal — encore faut-il l'appliquer au bon critère.** Le mauvais\n",
"glouton ne triait pas sur la puissance de la carte, mais sur ce qu'elle rapporte *vraiment*\n",
"par rapport à l'alternative.\n",
"\n",
"**2.** Parce que ce tableau exige de connaître **les 10 boosters à l'avance**. Or dans un vrai\n",
"draft, on découvre les boosters un par un : au moment de choisir dans le premier, les neuf\n",
"autres n'existent pas encore.\n",
"\n",
"C'est la distinction entre un problème **hors ligne** (toutes les données sont connues au\n",
"départ — rendu de monnaie, sac à dos) et un problème **en ligne** (les données arrivent au\n",
"fur et à mesure — le draft). Dans un problème en ligne, même le meilleur critère du monde\n",
"ne peut pas compenser une information qu'on n'a pas encore."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"Auteur : Florian Mathieu\n",
"\n",
"Licence CC BY-SA\n",
"\n",
"<a rel=\"license\" href=\"http://creativecommons.org/licenses/by-sa/4.0/\"><img alt=\"Licence Creative Commons\" style=\"border-width:0\" src=\"https://i.creativecommons.org/l/by-sa/4.0/88x31.png\" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel=\"license\" href=\"http://creativecommons.org/licenses/by-sa/4.0/\">Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International</a>"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}