Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • rvuillem/inf-tc1
  • hwei/inf-tc1
  • nmbengue/inf-tc1
  • fernanda/inf-tc1
  • mleger/inf-tc1
  • lmeng/inf-tc1
  • gferryla/inf-tc1
  • jconso/inf-tc1
  • smaghsou/inf-tc1
  • emarquet/inf-tc1
  • ecluzel/inf-tc1
  • aaudeoud/inf-tc1
  • tsegond/inf-tc1
  • aetienne/inf-tc1
  • djoly/inf-tc1
  • bcampeas/inf-tc1
  • dnovarez/inf-tc1
  • ruetm/inf-tc1
  • cchenu/inf-tc1
  • cguiotdu/inf-tc1
  • mclouard/inf-tc1
  • gwachowi/inf-tc1
  • qbaalaou/inf-tc1
  • sbrocas/inf-tc1
  • ppupion/inf-tc1
  • kinty/inf-tc1
  • hadomo/inf-tc1
  • tgicquel/inf-tc1
  • rhahn/inf-tc1
  • cguyau/inf-tc1
  • mpairaul/inf-tc1
  • rmuller/inf-tc1
  • rlecharp/inf-tc1
  • asebasty/inf-tc1
  • qmaler/inf-tc1
  • aoussaid/inf-tc1
  • kcherigu/inf-tc1
  • sgu/inf-tc1
  • malcalat/inf-tc1
  • afalourd/inf-tc1
  • phugues/inf-tc1
  • lsteunou/inf-tc1
  • llauschk/inf-tc1
  • langloia/inf-tc1
  • aboucard/inf-tc1
  • wmellali/inf-tc1
  • ifaraidi/inf-tc1
  • lir/inf-tc1
  • ynedjar/inf-tc1
  • schneidl/inf-tc1
  • zprandi/inf-tc1
  • acoradid/inf-tc1
  • amarcq/inf-tc1
  • dcombet/inf-tc1
  • gplaud/inf-tc1
  • mkernoaj/inf-tc1
  • ldiciocc/inf-tc1
  • gbichot/inf-tc1
  • tdutille/inf-tc1
59 results
Select Git revision
Show changes
Commits on Source (14)
Showing
with 333 additions and 183 deletions
File deleted
No preview for this file type
This diff is collapsed.
%% Cell type:markdown id:4770edbf tags:
%% Cell type:markdown id:39a8f123 tags:
NAME:
%% Cell type:markdown id:e1098061-ab50-4ba8-aa59-0b76ec1049a2 tags:
# INF TC1 - TD3 (2h) - Arbres binaires
%% Cell type:markdown id:fc6c7558-96c4-435b-a841-e38c5d14bc9f tags:
---
%% Cell type:markdown id:b0997389-5a87-4e8d-9600-29ed76e01759 tags:
<details style="border: 1px">
<summary> RAPPELS SUR L'UTILISATION DES NOTEBOOKS</summary>
### Comment utiliser ces notebooks ?
Le but de votre travail est de répondre aux questions des exercices en **remplissant certaines cellules de ce notebook avec votre solution**. Ces cellules, une foit remplies et lancées au fur et à mesure de vos avancées, permettront de valider des tests écrits dans d'autres cellules de ce notebook. **Il est donc important de bien suivre les instructions et répondre aux questions dans l'ordre**, et ne pas changer le nom des fonctions et/ou les cellules. En particulier :
1) Répondez aux questions dans les cellules en dessous des questions.
2) Votre code devra remplacer le texte suivant :
```python
# YOUR CODE HERE
raise NotImplementedError()
```
(vous pouvez effacer ces deux lignes quand vous les rencontrez mais ne modifiez pas les noms de fonctions sinon les tests ne marchent plus).
3) Exécuter enfin les cellules dans leur ordre d'apparition, de haut en bas et si votre code est correct alors les tests (sous forme d'`assert` seront validés (ils ne lanceront pas d'exception du type `AssertionError` ). Vous pouvez lancer plusieurs fois la même cellule, cela ne pose pas de soucis.
4) Vous pouvez créer de nouvelles cellules comme bon vous semble.
**En cas de problème, une solution est de relancer les cellules depuis le début du notebook une par une.** Pensez à bien sauvegarder ce notebook et ne pas le remplacer par un notebook qui a le même nom.
</details>
%% Cell type:markdown id:de645c99-52cf-4cff-9c6b-b156101ad47c tags:
## Objectif du TD
Ce TD vous fera manipuler les Arbres Binaires, qui sont une structure de données permettant de stocker des informations de manière hierarchique, et de les récupérer grace à des méthodes de parcours.
%% Cell type:markdown id:abde77ea-e21d-434e-b72e-a62ac464c793 tags:
## Exercice 1 : Introduction aux Arbres Binaires
Dans ce exercice nous allons créer et parcourir un Arbre Binaire. Un Arbre Binaire est un Arbre qui a les propriétés suivantes :
Dans ce exercice vous allez créer et parcourir un Arbre Binaire. Un Arbre Binaire est un Arbre qui a les propriétés suivantes :
- il comporte des noeuds ayant au _maximum deux enfants_
- il est dit _binaire_ car il comporte des noeuds ayant au _maximum_ deux enfants
- il est dit _complet_ si tous les niveaux de l'arbre sont remplis (sauf éventuellement le dernier), et dans lequel les feuilles sont alignées à gauche
- il est dit _équilibré_ si la différence de hauteur entre les sous-arbres gauche et droit de chaque nœud est au plus 1
Voici un exemple d'Arbre Binaire :
```
1
/ \
2 3
```
Dans cet exercice nous stockeront l'Arbre Binaire avec une structure de donnée _explicite_ en POO (comme ci-dessous) qui contient des valeurs entières (et uniques, à savoir deux noeuds n'auront pas la même valeur `value`) dans chaque noeud :
Dans cet exercice nous stockeront l'Arbre Binaire avec une structure de donnée _explicite_ (en POO comme ci-dessous, et non plus dans un tableau comme pour le tas dans le TD 2) qui contient des valeurs entières (et uniques, à savoir deux noeuds n'auront pas la même valeur `value`) dans chaque noeud :
%% Cell type:code id:9318f291-f289-4eb6-a7f7-94cbc4e3f459 tags:
``` python
class Node:
def __init__(self, value : int, left = None, right = None):
self.value = value
self.left = left
self.right = right
```
%% Cell type:markdown id:31993aa6-7361-4a58-856d-32f3a3fcec36 tags:
**Question 1.1** - Utilisez la structure de donnée `Node` ci-dessus afin d'implémenter l'arbre donné en introduction. Votre arbre sera stocké dans la variable `root`. Vous pouvez rajouter des noeuds supplémentaires à cet arbre (mais il doit rester binaire).
%% Cell type:code id:48a9b647-260e-40a5-a3da-c11ed77a9e62 tags:
``` python
root = Node(1) # a modifier
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:0a4461c4-09b8-4f9f-ad70-6b953dc4b7e6 tags:
Les tests suivant doivent être validés (vous pouvez rajouter d'autres tests) :
Les tests suivant doivent être validés (rajoutez d'autres tests) :
%% Cell type:code id:c08d3050-ccba-4b51-8a01-1d2689455e96 tags:
``` python
assert root.value == 1
assert root.left.value == 2
assert root.right.value == 3
```
%% Cell type:markdown id:24670502-ecd5-4d86-a32c-1d7d43eb04fe tags:
**Question 1.2.** Proposer une méthode `bfs` de _parcours en largeur_ de l'Arbre afin d'afficher la valeur des noeuds dans l'ordre croissant. Pour cela vous utiliserez une structure de données de File (sous forme de liste, cela sera suffisant). Une méthode possible pour mener cela à bien consiste à :
**Question 1.2.** Proposez une méthode `bfs` de _parcours en largeur_ de l'Arbre afin d'afficher la valeur des noeuds dans l'ordre croissant. Pour cela vous utiliserez une structure de données de File (sous forme de liste, cela sera suffisant). Une méthode possible pour mener cela à bien consiste à :
1. Intialiser la File avec la racine de l'Arbre
2. Dé-filer une valeur et la stocker dans une liste de résultat
3. En-filer ses enfants (si il y en a) dans la File
4. Répéter l'étape 2 jusqu'à ce que la File soit vide, renvoyer le résultat
1. intialiser la File avec la racine de l'Arbre;
2. dé-filer une valeur et la stocker dans une liste de résultats;
3. en-filer ses enfants (si il y en a) dans la File;
4. répéter l'étape 2 jusqu'à ce que la File soit vide, renvoyer le résultat.
%% Cell type:code id:1f92e83b-ba51-4fa4-a127-2aa970a96a26 tags:
``` python
def bfs(node: Node):
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:c1b198c7-8387-4552-a163-7a70a70c61a3 tags:
``` python
assert bfs(root) == [1, 2, 3]
```
%% Cell type:markdown id:675c8c46-ed69-4a2e-a9cf-57301112e3ab tags:
**Question 1.3** - Écrire une fonction `depth` de calcul de la _profondeur_ d'un noeud d'un Arbre. La profondeur est défini comme la distance entre ce noeud et la racine (celle-ci aura une profondeur de `0`) ou autrement dit le nombre d'arête entre le noeud et la racine. Cette fonction prendra en paramètre la racine de l'arbre `root`, ainsi que le noeud dont on veut calculer la profondeur avec le paramètre `target_value`.
**Question 1.3** - Écrivez une fonction `depth` de calcul de la _profondeur_ d'un noeud d'un Arbre. La profondeur est défini comme la distance entre ce noeud et la racine (celle-ci aura une profondeur de `0`). Autrement dit la profondeur correspond au nombre d'arêtes entre un noeud et la racine de l'arbre. Cette fonction prendra en paramètre la racine de l'arbre `root`, ainsi que le noeud dont on veut calculer la profondeur avec le paramètre `target_value`.
Conseils :
- s'inspirer de la fonction précédente en incluant la profondeur de chaque noeud parcouru lors de son ajout dans la file;
- autrement dit rajouter un tuple `(noeud, profondeur)` au lieu du simple noeud parcouru.
- s'inspirer de la fonction précédente de parcours en largeur en incluant la profondeur de chaque noeud parcouru lors de son ajout dans la file;
- autrement dit rajouter un tuple `(noeud, profondeur)` au lieu du simple noeud parcouru afin de mémoriser la profondeur des noeuds parcourus.
%% Cell type:code id:6c21f3f7-0f21-47ad-a4ce-a2af7b9743d4 tags:
``` python
def depth(root: Node, target_value: int) -> int:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:37a27a50-68f4-486b-a0da-b0905b8db8dd tags:
``` python
assert depth(root, 1) == 0
assert depth(root, 2) == 1
assert depth(root, 3) == 1
```
%% Cell type:markdown id:f8abb2f4-24dc-4346-9081-4400df30e3af tags:
**Question 1.4** - Écrire une fonction `height` de calcul de la _hauteur_ d'un Arbre définie comme la prodondeur maximale possible dans un Arbe. Vous pouvez l'implémenter comme la profondeur maximale des noeuds de l'Arbre, ou bien de manière récursive.
**Question 1.4** - Écrivez une fonction `height` de calcul de la _hauteur_ d'un Arbre définie comme la prodondeur maximale possible dans un Arbe. Vous pouvez l'implémenter comme la profondeur maximale des noeuds de l'Arbre, ou bien de manière récursive.
%% Cell type:code id:4944b0bb-5f59-41c2-b5d9-052a0d5386dd tags:
``` python
def height(root: Node) -> int:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:2d11935c-2212-4ec3-ba57-5303ec05b274 tags:
``` python
assert height(root) == 2
assert height(root) == 1
```
%% Cell type:markdown id:29fca7e0-aaf9-495b-822d-f20e1a672092 tags:
**Question 1.5** - Valider si l'Arbre est bien équilibré, autrement dit si il n'y a pas de différence de profondeur suppérieur à 1 entre les feuilles d'un arbre.
**Question 1.5** - Ecrivez une fonction de validation afin de déterminer si un Arbre est bien équilibré, autrement dit si il n'y a pas de différence de profondeur suppérieur à 1 entre les feuilles d'un arbre. Conseil : utilisez une approche récursive.
%% Cell type:code id:3b4f56e8-e4bf-4d27-97eb-b24995d741fe tags:
``` python
def est_equlibre(root: Node) -> bool:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:69b03c9b-2092-4343-b9e4-7d9904a445b0 tags:
``` python
assert est_equlibre(root)
```
%% Cell type:markdown id:9e29fddc-c249-4b2a-a9d3-26e56f5474ab tags:
## Exercice 2 : Arbres syntaxiques
Un Arbre _syntaxique_ permet le stockage d'une expression structurée, par exemple une équation. Dans cet exercice nous allons modéliser un tel arbre sous forme d'arbre binaire (exemple ci-dessous à gauche) afin de réaliser un calcul arithmétique simple à partir de l'expression fournie de manière textuelle :
Expression : `(3-2) * (7+(10/2))`
Résultat : `12.0`
Nous ferons l'hypothèse que les opérations sont limitées à `+ - / *`, seront toujours binaires et porteront sur des valeurs numériques entières (mais le résultat peut ne pas être un entier).
```
*
/ \
- +
/ \ / \
3 2 7 /
/ \
10 2
```
Vous utiliserez la structure d'arbre binaire ci-dessous afin de le stocker :
Vous utiliserez la structure d'arbre binaire ci-dessous afin de stocker l'arbre comme pour l'abre binaire précédent :
%% Cell type:code id:e78ec913-cd5f-4304-8c8a-438758d909f4 tags:
``` python
class Noeud:
def __init__(self, v = None, g = None, d = None) -> None:
self.valeur = v
self.gauche = g
self.droit = d
```
%% Cell type:markdown id:88fbc62f-ae5a-4765-bf8f-bb127f5d5a13 tags:
**Question 2.1** - Utilisez la structure de données d'Arbre ci-dessus afin de stocker l'Arbre syntaxique donné en exemple.
**Question 2.1** - Stockez l'Arbre syntaxique donné en exemple graphique en utilisant la structure de données d'arbre binaire ci-dessus (il s'agit d'écrire une instance d'arbre dans la variable `arbre`, et non une fonction).
%% Cell type:code id:4661ca46-489c-4330-9e72-64fe25e4b49c tags:
``` python
arbre = None # à changer
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:cee8c8d3-f4ab-4dcc-931e-f0b8681b225c tags:
``` python
assert arbre.valeur == "*"
assert arbre.gauche.valeur == "-"
assert arbre.droit.valeur == "+"
```
%% Cell type:markdown id:0be8aab9-1aba-4340-9235-eb3d1cc7fd29 tags:
**Question 2.2** - Implémenter désormais une méthode d'évaluation (autrement dit de calcul) automatique d'un arbre syntaxique tel que vous l'avez stocké dans la variable `arbre` ci-dessus.
**Question 2.2** - Implémentez désormais une méthode d'évaluation (autrement dit de calcul) automatique d'un arbre syntaxique tel que vous l'avez stocké dans la variable `arbre` ci-dessus.
Conseil : proposer une solution récursive avec un cas d'arrêt et des appels récursifs comme suit :
Conseil : proposez une solution récursive avec un cas d'arrêt et des appels récursifs comme suit :
- si la valeur du noeud en cours est un opérateur, l'appliquer sur les deux sous-branches;
- si c'est une valeur numérique, renvoyer cette valeur (cas d'arrêt car c'est une feuille de l'arbre)
- si il s'agit d'une valeur numérique, renvoyez cette valeur (cas d'arrêt car c'est une feuille de l'arbre).
%% Cell type:code id:dc899d17-67fd-4cc9-bf92-f5f32e08f89e tags:
``` python
def eval(r: Noeud) -> float:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:00eb3e33-64b1-486f-97c8-d87de80f3a1a tags:
``` python
eval(arbre)
```
%% Cell type:markdown id:de5c394d-df35-4d7b-b057-164620b0b38d tags:
**Question 2.3** - Écrire une méthode permettant de construire l'Arbre à partir d'une expression fournie sous forme de chaîne de caractère en entrée comme `( ( 3 - 2 ) * ( 7 + ( 10 / 2 ) ) )"`. Les espaces sont nécessaires et vous permettront de faire un `.split(" ")}`.
**Question 2.3** - Écrivez une méthode permettant de construire l'Arbre à partir d'une expression fournie sous forme de chaîne de caractère en entrée comme `( ( 3 - 2 ) * ( 7 + ( 10 / 2 ) ) )`. Les espaces sont nécessaires et vous permettront de faire un `.split(" ")`.
Conseil :
- parcourez caractère par caractère l'expression textuelle transformée en liste;
- créez au fur et à mesure l'arbre initialisé par une racine et une variable stockage le noeud courant en cours de construction
- identifiez les 4 cas possibles : début d'une expression, valeurs numériques, opérateur, fin d'une expression
- si une expression commence, créer un nouveau fils gauche au noeud courant et stockez le noeud courant dans une Pile
- créez au fur et à mesure l'arbre initialisé par une racine et une variable stockage le noeud courant en cours de construction;
- identifiez les 4 cas possibles : début d'une expression, valeurs numériques, opérateur, fin d'une expression :
- si une expression commence, créez un nouveau fils gauche au noeud courant et stockez le noeud courant dans une Pile
- si une expression finie, considérez comme noeud courant l'élément que vous dé-Pilerez
- si un opérateur, stocker sa valeur dans le noeud courant, empiliez ce noeud mais avant lui rajoute un fils droit qui devient le noeud courant
- si un opérateur, stockez sa valeur dans le noeud courant, empilez ce noeud mais avant lui rajoute un fils droit qui devient le noeud courant
- si une valeur numérique, la stocker dans le noeud courant et considérez comme noeud courant l'élément que vous dé-Pilerez
%% Cell type:code id:1c33faaa-2289-498f-9ef1-5afeb4da4628 tags:
``` python
def construit_arbre(exp: str) -> int:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:b56d0304-5d23-4e1c-91fb-09b511465ad5 tags:
``` python
r = construit_arbre("( ( 3 - 2 ) * ( 7 + ( 10 / 2 ) )")
```
%% Cell type:markdown id:89a9015c-dd4e-409d-91b2-9b64b55288e9 tags:
Enfin vérifiez que vous obtenez la bonne valeur en évaluant l'expression.
%% Cell type:code id:e5c49c1f-75e0-4872-8578-40a532c7cea1 tags:
``` python
assert eval(r) == 12.0
```
%% Cell type:markdown id:012a5378-a2b7-498f-8115-e35f55c529ce tags:
**Question 2.4 (Bonus) -** - Ecrire une fonction qui renvoie `True` ou `False` si l'Arbre est bien un arbre syntaxique tel que défini en introduction. Autrement dit qu'il est binaire, et comporte des opérateurs partout sauf aux feuilles.
**Question 2.4 (Bonus) -** Ecrire une fonction qui renvoie `True` ou `False` si l'Arbre est bien un arbre syntaxique tel que défini en introduction. Autrement dit qu'il est binaire, et comporte des opérateurs partout sauf aux feuilles.
Proposez une méthode itérative :
Proposez d'abord une méthode itérative :
%% Cell type:code id:823d29e7-66c3-41a0-bdad-da6862a5e420 tags:
``` python
def valide_ite(r: Noeud) -> bool:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:06e73b57-959a-436f-bd7a-e12c615c805c tags:
``` python
assert valide_ite(arbre)
```
%% Cell type:markdown id:dfdf1d42-60bb-4a7e-9cd4-e6cb72b8130a tags:
Proposez une méthode récursive :
Proposez ensuite une méthode récursive :
%% Cell type:code id:df0033e9-c076-4cde-ab6f-982a3677ce05 tags:
``` python
def valide_rec(r: Noeud) -> bool:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:9998c852-ec74-4225-9f01-4368899e303f tags:
``` python
assert valide_rec(arbre)
```
%% Cell type:markdown id:3503e6a5-2bc1-46d2-bef0-d1f1ceb6827e tags:
Montrez que les deux méthodes ont un comportement identique :
%% Cell type:code id:81e1b468-5d22-4a25-a2c3-17ab165627e5 tags:
``` python
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:81842c6c-1784-432b-8322-f3793d633e03 tags:
**Question 2.5 (Bonus) -** Créez une table de hachage afin de mémoriser les opérations déja réalisées pour l'Arbre syntaxique. Conseil : utilisez les informations sur le noeud comme clé (valeurs, opérateur).
%% Cell type:code id:fb91bda6-7076-44be-9014-ceb5507eb01c tags:
``` python
def eval_hash(r: Noeud, memo=None) -> float:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:22979a11-3455-4525-b816-a84b2164291c tags:
``` python
assert eval_hash(arbre) == eval(arbre)
```
%% Cell type:markdown id:85c6a3e6-dea0-4e86-b7d9-b0c5735c3630 tags:
## Pour aller plus loin
- Rajouter des tests dans les exemples ci-dessus avec des arbres plus complexes, des cas particuliers, etc.
- Inclure des Exceptions dans votre code afin de gérer par exemple l'accès à un index de liste inexistant, etc.
- Créer une table de hachage afin de mémoriser les opérations déja réalisées pour l'Arbre syntaxique
- Inclure des [Exceptions](https://docs.python.org/3/tutorial/errors.html) dans votre code afin de gérer par exemple l'accès à un index de liste inexistant, etc.
- Modulariser votre code afin de créer des fonctions auxiliaires (par exemple pour vérifier la valeur des noeuds de l'Arbre syntaxique).
%% Cell type:markdown id:08c48261 tags:
## Visualiser avec Graphviz
Graphviz est une bibliothèque de visualisation de graphe. Elle peut être très pratique afin de comprendre la structure de votre instance de graphe.
Cependant, la bibliothèque pose quelques soucis d'installation en Python, voici des étapes éventuellement à suivre.
Tout d'abord vérifiez que le module est bien installé :
%% Cell type:code id:c8c95b89 tags:
``` python
!pip install graphviz
```
%% Cell type:markdown id:5a9ebd95 tags:
Si oui (vous avez le message du type `Requirement already satisfied:`) testez l'import du module :
%% Cell type:code id:e897987f tags:
``` python
import graphviz
from graphviz import Digraph
```
%% Cell type:markdown id:2bf9b677 tags:
Si vous avez des erreurs, essayer l'installation du module à partir des exécutables
%% Cell type:code id:bd3cfb66 tags:
``` python
https://graphviz.org/
```
%% Cell type:markdown id:9373f3bc tags:
Un autre source d'erreur est l'installation à partir de la mauvaise version de Python qu'il faut utiliser avec pip.
%% Cell type:code id:504eff4a tags:
``` python
!python --version
```
%% Cell type:code id:5eb4ab77 tags:
``` python
from IPython.display import display
def visualize_oop(root):
def build(node, dot=None):
if dot is None:
dot = graphviz.Digraph(format='png')
if node is not None:
dot.node(str(node.value))
if node.left is not None:
dot.edge(str(node.value), str(node.left.value))
build(node.left, dot)
if node.right is not None:
dot.edge(str(node.value), str(node.right.value))
build(node.right, dot)
return dot
return build(root)
```
%% Cell type:code id:ded5b1fe tags:
``` python
visualize_oop(root)
```
......
This diff is collapsed.
%% Cell type:markdown id:052d9fc3 tags:
%% Cell type:markdown id:751c51f9 tags:
NAME:
%% Cell type:markdown id:b97bad7e-82ff-44a7-9779-13c139085623 tags:
# INF TC1 - TD5 (2h) - Devoir à rendre #1
# INF TC1 - TD5 (2h + 2h AUTONOMIE) - Devoir à rendre #1
%% Cell type:markdown id:1bb26026-8560-4a3c-90e6-2cfd7a49320a tags:
---
%% Cell type:markdown id:dd8d4905-55f9-4957-8008-a963cc6de061 tags:
Vous serez évalué sur le rendu de ce TD qui sera à déposer sur Moodle **deux (2) semaines** après les séances d'autonomie et de TD. Le rendu sera à réaliser sous forme de **notebook** qui contient votre code et images.
Vous serez évalué sur le rendu de ce TD qui sera à déposer sur Moodle **deux (2) semaines** après la séance d'autonomie #1. Le rendu sera à réaliser sous forme de **notebook** qui contient votre code et images.
%% Cell type:markdown id:99ee8fad-7f32-4fe2-85d3-3b8da49f317f tags:
<details style="border: 1px">
<summary> MODALITES DE RENDU</summary>
### Comment rendre son devoir ?
Vous serez évalué sur le rendu de ce TD qui sera à déposer sur Moodle **deux (2) semaines** après les séances d'autonomie et de TD. Vous devrez créer une archive (zip, rar, etc.) nomée `nom1-nom2-inf-tc1-td5.zip` qui contiendra tous les éléments de votre rendu (rapport en notebook, code, images de test). Vous pouvez rendre ce rapport seul ou en binôme. Le rendu du TD doit contenir a minima :
1. Toutes les étapes jusqu'à la 6ème doivent avoir été abordées
2. Justifications, illustrations et tests sur plusieurs images
**A garder en tête :**
- Un code fonctionnel et les tests appropriés devront être fournis dans l'archive qui doit être autonome (le correcteur ne doit pas avoir à rajouter d'image ou de fichier supplémentaire)
- Vous fournirez les images de test et leurs résultats; évitez cependant de prendre des tailles d'images trop importantes.
- Le rapport **devra être au format Notebook Jupyter** et comprendre :
- Le détail des étapes que vous avez suivies
- La description de parties de code difficiles
- Tout souci ou point bloquant dans votre code
- Les graphiques et diagrammes nécessaires
- Des analyses et discussions en lien avec votre approche
- Des exemples simples mais aussi difficiles
**Tout travail supplémentaire (méthode originale, optimisation poussée) fera l'objet de points en bonus.**
*Voici une suggestion afin de se faire un ordre d'idée*
En dessous de 10 :
- Les étapes suivies
- Un code fonctionnel et les méthodes basiques
- Un rapport de quelques pages
- Un code certes fonctionnel mais peu commenté
- Les exemples d'images fournies
Un groupe avec une note entre 10 et 12 :
- Les étapes suivies
- Un code fonctionnel et les méthodes basiques
- Un rapport de quelques pages
- Un rapport succint
- Un code certes fonctionnel mais peu commenté
- Les exemples d'images fournies
Un groupe entre 12 et 14 a en plus proposé :
- Des structures de données avancées (Set, Files, etc)
- Des structures de données avancées (Ensembles, Files, etc)
- Une justification de chaque étape
- Une méthode un petit peu plus poussée
Un groupe entre 14 et 16 a en plus proposé :
- Une méthode originale (K-Means, etc)
- Une démarche expérimentale très détaillée sur les optimisations
- Des tests plutôt originaux
Un groupe au-dessus de 16 comporte une ou plusieurs parties exceptionnelles :
- Rapport très détaillé et exemplaire sur le fond comme sur la forme
- Une démarche expérimentale très détaillée sur les optimisations
- Code et tests
</details>
%% Cell type:markdown id:d48155b2-8db8-4557-a66b-363351712560 tags:
## Objectif du devoir
Le but de ce devoir est de **déterminer automatiquement une palette de couleurs optimale** pour une image donnée. Cette palette devra valider les contraintes suivantes :
1. de taille réduite par rapport au nombre initial de couleurs
2. la plus représentative possible des couleurs initiales.
1. utiliser moins de couleurs que le nombre disponible dans l'image donnée;
2. être la plus représentative possible des couleurs de l'image donnée.
En effet une image affichée sur un ordinateur peut être encodée sur 8 bits par composantes rouge, verte et bleue (soit 256 valeurs possibles par composante) ainsi potentiellement utiliser $256 \times 256 \times 256 = 16 777 216$ couleurs. En réalité, beaucoup moins sont utilisées et surtout perceptibles par l'humain. Réduire le nombre de couleur ou réaliser une "_quantification de couleurs_" est une tâche fréquente et c'est une fonctionnalité classique des outils éditeurs d'images (Photoshop, Gimp, etc.) implémentée aussi dans le module Pillow de Python. A noter que cette réduction s'effectue avec perte de couleurs et doit être réalisée avec les bons paramètres (nombre et choix des couleurs) ce qui est votre objectif.
Comme nous l'avons vu dans le TD 4, les couleurs peuvent être encodée par composantes rouge, verte et bleue (soit 256 valeurs possibles par composante, autrement dit sur 8 bits) ainsi potentiellement utiliser $256 \times 256 \times 256 = 16 777 216$ couleurs. En réalité, beaucoup moins sont nécessaires et surtout perceptibles par l'humain. Réduire le nombre de couleurs ou réaliser une "_quantification de couleurs_" est une tâche fréquente et c'est une fonctionnalité classique des outils éditeurs d'images (Photoshop, Gimp, etc.) implémentée aussi dans le module Pillow de Python. A noter que cette réduction s'effectue avec perte de couleurs et doit être réalisée avec les bons paramètres (nombre et choix des couleurs) ce qui est votre objectif.
La figure ci-dessous illustre le problème à résoudre : étant donnée une image en entrée, proposer une liste de couleurs (que l'on appellera la palette), afin de re-colorier une image en sortie.
<div style="text-align:center;">
<table>
<tr>
<td>
<img src="figures/color-rainbow.png" alt="Image originale" style="height:5cm;">
<p>Image originale</p>
<p>Image donnée</p>
</td>
<td>
<img src="figures/rainbow-palette-8.png" alt="Palette de 8 couleurs représentatives" style="height:5cm;">
<p>Palette de 8 couleurs représentatives</p>
</td>
<td>
<img src="figures/rainbow-recoloriee.png" alt="Image originale recoloriée avec la palette" style="height:5cm;">
<p>Image originale recoloriée avec la palette</p>
<p>Image donnée recoloriée avec la palette</p>
</td>
</tr>
</table>
</div>
%% Cell type:markdown id:fd464e65-adfe-4e11-bf87-f12c513fbaea tags:
## Étapes de travail
Voici des étapes de travail suggérées :
1. Prendre en main une image de votre choix (pas trop grande) en la chargeant avec PIL. Lister les couleurs présentes, identifier celles qui sont uniques et leur fréquence. Vous pouvez pour cela utiliser [Matplotlib](https://matplotlib.org/stable/gallery/index.html).
1. Prenez une image de votre choix (pas trop grande) en la chargeant avec PIL. Lister les couleurs présentes, identifier celles qui sont uniques et leur fréquence.
2. Proposer une méthode (naïve pour commencer) de choix d'une palette de $k$ couleurs. Affichez là sous forme d'image (exemple de d'image au milieu de la figure du dessus) avec une nouvelle image PIL. Utilisez également des images simples où le résultat attendu est connu comme mour les images ci-dessous :
2. Proposez une méthode (naïve pour commencer) de choix d'une palette de $k$ couleurs. Affichez là sous forme d'image (exemple de d'image au milieu de la figure du dessus) avec une nouvelle image PIL. Utilisez également des images simples où le résultat attendu est connu comme pour les images ci-dessous :
<div style="text-align:center;">
<table>
<tr>
<td>
<img src="figures/1-color-back.png" alt="1 couleur noir" style="width:3cm;">
<p>1 couleur noir</p>
</td>
<td>
<img src="figures/4-color.png" alt="4 couleurs" style="width:3cm;">
<p>4 couleurs</p>
</td>
</tr>
</table>
</div>
3. Re-colorier une image avec une palette de $k$ couleurs, et afficher le résultat sous forme d'image PIL. Pour re-colorier chaque pixel, prendre la couleur la plus proche dans la palette en utilisant une fonction de distance (Euclidienne par exemple).
3. Re-coloriez une image avec une palette de $k$ couleurs, et affichez le résultat sous forme d'image PIL. Pour re-colorier chaque pixel, prendre la couleur la plus proche dans la palette en utilisant une fonction de distance (Euclidienne par exemple..).
4. Proposer une méthode de validation de votre approche. Par exemple afficher la différence entre l'image originale et celle re-coloriée. Calculer un score global d'erreur.
4. Proposez une méthode de validation de votre approche. Par exemple affichez la différence entre l'image originale et celle re-coloriée. Calculez un score global d'erreur.
5. Améliorer le choix des $k$ couleurs afin de minimiser l'erreur entre l'image originale et re-coloriée. Une piste possible est de trier les couleurs dans une liste, diviser cette liste en $k$ intervals de couleurs et prendre la couleur du milieu de chaque interval. D'autres méthodes plus avancées peuvent être explorées !
5. Améliorez le choix des $k$ couleurs afin de minimiser l'erreur entre l'image originale et re-coloriée. Une piste possible est de trier les couleurs dans une liste, diviser cette liste en $k$ intervals de couleurs et prendre la couleur du milieu de chaque interval. D'autres méthodes plus avancées peuvent être explorées !
6. Tester sur plusieurs images de votre choix ou générées automatiquement avec un nombre et une distribution connue de couleurs. Comparer les performances de vos techniques avec d'autres méthodes (cette fois vous pouvez utiliser un éditeur de texte ou la fonction _quantize_ de PIL [(doc)](https://pillow.readthedocs.io/en/stable/reference/Image.html).
6. Testez votre palette sur plusieurs images de votre choix ou générées automatiquement avec un nombre et une distribution connue de couleurs. Comparer les performances de vos techniques avec d'autres méthodes (cette fois vous pouvez utiliser un éditeur de texte ou la fonction _quantize_ de PIL [(doc)](https://pillow.readthedocs.io/en/stable/reference/Image.html).
7. Utiliser un pré-traitement des images (flou gaussien, etc) afin de lisser les couleurs est une piste afin de choisir de meilleurs couleurs représentatives. Proposez une quantification de cette amélioration (ou de déterioration éventuelle).
7. Utilisez un pré-traitement des images (flou gaussien, etc) afin de lisser les couleurs. Cela est une piste afin de choisir de meilleurs couleurs représentatives. Proposez une comparaison de cette amélioration (ou de déterioration éventuelle) avec les autres méthodes.
7. Proposer une méthode d'amélioration de calcul de la distance entre deux couleurs, vous pouvez vous baser sur d'autres espaces de couleur [(doc)](https://fr.wikipedia.org/wiki/Espace_de_couleur). Cette partie est difficile, les espaces de couleurs possibles sont complexes à comprendre.
8. Proposez une méthode d'amélioration de calcul de la distance entre deux couleurs, vous pouvez vous baser sur d'autres espaces de couleur [(doc)](https://fr.wikipedia.org/wiki/Espace_de_couleur). Cette partie est difficile, les espaces de couleurs possibles sont complexes à comprendre.
8. Optimiser les étapes précédentes (complexité, espace nécessaire, structures de données, etc.).
9. Optimisez les étapes précédentes (complexité, espace nécessaire, structures de données, etc.) et justifiez vos choix.
### Bonus
10. Créer une palette représentative à partir de plusieurs images.
10. Créez une palette représentative à partir de plusieurs images.
......
%% Cell type:markdown id:caaef4d1 tags:
%% Cell type:markdown id:886b66ed tags:
NAME:
%% Cell type:markdown id:6d71a1d5-6589-4320-900f-b07f08df01f4 tags:
# INF TC1 - TD6 (2h) - Automates
%% Cell type:markdown id:54c3bdf8-4ded-45da-a79a-6530af149f51 tags:
---
%% Cell type:markdown id:33320365-404e-4424-96d1-6e6b742c8f44 tags:
## Objectif du TD
Dans ce TD nous allons introduire les automates, qui sont (en informatique) un modèle de calcul permettant de déterminer si une séquence d'information est valide ou non, selon une règle déterminée. Dans un premier temps nous allons définir des automates simples, et ensuite les implémenter en Python et résoudre des problèmes de complexité croissante.
%% Cell type:markdown id:02ba4e95-34be-41b9-b36c-255b5af6b3de tags:
## Qu'est-ce qu'un automate ?
Un automate est un outil de calcul permettant la validation de séquences d'instructions, à base d'états et de transitions. Un exemple d'automate est un feu tricolore, où :
- les **états** sont la couleur du feu (rouge, vert ou orange)
- les **transitions** les changements possibles de couleurs (du rouge au vert, du vert au orange, et du orange au rouge).
Les automates permettent donc de formaliser le fonctionnement d'un système et de détecter des erreurs éventuelles qui ne respectent pas les changements pré-définis (en reprenant l'exemple du feu tricolore, passer du vert au rouge directement est une erreur). Les applications des automates sont nombreuses et offrent souvent un code plus facile à écire et lire.
%% Cell type:code id:ca93dafe-32d7-4332-9d26-c57c928805ee tags:
``` python
from IPython.core.display import HTML
HTML('<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><!-- Generated by graphviz version 7.1.0 (20230121.1956) --><!-- Pages: 1 --><svg width="131pt" height="52pt" viewBox="0.00 0.00 131.00 52.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 48)"><polygon fill="white" stroke="none" points="-4,4 -4,-48 127,-48 127,4 -4,4"/><g id="node1" class="node"><title>0</title><ellipse fill="none" stroke="black" stroke-width="2" cx="18" cy="-22" rx="18" ry="18"/><text text-anchor="middle" x="18" y="-18.3" font-family="Times,serif" font-size="14.00">0</text></g><g id="node2" class="node"><title>1</title><ellipse fill="none" stroke="black" cx="101" cy="-22" rx="18" ry="18"/><ellipse fill="none" stroke="black" cx="101" cy="-22" rx="22" ry="22"/><text text-anchor="middle" x="101" y="-18.3" font-family="Times,serif" font-size="14.00">1</text></g><g id="edge1" class="edge"><title>0→1</title><path fill="none" stroke="black" d="M36.18,-22C45.15,-22 56.45,-22 67.03,-22"/><polygon fill="black" stroke="black" points="67,-25.5 77,-22 67,-18.5 67,-25.5"/><text text-anchor="middle" x="57.5" y="-25.8" font-family="Times,serif" font-size="14.00">a</text></g></g></svg>')
```
%% Cell type:markdown id:07c95f05-1782-4f25-80fe-9dcf93bdedc6 tags:
## Définitions
Un automate possède une structure de données similaire à un graphe orienté, où **chaque nœud représente un état** et un **arc représente une transition possible d'un état à un autre**. Ce graphe est ensuite parcouru à partir de `mots` (par exemple : `aba`), qui sont une suite de symboles (comme les lettres `a` et `b`) permettant de passer d'un état à un autre. L'état initial (unique) est représenté visuellement par un cercle en gras, et le dernier état (il peut y en avoir plusieurs) par un double cercle. Les symboles `a` et `b` constituent l'alphabet de l'automate (il est possible d'utiliser tout type d'alphabet, comme les transitions d'un feu tricolore).
Dans l'exemple ci-dessus, si le mot à lire est `a`, l'automate commence à lire le mot depuis l'état `0` et réalise ensuite une transition vers l'état `1` et s'arrête. Comme l'état `1` est un état final le mot `a` est validé. On parlera de motif pour indiquer les familles de mots validés par un automate, comme par exemple les mots qui commencent par `a` nous notons `a*` (l'astérisque indiquant que tout symbole peut être utilisé).
De manière générale, un automate est défini comme $A = (\Sigma, S, s_{0}, \delta, F)$, avec :
- $\Sigma$, un ensemble fini, non vide de symboles qui est l'alphabet d'entrée
- $S$, un ensemble fini, non vide d'états
- $s_{0}$, l'état initial, élément de $S$
- $\delta$, la fonction de transition d'états: $\delta : S \times \Sigma \rightarrow S$
- $F$ est l'ensemble des états terminaux, un sous-ensemble (éventuellement vide) de $S$
%% Cell type:markdown id:15b6ca9c-be1c-4757-b02c-1511fed5df68 tags:
## Dessin d'automates
Pour dessiner des automates, nous utiliserons [Graphviz](https://graphviz.org/), un outil en ligne de commande qui permet de dessiner des graphes basé sur le langage [DOT](https://graphviz.org/doc/info/lang.html). Un exemple d'automate est donné ci-dessous :
```python
from graphviz import Digraph
dot = Digraph()
dot.graph_attr['rankdir'] = 'LR'
dot.node('A', shape='circle', style='bold', label='0')
dot.node('B', shape='doublecircle', label='1')
dot.edge('A', 'B', label='a')
dot.edge('B', 'A', label='a')
dot
``````
%% Cell type:markdown id:07d1cd6d-1845-4707-b126-fbe219408a92 tags:
**IMPORTANT :** vérifier que le code ci-dessus s'exécute bien (dans la cellule ci-dessous). Si cela n'est pas le cas alors suivez ces [instructions d'installation de la bibliothèque Graphviz](https://gitlab.ec-lyon.fr/rvuillem/inf-tc1/-/blob/master/graphviz.ipynb).
%% Cell type:code id:bff8d514-4ab5-4a24-81c2-ecd7ef39aa9e tags:
``` python
from graphviz import Digraph
dot = Digraph()
dot.graph_attr['rankdir'] = 'LR'
dot.node('0', shape='circle', style='bold', label='0')
dot.node('1', shape='doublecircle', label='1')
dot.edge('0', '1', label='a')
dot
```
%% Cell type:markdown id:5e0cae70-8346-4664-af97-02fb5a426518 tags:
**VERSION EN LIGNE SANS GRAPHVIZ** : si l'installation de graphviz ne fonctionne pas, vous pouvez utiliser une [version en ligne](https://dreampuf.github.io/GraphvizOnline/#digraph%20G%20%7B%0A%20%20rankdir%3DLR%3B%0A%20%20a%20-%3E%20b%20%5Blabel%3D%22X%22%5D%3B%0A%0A%20%20a%20%5Bshape%3Dcircle%2C%20style%3Dbold%5D%3B%0A%20%20b%20%5Bshape%3Ddoublecircle%5D%3B%0A%7D) afin de réaliser le dessins sous forme de code, et exporter le résultat en image à inclure dans une cellule comme ci-dessous.
%% Cell type:code id:0178c283 tags:
``` python
# version sans graphviz : version qui contient une image au lieu de code graphviz
from IPython.core.display import HTML
HTML('<img src="figures_automates/0_1.png">')
```
%% Cell type:markdown id:ad7810cc-eefc-4733-993e-50c131c3e514 tags:
## Exercice 1 : Automates simples
Dans cette section, nous vous demandons de proposer un automate qui valide un motif donné. Pour les questions 1.1, 1.2 et 1.3, nous considérons un alphabet contenant les lettres `a` et `b` seulement. Vous pouvez répondre aux questions sur papier de préférence, ou utiliser le code ci-dessus du module `graphviz` pour dessiner l'automate.
**Question 1.1 -** Proposer un automate qui contient un nombre paire de fois la lettre `a`.
%% Cell type:code id:d33a29aa-5e07-476d-bfa9-4ef7454bc6f4 tags:
``` python
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:3187d0f6 tags:
``` python
# version sans graphviz
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:87566002-5968-4b5f-aa05-c108bfe1cf33 tags:
**Question 1.2 -** Proposer un automate qui valide le motif `a*a` (les mots qui commencent et finissent par `a`, de taille > 2).
%% Cell type:code id:a8a32710-840f-4385-a85f-670c992ddca7 tags:
``` python
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:9017ef71 tags:
``` python
# version sans graphviz
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:70c4a996-c123-49c9-b771-b58046b6bb97 tags:
penser à proposer une liste de mots à valider et à ne pas valider afin de guider les étudiants.
%% Cell type:markdown id:14d7d169-fa9b-4c90-87d9-b07cfb1cc323 tags:
**Question 1.3 -** Quel langage valide l'automate ci-dessous ? Donnez un exemple de mots validés et le langage.
%% Cell type:code id:b83f2bad-2864-4eeb-9851-510c7d5dd273 tags:
``` python
from IPython.core.display import HTML
HTML('<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Generated by graphviz version 7.1.0 (20230121.1956) --><!-- Pages: 1 --><svg width="368pt" height="118pt" viewBox="0.00 0.00 368.00 118.00" version="1.1" id="svg1036" sodipodi:docname="a.svg" inkscape:version="1.2.2 (b0a8486, 2022-12-01)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <defs id="defs1040" /> <sodipodi:namedview id="namedview1038" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="pt" showgrid="false" inkscape:zoom="1.5" inkscape:cx="245.66667" inkscape:cy="78.666667" inkscape:window-width="1309" inkscape:window-height="456" inkscape:window-x="0" inkscape:window-y="25" inkscape:window-maximized="0" inkscape:current-layer="svg1036" /> <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 114)"> <polygon fill="white" stroke="none" points="-4,4 -4,-114 364,-114 364,4 -4,4" id="polygon906" /> <!-- Q_0 --> <g id="node1" class="node"> <title id="title908">Q_0</title> <ellipse fill="none" stroke="black" stroke-width="2" cx="18" cy="-39" rx="18" ry="18" id="ellipse910" /> <text text-anchor="middle" x="18" y="-35.3" font-family="Times,serif" font-size="14.00" id="text912">0</text> </g> <!-- Q_0&#45;&gt;Q_0 --> <g id="edge2" class="edge"> <title id="title915">Q_0-&gt;Q_0</title> <path fill="none" stroke="black" d="M11,-56.04C9.57,-65.86 11.91,-75 18,-75 21.52,-75 23.79,-71.94 24.8,-67.47" id="path917" /> <polygon fill="black" stroke="black" points="28.3,-67.61 24.97,-57.55 21.3,-67.49 28.3,-67.61" id="polygon919" /> <text text-anchor="middle" x="18" y="-78.8" font-family="Times,serif" font-size="14.00" id="text921">b</text> </g> <!-- Q_1 --> <g id="node2" class="node"> <title id="title924">Q_1</title> <ellipse fill="none" stroke="black" cx="97" cy="-52" rx="18" ry="18" id="ellipse926" /> <text text-anchor="middle" x="97" y="-48.3" font-family="Times,serif" font-size="14.00" id="text928">1</text> </g> <!-- Q_0&#45;&gt;Q_1 --> <g id="edge1" class="edge"> <title id="title931">Q_0-&gt;Q_1</title> <path fill="none" stroke="black" d="M34.18,-47.66C40.16,-50.6 47.21,-53.52 54,-55 58.45,-55.97 63.23,-56.29 67.9,-56.22" id="path933" /> <polygon fill="black" stroke="black" points="67.86,-59.73 77.58,-55.49 67.34,-52.75 67.86,-59.73" id="polygon935" /> <text text-anchor="middle" x="57.5" y="-58.8" font-family="Times,serif" font-size="14.00" id="text937">a</text> </g> <!-- Q_1&#45;&gt;Q_0 --> <g id="edge4" class="edge"> <title id="title940">Q_1-&gt;Q_0</title> <path fill="none" stroke="black" d="M80.82,-43.34C74.84,-40.4 67.79,-37.48 61,-36 56.55,-35.03 51.77,-34.71 47.1,-34.78" id="path942" /> <polygon fill="black" stroke="black" points="47.14,-31.27 37.42,-35.51 47.66,-38.25 47.14,-31.27" id="polygon944" /> <text text-anchor="middle" x="57.5" y="-39.8" font-family="Times,serif" font-size="14.00" id="text946">b</text> </g> <!-- Q_2 --> <g id="node3" class="node"> <title id="title949">Q_2</title> <ellipse fill="none" stroke="black" cx="176" cy="-52" rx="18" ry="18" id="ellipse951" /> <text text-anchor="middle" x="176" y="-48.3" font-family="Times,serif" font-size="14.00" id="text953">2</text> </g> <!-- Q_1&#45;&gt;Q_2 --> <g id="edge3" class="edge"> <title id="title956">Q_1-&gt;Q_2</title> <path fill="none" stroke="black" d="M115.47,-52C124.53,-52 135.83,-52 146.14,-52" id="path958" /> <polygon fill="black" stroke="black" points="146.08,-55.5 156.08,-52 146.08,-48.5 146.08,-55.5" id="polygon960" /> <text text-anchor="middle" x="136.5" y="-55.8" font-family="Times,serif" font-size="14.00" id="text962">a</text> </g> <!-- Q_2&#45;&gt;Q_2 --> <g id="edge6" class="edge"> <title id="title965">Q_2-&gt;Q_2</title> <path fill="none" stroke="black" d="M169,-69.04C167.57,-78.86 169.91,-88 176,-88 179.52,-88 181.79,-84.94 182.8,-80.47" id="path967" /> <polygon fill="black" stroke="black" points="186.3,-80.61 182.97,-70.55 179.3,-80.49 186.3,-80.61" id="polygon969" /> <text text-anchor="middle" x="176" y="-91.8" font-family="Times,serif" font-size="14.00" id="text971">a</text> </g> <!-- Q_3 --> <g id="node4" class="node"> <title id="title974">Q_3</title> <ellipse fill="none" stroke="black" cx="255" cy="-22" rx="18" ry="18" id="ellipse976" /> <text text-anchor="middle" x="255" y="-18.3" font-family="Times,serif" font-size="14.00" id="text978">3</text> </g> <!-- Q_2&#45;&gt;Q_3 --> <g id="edge5" class="edge"> <title id="title981">Q_2-&gt;Q_3</title> <path fill="none" stroke="black" d="M193.33,-45.64C203.13,-41.82 215.84,-36.87 227.09,-32.48" id="path983" /> <polygon fill="black" stroke="black" points="228.24,-35.79 236.28,-28.9 225.7,-29.27 228.24,-35.79" id="polygon985" /> <text text-anchor="middle" x="215.5" y="-40.8" font-family="Times,serif" font-size="14.00" id="text987">b</text> </g> <!-- Q_3&#45;&gt;Q_0 --> <g id="edge8" class="edge"> <title id="title990">Q_3-&gt;Q_0</title> <path fill="none" stroke="black" d="M236.53,-21.08C204.94,-19.69 136.3,-17.91 79,-25 68.32,-26.32 56.76,-28.77 46.67,-31.25" id="path992" /> <polygon fill="black" stroke="black" points="45.9,-27.83 37.1,-33.73 47.66,-34.61 45.9,-27.83" id="polygon994" /> <text text-anchor="middle" x="136.5" y="-23.8" font-family="Times,serif" font-size="14.00" id="text996">b</text> </g> <!-- Q_4 --> <g id="node5" class="node"> <title id="title999">Q_4</title> <ellipse fill="none" stroke="black" cx="338" cy="-22" rx="18" ry="18" id="ellipse1001" /> <ellipse fill="none" stroke="black" cx="338" cy="-22" rx="22" ry="22" id="ellipse1003" /> <text text-anchor="middle" x="338" y="-18.3" font-family="Times,serif" font-size="14.00" id="text1005">4</text> </g> <!-- Q_3&#45;&gt;Q_4 --> <g id="edge7" class="edge"> <title id="title1008">Q_3-&gt;Q_4</title> <path fill="none" stroke="black" d="M273.18,-22C282.15,-22 293.45,-22 304.03,-22" id="path1010" /> <polygon fill="black" stroke="black" points="304,-25.5 314,-22 304,-18.5 304,-25.5" id="polygon1012" /> <text text-anchor="middle" x="294.5" y="-25.8" font-family="Times,serif" font-size="14.00" id="text1014">a</text> </g> <!-- Q_4&#45;&gt;Q_4 --> <g id="edge9" class="edge"> <title id="title1017">Q_4-&gt;Q_4</title> <path fill="none" stroke="black" d="M333.99,-43.81C333.6,-53.56 334.94,-62 338,-62 339.72,-62 340.9,-59.33 341.53,-55.26" id="path1019" /> <polygon fill="black" stroke="black" points="345.02,-55.46 341.95,-45.32 338.03,-55.16 345.02,-55.46" id="polygon1021" /> <text text-anchor="middle" x="338" y="-65.8" font-family="Times,serif" font-size="14.00" id="text1023">a</text> </g> <!-- Q_4&#45;&gt;Q_4 --> <g id="edge10" class="edge"> <title id="title1026">Q_4-&gt;Q_4</title> <path fill="none" stroke="black" d="M331.14,-43.11C328.11,-61.1 330.4,-80 338,-80 344,-80 346.69,-68.23 346.07,-54.43" id="path1028" /> <polygon fill="black" stroke="black" points="349.56,-54.19 345.02,-44.62 342.6,-54.93 349.56,-54.19" id="polygon1030" /> <text text-anchor="middle" x="338" y="-83.8" font-family="Times,serif" font-size="14.00" id="text1032">b</text> </g> </g></svg>')
```
%% Cell type:code id:7aae22c9 tags:
``` python
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:b1c8a625-b2e5-4958-b49f-009c7bcdb038 tags:
**Question 1.4 -** Proposer un automate qui valide la propriété suivante : une chaîne de caractères est une adresse email. Les adresses email peuvent être définies comme suit (de manière simplifiée) :
- le premier caractère ne peut pas être un chiffre
- ensuite tous les caractères de `a` à `z` (majuscules et minuscules) et chiffres sont acceptés
- un `@` doit être présent
- ensuite un nom de domaine qui lui aussi ne peut commencer par un chiffre (et doit faire + de 1 caractère)
- un "."
- une extension parmis une liste autorisée (`fr`, `com`, etc)
Un exemple de chaîne qui n'est pas validée est `3toto@a.fr2` car la première partie commence par un chiffre, le nom de domaine est trop court et enfin l'extension n'est pas valide. Pour la partie de validation de l'extension (`.fr`, etc.), vous pouvez simplifier en proposant une reconnaissance de motifs pré-définis (`.fr`, `.com`, etc.). Vous pouvez vous référer à la page Wikipedia [ici](https://fr.wikipedia.org/wiki/Adresse_\%C3\%A9lectronique) ou à la RFC 8222 [ici](https://www.w3.org/Protocols/rfc822/) pour une définition plus précise.
%% Cell type:code id:2ee41daa-5611-433d-862a-f098127cad73 tags:
``` python
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:73d884f0 tags:
``` python
# version sans graphviz
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:6655a83e tags:
## Exercice 2 : Structure de données d'automate en Python
%% Cell type:markdown id:bd6c2f5f-a14a-4a72-bbbe-ad7ec0406dad tags:
**Question 2.1 -** Nous allons désormais implémenter en Python une structure de données d'automate. Celle-ci doit être en mesure de stocker toutes les informations relatives à la définition d'un automate (symboles reconnu, états, états initiaux/finaux) et valider un mot donné. Votre structure de données peut être composée comme suit :
1. Un constructeur `__init__` qui initialise l'automate avec les symboles du motif (ici `a` et `b`) et les variables d'état interne. En particulier l'état initial.
2. Une méthode `ajout_etat` qui rajoute un nouvel état et s'assure que l'état n'existe pas déjà; un paramètre additionnel `final` indiquera si il s'agit d'un état finaal
3. Une méthode `ajout_transition` qui rajoute un nouvel état et s'assure que l'état n'existe pas déjà.
3. Une méthode `ajout_transition` qui rajoute une nouvelle transition entre deux états (et s'assure que ces états existent bien et qu'il n'y a pas de transition existante entre ces états).
4. Une méthode `recherche_etat` qui étant donné un état source et un symbole, renvoie l'état correspondant (via la transition correspondant au symbole donné).
5. Une fonction `run` qui valide un mot donné, et renvoie `True` si l'état final est atteint et `False`.
%% Cell type:code id:445d2964 tags:
``` python
class automate:
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:dd5fe66b-8232-4ab6-94d7-0e49ca591f26 tags:
Voici un exemple attendu d'utilisation de votre structure de données :
%% Cell type:code id:51a7c400 tags:
``` python
a = automate("ab")
a.ajout_etat("0")
a.ajout_etat("1", True)
a.ajout_transition("0", "b", "0")
a.ajout_transition("0", "a", "1")
a.ajout_transition("1", "a", "1")
a.ajout_transition("1", "b", "1")
assert a.run("abaaaaa") == True
assert a.run("bbb") == False
```
%% Cell type:markdown id:d16f624a-7810-489c-9b6a-f0bba5eb25f2 tags:
**Question 2.2 -** Utilisez votre structure de données pour implémenter les automates de la partie précédente.
%% Cell type:markdown id:244afe6c-a606-4e1f-b09b-f9dd59882135 tags:
Question 1.1 (solution) :
%% Cell type:code id:428cf819-3d60-4776-ba94-3e8733eeadea tags:
``` python
# automate qui valide un nombre pair de fois la lettre "a" avec langage a, b
# YOUR CODE HERE
raise NotImplementedError()
# tests valides
assert a.run("") == True
assert a.run("aa") == True
assert a.run("aaaa") == True
assert a.run(''.join("a" for i in range(100))) == True
# tests non-valides
assert a.run("a") == False
assert a.run(''.join("a" for i in range(100 + 1))) == False
```
%% Cell type:markdown id:64e021b0-4613-444d-87d8-b0abeed5aef2 tags:
Question 1.2 (solution) :
%% Cell type:code id:f51ce343-fcdb-49c0-a126-4ffeaf4e1920 tags:
``` python
# automate qui valide a*a
# YOUR CODE HERE
raise NotImplementedError()
# tests valides
assert a.run("aa") == True
assert a.run("aaaa") == True
assert a.run(''.join("a" for i in range(100))) == True
# tests non-valides
assert a.run("") == False
assert a.run("a") == False
assert a.run("aabb") == False
assert a.run("b") == False
```
%% Cell type:markdown id:9b44355d-e2a6-4ef2-b427-d5c39cc72662 tags:
Question 1.3 (solution) :
%% Cell type:code id:6c1a0049-03c3-47b1-adf7-35a59d8c78f7 tags:
``` python
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:34eb2bb6-aba0-4263-9702-761694af7348 tags:
**Question 2.3 (bonus) -** Implémentez une méthode `visualize(self)` afin d'afficher votre automate en utilisant le code `graphviz` fourni dans les questions précédentes. En voici les principales étapes (il s'agit de construire un graphe..) :
1. Initialiser les sommes
2. Rajouter les arrêtes
3. Inclure les propriétés et labels
%% Cell type:markdown id:954a7d30-9e8a-4269-aee4-e36924da2e86 tags:
**Question 2.4 (bonus) -** Implémentez une méthode `__str__` afin que la commande `print(a)` affiche les états internes à l'automate comme ci-dessous :
%% Cell type:markdown id:0a4548cc-197f-4c70-a4f9-e3c5c706d378 tags:
```
automate :
- alphabet : 'ab'
- init : 0
- final : ['1']
- etats (2) :
- (0)automate :
- alphabet : 'ab'
- init : 0
- final : ['1']
- etats (2) :
- (0):
--(b)--> (0)
--(a)--> (1)
- (1):
--(a)--> (1)
--(b)--> (1)
```
%% Cell type:markdown id:6764667b-ab2b-46c1-a5bb-2a12bb670afd tags:
## Exercice 3 : Analyse de texte avec un automate
%% Cell type:markdown id:b13e013c-8c73-4008-ac92-cf981b2a5cb0 tags:
Nous allons maintenant développer un programme qui utilise votre structure de données d'automate implémentée en Python dans la section précédente. L'objectif de ce programme sera le suivant : proposer de compléter un mot, à partir d'une séquence de lettres partielle donnée. Par exemple si votre programme prend en entrée la séquence `bon`, en retour vous devez proposer une séquence de lettres pertinentes afin de compléter ce mot comme `bonjour` ou `bonsoir`.
Vous êtes libres de proposer la stratégie de recommandation de lettres que vous souhaitez. Nous vous proposons de vous baser sur es listes de mots les plus fréquents en Français [ce lien](http://www.pallier.org/extra/liste.de.mots.francais.frgut.txt) (fourni dans le fichier `mots.txt`). Ces mots permettent de réaliser des statistiques de co-occurences. Par exemple, étant donné les mots suivants :
Vous êtes libres de proposer la stratégie de recommandation de lettres que vous souhaitez. Nous vous proposons de vous baser sur des listes de mots les plus fréquents en Français [ce lien](http://www.pallier.org/extra/liste.de.mots.francais.frgut.txt) (fourni dans le fichier `mots.txt`). Ces mots permettent de réaliser des statistiques de co-occurences. Par exemple, étant donné les mots suivants :
```
abaissa
abaissable
abaissables
abaissai
abaissaient
abaissais
abaissait
abaissâmes
```
Si le mot d'entrée est `abaissa` alors votre programme suggère les lettres suivantes ordonnées par ordre de probabilité de transition pour compléter le mot (basé sur l'analyse du fichier `code/mots-10.txt` qui contient les mots ci-dessus):
```
i (4)
b (2)
m (1)
```
Conseils :
1. Utiliser les fichiers de listes de mots (`mots.txt`, ..) en analysant la fréquence de co-occurrences de lettres (autrement dit calculer probabilité d'être l'une après l'autre)
2. Construire un automate dont les transitions sont les probabilités de co-occurence entre les lettres
3. Proposer une méthode de recommandation de transition à partir de quelques lettres fournies en entrée
%% Cell type:code id:b5309ac0 tags:
``` python
# lecture du fichier de mots
data = []
with open("mots-10.txt") as f:
keys = None
for line in f:
l = [w.strip() for w in line.split(';')]
if keys is None:
keys = l
else:
data.append(l)
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:77408003 tags:
## Pour aller plus loin
- Vérifier si les automates sont [déterministes](https://fr.wikipedia.org/wiki/Automate_fini_d%C3%A9terministe)
- Comparer vos résultats avec une implémentation [Python d'Automate](https://pypi.org/project/python-automaton/) :
- Enregistrer les automates en utilisant la commande
`dot.render('graph', format='svg', view=True)`
......
TD06/figures_automates/0_1.png

15.7 KiB

TD06/figures_automates/0_1_2.png

25.4 KiB

TD06/figures_automates/0_1_2_3_4.png

43.5 KiB

TD06/figures_automates/0_1_a_b.png

20.5 KiB

TD06/figures_automates/email.png

55.2 KiB

%% Cell type:markdown id:931d31d6 tags:
%% Cell type:markdown id:7f210b68 tags:
NAME:
%% Cell type:markdown id:57c2dc5b-0ce7-47e4-bfeb-445c2f90cb6e tags:
# INF TC1 - TD7 (2h) - Rendu de monnaie
%% Cell type:markdown id:2a40aa1b-c3fe-4cd2-9236-3c7aeddedf55 tags:
---
%% Cell type:markdown id:25995ac6-8673-46f2-9c77-37b48a889b73 tags:
## Objectif du TD
Dans ce TD nous allons aborder le problème de rendu de monnaie selon plusieurs méthodes algorithmiques :
Dans ce TD nous allons aborder le problème dit de de _rendu de monnaie_ selon plusieurs méthodes algorithmiques :
- technique dite Gloutonne
- technique de programmation Gloutonne
- chemin minimal dans un arbre de recherche
- Programmation Dynamique (**prochain TD7bis et devoir à rendre**)
- Programmation Dynamique (cela fera l'objet du **prochain TD7bis et du devoir à rendre**)
Vous serez amené également à créer une structure de données de graphe et une méthode de parcours de celui-ci.
Vous serez amené dans ce TD (et les suivant) à créer une structure de données de graphe et une méthode de parcours de celui-ci.
**Les réponses de la partie sur la programmation dynamique feront l'objet d'un autre sujet de TD et d'un rendu sous forme de rapport à rendre sur Moodle.**
%% Cell type:markdown id:c38c9de3-f716-4907-8387-7cf6f4f5e926 tags:
## Le problème de "rendu de monnaie"
Le problème de rendu de monnaie est très fréquent dans la vie de tous les jours et peut être défini comme suit : étant donné un montant, une machine capable de rendre la monnaie doit rendre ce montant au client à partir de pièces (1c à 2€) et de billets. On suppose pour simplifier qu'il n'y a que des pièces en centimes; un billet de 5€ sera représenté comme une pièce de 500 centimes. On supposera dans un premier temps qu'il existe un nombre suffisant (autant que nécessaire) de chaque pièce, mais dans un second temps nous introduirons des contraintes de disponibilité des pièces.
Le problème de rendu de monnaie est très fréquent dans la vie de tous les jours et peut être défini comme suit : étant donné un montant, une machine capable de rendre la monnaie doit rendre ce montant au client à partir de pièces (1c, 2€, etc.) et de billets (10€, 50€, etc.). On suppose pour simplifier qu'il n'y a que des pièces en centimes; un billet de 5€ sera représenté comme une pièce de 500 centimes. On suppose également (dans un premier temps) qu'il existe un nombre suffisant (autant que nécessaire) de chaque pièce, mais dans un second temps nous introduirons des contraintes de disponibilité des pièces.
%% Cell type:markdown id:5a29e913-8954-4e88-b477-234c4ee0fd9b tags:
| | **la table S** | **la table D** |
|---------------|------------------------|----------------------------------|
| **indice $i$**| **Valeur ($v_i$)** | **Disponibilité ($d_i$)** |
| 1 | 1c | nombre de pièces de 1c disponibles |
| 2 | 2c | nombre de pièces de 2c disponibles |
| 3 | 5c | ... |
| 4 | 10c | ... |
| 5 | 20c | ... |
| 6 | 50c | ... |
| 7 | 100 (1€) | pièces de 1€ |
| 8 | 200 (2€) | pièces de 2€ |
| 9 | 500 (5€) | billets de 5€ |
| 10 | 1000 | billets de 10€ |
| 11 | 2000 | billets de 20€ |
| 12 | 5000 | billets de 50€ |
| 13 | 10000 | billets de 100€ |
%% Cell type:markdown id:7f845f7b-8bd0-4544-8110-fa5ec1a7a960 tags:
De manière plus formelle, un stock de pièces est un tuple $S=(v_1, v_2, ..., v_n)$ où l'entier $v_i > 0$ est la valeur de la $i^{ème}$ pièce. Pour refléter le fait qu'on a des pièces de 1, 2 et 5c, $S$ contiendra $v_1=1$ (1 centime), $v_2=2, v_3=5$. Le problème de monnaie est un problème d'optimisation combinatoire $(S,M)$ permettant de trouver le tuple $T=(x_1, x_2, ..., x_n)$ avec $x_i \geq 0$ qui minimise $ \sum_{i=1}^n x_i$ sous la contrainte $\sum_{i=1}^n x_i.v_i=M$. Autrement dit, nous souhaitons aussi bien obtenir le montant exact, que minimiser le nombre total de pièces $x_i$ de valeur $v_i$ utilisées. Appelons $Q(S,M) = \sum_{i=1}^n x_i$ la quantité de pièces à rendre pour le montant *M* étant donné le système *S* décrit dans la Table~1. Une solution optimale $Q_{opt}$ à ce problème est telle que *Q(S,M)* soit minimale :
$Q_{opt}(S,M) = min \ \sum_{i=1}^n x_i$.
Dans certaines situations il faudra gérer le nombre de pièces/billets disponibles (la table *D*). Nous noterons *d[i]=k* pour dire : il y a *k* pièces/billets du montant *$v_i$* disponibles (pièces ou billets du montant *v[i]*) à l'indice *i* dans la table *S*. On supposera cependant dans un premier temps qu'il y a un nombre suffisant de chaque pièce/billet dans le tableau S. On supposera également que *S* est ordonné dans un ordre croissant.
%% Cell type:markdown id:077ca702-bc18-4f2d-95e6-86fd4d53ebe7 tags:
## Exemples
**M = 9€:** étant donné S dans la Table ci-dessous, la solution qui minimise le nombre total de pièces rendues à 3 est *T=(0,0,0,0,0,0,0,2,1,0,0,0,0)*. Donc, $Q_{opt}(S,9) = \min \ Q(S,9) = 3$. Détails (avec des pièces $\geq$ 1€) :
| Description | T | Rendu |
|--------------------------------------------------------|----------------------------------------|---------------------------------------------|
| 9 pièces de 1€ et 0 pour toutes les autres | T=(0,0,0,0,0,0,0,9,0,0,0...0) | $\rightarrow$ 9 pièces | $Q(S,9) =9$ |
| $\circ$ 5 $\times$ 1€ + 2 $\times$ 2€, 0 pour les autres | T=(0,0,0,0,0,0,0,5,2,0,0...0) | $\rightarrow$ 7 pièces | $Q(S,9) =7$ |
| $\circ$ 1 $\times$ 1€ + 4 $\times$ 2€, 0 pour les autres | T=(0,0,0,0,0,0,0,1,4,0,0...0) | $\rightarrow$ 5 pièces | $Q(S,9) =5$ |
| $\circ$ 2 $\times$ 2€ + 1 $\times$ 5€, 0 pour les autres | T=(0,0,0,0,0,0,0,0,2,1,0...0) | $\rightarrow$ 3 pièces | **$Q(S,9) =3$** |
| $\circ$ 3 $\times$ 1 + 3 $\times$ 2, 0 pour les autres | T=(0,0,0,0,0,0,0,3,3,0,0...0) | $\rightarrow$ 6 pièces | |
| $\circ$ 4 $\times$ 1 + 1 $\times$ 5, 0 pour les autres | T=(0,0,0,0,0,0,0,4,0,1,0...0) | $\rightarrow$ 5 pièces | |
| $\circ$ etc. sans parler des solutions avec des centimes ! | | | |
<center>
<i>Table 1</i>
</center>
**M = 1989€:** pour rendre la somme de 1989€ pièces (sans les centimes), on aura : $1989 = 500 \times 3 + 488 = 500 \times 3 + 200 \times 2 + 50 \times 1 + 20 \times 1 + 10 \times 1 + 5 \times 1 + 2 \times 2$, soit $3+2+1+1+1 = 8$ grosses pièces (billets) et $1+2 = 3$ pièces.
%% Cell type:markdown id:254f7b95-aaeb-4f96-b8ec-47df74a3c3a3 tags:
## Résolution du problème
L'exemple de la Table 1 est un système *canonique*, c'est à dire qu'en choisissant systématiquement les pièces de plus grande valeur (algorithme glouton) on obtient toujours la solution optimale. Il existe des systèmes pour lesquels c'est moins simple, par exemple $S=(1,7,23)$. Pour **M = 28**, en choisissant en priorité les pièces de plus grande valeur on trouvera $T=(5,0,1)$ (6 pièces), alors que la solution optimale est $T=(0,4,0)$ (4 pièces).
Dans le cas général, ce problème est démontré NP-difficile, c'est-à-dire qu'on ne connaît pas d'algorithme qui puisse le résoudre en complexité polynomiale par rapport à la taille de $S$.
Il existe plusieurs approches :
1. Approche gloutonne (pas toujours optimale)
2. Recherche de chemin de longueur minimale avec (ou sans) un arbre de recherche, ...
3. Programmation Dynamique, ... et autres méthodes algorithmiques (cf. quasi-Dijkstra)
Il existe d'autres approches de résolutions algorithmiques ou non-algorithmiques comme par exemple un système d'équations à optimiser. Nous allons dans ce TD aborder les différentes approches algorithmiques mentionnées ci-dessus.
%% Cell type:markdown id:c8b2e4fe-cbfa-4301-89af-d02ea55473f0 tags:
## 1. Algorithme Glouton
Les techniques de programmation gloutonnes ont la particularité de faire des choix locaux à chaque étape de calcul. Cette méthode ne donne pas forcément un nombre minimal de pièces mais elle est simple à comprendre et à implémenter. Son application au problème de rendu de monnaie est simple : on trouve la pièce la plus grande inférieure ou égale à $M$. Soit $v_i$ cette pièce. On utilise ($x_i = M \ div \ v_i$) fois la pièce $v_i$; le reste à traiter sera $M'=(M \mod v_i)$. Ensuite, on recommence suivant le même principe pour satisfaire $M'$. Ce qui donne le pseudo-code d'algorithme suivant (qui fait l'hypothèse d'un nombre illimité de pièces, on ne tient pas compte ici de $D$ : $d_i=\infty$) :
```
Fonction Monnaie_Gloutonne
Entrées : la somme S, M
Sorties : le vecteur T = Q(S,M) : le nombre de pièces nécessaires
M'=M
Répéter
Chercher dans S l'indice i tel que Vi =< M'
Ti = M' div Vi
M' = M' mod Si
Jusqu'à M' = 0
Constituer T avec les Ti utilisés
Q(S,M) = somme de i=1 a i=n de T_i
T est la valeur de sortie de l'algorithme
Fin Monnaie_Gloutonne
```
Pour **M = 236,65€** cet algorithme se déroule de la manière suivante :
$23665 \geq 10000$
- $T_{13}=23665 \ div \ 10000 = 2$ ($T_{13}$ correspond à 100€)
- $M'=23665 \ mod \ 10000 = 3665$
$3665 \geq 2000$
- $T_{11}=3665 \ div \ 2000 = 1$
- $M'=3665 \ mod \ 2000 = 1665$
...
$\rightarrow$ On obtient $T_{1..13}=(0,0,1,1,0,1,1,0,1,1,1,0,2)$ et $Q(S,M) = 9$
A noter qu'en Python les indices commencent à zéro ! Faire -1 sur les indices ci-dessus.
%% Cell type:markdown id:425fe58a-b0bc-41eb-acea-0d88e1daf18f tags:
**Question 1.1 -** Implémenter l'algorithme glouton ci-dessus.
%% Cell type:code id:e3880c0c-4c2c-48c4-96b1-0c95c712b489 tags:
``` python
def gloutonne(S: list, M: int) -> tuple:
"""
Algorithme glouton sans nombre limité de pièces
Args:
- S (list): Une liste d'entier représentant les pièces
- M (int): La somme à atteindre
Returns:
- tuple: (True, Q(S,M)) si succès OU (False, None) si échec
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:2a39cbfb-d35d-4da7-bbaa-d273bc900c2d tags:
Les tests suivants doivent être validés :
%% Cell type:code id:90cd609d-71d9-41b0-ba28-c66fd94a2524 tags:
``` python
Rep= gloutonne(sorted([1, 7, 23]), 28)
Rep = gloutonne(sorted([1, 7, 23], reverse=True), 28)
print(Rep[1], ': ', sum(Rep[1]),' pièces') if Rep[0] else print('Echec')
# [5, 0, 1] : 3 pièces Un pièce de 23 et 5 pièces de 1
# [28, 0, 0] : 28 pièces
Rep= gloutonne(sorted([7, 23]), 5)
Rep = gloutonne(sorted([7, 23], reverse=True), 5)
print(Rep[1], ': ', sum(Rep[1]),' pièces') if Rep[0] else print('Echec')
# Echec car M < la + petite pièce
Rep= gloutonne(sorted([7, 23]), 8)
Rep = gloutonne(sorted([7, 23], reverse=True), 8)
print(Rep[1], ': ', sum(Rep[1]),' pièces') if Rep[0] else print('Echec')
# Echec car on ne peut jamais faire la monnaie avec cette S
```
%% Cell type:markdown id:c5d61f32-13cc-4176-8f47-b0ca861a74ba tags:
**Question 1.2 -** Proposez une modification du pseudo-code ci-dessus pour prendre en compte un nombre **limité** de pièces (en prenant en compte la table $D$). Implémenter cet algorithme modifié (vous fournirez vos propres valeurs de disponibilité dans $D$).
%% Cell type:code id:91951f93-bf34-4ea9-ad08-76c73ff3ce44 tags:
``` python
def gloutonne_avec_D(S: list, M: int, D: list) -> tuple:
"""
Algorithme glouton avec nombre limité de pièces
Args:
- S (list): Une liste d'entier représentant les pièces
- M (int): La somme à atteindre
- D (list): La disponibilité des pièces
Returns:
- tuple: (True, Q(S,M)) si succès OU (False, None) si échec
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:markdown id:9786ca50-6bfe-491d-8971-89be91ccfd8b tags:
Les tests suivants doivent être validés :
%% Cell type:code id:a09ba678-bff6-4728-b301-6579f8a62e40 tags:
``` python
Rep= gloutonne_avec_D(sorted([1,7,23]), 28, [10,10,0])
Rep= gloutonne_avec_D([23, 7, 1], 28, [0, 10, 10]) # On ne fait pas de tri car il faudrait trier D aussi
print(Rep[1], ': ', sum(Rep[1]),' pièces utilisées') if Rep[0] else print('Echec')
# Echec
Rep= gloutonne_avec_D([23, 7, 1], 28, [0, 1, 21])
print(Rep[1], ': ', sum(Rep[1]),' pièces utilisées') if Rep[0] else print('Echec')
# [0, 4, 0] : # On utilise 4 pièces
# [21, 1, 0] : 22 pièces utilisées
```
%% Cell type:markdown id:70534a4f-cc60-4c29-b5e6-1392126fc9f5 tags:
Vous noterez que la méthode Gloutonne ne garantit pas que *Q(S, M)* soit minimal comme les tests l'ont montré pour M=28 et S=(1, 7, 23). La méthode Gloutonne utilise un optimum local (choix de la plus grande pièce) qui ne débouche pas forcément sur un optimum global. Cependant, elle est très simple à calculer.
%% Cell type:markdown id:321b284b-a735-47b5-a889-ef52006c967e tags:
## 2. Chemin minimal dans un arbre
Une deuxième méthode consiste à construire toutes les solutions possibles sous forme d'arbre, et ensuite de choisir la solution de manière globale. Cette méthode est plus complexe à mettre en oeuvre mais donne une solution optimale.
%% Cell type:markdown id:431ee4d7-1fa8-4bfd-bf9c-ec0725d08e5b tags:
<center>
<img src="monnaie-graph.png" alt="Image originale" style="height:10cm;">
</center>
Exemple d'arbre de recherche résultant *M=28* et *S=(1,7,23)*. C'est à dire, on a seulement des pièces de 1, 7 et 23 (€ ou cents) en nombre suffisant. Dès qu'on atteint 0, on remonte de ce 0 à la racine pour obtenir la solution minimale donnant le nombre minimal d'arcs entre ce 0 et la racine. On remarque qu'on utilisera 4 pièces de 7c pour avoir 28c. Par ailleurs, on remarque que le choix initial de 23 (à gauche de l'arbre) laisse le montant *M'=5c* à satisfaire lequel impose le choix de 5 pièces de 1c. En bleu les autres chemins choisissant une pièce de 23.
%% Cell type:markdown id:36bc9ae5-81f2-4f12-9b94-03d90db050c0 tags:
On construit un arbre de recherche (arbre des possibilités) dont la racine est *M*. Chaque noeud de l'arbre représente un montant : les noeuds autres que la racine initiale représentent $M' < M$ une fois qu'on aura utilisé une (seule) des pièces de $S$ (parmi 1, 7 ou 23€). La pièce utilisée pour aller de $M$ à $M'$ sera la valeur de l'arc reliant ces deux montants. Cet arbre est donc développé par niveau (en largeur). On arrête de développer un niveau supplémentaire dès qu'un noeud a atteint 0 auquel cas une solution sera le vecteur des valeurs (des arcs) allant de la racine à ce noeud.
Le principe de l'algorithme correspondant à un parcours en largeur dont la version itérative utilise une *File d'attente* est :
```
Fonction Monnaie_graphe
% on suppose qu'il y a un nombre suffisant de chaque pièce/billet dans la tableau S
Entrées : la somme M
Sorties : le vecteur T et Qopt(S,M) le nombre de pièces nécessaires
File F = vide ;
Arbre A contenant un noeud racine dont la valeur = M
Enfiler(M) % la file F contient initialement M
Répéter
M'= défiler()
Pour chaque pièce vi =< M' disponible dans S :
S'il existe dans l'arbre A un noeud dont la valeur est M' - vi
Alors établir un arc étiqueté par vi allant de M' à ce noeud
Sinon
Créer un nouveau noeud de valeur M' - vi dans A et lier ce noeud
à M' par un arc étiqueté par vi
Enfiler ce nouveau noeud
Fin Si
Jusqu'à (M' - vi = 0) ou (F = vide)
Si (F est vide Et M' - vi /= 0)
Alors il y a un problème dans les calculs ! STOP.
Sinon Le dernier noeud créer porte la valeur 0
On remonte de ce noeud à la racine et on comptabilise dans T où
Ti = le nombre d'occurrences des arcs étiquetés vi de la racine
au noeud v de valeur 0
$Q(S,M) = somme de i=1 a i=n de T_i$
Fin si
Fin Monnaie_graphe
```
%% Cell type:markdown id:d15c2fca-64f1-4d6d-b6bf-5edf9b095828 tags:
**Question 2.1 -** Ecrire un algorithme de construction de l'arbre.
%% Cell type:code id:2d2556ed-8be9-416f-a976-8964ccca8061 tags:
``` python
def Monnaie_graphe(S: list, M: int) -> dict:
"""
Génère un graphe représentant les combinaisons de pièces possibles pour atteindre un montant spécifié
en utilisant un ensemble donné de valeurs de pièces.
Args:
- S (list): Une liste d'entiers représentant les valeurs disponibles des pièces.
- M (int): Un entier spécifiant le montant cible à atteindre.
Returns:
- dict: Un graphe (représenté sous forme de dictionnaire)
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:c2709c24-bbe0-4c6a-aaf8-f2e3712131f5 tags:
``` python
A = Monnaie_graphe([1, 2, 5], 7)
print("Arbre créé", A)
```
%% Cell type:markdown id:91d7262d-9a9c-4f22-b60e-6d43267a59bb tags:
Vous devriez pouvoir visualiser votre arbre avec la méthode `plot_graph` (chargez d'abord les dernières cellules du notebook afin d'initialiser cette fonction) :
%% Cell type:code id:701a7359-145f-4077-a8ee-e056eb3447f1 tags:
``` python
plot_graph(A)
```
%% Cell type:markdown id:9de0a303-b587-4901-b0ac-68a7cd04bfc7 tags:
**Question 2.2 -** Ecrire un algorithme de recherche du chemin le plus court dans l'arbre.
%% Cell type:code id:f5f033d8-1d49-49c7-9af7-e1cd59905d96 tags:
``` python
def plus_court_chemin(graph: dict, start: str) -> int:
"""
Calcule les distances les plus courtes depuis un nœud de départ dans un graphe.
Args:
- graph (dict): Un dictionnaire représentant le graphe
- start (str): Le nœud de départ à partir duquel calculer les distances.
Returns:
- int: la taille du chemin
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:7aca69d8-79ed-4f06-bb25-5c42b116cbc8 tags:
``` python
assert plus_court_chemin(A, 3)[0] == 2
```
%% Cell type:markdown id:72832556-e0d7-4959-8d93-2b55145e7c25 tags:
**Question 2.3 -** Compléter l'algorithme précédent avec un mécanisme de backtracking permettant de retourner le plus court chemin utilisé.
%% Cell type:code id:636ed054-38e5-46d1-b44f-c2d861bf1cc0 tags:
``` python
def plus_court_chemin_backtracking(graph: dict, start: str) -> list:
"""
Calcule les distances les plus courtes depuis un nœud de départ dans un graphe.
Args:
- graph (dict): Un dictionnaire représentant le graphe
- start (str): Le nœud de départ à partir duquel calculer les distances.
Returns:
- list: le chemin utilisé et les montants intermédiaires
"""
# YOUR CODE HERE
raise NotImplementedError()
```
%% Cell type:code id:b0253f7c-0f07-436c-b72c-2411fb757e47 tags:
``` python
assert plus_court_chemin_backtracking(A, 7) == [7, 5, 0]
```
%% Cell type:markdown id:f358da32-9534-493c-80b1-60d387f89229 tags:
A noter que vous pouvez combiner la construction et la recherche du plus court chemin (en vous arrêtant au premier 0 rencontré). Cependant, cette methode a pour inconvénient d'explorer un très large espace de recherche (qui devient vite très grand). Un autre inconvénient est le calcul de solutions dont on sait qu'elles ne seront pas optimales.
%% Cell type:markdown id:91af94f8-c687-4739-b4fb-ef58cb7e6081 tags:
## Utils
%% Cell type:code id:7cbf74a0-525c-4b6f-b078-5f0eea6fe09d tags:
``` python
from graphviz import Digraph
from IPython.display import display, Image
import graphviz
```
%% Cell type:code id:83f489e1-8fb7-45ec-a906-4d2699c63a46 tags:
``` python
def plot_graph(graph):
dot = graphviz.Digraph()
for node in graph:
dot.node(str(node))
for node, neighbors in graph.items():
for neighbor, weight in neighbors.items():
dot.edge(str(node), str(neighbor), label=str(weight))
display(dot)
```
......
TD07bis/monnaie-graph.png

69.5 KiB

TD07bis/monnaie-progdyn.png

67.4 KiB

File deleted