"1) Répondez aux questions dans les cellules en dessous des questions.\n",
"1) Répondez aux questions dans les cellules en dessous des questions.\n",
"\n",
"\n",
"2) Votre code devra remplacer le texte suivant : \n",
"2) Votre code devra remplacer le texte suivant : \n",
"\n",
"erge\n",
"```python\n",
"```python\n",
"# YOUR CODE HERE\n",
"# YOUR CODE HERE\n",
"raise NotImplementedError()\n",
"raise NotImplementedError()\n",
...
@@ -788,7 +788,7 @@
...
@@ -788,7 +788,7 @@
"id": "5f3fc128-2a2e-42bd-924a-d691c09150d6",
"id": "5f3fc128-2a2e-42bd-924a-d691c09150d6",
"metadata": {},
"metadata": {},
"source": [
"source": [
"Nous allons maintenant implémenter **arbre binaire complet**. Cet arbre sera utile pour l'exercice suivant et la création d'un Tas. Cet arbre (dans sa configuration [min-heap](https://en.wikipedia.org/wiki/Min-max_heaphttps://en.wikipedia.org/wiki/Min-max_heap), où les données sont renvoyées par ordre croissant) satisfait la propriété suivante : la valeur de tout noeud est inférieure à celle de ses enfant. Cet arbre binaire sera de surcroît implémenté en utilisant un tableau (car il s'agit d'un arbre _complet_ où tous les niveaux sont remplis, sauf éventuellement le dernier). L'arbre binaire possède des noeuds ayant un index $i$, avec un fils gauche et un fils droit. Le tableau et l'arbre sont reliés de la façon suivante : \n",
"Nous allons maintenant implémenter **arbre binaire complet**. Cet arbre sera utile pour l'exercice suivant et la création d'un Tas. Cet arbre binaire sera implémenté en utilisant un tableau (car il s'agit d'un arbre _complet_ où tous les niveaux sont remplis, sauf éventuellement le dernier). L'arbre binaire possède des noeuds ayant un index $i$, avec un fils gauche et un fils droit. Le tableau et l'arbre sont reliés de la façon suivante : \n",
"\n",
"\n",
"- La racine a la position $i = 0$ (cette valeur sera renvoyée par la fonction `get_racine`)\n",
"- La racine a la position $i = 0$ (cette valeur sera renvoyée par la fonction `get_racine`)\n",
"- Le parent a la position $\\lfloor (i - 1)/ 2 \\rfloor$ (fonction `get_parent`)\n",
"- Le parent a la position $\\lfloor (i - 1)/ 2 \\rfloor$ (fonction `get_parent`)\n",
...
@@ -796,15 +796,15 @@
...
@@ -796,15 +796,15 @@
"- Le fils droit a la position $2 \\times i + 2$ (fonction `get_fils_droit`)\n",
"- Le fils droit a la position $2 \\times i + 2$ (fonction `get_fils_droit`)\n",
"Cette structure de donnée sera utile pour la question suivante afin de créer un `Tas`."
"Cette structure de donnée sera utile pour la question suivante afin de créer un `Tas`."
]
]
},
},
{
"cell_type": "markdown",
"id": "5f35e9de-025f-4c89-a004-a24af997474a",
"metadata": {},
"source": [
"**Exercice 3.3** - L'abre que vous venez de créer sera utilisé dans l'exercice suivant afin de créer un tas. Pour cela nous devrons nous assurer que l'arbre atisfait la propriété suivante : la valeur de tout noeud est inférieure à celle de ses enfant. Ecrire une fonction de parcours de l'arbre qui renvoie `True` si cette propriété est vérifée, sinon `False`. Conseil : \n",
"\n",
"1. Proposez une approche récursive\n",
"2. Le cas d'arrêt est un noeud vide\n",
"3. L'appel récursif est déclanché en fonction de la valeur du noeud en cours et celle de ses enfants"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "11065fbe-7209-4e87-8b9c-04d19d14fcee",
"metadata": {
"deletable": false,
"nbgrader": {
"cell_type": "code",
"checksum": "fc1f2a4ce40504acecf986a88152ab86",
"grade": false,
"grade_id": "cell-f45461f84548c912",
"locked": false,
"schema_version": 3,
"solution": true,
"task": false
},
"tags": []
},
"outputs": [],
"source": [
"def check_min_tree(T, i=0):\n",
" # YOUR CODE HERE\n",
" raise NotImplementedError()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "da01b2ab-4633-443f-97ea-b1022bc714ef",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"assert check_min_tree(tree_values) == True"
]
},
{
{
"cell_type": "markdown",
"cell_type": "markdown",
"id": "d9f0166d-d12f-42a1-989a-b7014117e73d",
"id": "d9f0166d-d12f-42a1-989a-b7014117e73d",
...
@@ -900,7 +949,9 @@
...
@@ -900,7 +949,9 @@
"source": [
"source": [
"## Exercice 4 (Bonus) : Création d'un tas et tri par tas\n",
"## Exercice 4 (Bonus) : Création d'un tas et tri par tas\n",
"\n",
"\n",
"Une structure de donnée de `Tas` permet de réduire la complexité de manipulation d'une file de priorité. La particularité de cette méthode est de répartir le coût de la recherche du plus petit élément (qui sera renvoyé) entre l'ajout et la suppression. "
"Nous allons désormais créer une nouvelle structure de donnée : le `Tas`. Celle-ci permettra à termes de réduire la complexité de manipulation d'une file de priorité, en répartissant le coût de la recherche du plus petit élément (qui sera renvoyé) entre l'ajout et la suppression.\n",
"\n",
"Nous nous baserons pour cela sur l'arbre binaire de la question précédente, qui est dit dans une configuration [min-heap](https://en.wikipedia.org/wiki/Min-max_heaphttps://en.wikipedia.org/wiki/Min-max_heap), où les données sont renvoyées par ordre croissant."
### IMPORTANT A LIRE (SUR L'UTILISATION DE CE NOTEBOOK)
### IMPORTANT A LIRE (SUR L'UTILISATION DE CE NOTEBOOK)
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 :
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.
1) Répondez aux questions dans les cellules en dessous des questions.
2) Votre code devra remplacer le texte suivant :
2) Votre code devra remplacer le texte suivant :
erge
```python
```python
# YOUR CODE HERE
# YOUR CODE HERE
raiseNotImplementedError()
raiseNotImplementedError()
```
```
(vous pouvez effacer ces deux lignes quand vous les rencontrez mais ne modifiez pas les noms de fonctions sinon les tests ne marchent plus).
(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.
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.
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.
**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.
Ce TD vous fera manipuler plusieurs structures de données standard en Python (listes, dictionnaires) mais également des structures avancées (piles, files tas) que vous allez créer au moyen de classes. Au final nous allons créer une méthode de tri efficace (tri par tas) et la comparer avec d'autres méthodes de tri de Python.
Ce TD vous fera manipuler plusieurs structures de données standard en Python (listes, dictionnaires) mais également des structures avancées (piles, files tas) que vous allez créer au moyen de classes. Au final nous allons créer une méthode de tri efficace (tri par tas) et la comparer avec d'autres méthodes de tri de Python.
Le but de cet exercice est de charger une liste de dictionnaires et réaliser des méthodes de tri. Vous disposez pour cela d'un fichier appelé [`etudiants.txt`](etudiants.txt) où chaque ligne contient des informations sur des étudiants d'un cour. Pour commencer nous allons réaliser des tris simples et les rendre de plus en plus complexes.
Le but de cet exercice est de charger une liste de dictionnaires et réaliser des méthodes de tri. Vous disposez pour cela d'un fichier appelé [`etudiants.txt`](etudiants.txt) où chaque ligne contient des informations sur des étudiants d'un cour. Pour commencer nous allons réaliser des tris simples et les rendre de plus en plus complexes.
Soit vous créez une nouvelle liste triée qui ne modifie pas la liste actuelle en utilisant ```sorted```[(doc)](https://docs.python.org/3/howto/sorting.html) :
Soit vous créez une nouvelle liste triée qui ne modifie pas la liste actuelle en utilisant ```sorted```[(doc)](https://docs.python.org/3/howto/sorting.html) :
Enfin les fonctions de tri peuvent prendre un paramètre `key` afin d'indiquer sur quel attribut de la liste réaliser le tri. Ci dessous le tri sera fait sur le premier élément d'une liste de listes à trier :
Enfin les fonctions de tri peuvent prendre un paramètre `key` afin d'indiquer sur quel attribut de la liste réaliser le tri. Ci dessous le tri sera fait sur le premier élément d'une liste de listes à trier :
Un echantillon du jeu de données vous est donné comme suit, il s'agit une liste de dictionnaires [(doc)](https://docs.python.org/3/tutorial/datastructures.html#dictionaries) :
Un echantillon du jeu de données vous est donné comme suit, il s'agit une liste de dictionnaires [(doc)](https://docs.python.org/3/tutorial/datastructures.html#dictionaries) :
**Question 1.1 -** Calculez la moyenne de tous les étudiants disponibles dans la liste d'étudiants `students_list`. Vous nommerez votre fonction `average_grade` et lui donnerez en paramètre la variable `L` qui contient la liste d'étudiant. Conseil : pensez à convertir les variables au bon type de données (par ex. en utilisant `int()`ou `float()`).
**Question 1.1 -** Calculez la moyenne de tous les étudiants disponibles dans la liste d'étudiants `students_list`. Vous nommerez votre fonction `average_grade` et lui donnerez en paramètre la variable `L` qui contient la liste d'étudiant. Conseil : pensez à convertir les variables au bon type de données (par ex. en utilisant `int()`ou `float()`).
**Question 1.2 -** Trouver la note maximale de manière _récursive_ et comparez avec la fonction `max` (qui peut prendre un argument `key` afin de trier par note) donnée ci-dessous. Attention aux types des données.
**Question 1.2 -** Trouver la note maximale de manière _récursive_ et comparez avec la fonction `max` (qui peut prendre un argument `key` afin de trier par note) donnée ci-dessous. Attention aux types des données.
**Question 1.4 -** Trier la liste de données par ordre croissant en implémentant un _tri par sélection_ fourni dans le pseudo-code ci-dessous (issu de [cette page](https://fr.wikipedia.org/wiki/Tri_par_s%C3%A9lection)). L'argument `key` permet d'indiquer sur quel attribut réaliser le tri (ici ce sera la note). Voir l'usage de cet attribut dans la cellule de test suivante.
**Question 1.4 -** Trier la liste de données par ordre croissant en implémentant un _tri par sélection_ fourni dans le pseudo-code ci-dessous (issu de [cette page](https://fr.wikipedia.org/wiki/Tri_par_s%C3%A9lection)). L'argument `key` permet d'indiquer sur quel attribut réaliser le tri (ici ce sera la note). Voir l'usage de cet attribut dans la cellule de test suivante.
Désormais nous allons implémenter de nouvelles structures de manipulation de listes : les Piles et les Files. Et à terme réaliser un tri de plus en plus efficace (avec un Tas). Nous allons commencer avec la Pile dont nous vous fournissons la structure de données, nommée `Stack` et disponible ci-desous:
Désormais nous allons implémenter de nouvelles structures de manipulation de listes : les Piles et les Files. Et à terme réaliser un tri de plus en plus efficace (avec un Tas). Nous allons commencer avec la Pile dont nous vous fournissons la structure de données, nommée `Stack` et disponible ci-desous:
La pile ci-dessus a son équivalent avec la méthode `LifoQueue` du module `queue` de pile défini ci-dessous [(doc)](https://docs.python.org/3/library/queue.html). Nous voyons bien que le dépilement renvoie les données dans l'ordre inverse de leur empillement.
La pile ci-dessus a son équivalent avec la méthode `LifoQueue` du module `queue` de pile défini ci-dessous [(doc)](https://docs.python.org/3/library/queue.html). Nous voyons bien que le dépilement renvoie les données dans l'ordre inverse de leur empillement.
**Question 2.1 -** Utiliser la pile `Stack` afin d'empiler les données de la liste `students_list`. Maintenant dépilez cette pile et comparer les résultats avec la méthode `LifoQueue` afin de vérifier que vous obtenez les mêmes résultats (avec le `while` fourni dans le code ci-dessous) :
**Question 2.1 -** Utiliser la pile `Stack` afin d'empiler les données de la liste `students_list`. Maintenant dépilez cette pile et comparer les résultats avec la méthode `LifoQueue` afin de vérifier que vous obtenez les mêmes résultats (avec le `while` fourni dans le code ci-dessous) :
**Question 2.2 -** Transformer la structure de Pile `Stack` en une File (que vous nommerez `Queue`) et vérifiez que vous obtenez les mêmes résultats en récupérant les données qu'avec le module `Queue()` de Python.
**Question 2.2 -** Transformer la structure de Pile `Stack` en une File (que vous nommerez `Queue`) et vérifiez que vous obtenez les mêmes résultats en récupérant les données qu'avec le module `Queue()` de Python.
**Question 2.3** - Mettez à jour votre File afin de ne pas générer d'exception `IndexError`. On peut par exemple renvoyer une valeur de type `None` si aucune valeur n'est disponible.
**Question 2.3** - Mettez à jour votre File afin de ne pas générer d'exception `IndexError`. On peut par exemple renvoyer une valeur de type `None` si aucune valeur n'est disponible.
**Question 2.4** - Enfin, transformer la file (classe `Queue`) pour en faire une file de priorité `FilePriorite`. Pour rappel, une file de priorité renvoie les éléments selon un critère particulier (par exemple la note minimale des valeurs contenues dans la file).
**Question 2.4** - Enfin, transformer la file (classe `Queue`) pour en faire une file de priorité `FilePriorite`. Pour rappel, une file de priorité renvoie les éléments selon un critère particulier (par exemple la note minimale des valeurs contenues dans la file).
Conseil
Conseil
- garder la liste des valeurs internes constamment triée lors de l'ajout;
- garder la liste des valeurs internes constamment triée lors de l'ajout;
- pour cela inclure la nouvelle valeur avec la méthode `ajoute()` à la bonne place (en conservant l'ordre de la liste interne) avec `.insert(index, valeur)`
- pour cela inclure la nouvelle valeur avec la méthode `ajoute()` à la bonne place (en conservant l'ordre de la liste interne) avec `.insert(index, valeur)`
Nous vous fournissons aussi le module `PriorityQueue` qui est une file de priorité existante [(doc)](https://docs.python.org/3/library/queue.html) afin de comparer votre code.
Nous vous fournissons aussi le module `PriorityQueue` qui est une file de priorité existante [(doc)](https://docs.python.org/3/library/queue.html) afin de comparer votre code.
Nous allons maintenant implémenter **arbre binaire complet**. Cet arbre sera utile pour l'exercice suivant et la création d'un Tas. Cet arbre (dans sa configuration [min-heap](https://en.wikipedia.org/wiki/Min-max_heaphttps://en.wikipedia.org/wiki/Min-max_heap), où les données sont renvoyées par ordre croissant) satisfait la propriété suivante : la valeur de tout noeud est inférieure à celle de ses enfant. Cet arbre binaire sera de surcroît implémenté en utilisant un tableau (car il s'agit d'un arbre _complet_ où tous les niveaux sont remplis, sauf éventuellement le dernier). L'arbre binaire possède des noeuds ayant un index $i$, avec un fils gauche et un fils droit. Le tableau et l'arbre sont reliés de la façon suivante :
Nous allons maintenant implémenter **arbre binaire complet**. Cet arbre sera utile pour l'exercice suivant et la création d'un Tas. Cet arbre binaire sera implémenté en utilisant un tableau (car il s'agit d'un arbre _complet_ où tous les niveaux sont remplis, sauf éventuellement le dernier). L'arbre binaire possède des noeuds ayant un index $i$, avec un fils gauche et un fils droit. Le tableau et l'arbre sont reliés de la façon suivante :
- La racine a la position $i = 0$ (cette valeur sera renvoyée par la fonction `get_racine`)
- La racine a la position $i = 0$ (cette valeur sera renvoyée par la fonction `get_racine`)
- Le parent a la position $\lfloor (i - 1)/ 2 \rfloor$ (fonction `get_parent`)
- Le parent a la position $\lfloor (i - 1)/ 2 \rfloor$ (fonction `get_parent`)
- Le fils gauche a la position $2 \times i + 1$ (fonction `get_fils_gauche`)
- Le fils gauche a la position $2 \times i + 1$ (fonction `get_fils_gauche`)
- Le fils droit a la position $2 \times i + 2$ (fonction `get_fils_droit`)
- Le fils droit a la position $2 \times i + 2$ (fonction `get_fils_droit`)
**Exercice 3.1** - Implémentez un arbre binaire sous forme de classe appellée `BinaryTree` (basée sur la file de priorité) avec les fonctions ci-dessus (`get_racine`, `get_parent`, `get_fils_gauche`, `get_fils_droit`). Vous rajouterez une méthode `taille`qui renvoie la taille de l'arbre binaire (longueur de la liste interne).
**Exercice 3.1** - Implémentez un arbre binaire sous forme de classe appellée `BinaryTree` (basée sur la file de priorité) avec les fonctions ci-dessus (`get_racine`, `get_parent`, `get_fils_gauche`, `get_fils_droit`). Vous rajouterez une méthode `taille`qui renvoie la taille de l'arbre binaire (longueur de la liste interne).
**Exercice 3.3** - L'abre que vous venez de créer sera utilisé dans l'exercice suivant afin de créer un tas. Pour cela nous devrons nous assurer que l'arbre atisfait la propriété suivante : la valeur de tout noeud est inférieure à celle de ses enfant. Ecrire une fonction de parcours de l'arbre qui renvoie `True` si cette propriété est vérifée, sinon `False`. Conseil :
1. Proposez une approche récursive
2. Le cas d'arrêt est un noeud vide
3. L'appel récursif est déclanché en fonction de la valeur du noeud en cours et celle de ses enfants
## Exercice 4 (Bonus) : Création d'un tas et tri par tas
## Exercice 4 (Bonus) : Création d'un tas et tri par tas
Une structure de donnée de `Tas` permet de réduire la complexité de manipulation d'une file de priorité. La particularité de cette méthode est de répartir le coût de la recherche du plus petit élément (qui sera renvoyé) entre l'ajout et la suppression.
Nous allons désormais créer une nouvelle structure de donnée : le `Tas`. Celle-ci permettra à termes de réduire la complexité de manipulation d'une file de priorité, en répartissant le coût de la recherche du plus petit élément (qui sera renvoyé) entre l'ajout et la suppression.
Nous nous baserons pour cela sur l'arbre binaire de la question précédente, qui est dit dans une configuration [min-heap](https://en.wikipedia.org/wiki/Min-max_heaphttps://en.wikipedia.org/wiki/Min-max_heap), où les données sont renvoyées par ordre croissant.
**Question 4.1 -** Implémentez une structure de `Tas`comme suit :
**Question 4.1 -** Implémentez une structure de `Tas`comme suit :
- Créez une structure de données de `Tas` similaire au `BinaryTree`
- Créez une structure de données de `Tas` similaire au `BinaryTree`
- Créez une méthode `inserer` (que l'on utilisera à la place d'`ajoute`) dont le principe est le suivant :
- Créez une méthode `inserer` (que l'on utilisera à la place d'`ajoute`) dont le principe est le suivant :
- Chaque nouveau noeud est rajouté comme dernier élément du tableau (à la fin donc)
- Chaque nouveau noeud est rajouté comme dernier élément du tableau (à la fin donc)
- Comparez ce noeud à son parent et si il est plus grand que ce parent inversez-le
- Comparez ce noeud à son parent et si il est plus grand que ce parent inversez-le
- Répétez tant que la condition ci-dessus est vraie et que la racine n'est pas atteinte
- Répétez tant que la condition ci-dessus est vraie et que la racine n'est pas atteinte
- Créez une méthode `enlever` dont le principe est le suivant :
- Créez une méthode `enlever` dont le principe est le suivant :
- Enlever l'élément racine de l'arbre (premier élément du tableau)
- Enlever l'élément racine de l'arbre (premier élément du tableau)
- Déplacer le dernier noeud de l'arbre (dernier élément du tableau) à la place de la racine de l'arbre
- Déplacer le dernier noeud de l'arbre (dernier élément du tableau) à la place de la racine de l'arbre
- Vérifier que la racine conserve la propriété de Tas (qu'elle est inférieur à ses enfants); si ce n'est pas le cas alors implémenter une méthode `descendre` définie par la suite.
- Vérifier que la racine conserve la propriété de Tas (qu'elle est inférieur à ses enfants); si ce n'est pas le cas alors implémenter une méthode `descendre` définie par la suite.
- Créez une méthode `descendre` qui :
- Créez une méthode `descendre` qui :
- Prend le plus petit des enfants
- Prend le plus petit des enfants
- Echange sa place avec lui si il est plus petit
- Echange sa place avec lui si il est plus petit
- Répéte cela tant qu'il existe des enfants
- Répéte cela tant qu'il existe des enfants
Attention : pensez à tester si il existe un fils droit et un fils gauche lors des opération de descente lors de l'insertion.
Attention : pensez à tester si il existe un fils droit et un fils gauche lors des opération de descente lors de l'insertion.
Pour en savoir plus comment Python réalise le tri, lire la documentation du `TimSort` (doc)[https://en.wikipedia.org/wiki/Timsort] qui est l'algorithme de tri utilisé.
Pour en savoir plus comment Python réalise le tri, lire la documentation du `TimSort` (doc)[https://en.wikipedia.org/wiki/Timsort] qui est l'algorithme de tri utilisé.
- Mettez à jour votre file afin de renvoyer [une exception](https://docs.python.org/3/tutorial/errors.html) si on demande une valeur qui n'est pas dans la structure de données (pile, file, etc.)
- Mettez à jour votre file afin de renvoyer [une exception](https://docs.python.org/3/tutorial/errors.html) si on demande une valeur qui n'est pas dans la structure de données (pile, file, etc.)
- Utilisez un [grand jeu de donnée](https://generatedata.com/) d'étudiants pour les premières questions.
- Utilisez un [grand jeu de donnée](https://generatedata.com/) d'étudiants pour les premières questions.
"### IMPORTANT A LIRE (SUR L'UTILISATION DE CE NOTEBOOK)\n",
"<details style=\"border: 1px\">\n",
"<summary> RAPPELS SUR L'UTILISATION DES NOTEBOOKS</summary>\n",
"\n",
"\n",
"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. Pour résumer :\n",
"### Comment utiliser ces notebooks ?\n",
"\n",
"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 :\n",
" \n",
" \n",
"1) Répondez aux questions dans les cellules en dessous des questions.\n",
"1) Répondez aux questions dans les cellules en dessous des questions.\n",
"\n",
"\n",
...
@@ -46,15 +49,12 @@
...
@@ -46,15 +49,12 @@
"\n",
"\n",
"(vous pouvez effacer ces deux lignes quand vous les rencontrez mais ne modifiez pas les noms de fonctions sinon les tests ne marchent plus).\n",
"(vous pouvez effacer ces deux lignes quand vous les rencontrez mais ne modifiez pas les noms de fonctions sinon les tests ne marchent plus).\n",
"\n",
"\n",
"3) Exécuter enfin les cellules dans l'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.\n",
"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.\n",
" \n",
" \n",
"4) Vous pouvez créer de nouvelles cellules comme bon vous semble.\n",
"4) Vous pouvez créer de nouvelles cellules comme bon vous semble.\n",
"\n",
"\n",
"**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.\n",
"**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.\n",
"\n",
"</details>"
"\n",
"## Chargement de fonctions\n",
"_Les fonctions ci-dessous seront nécessaire afin de mener à bien ce TD. Il faut donc les charger en exécutant leur cellulle. Ne pas les modifier._"
]
]
},
},
{
{
...
@@ -134,7 +134,7 @@
...
@@ -134,7 +134,7 @@
"outputs": [],
"outputs": [],
"source": [
"source": [
"class Node:\n",
"class Node:\n",
" def __init__(self, value : int, left : Node = None, right : Node = None):\n",
" def __init__(self, value : int, left = None, right = None):\n",
" self.value = value\n",
" self.value = value\n",
" self.left = left\n",
" self.left = left\n",
" self.right = right"
" self.right = right"
...
@@ -716,18 +716,10 @@
...
@@ -716,18 +716,10 @@
"source": [
"source": [
"## Pour aller plus loin\n",
"## Pour aller plus loin\n",
"\n",
"\n",
"- Rajouter des tests dans les exemples ci-dessus avec des arbres plus complexes\n",
"- Rajouter des tests dans les exemples ci-dessus avec des arbres plus complexes, des cas particuliers, etc.\n",
"- Inclure des Exceptions dans votre code\n",
"- Inclure des Exceptions dans votre code afin de gérer par exemple l'accès à un index de liste inexistant, etc.\n",
"- Créer une table de hachage afin de mémoriser les opérations déja réalisées pour l'arbre syntaxique"
"- Créer une table de hachage afin de mémoriser les opérations déja réalisées pour l'arbre syntaxique"
### IMPORTANT A LIRE (SUR L'UTILISATION DE CE NOTEBOOK)
<detailsstyle="border: 1px">
<summary> RAPPELS SUR L'UTILISATION DES NOTEBOOKS</summary>
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. Pour résumer :
### 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.
1) Répondez aux questions dans les cellules en dessous des questions.
2) Votre code devra remplacer le texte suivant :
2) Votre code devra remplacer le texte suivant :
```python
```python
# YOUR CODE HERE
# YOUR CODE HERE
raiseNotImplementedError()
raiseNotImplementedError()
```
```
(vous pouvez effacer ces deux lignes quand vous les rencontrez mais ne modifiez pas les noms de fonctions sinon les tests ne marchent plus).
(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 l'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.
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.
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.
**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>
## Chargement de fonctions
_Les fonctions ci-dessous seront nécessaire afin de mener à bien ce TD. Il faut donc les charger en exécutant leur cellulle. Ne pas les modifier._
Ce TD vous fera manipuler les arbres binaires, qui sont une structure de donnée efficace afin de trier des listes mais aussi de réaliser des opérations plus avancées grace à des méthodes de parcours en largeur et en profondeur.
Ce TD vous fera manipuler les arbres binaires, qui sont une structure de donnée efficace afin de trier des listes mais aussi de réaliser des opérations plus avancées grace à des méthodes de parcours en largeur et en profondeur.
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 nous allons 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 comporte des noeuds ayant au _maximum deux enfants_
- il est _complet_ si tous les noeuds de tous les niveaux ont deux enfants
- il est _complet_ si tous les noeuds de tous les niveaux ont deux enfants
- il est _équilibré_ si l'arbre est complet sauf pour le dernier niveau.
- il est _équilibré_ si l'arbre est complet sauf pour le dernier niveau.
Voici un exemple d'arbre binaire :
Voici un exemple d'arbre binaire :
```
```
1
1
/ \
/ \
2 3
2 3
```
```
Dans cet exercice nous stockeront l'arbre 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 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 :
**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).
**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).
**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.** 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 à :
1. Intialiser la file avec la racine de l'arbre
1. Intialiser la file avec la racine de l'arbre
2. Dé-filer une valeur et la stocker dans une liste de résultat
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
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
4. Répéter l'étape 2 jusqu'à ce que la file soit vide, renvoyer le résultat
**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`). Cette fonction prendra la racine de l'arbre `root`en paramètre, ainsi que le noeud dont on veut calculer la profondeur avec le paramètre `target_value`.
**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`). Cette fonction prendra la racine de l'arbre `root`en paramètre, ainsi que le noeud dont on veut calculer la profondeur avec le paramètre `target_value`.
Conseil : 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 noeud parcouru seulement.
Conseil : 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 noeud parcouru seulement.
**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** - É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.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** - 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.
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 :
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)`
Expression : `(3-2) * (7+(10/2)`
Résultat : `12.0`
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).
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 /
3 2 7 /
/ \
/ \
10 2
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 le stocker :
**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é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.
Conseil : proposer une solution récursive avec un cas d'arrêt et des appels récursifs comme suit :
Conseil : proposer 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 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 c'est une valeur numérique, renvoyer cette valeur (cas d'arrêt car c'est une feuille de l'arbre)
**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** - É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(" ")}`.
Conseil :
Conseil :
- Parcourez caractère par caractère l'expression textuelle
- Parcourez caractère par caractère l'expression textuelle
- Et utilisez une Pile permettant la bonne construction de l'arbre au fur et à mesure de son parcours
- Et utilisez une Pile permettant la bonne construction de l'arbre au fur et à mesure de son parcours
**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.