ajout de tous les cours et TP préparés cet été
This commit is contained in:
419
Arbres/ABR.md
Normal file
419
Arbres/ABR.md
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
# Arbres Binaires de Recherche (ABR)
|
||||||
|
|
||||||
|
> Les arbres binaires de recherche sont une structure de données fondamentale en informatique. Ils permettent d'effectuer des opérations de recherche, d'insertion et de suppression de manière efficace, avec une complexité moyenne en O(log n).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Définition
|
||||||
|
|
||||||
|
Un **Arbre Binaire de Recherche** (ABR) est un arbre binaire qui vérifie la propriété suivante pour **tout nœud N** :
|
||||||
|
|
||||||
|
- Toutes les valeurs du **sous-arbre gauche** sont **strictement inférieures** à la valeur de N
|
||||||
|
- Toutes les valeurs du **sous-arbre droit** sont **strictement supérieures** à la valeur de N
|
||||||
|
|
||||||
|
### Exemple d'ABR
|
||||||
|
|
||||||
|
```
|
||||||
|
8
|
||||||
|
/ \
|
||||||
|
3 10
|
||||||
|
/ \ \
|
||||||
|
1 6 14
|
||||||
|
/ \ /
|
||||||
|
4 7 13
|
||||||
|
```
|
||||||
|
|
||||||
|
Cet arbre est un ABR car :
|
||||||
|
- Pour le nœud 8 : tous les nœuds à gauche (3, 1, 6, 4, 7) sont < 8, tous ceux à droite (10, 14, 13) sont > 8
|
||||||
|
- Pour le nœud 3 : 1 < 3 et 6 > 3
|
||||||
|
- Et ainsi de suite pour tous les nœuds...
|
||||||
|
|
||||||
|
### Contre-exemple
|
||||||
|
|
||||||
|
```
|
||||||
|
8
|
||||||
|
/ \
|
||||||
|
3 10
|
||||||
|
/ \
|
||||||
|
1 9 ← 9 > 8, il devrait être à droite de 8 !
|
||||||
|
```
|
||||||
|
|
||||||
|
Cet arbre n'est **pas** un ABR car 9 est dans le sous-arbre gauche de 8 alors que 9 > 8.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Intérêt des ABR
|
||||||
|
|
||||||
|
### Pourquoi utiliser un ABR plutôt qu'une liste ?
|
||||||
|
|
||||||
|
| Opération | Liste non triée | Liste triée | ABR (équilibré) |
|
||||||
|
|-----------|-----------------|-------------|-----------------|
|
||||||
|
| Recherche | O(n) | O(log n) | **O(log n)** |
|
||||||
|
| Insertion | O(1) | O(n) | **O(log n)** |
|
||||||
|
| Suppression | O(n) | O(n) | **O(log n)** |
|
||||||
|
|
||||||
|
L'ABR combine les avantages :
|
||||||
|
- Recherche rapide (comme une liste triée avec dichotomie)
|
||||||
|
- Insertion rapide (pas besoin de décaler des éléments)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Implémentation en Python
|
||||||
|
|
||||||
|
Nous utilisons une classe `Noeud` pour représenter chaque nœud de l'arbre :
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Noeud:
|
||||||
|
def __init__(self, valeur):
|
||||||
|
self.valeur = valeur
|
||||||
|
self.gauche = None
|
||||||
|
self.droit = None
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Recherche dans un ABR
|
||||||
|
|
||||||
|
### Principe
|
||||||
|
|
||||||
|
Pour rechercher une valeur `v` dans un ABR :
|
||||||
|
1. Si l'arbre est vide → la valeur n'est pas présente
|
||||||
|
2. Si `v == valeur du nœud courant` → trouvé !
|
||||||
|
3. Si `v < valeur du nœud courant` → chercher dans le sous-arbre gauche
|
||||||
|
4. Si `v > valeur du nœud courant` → chercher dans le sous-arbre droit
|
||||||
|
|
||||||
|
### Algorithme récursif
|
||||||
|
|
||||||
|
```python
|
||||||
|
def rechercher(noeud, valeur):
|
||||||
|
"""
|
||||||
|
Recherche une valeur dans un ABR.
|
||||||
|
Retourne True si trouvée, False sinon.
|
||||||
|
"""
|
||||||
|
# Cas de base : arbre vide
|
||||||
|
if noeud is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Valeur trouvée
|
||||||
|
if valeur == noeud.valeur:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Recherche dans le sous-arbre approprié
|
||||||
|
if valeur < noeud.valeur:
|
||||||
|
return rechercher(noeud.gauche, valeur)
|
||||||
|
else:
|
||||||
|
return rechercher(noeud.droit, valeur)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exemple de déroulement
|
||||||
|
|
||||||
|
Recherche de 7 dans l'ABR :
|
||||||
|
```
|
||||||
|
8 8 > 7 → aller à gauche
|
||||||
|
/ \
|
||||||
|
3 10 3 < 7 → aller à droite
|
||||||
|
/ \
|
||||||
|
1 6 6 < 7 → aller à droite
|
||||||
|
/ \
|
||||||
|
4 7 7 == 7 → TROUVÉ !
|
||||||
|
```
|
||||||
|
|
||||||
|
### Complexité
|
||||||
|
|
||||||
|
- **Meilleur cas** : O(1) — la valeur est à la racine
|
||||||
|
- **Cas moyen** (arbre équilibré) : **O(log n)** — on divise par 2 à chaque étape
|
||||||
|
- **Pire cas** (arbre dégénéré/filiforme) : O(n) — l'arbre ressemble à une liste
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Insertion dans un ABR
|
||||||
|
|
||||||
|
### Principe
|
||||||
|
|
||||||
|
Pour insérer une valeur `v` dans un ABR :
|
||||||
|
1. Si l'arbre est vide → créer un nouveau nœud avec `v`
|
||||||
|
2. Si `v < valeur du nœud courant` → insérer dans le sous-arbre gauche
|
||||||
|
3. Si `v > valeur du nœud courant` → insérer dans le sous-arbre droit
|
||||||
|
4. Si `v == valeur du nœud courant` → ne rien faire (pas de doublons)
|
||||||
|
|
||||||
|
### Algorithme récursif
|
||||||
|
|
||||||
|
```python
|
||||||
|
def inserer(noeud, valeur):
|
||||||
|
"""
|
||||||
|
Insère une valeur dans un ABR.
|
||||||
|
Retourne la racine de l'arbre (éventuellement modifiée).
|
||||||
|
"""
|
||||||
|
# Cas de base : arbre vide, on crée un nouveau nœud
|
||||||
|
if noeud is None:
|
||||||
|
return Noeud(valeur)
|
||||||
|
|
||||||
|
# Insertion récursive
|
||||||
|
if valeur < noeud.valeur:
|
||||||
|
noeud.gauche = inserer(noeud.gauche, valeur)
|
||||||
|
elif valeur > noeud.valeur:
|
||||||
|
noeud.droit = inserer(noeud.droit, valeur)
|
||||||
|
# Si valeur == noeud.valeur, on ne fait rien (pas de doublons)
|
||||||
|
|
||||||
|
return noeud
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exemple : construction d'un ABR
|
||||||
|
|
||||||
|
Insertion successive de : 8, 3, 10, 1, 6, 14, 4, 7, 13
|
||||||
|
|
||||||
|
```
|
||||||
|
Étape 1: 8 Étape 2: 8 Étape 3: 8
|
||||||
|
/ / \
|
||||||
|
3 3 10
|
||||||
|
|
||||||
|
Étape 4: 8 Étape 5: 8 ... Résultat final:
|
||||||
|
/ \ / \
|
||||||
|
3 10 3 10 8
|
||||||
|
/ / \ / \
|
||||||
|
1 1 6 3 10
|
||||||
|
/ \ \
|
||||||
|
1 6 14
|
||||||
|
/ \ /
|
||||||
|
4 7 13
|
||||||
|
```
|
||||||
|
|
||||||
|
### Complexité
|
||||||
|
|
||||||
|
- **Cas moyen** : **O(log n)**
|
||||||
|
- **Pire cas** : O(n) — si on insère des valeurs déjà triées, l'arbre devient filiforme
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Suppression dans un ABR
|
||||||
|
|
||||||
|
La suppression est l'opération la plus complexe. Il y a trois cas à considérer :
|
||||||
|
|
||||||
|
### Cas 1 : Le nœud à supprimer est une feuille
|
||||||
|
|
||||||
|
On le supprime simplement.
|
||||||
|
|
||||||
|
```
|
||||||
|
Supprimer 4 :
|
||||||
|
6 6
|
||||||
|
/ \ → / \
|
||||||
|
4 7 7
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cas 2 : Le nœud a un seul enfant
|
||||||
|
|
||||||
|
On le remplace par son unique enfant.
|
||||||
|
|
||||||
|
```
|
||||||
|
Supprimer 10 :
|
||||||
|
8 8
|
||||||
|
/ \ → / \
|
||||||
|
3 10 3 14
|
||||||
|
\ /
|
||||||
|
14 13
|
||||||
|
/
|
||||||
|
13
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cas 3 : Le nœud a deux enfants
|
||||||
|
|
||||||
|
On le remplace par :
|
||||||
|
- Son **successeur** : le plus petit élément du sous-arbre droit
|
||||||
|
- Ou son **prédécesseur** : le plus grand élément du sous-arbre gauche
|
||||||
|
|
||||||
|
Puis on supprime récursivement ce successeur/prédécesseur.
|
||||||
|
|
||||||
|
```
|
||||||
|
Supprimer 3 (avec successeur = 4) :
|
||||||
|
8 8
|
||||||
|
/ \ / \
|
||||||
|
3 10 → 4 10
|
||||||
|
/ \ \ / \ \
|
||||||
|
1 6 14 1 6 14
|
||||||
|
/ \ / \ /
|
||||||
|
4 7 13 7 13
|
||||||
|
```
|
||||||
|
|
||||||
|
### Algorithme récursif
|
||||||
|
|
||||||
|
```python
|
||||||
|
def trouver_min(noeud):
|
||||||
|
"""Trouve le nœud avec la valeur minimale (le plus à gauche)."""
|
||||||
|
while noeud.gauche is not None:
|
||||||
|
noeud = noeud.gauche
|
||||||
|
return noeud
|
||||||
|
|
||||||
|
def supprimer(noeud, valeur):
|
||||||
|
"""
|
||||||
|
Supprime une valeur d'un ABR.
|
||||||
|
Retourne la racine de l'arbre (éventuellement modifiée).
|
||||||
|
"""
|
||||||
|
# Cas de base : arbre vide
|
||||||
|
if noeud is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Recherche du nœud à supprimer
|
||||||
|
if valeur < noeud.valeur:
|
||||||
|
noeud.gauche = supprimer(noeud.gauche, valeur)
|
||||||
|
elif valeur > noeud.valeur:
|
||||||
|
noeud.droit = supprimer(noeud.droit, valeur)
|
||||||
|
else:
|
||||||
|
# Nœud trouvé, on le supprime
|
||||||
|
|
||||||
|
# Cas 1 et 2 : 0 ou 1 enfant
|
||||||
|
if noeud.gauche is None:
|
||||||
|
return noeud.droit
|
||||||
|
elif noeud.droit is None:
|
||||||
|
return noeud.gauche
|
||||||
|
|
||||||
|
# Cas 3 : 2 enfants
|
||||||
|
# On remplace par le successeur (min du sous-arbre droit)
|
||||||
|
successeur = trouver_min(noeud.droit)
|
||||||
|
noeud.valeur = successeur.valeur
|
||||||
|
noeud.droit = supprimer(noeud.droit, successeur.valeur)
|
||||||
|
|
||||||
|
return noeud
|
||||||
|
```
|
||||||
|
|
||||||
|
### Complexité
|
||||||
|
|
||||||
|
- **Cas moyen** : **O(log n)**
|
||||||
|
- **Pire cas** : O(n)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Parcours infixe et tri
|
||||||
|
|
||||||
|
Une propriété remarquable des ABR : le **parcours infixe** donne les éléments **dans l'ordre croissant**.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def parcours_infixe(noeud):
|
||||||
|
"""Affiche les éléments de l'ABR dans l'ordre croissant."""
|
||||||
|
if noeud is not None:
|
||||||
|
parcours_infixe(noeud.gauche)
|
||||||
|
print(noeud.valeur, end=" ")
|
||||||
|
parcours_infixe(noeud.droit)
|
||||||
|
```
|
||||||
|
|
||||||
|
Pour l'ABR d'exemple :
|
||||||
|
```
|
||||||
|
Parcours infixe : 1 3 4 6 7 8 10 13 14
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Vérifier si un arbre est un ABR
|
||||||
|
|
||||||
|
Attention : il ne suffit pas de vérifier localement que `gauche < noeud < droit`. Il faut vérifier que **toutes** les valeurs du sous-arbre gauche sont inférieures.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def est_ABR(noeud, mini=float('-inf'), maxi=float('inf')):
|
||||||
|
"""
|
||||||
|
Vérifie si l'arbre est un ABR valide.
|
||||||
|
mini et maxi définissent l'intervalle autorisé pour la valeur du nœud.
|
||||||
|
"""
|
||||||
|
if noeud is None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# La valeur doit être dans l'intervalle ]mini, maxi[
|
||||||
|
if not (mini < noeud.valeur < maxi):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Vérification récursive avec mise à jour des bornes
|
||||||
|
return (est_ABR(noeud.gauche, mini, noeud.valeur) and
|
||||||
|
est_ABR(noeud.droit, noeud.valeur, maxi))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Problème des arbres dégénérés
|
||||||
|
|
||||||
|
Si on insère des valeurs dans l'ordre croissant (1, 2, 3, 4, 5...), l'ABR devient une liste chaînée :
|
||||||
|
|
||||||
|
```
|
||||||
|
1
|
||||||
|
\
|
||||||
|
2
|
||||||
|
\
|
||||||
|
3
|
||||||
|
\
|
||||||
|
4
|
||||||
|
\
|
||||||
|
5
|
||||||
|
```
|
||||||
|
|
||||||
|
Dans ce cas, toutes les opérations deviennent en O(n) au lieu de O(log n).
|
||||||
|
|
||||||
|
### Solution : les arbres équilibrés
|
||||||
|
|
||||||
|
Pour garantir O(log n), on utilise des **arbres équilibrés** :
|
||||||
|
- **AVL** : après chaque insertion/suppression, on rééquilibre si nécessaire
|
||||||
|
- **Arbres rouge-noir** : utilisés dans beaucoup de bibliothèques standard
|
||||||
|
|
||||||
|
> Ces structures sont hors programme en NSI, mais il est important de comprendre pourquoi elles existent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Résumé des complexités
|
||||||
|
|
||||||
|
| Opération | ABR équilibré | ABR dégénéré |
|
||||||
|
|-----------|---------------|--------------|
|
||||||
|
| Recherche | O(log n) | O(n) |
|
||||||
|
| Insertion | O(log n) | O(n) |
|
||||||
|
| Suppression | O(log n) | O(n) |
|
||||||
|
| Parcours complet | O(n) | O(n) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Exercices
|
||||||
|
|
||||||
|
### Exercice 1 : Construction d'un ABR
|
||||||
|
Dessiner l'ABR obtenu après insertion successive des valeurs : 50, 30, 70, 20, 40, 60, 80, 35
|
||||||
|
|
||||||
|
### Exercice 2 : Recherche
|
||||||
|
Sur l'ABR de l'exercice 1, combien de comparaisons faut-il pour :
|
||||||
|
- Trouver 35 ?
|
||||||
|
- Constater que 45 n'est pas présent ?
|
||||||
|
|
||||||
|
### Exercice 3 : Suppression
|
||||||
|
Sur l'ABR de l'exercice 1, dessiner l'arbre après suppression de :
|
||||||
|
1. 20 (feuille)
|
||||||
|
2. 30 (nœud avec 2 enfants)
|
||||||
|
|
||||||
|
### Exercice 4 : Implémentation
|
||||||
|
Compléter la classe suivante avec les méthodes manquantes :
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ABR:
|
||||||
|
def __init__(self):
|
||||||
|
self.racine = None
|
||||||
|
|
||||||
|
def inserer(self, valeur):
|
||||||
|
"""Insère une valeur dans l'ABR."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def rechercher(self, valeur):
|
||||||
|
"""Retourne True si valeur est dans l'ABR."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def supprimer(self, valeur):
|
||||||
|
"""Supprime une valeur de l'ABR."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def afficher_trie(self):
|
||||||
|
"""Affiche toutes les valeurs dans l'ordre croissant."""
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice 5 : Analyse
|
||||||
|
On insère n valeurs aléatoires dans un ABR initialement vide.
|
||||||
|
1. Quelle est la hauteur moyenne de l'arbre obtenu ?
|
||||||
|
2. Que se passe-t-il si les valeurs sont insérées dans l'ordre croissant ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
770
Arbres/ARBRES_BINAIRES.ipynb
Normal file
770
Arbres/ARBRES_BINAIRES.ipynb
Normal file
@@ -0,0 +1,770 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Les arbres binaires\n",
|
||||||
|
"\n",
|
||||||
|
"> Parmi la forêt d'arbres possibles, on s'intéressera essentiellement aux arbres dit binaires.\n",
|
||||||
|
"> Ceux ci sont principalement utilisés pour les bases de données (indexation), les moteurs de recherche, et les compilateurs."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Définition\n",
|
||||||
|
"\n",
|
||||||
|
"Un arbre **binaire** est un arbre de degré 2 (dont les noeuds sont de degré 2 au plus).\n",
|
||||||
|
"\n",
|
||||||
|
"Les enfants d'un noeud sont lus de **gauche à droite** et sont appelés **fils gauche** et **fils droit**.\n",
|
||||||
|
"\n",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 1\n",
|
||||||
|
"\n",
|
||||||
|
"Parmi les arbres du cours précédent, lesquels sont binaires ?\n",
|
||||||
|
"\n",
|
||||||
|
"**Réponse :**"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Remarque\n",
|
||||||
|
"\n",
|
||||||
|
"Les arbres binaires forment une structure de données qui peut se définir de façon récursive.\n",
|
||||||
|
"\n",
|
||||||
|
"Un arbre binaire est:\n",
|
||||||
|
"- soit vide\n",
|
||||||
|
"- soit composé d'une racine portant une étiquette (clé) et d'une paire d'arbres binaires, appelés sous-arbre gauche et sous-arbre droit.\n",
|
||||||
|
"\n",
|
||||||
|
"Les arbres binaires sont utilisés dans de très nombreuses activités informatiques, nous allons étudier et implanter cette structure de données."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"---\n",
|
||||||
|
"\n",
|
||||||
|
"## Comment représenter un arbre binaire... à la main...\n",
|
||||||
|
"\n",
|
||||||
|
"L'idée est de représenter l'arbre avec un tableau.\n",
|
||||||
|
"\n",
|
||||||
|
"`[r, a, b]` La racine r suivie de ses fils gauche et droit.\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"Puis on rajoute dans l'ordre les fils gauche et droit de a, puis ceux de b.\n",
|
||||||
|
"\n",
|
||||||
|
"`[r, a, b, c, d, e, f]`"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Remarque\n",
|
||||||
|
"\n",
|
||||||
|
"Chaque noeud se repère par son indice *n* dans la liste, son fils gauche se trouvant alors à l'indice *2n + 1* et son fils droit à l'indice *2n + 2*.\n",
|
||||||
|
"\n",
|
||||||
|
"**Exemple:** **b** est d'indice 2, son fils gauche se trouve alors à l'indice 5 et son fils droit à l'indice 6.\n",
|
||||||
|
"\n",
|
||||||
|
"Si un noeud n'a pas de fils, on le précise en mettant *None* à sa place. Notre arbre est alors représenté par le tableau:\n",
|
||||||
|
"\n",
|
||||||
|
"`[r, a, b, c, d, e, f, None, None, None, None, None, None, None, None]`"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Quelle taille doit avoir le tableau ?\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"Cet arbre est complet (tous les noeuds internes ont deux fils).\n",
|
||||||
|
"\n",
|
||||||
|
"Il possède 3 niveaux, sa hauteur ou profondeur est donc de 2. La taille du tableau sera de:\n",
|
||||||
|
"\n",
|
||||||
|
"$$2^0 + 2^1 + 2^2 + 2^3 = 2^4 -1 = 15$$\n",
|
||||||
|
"\n",
|
||||||
|
"*Il faut compter le nombre de noeuds, y compris les noeuds \"fantômes\" des feuilles.*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 2\n",
|
||||||
|
"\n",
|
||||||
|
"1. Quelle est la taille du tableau qui permet de représenter cet arbre ?\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"2. Écrire le tableau représentant cet arbre et le stocker dans une variable **t**\n",
|
||||||
|
"\n",
|
||||||
|
"3. Quelle propriété ont les indices des fils gauches et droits ?\n",
|
||||||
|
"\n",
|
||||||
|
"4. Voici un tableau représentant un arbre binaire. Le dessiner. Que peut-il représenter ?"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Tableau de la question 4\n",
|
||||||
|
"arbre_expression = ['*', '-', 5, 2, 6, None, None, None, None, None, None, None, None, None, None]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Question 2 : Écrire le tableau représentant l'arbre\n",
|
||||||
|
"t = # À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Code Python pour créer un arbre\n",
|
||||||
|
"\n",
|
||||||
|
"Voici un code Python qui crée la liste représentant l'arbre de l'exercice 2 :"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def creation_arbre(r, profondeur):\n",
|
||||||
|
" \"\"\"r : la racine (str ou int). la profondeur de l'arbre (int)\"\"\"\n",
|
||||||
|
" Arbre = [r] + [None for i in range(2**(profondeur+1)-2)]\n",
|
||||||
|
" return Arbre\n",
|
||||||
|
"\n",
|
||||||
|
"def insertion_noeud(arbre, n, fg, fd):\n",
|
||||||
|
" \"\"\"Insère les noeuds et leurs enfants dans l'arbre\"\"\"\n",
|
||||||
|
" indice = arbre.index(n)\n",
|
||||||
|
" arbre[2*indice+1] = fg\n",
|
||||||
|
" arbre[2*indice+2] = fd\n",
|
||||||
|
"\n",
|
||||||
|
"# création de l'arbre\n",
|
||||||
|
"arbre = creation_arbre(\"r\", 5)\n",
|
||||||
|
"# ajout des noeuds par niveau de gauche à droite\n",
|
||||||
|
"insertion_noeud(arbre, \"r\", \"a\", \"b\")\n",
|
||||||
|
"insertion_noeud(arbre, \"a\", \"c\", \"d\")\n",
|
||||||
|
"insertion_noeud(arbre, \"b\", \"e\", \"f\")\n",
|
||||||
|
"insertion_noeud(arbre, \"c\", None, \"h\")\n",
|
||||||
|
"insertion_noeud(arbre, \"d\", \"i\", \"j\")\n",
|
||||||
|
"insertion_noeud(arbre, \"e\", \"k\", None)\n",
|
||||||
|
"insertion_noeud(arbre, \"f\", None, None)\n",
|
||||||
|
"insertion_noeud(arbre, \"h\", None, None)\n",
|
||||||
|
"insertion_noeud(arbre, \"i\", None, None)\n",
|
||||||
|
"insertion_noeud(arbre, \"j\", \"m\", None)\n",
|
||||||
|
"insertion_noeud(arbre, \"k\", None, None)\n",
|
||||||
|
"insertion_noeud(arbre, \"m\", None, None)\n",
|
||||||
|
"\n",
|
||||||
|
"# pour vérifier\n",
|
||||||
|
"print(\"Taille du tableau :\", len(arbre))\n",
|
||||||
|
"print(\"Arbre :\", arbre)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 3\n",
|
||||||
|
"\n",
|
||||||
|
"1. Écrire une fonction qui retourne le parent d'un noeud s'il est dans l'arbre et None sinon.\n",
|
||||||
|
"2. Écrire une fonction qui retourne **True** si l'arbre est vide\n",
|
||||||
|
"3. Écrire une fonction qui retourne les enfants d'un noeud.\n",
|
||||||
|
"4. Une fonction qui renvoie le fils gauche d'un noeud s'il existe et None sinon\n",
|
||||||
|
"5. Même question pour le fils droit\n",
|
||||||
|
"6. Écrire une fonction qui renvoie **True** si le noeud est la racine de l'arbre\n",
|
||||||
|
"7. Écrire une fonction qui renvoie **True** si le noeud est une feuille\n",
|
||||||
|
"8. Écrire une fonction qui renvoie **True** si le noeud comporte un frère gauche ou droit.\n",
|
||||||
|
"\n",
|
||||||
|
"**Rappel : dans un tableau, si on note i l'indice d'un noeud, alors ses fils auront comme indice 2i+1 et 2i+2.**\n",
|
||||||
|
"\n",
|
||||||
|
"**Inversement, pour trouver le parent d'un noeud, il faut calculer : (i-1) // 2**"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Question 1 : Parent d'un noeud\n",
|
||||||
|
"def parent(arbre, noeud):\n",
|
||||||
|
" \"\"\"Retourne le parent du noeud s'il existe, None sinon\"\"\"\n",
|
||||||
|
" pass # À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Question 2 : Arbre vide\n",
|
||||||
|
"def est_vide(arbre):\n",
|
||||||
|
" \"\"\"Retourne True si l'arbre est vide\"\"\"\n",
|
||||||
|
" pass # À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Question 3 : Enfants d'un noeud\n",
|
||||||
|
"def enfants(arbre, noeud):\n",
|
||||||
|
" \"\"\"Retourne les enfants du noeud\"\"\"\n",
|
||||||
|
" pass # À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Question 4 : Fils gauche\n",
|
||||||
|
"def fils_gauche(arbre, noeud):\n",
|
||||||
|
" \"\"\"Retourne le fils gauche s'il existe, None sinon\"\"\"\n",
|
||||||
|
" pass # À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Question 5 : Fils droit\n",
|
||||||
|
"def fils_droit(arbre, noeud):\n",
|
||||||
|
" \"\"\"Retourne le fils droit s'il existe, None sinon\"\"\"\n",
|
||||||
|
" pass # À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Question 6 : Est racine\n",
|
||||||
|
"def est_racine(arbre, noeud):\n",
|
||||||
|
" \"\"\"Retourne True si le noeud est la racine\"\"\"\n",
|
||||||
|
" pass # À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Question 7 : Est feuille\n",
|
||||||
|
"def est_feuille(arbre, noeud):\n",
|
||||||
|
" \"\"\"Retourne True si le noeud est une feuille\"\"\"\n",
|
||||||
|
" pass # À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Question 8 : A un frère\n",
|
||||||
|
"def a_frere(arbre, noeud):\n",
|
||||||
|
" \"\"\"Retourne True si le noeud a un frère\"\"\"\n",
|
||||||
|
" pass # À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"---\n",
|
||||||
|
"\n",
|
||||||
|
"## Une seconde façon de faire...\n",
|
||||||
|
"\n",
|
||||||
|
"Comme vous l'avez sans doute constaté, il est assez fastidieux de représenter un arbre avec un unique tableau surtout pour un arbre très profond.\n",
|
||||||
|
"\n",
|
||||||
|
"L'idée est de représenter l'arbre avec un tableau contenant des tableaux.\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"Cet arbre se représente par le tableau:\n",
|
||||||
|
"\n",
|
||||||
|
"`['r', ['a', [], []], ['b', [], []]]`"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 4\n",
|
||||||
|
"\n",
|
||||||
|
"Écrire le tableau représentant l'arbre ci-dessous et le stocker dans une variable **t**\n",
|
||||||
|
"\n",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Exercice 4 : Représentation avec tableaux imbriqués\n",
|
||||||
|
"t = # À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Construction récursive avec dictionnaires\n",
|
||||||
|
"\n",
|
||||||
|
"Le code Python ci-dessous construit l'arbre de manière récursive.\n",
|
||||||
|
"\n",
|
||||||
|
"* Les noeuds sont représentés par un dictionnaire.\n",
|
||||||
|
"* L'arbre se construit depuis la racine en construisant les sous-arbres des fils gauche et droit."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"def noeud(nom, fg=None, fd=None):\n",
|
||||||
|
" return {'racine': nom, 'fg': fg, 'fd': fd}\n",
|
||||||
|
"\n",
|
||||||
|
"# création des noeuds\n",
|
||||||
|
"k = noeud('k')\n",
|
||||||
|
"f = noeud('f')\n",
|
||||||
|
"e = noeud('e', k, None)\n",
|
||||||
|
"b = noeud('b', e, f)\n",
|
||||||
|
"m = noeud('m')\n",
|
||||||
|
"j = noeud('j', m, None)\n",
|
||||||
|
"i = noeud('i')\n",
|
||||||
|
"d = noeud('d', i, j)\n",
|
||||||
|
"h = noeud('h')\n",
|
||||||
|
"c = noeud('c', None, h)\n",
|
||||||
|
"a = noeud('a', c, d)\n",
|
||||||
|
"racine = noeud('r', a, b)\n",
|
||||||
|
"\n",
|
||||||
|
"# création de l'arbre sous forme de liste imbriquée\n",
|
||||||
|
"def construit(arbre):\n",
|
||||||
|
" if arbre is None:\n",
|
||||||
|
" return []\n",
|
||||||
|
" else:\n",
|
||||||
|
" return [arbre['racine'], construit(arbre['fg']), construit(arbre['fd'])]\n",
|
||||||
|
"\n",
|
||||||
|
"arbre1 = construit(racine)\n",
|
||||||
|
"print(arbre1)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 5\n",
|
||||||
|
"\n",
|
||||||
|
"Écrire toutes les fonctions de l'exercice 3 dans le cas de cette implémentation de l'arbre (tableaux imbriqués)."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Exercice 5 : Fonctions pour tableaux imbriqués\n",
|
||||||
|
"# À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"---\n",
|
||||||
|
"\n",
|
||||||
|
"## Hauteur et parcours d'un arbre\n",
|
||||||
|
"\n",
|
||||||
|
"Nous allons maintenant nous intéresser à la hauteur de l'arbre, ainsi qu'aux différentes façons de le parcourir:\n",
|
||||||
|
"* Le parcours en largeur\n",
|
||||||
|
"* Le parcours en profondeur (parcours préfixe, parcours infixe et parcours suffixe)."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Calcul de la hauteur de l'arbre\n",
|
||||||
|
"\n",
|
||||||
|
"L'idée est la suivante:\n",
|
||||||
|
"* Si l'arbre est vide, la hauteur vaut -1\n",
|
||||||
|
"* Sinon la hauteur vaut 1 auquel il faut ajouter le maximum entre les hauteurs des sous-arbres gauche et droit.\n",
|
||||||
|
"* Ces sous-arbres sont eux-mêmes des arbres dont il faut calculer la hauteur.\n",
|
||||||
|
"\n",
|
||||||
|
"Une méthode **récursive** semble donc tout à fait adaptée à la situation.\n",
|
||||||
|
"\n",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 6\n",
|
||||||
|
"\n",
|
||||||
|
"Écrire cette fonction pour l'arbre précédent et vérifier que sa profondeur est de 4."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Exercice 6 : Hauteur de l'arbre\n",
|
||||||
|
"def hauteur(arbre):\n",
|
||||||
|
" \"\"\"Retourne la hauteur de l'arbre\"\"\"\n",
|
||||||
|
" pass # À compléter\n",
|
||||||
|
"\n",
|
||||||
|
"# Test\n",
|
||||||
|
"# print(hauteur(arbre1)) # Doit afficher 4"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"---\n",
|
||||||
|
"\n",
|
||||||
|
"## Les parcours\n",
|
||||||
|
"\n",
|
||||||
|
"### Le parcours en largeur\n",
|
||||||
|
"\n",
|
||||||
|
"Le parcours en largeur d'un arbre consiste à partir de la racine. On visite ensuite son fils gauche puis son fils droit, puis le fils gauche du fils gauche etc... Comme le montre le schéma ci-dessous:\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"L'idée est la suivante:\n",
|
||||||
|
"\n",
|
||||||
|
"On utilise une **File**.\n",
|
||||||
|
"* On met l'arbre dans la file.\n",
|
||||||
|
"* Puis tant que la file n'est pas vide:\n",
|
||||||
|
" * On défile la file\n",
|
||||||
|
" * On récupère sa racine\n",
|
||||||
|
" * On enfile son fils gauche s'il existe\n",
|
||||||
|
" * On enfile son fils droit s'il existe\n",
|
||||||
|
"\n",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 7\n",
|
||||||
|
"\n",
|
||||||
|
"1. Rappeler les fonctions permettant de définir une structure de file en Python.\n",
|
||||||
|
"2. Implémenter alors cette fonction et l'essayer sur l'arbre précédent."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Exercice 7 : Parcours en largeur\n",
|
||||||
|
"def parcours_largeur(arbre):\n",
|
||||||
|
" \"\"\"Parcours en largeur de l'arbre\"\"\"\n",
|
||||||
|
" pass # À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Les parcours en profondeur\n",
|
||||||
|
"\n",
|
||||||
|
"On se balade autour de l'arbre en suivant les pointillés.\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"Voici un algorithme permettant de réaliser ce parcours:\n",
|
||||||
|
"\n",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 8\n",
|
||||||
|
"\n",
|
||||||
|
"Proposer une fonction permettant de réaliser le parcours en profondeur d'un arbre."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Exercice 8 : Parcours en profondeur\n",
|
||||||
|
"def parcours_profondeur(arbre):\n",
|
||||||
|
" \"\"\"Parcours en profondeur de l'arbre\"\"\"\n",
|
||||||
|
" pass # À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Les trois types de parcours en profondeur\n",
|
||||||
|
"\n",
|
||||||
|
"Dans le schéma ci-dessus, on a rajouté des \"noeuds fantômes\" pour montrer que l'on peut considérer que chaque noeud est visité trois fois:\n",
|
||||||
|
"* une fois par la gauche\n",
|
||||||
|
"* une fois par en dessous\n",
|
||||||
|
"* une fois par la droite\n",
|
||||||
|
"\n",
|
||||||
|
"---\n",
|
||||||
|
"\n",
|
||||||
|
"**Définition - Parcours préfixe :**\n",
|
||||||
|
"Dans un parcours **préfixe**, on liste le noeud la **première fois** qu'on le rencontre.\n",
|
||||||
|
"\n",
|
||||||
|
"---\n",
|
||||||
|
"\n",
|
||||||
|
"**Définition - Parcours infixe :**\n",
|
||||||
|
"Dans un parcours **infixe**, on liste le noeud la **seconde fois** qu'on le rencontre.\n",
|
||||||
|
"\n",
|
||||||
|
"Ce qui correspond à:\n",
|
||||||
|
"* On liste chaque noeud ayant un fils gauche la seconde fois qu'on le voit\n",
|
||||||
|
"* On liste chaque noeud sans fils gauche la première fois qu'on le voit\n",
|
||||||
|
"\n",
|
||||||
|
"---\n",
|
||||||
|
"\n",
|
||||||
|
"**Définition - Parcours suffixe :**\n",
|
||||||
|
"Dans un parcours **suffixe**, on note le noeud la **dernière fois** qu'on le rencontre."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 9\n",
|
||||||
|
"\n",
|
||||||
|
"Écrire les sommets dans l'ordre d'un parcours préfixe."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"**Réponse :**"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 10\n",
|
||||||
|
"\n",
|
||||||
|
"Écrire les sommets dans l'ordre d'un parcours infixe."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"**Réponse :**"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 11\n",
|
||||||
|
"\n",
|
||||||
|
"Écrire les sommets dans l'ordre d'un parcours suffixe."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"**Réponse :**"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 12\n",
|
||||||
|
"\n",
|
||||||
|
"1. Voici trois algorithmes récursifs, dire pour chacun d'entre eux à quel parcours il correspond.\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"2. Implémenter ces trois fonctions en Python et confirmer les réponses des questions précédentes."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Exercice 12 : Implémentation des trois parcours\n",
|
||||||
|
"\n",
|
||||||
|
"def parcours_1(arbre):\n",
|
||||||
|
" \"\"\"Premier algorithme - À identifier\"\"\"\n",
|
||||||
|
" pass # À compléter\n",
|
||||||
|
"\n",
|
||||||
|
"def parcours_2(arbre):\n",
|
||||||
|
" \"\"\"Deuxième algorithme - À identifier\"\"\"\n",
|
||||||
|
" pass # À compléter\n",
|
||||||
|
"\n",
|
||||||
|
"def parcours_3(arbre):\n",
|
||||||
|
" \"\"\"Troisième algorithme - À identifier\"\"\"\n",
|
||||||
|
" pass # À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"---\n",
|
||||||
|
"\n",
|
||||||
|
"## Une troisième façon de faire... en programmation objet\n",
|
||||||
|
"\n",
|
||||||
|
"Nous allons créer une classe **Noeud** dont les attributs d'instances seront:\n",
|
||||||
|
"* le nom (ou valeur) de la racine\n",
|
||||||
|
"* son fils gauche (None par défaut) ou de type Noeud\n",
|
||||||
|
"* son fils droit (None par défaut) ou de type Noeud"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 13\n",
|
||||||
|
"\n",
|
||||||
|
"1. Implémentez cette classe Noeud\n",
|
||||||
|
"2. Stocker l'arbre comme étant l'objet de type \"Noeud\" correspondant à la racine.\n",
|
||||||
|
"3. Écrire une méthode `est_feuille` qui renvoie True si le noeud est une feuille et False sinon\n",
|
||||||
|
"4. Définir une fonction `hauteur` prenant comme argument un arbre permettant de renvoyer la hauteur de cet arbre\n",
|
||||||
|
"5. Écrire une méthode `hauteur` dans la classe Noeud permettant de faire la même chose\n",
|
||||||
|
"6. Proposer des fonctions pour le parcours infixe, préfixe et suffixe pour cette façon de coder un arbre.\n",
|
||||||
|
"7. Transformer alors vos fonctions en méthodes de la classe Noeud"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Exercice 13 : Classe Noeud\n",
|
||||||
|
"\n",
|
||||||
|
"class Noeud:\n",
|
||||||
|
" def __init__(self, valeur, gauche=None, droit=None):\n",
|
||||||
|
" \"\"\"Constructeur de la classe Noeud\"\"\"\n",
|
||||||
|
" pass # À compléter\n",
|
||||||
|
" \n",
|
||||||
|
" def est_feuille(self):\n",
|
||||||
|
" \"\"\"Retourne True si le noeud est une feuille\"\"\"\n",
|
||||||
|
" pass # À compléter\n",
|
||||||
|
" \n",
|
||||||
|
" def hauteur(self):\n",
|
||||||
|
" \"\"\"Retourne la hauteur de l'arbre\"\"\"\n",
|
||||||
|
" pass # À compléter\n",
|
||||||
|
" \n",
|
||||||
|
" def parcours_prefixe(self):\n",
|
||||||
|
" \"\"\"Parcours préfixe de l'arbre\"\"\"\n",
|
||||||
|
" pass # À compléter\n",
|
||||||
|
" \n",
|
||||||
|
" def parcours_infixe(self):\n",
|
||||||
|
" \"\"\"Parcours infixe de l'arbre\"\"\"\n",
|
||||||
|
" pass # À compléter\n",
|
||||||
|
" \n",
|
||||||
|
" def parcours_suffixe(self):\n",
|
||||||
|
" \"\"\"Parcours suffixe de l'arbre\"\"\"\n",
|
||||||
|
" pass # À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Construction de l'arbre avec la classe Noeud\n",
|
||||||
|
"# À compléter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"---\n",
|
||||||
|
"\n",
|
||||||
|
"### Sources\n",
|
||||||
|
"\n",
|
||||||
|
"- [Cours de Stephan Van Zulen](https://www.nsi-ljm.fr/NSI-TLE/co/section_chapitre3.html)\n",
|
||||||
|
"- [Cours de Jeremy Camponovo](https://github.com/jcamponovo)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 4
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
|
|
||||||
### Définition:
|
### Définition:
|
||||||
Un arbre **binaire** est un arbre de degré 2 (dont lesnoeuds sont de degré 2 au plus).
|
Un arbre **binaire** est un arbre de degré 2 (dont les noeuds sont de degré 2 au plus).
|
||||||
|
|
||||||
Les enfants d'un noeud sont lus de **gauche à droite** et sont appelés **fils gauche** et **fils droit**.
|
Les enfants d'un noeud sont lus de **gauche à droite** et sont appelés **fils gauche** et **fils droit**.
|
||||||
|
|
||||||
@@ -72,30 +72,16 @@ Il possède 3 niveaux, sa hauteur ou profondeur est donc de 2. La taille du tabl
|
|||||||
|
|
||||||
4. Voici un tableau représentant un arbre binaire:
|
4. Voici un tableau représentant un arbre binaire:
|
||||||
|
|
||||||
|
```python
|
||||||
['*','-',5,2,6,None,None,None,None,None,None,None,None,None,None]
|
['*','-',5,2,6,None,None,None,None,None,None,None,None,None,None]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Le dessiner. Que peut-il représenter ?
|
Le dessiner. Que peut-il représenter ?
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
> Voici un code python Python qui crée la liste représentant l'arbre de l'exercice 2
|
> Voici un code python Python qui crée la liste représentant l'arbre de l'exercice 2
|
||||||
|
|
||||||
@@ -152,53 +138,13 @@ print(arbre)
|
|||||||
|
|
||||||
**<u>Inversement, pour trouver le parent d'un noeud, il faut vérifier : i - 1 //2</u>**
|
**<u>Inversement, pour trouver le parent d'un noeud, il faut vérifier : i - 1 //2</u>**
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
---------
|
---------
|
||||||
|
|
||||||
## Une seconde façon de faire...
|
## Une seconde façon de faire...
|
||||||
|
|
||||||
Comme vous l'avez sans doute constaté, il est assez fastidieux de représenter un arbre avec un unique tableau surtout pour un arbre très profond.
|
Comme vous l'avez sans doute constaté, il est assez fastidieux de représenter un arbre avec un unique tableau surtout pour un arbre très profond.
|
||||||
|
|
||||||
L'idée est de représenter l'arbre ave cun tableau contenant des tableaux
|
L'idée est de représenter l'arbre avec un tableau contenant des tableaux
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -251,52 +197,14 @@ print(arbre1)
|
|||||||
### Exercice :
|
### Exercice :
|
||||||
Ecrire toutes les fonctions de l'exercice 3 dans le cas de cette implémentation de l'arbre.
|
Ecrire toutes les fonctions de l'exercice 3 dans le cas de cette implémentation de l'arbre.
|
||||||
|
|
||||||
|
--------
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Nous allons maintenant nous intéresser à la hauteur de l'arbre, ainsi qu'aux différentes façons de le parcourir:
|
Nous allons maintenant nous intéresser à la hauteur de l'arbre, ainsi qu'aux différentes façons de le parcourir:
|
||||||
* Le parcours en largeur
|
* Le parcours en largeur
|
||||||
* Le parcours en profondeur (parcours fixe, parcours infixe et parcours suffixe).
|
* Le parcours en profondeur (parcours préfixe, parcours infixe et parcours suffixe).
|
||||||
|
|
||||||
**Calcul de la hauteur de l'arbre:**
|
**Calcul de la hauteur de l'arbre:**
|
||||||
L'idée ets la suivante:
|
L'idée est la suivante:
|
||||||
* Si l'arbre est vide, la hauteur vaut -1
|
* Si l'arbre est vide, la hauteur vaut -1
|
||||||
* Sinon la hauteur vaut 1 auquel il faut ajouter le maximum entre les hauteurs des sous arbres gauche et droit.
|
* Sinon la hauteur vaut 1 auquel il faut ajouter le maximum entre les hauteurs des sous arbres gauche et droit.
|
||||||
* Ces sous-arbres sont eux même des arbres dont il faut calculer la hauteur.
|
* Ces sous-arbres sont eux même des arbres dont il faut calculer la hauteur.
|
||||||
@@ -318,7 +226,7 @@ Voici l'algorithme:
|
|||||||
## Les parcours
|
## Les parcours
|
||||||
|
|
||||||
**Le parcours en largeur:**
|
**Le parcours en largeur:**
|
||||||
Le parcours en largeur d'un arbre consiste à partir de la racine. On visite ensuite son fils gauche puis sont fils droit, puis le fils gauche du fils gauche etc... Comme le montre le schéma ci-dessous:
|
Le parcours en largeur d'un arbre consiste à partir de la racine. On visite ensuite son fils gauche puis son fils droit, puis le fils gauche du fils gauche etc... Comme le montre le schéma ci-dessous:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -344,14 +252,6 @@ Voici l'algorithme:
|
|||||||
2. Implémenter alors cette fonction et l'essayer sur l'arbre précédent.
|
2. Implémenter alors cette fonction et l'essayer sur l'arbre précédent.
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
**Les parcours en profondeur**
|
**Les parcours en profondeur**
|
||||||
On se balade autour de l'arbre en suivant les pointillés
|
On se balade autour de l'arbre en suivant les pointillés
|
||||||
@@ -374,8 +274,8 @@ Dans le schéma ci-dessus, on a rajouté des "noeuds fantômes" pour montrer que
|
|||||||
* une fois par en dessous
|
* une fois par en dessous
|
||||||
* une fois par la droite
|
* une fois par la droite
|
||||||
|
|
||||||
### Definition:
|
### Définition:
|
||||||
Dans un parcours **prefixe**, on liste le noeud la première fois qu'on le rencontre.
|
Dans un parcours **préfixe**, on liste le noeud la première fois qu'on le rencontre.
|
||||||
|
|
||||||
|
|
||||||
### Exercice
|
### Exercice
|
||||||
@@ -429,9 +329,6 @@ Nous allons créer une classe **Noeud** dont les attributs d'instances seront:
|
|||||||
7. Transformer alors vos fonctions en méthodes de la classe Noeud
|
7. Transformer alors vos fonctions en méthodes de la classe Noeud
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
## Structures Arborescentes
|
# Structures Arborescentes
|
||||||
|
|
||||||
> Structure présente partout dans le monde numérique, des répertoires de fichiers sur un ordinateur aux bases de données et algorithmes de recherche, les abres sont un pan essentiel de l'informatique. Ils permettent de structurer efficacement des données hiérarchiques et d’optimiser certaines opérations comme la recherche ou le tri. En NSI, nous allons explorer une version particulière de ces structures : les arbres binaires.
|
> Structure présente partout dans le monde numérique, des répertoires de fichiers sur un ordinateur aux bases de données et algorithmes de recherche, les arbres sont un pan essentiel de l'informatique. Ils permettent de structurer efficacement des données hiérarchiques et d’optimiser certaines opérations comme la recherche ou le tri. En NSI, nous allons explorer une version particulière de ces structures : les arbres binaires.
|
||||||
|
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
|||||||
12
Arbres/TD.md
12
Arbres/TD.md
@@ -3,13 +3,13 @@
|
|||||||
Soit les 4 arbres binaires suivant :
|
Soit les 4 arbres binaires suivant :
|
||||||
|
|
||||||
<div style="display: flex; justify-content: space-around;">
|
<div style="display: flex; justify-content: space-around;">
|
||||||
<img src="assets/img2.png" alt="Méthodes_natives" style="zoom: 80%;" />
|
<img src="assets/img2.png" alt="Arbre 1" style="zoom: 80%;" />
|
||||||
|
<img src="assets/img3.png" alt="Arbre 2" style="zoom: 80%;" />
|
||||||
<img src="assets/img3.png" alt="Méthodes_natives" style="zoom: 80%;" />
|
</div>
|
||||||
|
|
||||||
<div style="display: flex; justify-content: space-around;">
|
<div style="display: flex; justify-content: space-around;">
|
||||||
<img src="assets/img4.png" alt="Méthodes_natives" style="zoom: 80%;" />
|
<img src="assets/img4.png" alt="Arbre 3" style="zoom: 80%;" />
|
||||||
<img src="assets/img5.png" alt="Méthodes_natives" style="zoom: 80%;" />
|
<img src="assets/img5.png" alt="Arbre 4" style="zoom: 80%;" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ Question 1 - Compléter le tableau :
|
|||||||
|arbre 3| | | | | |
|
|arbre 3| | | | | |
|
||||||
|arbre 4| | | | | |
|
|arbre 4| | | | | |
|
||||||
|
|
||||||
Question 2 - En utilisant la classe **BinnaryTree** du module ```arbres_binary_tree```, donner la définition :
|
Question 2 - En utilisant la classe **BinaryTree** du module ```arbres_binary_tree```, donner la définition :
|
||||||
|
|
||||||
- en plusieurs affectations (plusieurs lignes) de l'arbre ayant pour racine le noeud **4** ;
|
- en plusieurs affectations (plusieurs lignes) de l'arbre ayant pour racine le noeud **4** ;
|
||||||
- en une seule affectation (une ligne) de l'arbre ayant pour racine le noeud **1**.
|
- en une seule affectation (une ligne) de l'arbre ayant pour racine le noeud **1**.
|
||||||
|
|||||||
281
Arbres/TP_Recommandation.md
Normal file
281
Arbres/TP_Recommandation.md
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
# TP : L'algorithme de recommandation — Comment TikTok/YouTube vous connaît
|
||||||
|
|
||||||
|
> **Thème** : Arbres de décision et algorithmes de recommandation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
En 2026, les algorithmes de recommandation sont omniprésents : TikTok, YouTube, Spotify, Netflix... Ces plateformes utilisent des **arbres de décision** pour prédire vos goûts et vous proposer du contenu personnalisé.
|
||||||
|
|
||||||
|
Dans ce TP, vous allez construire un mini-système de recommandation de films basé sur un **arbre binaire de décision**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 1 : Comprendre l'arbre de décision
|
||||||
|
|
||||||
|
Un arbre de décision pose des questions binaires (oui/non) pour classifier des données.
|
||||||
|
|
||||||
|
### Exemple
|
||||||
|
|
||||||
|
```
|
||||||
|
Aimes-tu l'action ?
|
||||||
|
/ \
|
||||||
|
OUI NON
|
||||||
|
| |
|
||||||
|
Aimes-tu la SF ? Aimes-tu la romance ?
|
||||||
|
/ \ / \
|
||||||
|
OUI NON OUI NON
|
||||||
|
| | | |
|
||||||
|
"Matrix" "Die Hard" "Titanic" "Le Parrain"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Questions préliminaires
|
||||||
|
|
||||||
|
1. Quel film sera recommandé à quelqu'un qui aime l'action mais pas la SF ?
|
||||||
|
|
||||||
|
2. Combien de questions faut-il poser au maximum pour recommander un film ?
|
||||||
|
|
||||||
|
3. Si on veut recommander 16 films différents, combien de questions faudra-t-il poser au maximum ? (Indice : pensez à la hauteur de l'arbre)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : Modélisation avec une classe
|
||||||
|
|
||||||
|
Nous allons créer une classe `NoeudDecision` pour représenter notre arbre.
|
||||||
|
|
||||||
|
### Structure d'un nœud
|
||||||
|
|
||||||
|
Un nœud peut être :
|
||||||
|
- **Un nœud interne** : il contient une question et deux sous-arbres (oui/non)
|
||||||
|
- **Une feuille** : elle contient une recommandation (un film)
|
||||||
|
|
||||||
|
### Exercice 1 : Compléter la classe
|
||||||
|
|
||||||
|
```python
|
||||||
|
class NoeudDecision:
|
||||||
|
def __init__(self, question=None, film=None):
|
||||||
|
"""
|
||||||
|
Crée un nœud de l'arbre de décision.
|
||||||
|
|
||||||
|
Si film is not None : c'est une feuille (recommandation)
|
||||||
|
Sinon : c'est un nœud interne avec une question
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
question (str): La question à poser (None si feuille)
|
||||||
|
film (str): Le film à recommander (None si nœud interne)
|
||||||
|
"""
|
||||||
|
self.question = question
|
||||||
|
self.film = film
|
||||||
|
self.oui = None # Sous-arbre si réponse = oui
|
||||||
|
self.non = None # Sous-arbre si réponse = non
|
||||||
|
|
||||||
|
def est_feuille(self):
|
||||||
|
"""Retourne True si ce nœud est une feuille (recommandation)."""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Représentation textuelle du nœud."""
|
||||||
|
if self.est_feuille():
|
||||||
|
return f"Film: {self.film}"
|
||||||
|
else:
|
||||||
|
return f"Question: {self.question}"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : Construction de l'arbre
|
||||||
|
|
||||||
|
### Exercice 2 : Construire l'arbre d'exemple
|
||||||
|
|
||||||
|
Complétez la fonction suivante pour construire l'arbre de décision présenté en partie 1.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def construire_arbre_films():
|
||||||
|
"""Construit et retourne l'arbre de recommandation de films."""
|
||||||
|
|
||||||
|
# Création des feuilles (recommandations)
|
||||||
|
matrix = NoeudDecision(film="Matrix")
|
||||||
|
die_hard = NoeudDecision(film="Die Hard")
|
||||||
|
titanic = NoeudDecision(film="Titanic")
|
||||||
|
parrain = NoeudDecision(film="Le Parrain")
|
||||||
|
|
||||||
|
# Création des nœuds internes (questions)
|
||||||
|
# À compléter...
|
||||||
|
|
||||||
|
# Retourner la racine de l'arbre
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice 3 : Tester la construction
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Créer l'arbre
|
||||||
|
arbre = construire_arbre_films()
|
||||||
|
|
||||||
|
# Vérifier la structure
|
||||||
|
print(arbre) # Doit afficher la question racine
|
||||||
|
print(arbre.oui) # Doit afficher la question sur la SF
|
||||||
|
print(arbre.oui.oui) # Doit afficher "Film: Matrix"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : Parcours et recommandation
|
||||||
|
|
||||||
|
### Exercice 4 : Fonction de recommandation interactive
|
||||||
|
|
||||||
|
Complétez la fonction suivante qui parcourt l'arbre en posant les questions à l'utilisateur :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def recommander(noeud):
|
||||||
|
"""
|
||||||
|
Parcourt l'arbre de décision en posant des questions à l'utilisateur.
|
||||||
|
Retourne le film recommandé.
|
||||||
|
"""
|
||||||
|
# Cas de base : on est sur une feuille
|
||||||
|
if noeud.est_feuille():
|
||||||
|
return noeud.film
|
||||||
|
|
||||||
|
# Afficher la question et récupérer la réponse
|
||||||
|
print(noeud.question)
|
||||||
|
reponse = input("Votre réponse (oui/non) : ").lower().strip()
|
||||||
|
|
||||||
|
# Suivre la branche correspondante
|
||||||
|
# À compléter...
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice 5 : Programme principal
|
||||||
|
|
||||||
|
```python
|
||||||
|
def main():
|
||||||
|
print("=== Système de recommandation de films ===\n")
|
||||||
|
|
||||||
|
arbre = construire_arbre_films()
|
||||||
|
film = recommander(arbre)
|
||||||
|
|
||||||
|
print(f"\n>>> Je vous recommande : {film}")
|
||||||
|
|
||||||
|
# Lancer le programme
|
||||||
|
main()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 5 : Enrichir l'arbre
|
||||||
|
|
||||||
|
### Exercice 6 : Ajouter des films
|
||||||
|
|
||||||
|
Modifiez la fonction `construire_arbre_films()` pour ajouter au moins **4 films supplémentaires** en créant de nouvelles questions.
|
||||||
|
|
||||||
|
Suggestions de questions :
|
||||||
|
- "Préférez-vous les films récents (après 2010) ?"
|
||||||
|
- "Aimez-vous les films avec beaucoup d'humour ?"
|
||||||
|
- "Préférez-vous les films européens ?"
|
||||||
|
|
||||||
|
### Exercice 7 : Affichage de l'arbre
|
||||||
|
|
||||||
|
Écrivez une fonction qui affiche l'arbre de manière indentée :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def afficher_arbre(noeud, niveau=0):
|
||||||
|
"""
|
||||||
|
Affiche l'arbre de décision de manière indentée.
|
||||||
|
|
||||||
|
Exemple de sortie attendue:
|
||||||
|
Aimes-tu l'action ?
|
||||||
|
├── OUI: Aimes-tu la SF ?
|
||||||
|
│ ├── OUI: Matrix
|
||||||
|
│ └── NON: Die Hard
|
||||||
|
└── NON: Aimes-tu la romance ?
|
||||||
|
├── OUI: Titanic
|
||||||
|
└── NON: Le Parrain
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 6 : Statistiques sur l'arbre
|
||||||
|
|
||||||
|
### Exercice 8 : Hauteur de l'arbre
|
||||||
|
|
||||||
|
Écrivez une fonction récursive qui calcule la hauteur de l'arbre de décision.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def hauteur(noeud):
|
||||||
|
"""Retourne la hauteur de l'arbre."""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice 9 : Nombre de films
|
||||||
|
|
||||||
|
Écrivez une fonction qui compte le nombre de films (feuilles) dans l'arbre.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def nombre_films(noeud):
|
||||||
|
"""Retourne le nombre de films (feuilles) dans l'arbre."""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice 10 : Liste de tous les films
|
||||||
|
|
||||||
|
Écrivez une fonction qui retourne la liste de tous les films de l'arbre.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def tous_les_films(noeud):
|
||||||
|
"""Retourne la liste de tous les films de l'arbre."""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 7 : Pour aller plus loin (Bonus)
|
||||||
|
|
||||||
|
### Réflexion : vers le Machine Learning
|
||||||
|
|
||||||
|
En réalité, les algorithmes de recommandation modernes **apprennent** automatiquement l'arbre à partir de données.
|
||||||
|
|
||||||
|
**Question de réflexion :**
|
||||||
|
|
||||||
|
Imaginez que vous avez une base de données de 1000 utilisateurs avec :
|
||||||
|
- Leurs réponses aux questions (oui/non)
|
||||||
|
- Le film qu'ils ont finalement aimé
|
||||||
|
|
||||||
|
Comment pourriez-vous automatiquement construire le "meilleur" arbre de décision ?
|
||||||
|
|
||||||
|
*Indice : Il faudrait choisir à chaque nœud la question qui "sépare" le mieux les utilisateurs selon leurs préférences.*
|
||||||
|
|
||||||
|
### Recherche
|
||||||
|
|
||||||
|
L'algorithme **ID3** (Iterative Dichotomiser 3) est un algorithme classique pour construire des arbres de décision.
|
||||||
|
|
||||||
|
1. Faites une recherche sur cet algorithme.
|
||||||
|
2. Qu'est-ce que l'**entropie** dans ce contexte ?
|
||||||
|
3. Comment ID3 choisit-il la meilleure question à poser à chaque nœud ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Résumé des notions travaillées
|
||||||
|
|
||||||
|
| Notion | Application dans ce TP |
|
||||||
|
|--------|------------------------|
|
||||||
|
| Arbre binaire | Structure de l'arbre de décision |
|
||||||
|
| Nœud / Feuille | Questions / Recommandations |
|
||||||
|
| Parcours d'arbre | Fonction `recommander()` |
|
||||||
|
| Récursivité | Hauteur, comptage, affichage |
|
||||||
|
| POO | Classe `NoeudDecision` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -91,7 +91,7 @@ class BinaryTree():
|
|||||||
|
|
||||||
def size(self):
|
def size(self):
|
||||||
"""
|
"""
|
||||||
:return: (int) numbre of Nodes in Tree
|
:return: (int) number of Nodes in Tree
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -42,13 +42,58 @@ Le **Modèle Conceptuel de Données (MCD)**, ou **Modèle Entité-Association**,
|
|||||||
|
|
||||||
Imaginons une boutique où des clients achètent des produits.
|
Imaginons une boutique où des clients achètent des produits.
|
||||||
|
|
||||||
[Client] — achète —< [Vente] >— concerne —> [Produit]
|
```
|
||||||
|
┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ CLIENT │ │ PRODUIT │
|
||||||
|
├─────────────────┤ ├─────────────────┤
|
||||||
|
│ #ID_Client (PK) │ │ #ID_Produit (PK)│
|
||||||
|
│ Nom │ │ Nom_Produit │
|
||||||
|
│ Email │ │ Prix │
|
||||||
|
└────────┬────────┘ └────────┬────────┘
|
||||||
|
│ │
|
||||||
|
│ 1,n 1,n │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────┐ │
|
||||||
|
└─────────┤ ACHETE ├─────────────────┘
|
||||||
|
│ (Association) │
|
||||||
|
├─────────────────┤
|
||||||
|
│ Date │
|
||||||
|
│ Quantité │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lecture des cardinalités** :
|
||||||
|
- Un client peut acheter **1 à n** produits (1,n)
|
||||||
|
- Un produit peut être acheté par **1 à n** clients (1,n)
|
||||||
|
- C'est une relation **n-n** (plusieurs à plusieurs)
|
||||||
|
|
||||||
|
**Légende** :
|
||||||
|
- `#` : clé primaire
|
||||||
|
- `(PK)` : Primary Key
|
||||||
|
- Les rectangles représentent les **entités**
|
||||||
|
- Les losanges (ici simplifiés) représentent les **associations**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Autre exemple : Relation 1-n
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐ 1,1 ┌─────────────────┐
|
||||||
|
│ PROFESSEUR │◄────────────────────┤ COURS │
|
||||||
|
├─────────────────┤ enseigne ├─────────────────┤
|
||||||
|
│ #ID_Prof (PK) │ │ #ID_Cours (PK) │
|
||||||
|
│ Nom │ 1,n │ Nom_Cours │
|
||||||
|
│ Spécialité │ │ Horaire │
|
||||||
|
└─────────────────┘ └─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lecture** : Un professeur enseigne **1 à n** cours. Un cours est enseigné par **1 et 1 seul** professeur.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
- L'entité **Client** possède des attributs comme `Nom` et `Email`.
|
- L'entité **Client** possède des attributs comme `Nom` et `Email`.
|
||||||
- L'entité **Produit** possède des attributs comme `Nom_Produit` et `Prix`.
|
- L'entité **Produit** possède des attributs comme `Nom_Produit` et `Prix`.
|
||||||
- L'entité **Vente** représente l'achat d'un produit par un client, avec des attributs comme `Date`.
|
- L'association **Achète** représente l'achat d'un produit par un client, avec des attributs comme `Date` et `Quantité`.
|
||||||
|
|
||||||
#### Pourquoi faire un MCD ?
|
#### Pourquoi faire un MCD ?
|
||||||
|
|
||||||
@@ -109,10 +154,113 @@ Chaque attribut doit contenir des valeurs valides en fonction de son type (domai
|
|||||||
|
|
||||||
#### Introduction à la Normalisation
|
#### Introduction à la Normalisation
|
||||||
|
|
||||||
La normalisation est un processus pour structurer une base et réduire la redondance. Voici les trois premières **formes normales** (FN) :
|
La normalisation est un processus pour structurer une base et réduire la redondance. Voici les trois premières **formes normales** (FN).
|
||||||
1. **1NF** : Chaque colonne contient des valeurs atomiques.
|
|
||||||
2. **2NF** : Les colonnes dépendent entièrement de la clé primaire.
|
---
|
||||||
3. **3NF** : Pas de dépendance transitives entre attributs.
|
|
||||||
|
#### Première Forme Normale (1NF)
|
||||||
|
|
||||||
|
**Règle** : Chaque colonne contient des valeurs **atomiques** (indivisibles).
|
||||||
|
|
||||||
|
**Exemple de table NON en 1NF** :
|
||||||
|
|
||||||
|
| ID_Commande | Client | Produits |
|
||||||
|
|-------------|---------|----------------------|
|
||||||
|
| 1 | Dupont | Livre, Stylo, Cahier |
|
||||||
|
| 2 | Martin | Clavier |
|
||||||
|
|
||||||
|
Le champ `Produits` contient plusieurs valeurs → violation de 1NF.
|
||||||
|
|
||||||
|
**Table en 1NF** :
|
||||||
|
|
||||||
|
| ID_Commande | Client | Produit |
|
||||||
|
|-------------|---------|---------|
|
||||||
|
| 1 | Dupont | Livre |
|
||||||
|
| 1 | Dupont | Stylo |
|
||||||
|
| 1 | Dupont | Cahier |
|
||||||
|
| 2 | Martin | Clavier |
|
||||||
|
|
||||||
|
Chaque cellule contient une seule valeur.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Deuxième Forme Normale (2NF)
|
||||||
|
|
||||||
|
**Règle** : La table est en 1NF **ET** chaque attribut non-clé dépend de **toute** la clé primaire (pas seulement d'une partie).
|
||||||
|
|
||||||
|
**Exemple de table en 1NF mais PAS en 2NF** :
|
||||||
|
|
||||||
|
| ID_Commande | ID_Produit | Nom_Produit | Quantité |
|
||||||
|
|-------------|------------|-------------|----------|
|
||||||
|
| 1 | 101 | Livre | 2 |
|
||||||
|
| 1 | 102 | Stylo | 5 |
|
||||||
|
| 2 | 101 | Livre | 1 |
|
||||||
|
|
||||||
|
Clé primaire : `(ID_Commande, ID_Produit)`
|
||||||
|
|
||||||
|
Problème : `Nom_Produit` dépend uniquement de `ID_Produit`, pas de toute la clé.
|
||||||
|
|
||||||
|
**Tables en 2NF** :
|
||||||
|
|
||||||
|
**Table Commandes_Produits** :
|
||||||
|
|
||||||
|
| ID_Commande | ID_Produit | Quantité |
|
||||||
|
|-------------|------------|----------|
|
||||||
|
| 1 | 101 | 2 |
|
||||||
|
| 1 | 102 | 5 |
|
||||||
|
| 2 | 101 | 1 |
|
||||||
|
|
||||||
|
**Table Produits** :
|
||||||
|
|
||||||
|
| ID_Produit | Nom_Produit |
|
||||||
|
|------------|-------------|
|
||||||
|
| 101 | Livre |
|
||||||
|
| 102 | Stylo |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Troisième Forme Normale (3NF)
|
||||||
|
|
||||||
|
**Règle** : La table est en 2NF **ET** aucun attribut non-clé ne dépend d'un autre attribut non-clé (pas de dépendance transitive).
|
||||||
|
|
||||||
|
**Exemple de table en 2NF mais PAS en 3NF** :
|
||||||
|
|
||||||
|
| ID_Etudiant | Nom | ID_Classe | Nom_Classe |
|
||||||
|
|-------------|--------|-----------|------------|
|
||||||
|
| 1 | Alice | A1 | Terminale |
|
||||||
|
| 2 | Bob | A1 | Terminale |
|
||||||
|
| 3 | Clara | B2 | Première |
|
||||||
|
|
||||||
|
Clé primaire : `ID_Etudiant`
|
||||||
|
|
||||||
|
Problème : `Nom_Classe` dépend de `ID_Classe` (qui n'est pas la clé) → dépendance transitive.
|
||||||
|
|
||||||
|
**Tables en 3NF** :
|
||||||
|
|
||||||
|
**Table Etudiants** :
|
||||||
|
|
||||||
|
| ID_Etudiant | Nom | ID_Classe |
|
||||||
|
|-------------|--------|-----------|
|
||||||
|
| 1 | Alice | A1 |
|
||||||
|
| 2 | Bob | A1 |
|
||||||
|
| 3 | Clara | B2 |
|
||||||
|
|
||||||
|
**Table Classes** :
|
||||||
|
|
||||||
|
| ID_Classe | Nom_Classe |
|
||||||
|
|-----------|------------|
|
||||||
|
| A1 | Terminale |
|
||||||
|
| B2 | Première |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Résumé des formes normales
|
||||||
|
|
||||||
|
| Forme | Condition |
|
||||||
|
|-------|-----------|
|
||||||
|
| **1NF** | Valeurs atomiques dans chaque cellule |
|
||||||
|
| **2NF** | 1NF + pas de dépendance partielle à la clé |
|
||||||
|
| **3NF** | 2NF + pas de dépendance transitive |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
255
BDD_SGBD/Corrige_Exercices.md
Normal file
255
BDD_SGBD/Corrige_Exercices.md
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
# Corrigé : Entraînement sur les Schémas Relationnels
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Tables initiales (Sandwicherie)
|
||||||
|
|
||||||
|
### Question 1 : Analyse des clés
|
||||||
|
|
||||||
|
**Clés primaires :**
|
||||||
|
|
||||||
|
| Table | Clé primaire | Justification |
|
||||||
|
|-------|-------------|---------------|
|
||||||
|
| Sandwichs | `Nom_Sandwich` | Identifie de manière unique chaque sandwich |
|
||||||
|
| Clients | `ID_Client` | Identifiant unique pour chaque client |
|
||||||
|
| Commandes | `ID_Commande` | Identifiant unique pour chaque commande |
|
||||||
|
|
||||||
|
**Clés étrangères :**
|
||||||
|
|
||||||
|
| Table | Clé étrangère | Référence |
|
||||||
|
|-------|--------------|-----------|
|
||||||
|
| Commandes | `ID_Client` | → Clients(ID_Client) |
|
||||||
|
| Commandes | `Nom_Sandwich` | → Sandwichs(Nom_Sandwich) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Question 2 : Problèmes de modélisation
|
||||||
|
|
||||||
|
**Problèmes identifiés :**
|
||||||
|
|
||||||
|
1. **Clé primaire de Sandwichs** : Utiliser `Nom_Sandwich` comme clé primaire pose problème si deux sandwichs ont le même nom ou si on renomme un sandwich. Il vaudrait mieux ajouter un `ID_Sandwich`.
|
||||||
|
|
||||||
|
2. **Redondance potentielle** : Si un client commande plusieurs fois le même sandwich, le nom du sandwich est répété.
|
||||||
|
|
||||||
|
3. **Pas de gestion des quantités multiples** : Une commande ne peut contenir qu'un seul type de sandwich. Si un client veut commander 2 Cheeseburgers ET 1 Italien, il faut 2 commandes.
|
||||||
|
|
||||||
|
**Schéma amélioré :**
|
||||||
|
|
||||||
|
```
|
||||||
|
Sandwichs(#ID_Sandwich, Nom_Sandwich, Type, Prix)
|
||||||
|
Clients(#ID_Client, Nom, Prénom, Adresse)
|
||||||
|
Commandes(#ID_Commande, Date, ID_Client*)
|
||||||
|
Lignes_Commande(#ID_Commande*, #ID_Sandwich*, Quantité)
|
||||||
|
```
|
||||||
|
|
||||||
|
Avec cette structure :
|
||||||
|
- Une commande peut contenir plusieurs sandwichs différents
|
||||||
|
- On utilise des identifiants numériques comme clés primaires
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Modélisation d'une base pour un forum
|
||||||
|
|
||||||
|
### Question 1 : Schéma de la table Users
|
||||||
|
|
||||||
|
```
|
||||||
|
Users(#ID_User, Pseudonyme, Email, Role, Date_Inscription)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `ID_User` : clé primaire (entier auto-incrémenté)
|
||||||
|
- `Pseudonyme` : chaîne de caractères, UNIQUE
|
||||||
|
- `Email` : chaîne de caractères, UNIQUE
|
||||||
|
- `Role` : chaîne de caractères (ex: "membre", "modérateur", "admin")
|
||||||
|
- `Date_Inscription` : date
|
||||||
|
|
||||||
|
### Question 2 : Schéma de la table Posts
|
||||||
|
|
||||||
|
```
|
||||||
|
Posts(#ID_Post, Titre, Contenu, Date_Publication, ID_User*)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `ID_Post` : clé primaire
|
||||||
|
- `Titre` : chaîne de caractères
|
||||||
|
- `Contenu` : texte long
|
||||||
|
- `Date_Publication` : date et heure
|
||||||
|
- `ID_User` : clé étrangère vers Users
|
||||||
|
|
||||||
|
### Question 3 : Clés primaires et étrangères
|
||||||
|
|
||||||
|
| Table | Clé primaire | Clé(s) étrangère(s) |
|
||||||
|
|-------|-------------|---------------------|
|
||||||
|
| Users | `ID_User` | Aucune |
|
||||||
|
| Posts | `ID_Post` | `ID_User` → Users(ID_User) |
|
||||||
|
|
||||||
|
### Question 4 : Gestion des modifications de pseudonymes
|
||||||
|
|
||||||
|
**Problème** : Si on utilise le pseudonyme comme référence dans d'autres tables, tout changement de pseudo nécessiterait de modifier toutes les références.
|
||||||
|
|
||||||
|
**Solution** : Utiliser `ID_User` (clé primaire numérique) comme référence dans les autres tables. Ainsi, le pseudonyme peut être modifié librement dans la table Users sans impacter les autres tables.
|
||||||
|
|
||||||
|
C'est pourquoi on préfère toujours utiliser un identifiant numérique comme clé primaire plutôt qu'un attribut "métier" comme le pseudonyme.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Extension : Albums sur le forum
|
||||||
|
|
||||||
|
### Question 1 : Modèle Entité-Association
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ USER │ │ POST │
|
||||||
|
├─────────────────┤ ├─────────────────┤
|
||||||
|
│ #ID_User (PK) │ │ #ID_Post (PK) │
|
||||||
|
│ Pseudonyme │ 1,n │ Titre │
|
||||||
|
│ Email │◄──────────────────────────┤ Contenu │
|
||||||
|
└────────┬────────┘ écrit │ Date │
|
||||||
|
│ └────────┬────────┘
|
||||||
|
│ 1,n │ 0,n
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────┐ │
|
||||||
|
└────────►│ ALBUM │ │
|
||||||
|
├─────────────────┤ │
|
||||||
|
possède │ #ID_Album (PK) │ contient │
|
||||||
|
│ Nom_Album │◄────────────────┘
|
||||||
|
│ Date_Creation │ 0,n
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cardinalités** :
|
||||||
|
- Un utilisateur possède 0 à n albums (0,n)
|
||||||
|
- Un album appartient à 1 et 1 seul utilisateur (1,1)
|
||||||
|
- Un album contient 0 à n posts (0,n)
|
||||||
|
- Un post peut être dans 0 à n albums (0,n)
|
||||||
|
|
||||||
|
### Question 2 : Schéma Relationnel
|
||||||
|
|
||||||
|
```
|
||||||
|
Users(#ID_User, Pseudonyme, Email, Role)
|
||||||
|
Posts(#ID_Post, Titre, Contenu, Date, ID_User*)
|
||||||
|
Albums(#ID_Album, Nom_Album, Date_Creation, ID_User*)
|
||||||
|
Album_Posts(#ID_Album*, #ID_Post*)
|
||||||
|
```
|
||||||
|
|
||||||
|
La table `Album_Posts` est une **table de liaison** nécessaire pour la relation n-n entre Albums et Posts.
|
||||||
|
|
||||||
|
### Question 3 : Exemple d'enregistrements
|
||||||
|
|
||||||
|
**Table Users :**
|
||||||
|
|
||||||
|
| ID_User | Pseudonyme | Email | Role |
|
||||||
|
|---------|------------|-------|------|
|
||||||
|
| 1 | GameMaster42 | gm42@mail.com | membre |
|
||||||
|
|
||||||
|
**Table Posts :**
|
||||||
|
|
||||||
|
| ID_Post | Titre | Contenu | Date | ID_User |
|
||||||
|
|---------|-------|---------|------|---------|
|
||||||
|
| 101 | Mon avis sur Zelda | Super jeu... | 2026-01-15 | 1 |
|
||||||
|
| 102 | Guide débutant | Voici mes conseils... | 2026-01-16 | 1 |
|
||||||
|
|
||||||
|
**Table Albums :**
|
||||||
|
|
||||||
|
| ID_Album | Nom_Album | Date_Creation | ID_User |
|
||||||
|
|----------|-----------|---------------|---------|
|
||||||
|
| 1 | Mes meilleurs posts gaming | 2026-01-17 | 1 |
|
||||||
|
|
||||||
|
**Table Album_Posts :**
|
||||||
|
|
||||||
|
| ID_Album | ID_Post |
|
||||||
|
|----------|---------|
|
||||||
|
| 1 | 101 |
|
||||||
|
| 1 | 102 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Normalisation : Exemple pour un lycée
|
||||||
|
|
||||||
|
### Question 1 : Schéma relationnel actuel
|
||||||
|
|
||||||
|
```
|
||||||
|
Eleves(Nom, Prénom, Date_Naissance, Classe, Option1, Option2, Option3)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Question 2 : Clé primaire et clés étrangères
|
||||||
|
|
||||||
|
**Clé primaire** : Il n'y a pas de clé primaire clairement définie. On pourrait utiliser `(Nom, Prénom, Date_Naissance)` mais ce n'est pas idéal (deux élèves peuvent avoir le même nom et être nés le même jour).
|
||||||
|
|
||||||
|
**Clés étrangères** : Aucune. La table est isolée.
|
||||||
|
|
||||||
|
### Question 3 : Défauts de conception
|
||||||
|
|
||||||
|
1. **Pas de clé primaire fiable** : Le couple (Nom, Prénom) n'est pas unique (deux "Michel" existent).
|
||||||
|
|
||||||
|
2. **Violation de 1NF** : Les colonnes Option1, Option2, Option3 représentent la même information (une option). C'est une répétition de groupe.
|
||||||
|
|
||||||
|
3. **Valeurs NULL** : Beaucoup de valeurs NULL pour les options non choisies.
|
||||||
|
|
||||||
|
4. **Pas de normalisation** : La classe est répétée pour chaque élève de la même classe.
|
||||||
|
|
||||||
|
5. **Rigidité** : Si un élève prend 4 options, il faut modifier la structure de la table.
|
||||||
|
|
||||||
|
### Amélioration : Schéma normalisé
|
||||||
|
|
||||||
|
```
|
||||||
|
Eleves(#ID_Eleve, Nom, Prénom, Date_Naissance, ID_Classe*)
|
||||||
|
Classes(#ID_Classe, Nom_Classe)
|
||||||
|
Options(#ID_Option, Nom_Option)
|
||||||
|
Eleves_Options(#ID_Eleve*, #ID_Option*)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tables résultantes :**
|
||||||
|
|
||||||
|
**Table Eleves :**
|
||||||
|
|
||||||
|
| ID_Eleve | Nom | Prénom | Date_Naissance | ID_Classe |
|
||||||
|
|----------|-----|--------|----------------|-----------|
|
||||||
|
| 1 | Alan | Michel | 12/12/2005 | 1 |
|
||||||
|
| 2 | Bergue | John | 13/01/2006 | 1 |
|
||||||
|
| 3 | Zidane | Michel | 12/12/2005 | 2 |
|
||||||
|
| 4 | Bergue | Inès | 06/04/2004 | 3 |
|
||||||
|
|
||||||
|
**Table Classes :**
|
||||||
|
|
||||||
|
| ID_Classe | Nom_Classe |
|
||||||
|
|-----------|------------|
|
||||||
|
| 1 | 2de1 |
|
||||||
|
| 2 | 1S2 |
|
||||||
|
| 3 | T-STL |
|
||||||
|
|
||||||
|
**Table Options :**
|
||||||
|
|
||||||
|
| ID_Option | Nom_Option |
|
||||||
|
|-----------|------------|
|
||||||
|
| 1 | CIT |
|
||||||
|
| 2 | Chinois |
|
||||||
|
| 3 | Latin |
|
||||||
|
| 4 | Maths |
|
||||||
|
| 5 | Physique |
|
||||||
|
| 6 | NSI |
|
||||||
|
|
||||||
|
**Table Eleves_Options :**
|
||||||
|
|
||||||
|
| ID_Eleve | ID_Option |
|
||||||
|
|----------|-----------|
|
||||||
|
| 1 | 1 |
|
||||||
|
| 1 | 2 |
|
||||||
|
| 2 | 1 |
|
||||||
|
| 2 | 2 |
|
||||||
|
| 2 | 3 |
|
||||||
|
| 3 | 4 |
|
||||||
|
| 3 | 5 |
|
||||||
|
| 3 | 6 |
|
||||||
|
|
||||||
|
**Avantages du nouveau schéma :**
|
||||||
|
- Chaque élève a un identifiant unique
|
||||||
|
- Plus de colonnes Option1/2/3 : on peut avoir n'importe quel nombre d'options
|
||||||
|
- Les noms de classes ne sont plus répétés
|
||||||
|
- Conforme aux formes normales 1NF, 2NF et 3NF
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
### Entraînement sur les Schémas Relationnels
|
# Entraînement sur les Schémas Relationnels
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
@@ -91,3 +91,11 @@ Chaque utilisateur peut créer un ou plusieurs **albums** contenant des messages
|
|||||||
|
|
||||||
### **Amélioration** :
|
### **Amélioration** :
|
||||||
- Proposez un schéma relationnel alternatif pour corriger ces défauts.
|
- Proposez un schéma relationnel alternatif pour corriger ces défauts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
## Conception des bases de données
|
# Conception des bases de données
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -60,10 +60,10 @@ Supposons que vous ajoutiez une colonne pour l'éditeur de chaque jeu :
|
|||||||
|
|
||||||
Ici, "Nintendo" est répété plusieurs fois. En cas d'erreur de saisie (`Nintnedo`), les données deviennent incohérentes.
|
Ici, "Nintendo" est répété plusieurs fois. En cas d'erreur de saisie (`Nintnedo`), les données deviennent incohérentes.
|
||||||
|
|
||||||
#### **1.2.2 Difficile à maintenir**
|
#### Difficile à maintenir
|
||||||
Si l'éditeur de "Zelda" change, il faut parcourir tout le tableau pour le modifier, ce qui est fastidieux et sujet aux erreurs.
|
Si l'éditeur de "Zelda" change, il faut parcourir tout le tableau pour le modifier, ce qui est fastidieux et sujet aux erreurs.
|
||||||
|
|
||||||
#### **1.2.3 Limite des relations**
|
#### Limite des relations
|
||||||
Il devient complexe de représenter les relations entre différentes entités. Par exemple, comment savoir quel client a acheté quel jeu ? Cela nécessiterait des tableaux imbriqués ou des fichiers séparés, augmentant le risque d'incohérences.
|
Il devient complexe de représenter les relations entre différentes entités. Par exemple, comment savoir quel client a acheté quel jeu ? Cela nécessiterait des tableaux imbriqués ou des fichiers séparés, augmentant le risque d'incohérences.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ Dans les années 1990, une banque a oublié d’appliquer la contrainte d’int
|
|||||||
|
|
||||||
Le modèle relationnel repose sur des concepts simples mais puissants. Les clés primaires et étrangères permettent de lier les données de manière cohérente, tandis que les contraintes d'intégrité assurent la fiabilité des informations.
|
Le modèle relationnel repose sur des concepts simples mais puissants. Les clés primaires et étrangères permettent de lier les données de manière cohérente, tandis que les contraintes d'intégrité assurent la fiabilité des informations.
|
||||||
|
|
||||||
**Pousuite :** nous verrons comment utiliser le langage SQL pour manipuler ces données et appliquer ces contraintes.
|
**Poursuite :** nous verrons comment utiliser le langage SQL pour manipuler ces données et appliquer ces contraintes.
|
||||||
|
|
||||||
Et souvenez-vous : "Une base bien modélisée est comme une maison bien construite : elle résistera aux tempêtes (ou presque) !"
|
Et souvenez-vous : "Une base bien modélisée est comme une maison bien construite : elle résistera aux tempêtes (ou presque) !"
|
||||||
|
|
||||||
|
|||||||
380
BDD_SGBD/TP_StreamFlix.md
Normal file
380
BDD_SGBD/TP_StreamFlix.md
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
# TP : Modéliser StreamFlix - La base de données d'une plateforme de streaming
|
||||||
|
|
||||||
|
> **Thème** : Conception de bases de données et schémas relationnels
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
En 2026, les plateformes de streaming (Netflix, Disney+, Prime Video, Apple TV+) dominent le marché du divertissement. Derrière leurs interfaces élégantes se cachent des **bases de données massives** qui gèrent des millions d'utilisateurs, de contenus et de visionnages.
|
||||||
|
|
||||||
|
Vous êtes embauché(e) comme stagiaire chez **StreamFlix**, une nouvelle plateforme de streaming française qui veut concurrencer les géants américains. Votre mission : **concevoir la base de données** qui fera tourner la plateforme.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 1 : Analyse des besoins
|
||||||
|
|
||||||
|
### Les fonctionnalités de StreamFlix
|
||||||
|
|
||||||
|
La plateforme doit permettre :
|
||||||
|
- Aux utilisateurs de **créer un compte** et de **s'abonner**
|
||||||
|
- De **parcourir un catalogue** de films et séries
|
||||||
|
- De **regarder** des contenus et de **reprendre** là où on s'est arrêté
|
||||||
|
- De créer une **liste de favoris** ("Ma Liste")
|
||||||
|
- De **noter** les contenus (1 à 5 étoiles)
|
||||||
|
|
||||||
|
### Exercice 1 : Identifier les entités
|
||||||
|
|
||||||
|
À partir de la description ci-dessus, identifiez les **entités** principales de la base de données.
|
||||||
|
|
||||||
|
**Indice** : Une entité est un "objet" du monde réel qu'on souhaite représenter. Pensez aux noms communs dans la description.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : Le Modèle Entité-Association
|
||||||
|
|
||||||
|
### Exercice 2 : Définir les attributs
|
||||||
|
|
||||||
|
Pour chaque entité identifiée, listez les **attributs** pertinents.
|
||||||
|
|
||||||
|
**Exemple** :
|
||||||
|
```
|
||||||
|
Entité : Utilisateur
|
||||||
|
Attributs : ID, Email, Mot_de_passe, Nom, Prénom, Date_naissance, Date_inscription
|
||||||
|
```
|
||||||
|
|
||||||
|
Faites de même pour :
|
||||||
|
- Film
|
||||||
|
- Série
|
||||||
|
- Épisode
|
||||||
|
- Abonnement
|
||||||
|
|
||||||
|
### Exercice 3 : Identifier les associations
|
||||||
|
|
||||||
|
Quelles sont les **relations** entre les entités ? Pour chaque relation, précisez :
|
||||||
|
- Les entités concernées
|
||||||
|
- Le nom de l'association
|
||||||
|
- Les **cardinalités** (1-1, 1-n, n-n)
|
||||||
|
|
||||||
|
**Exemple** :
|
||||||
|
```
|
||||||
|
Utilisateur ---< possède >--- Abonnement
|
||||||
|
Cardinalités : Un utilisateur possède 0 ou 1 abonnement actif.
|
||||||
|
Un abonnement appartient à 1 et 1 seul utilisateur.
|
||||||
|
→ Relation 1-1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice 4 : Dessiner le MCD
|
||||||
|
|
||||||
|
Représentez le **Modèle Conceptuel de Données** complet sous forme de schéma.
|
||||||
|
|
||||||
|
Utilisez la notation suivante :
|
||||||
|
```
|
||||||
|
┌─────────────┐ ┌─────────────┐
|
||||||
|
│ ENTITE1 │ │ ENTITE2 │
|
||||||
|
├─────────────┤ 1,n ├─────────────┤
|
||||||
|
│ #clé │◄───────►│ #clé │
|
||||||
|
│ attribut1 │ nom │ attribut1 │
|
||||||
|
│ attribut2 │ │ attribut2 │
|
||||||
|
└─────────────┘ └─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : Du MCD au Schéma Relationnel
|
||||||
|
|
||||||
|
### Rappel des règles de conversion
|
||||||
|
|
||||||
|
| Élément MCD | Conversion en relationnel |
|
||||||
|
|-------------|---------------------------|
|
||||||
|
| Entité | Table |
|
||||||
|
| Attribut | Colonne |
|
||||||
|
| Association 1-1 | Clé étrangère dans l'une des tables |
|
||||||
|
| Association 1-n | Clé étrangère côté "n" |
|
||||||
|
| Association n-n | Table de liaison |
|
||||||
|
|
||||||
|
### Exercice 5 : Convertir en schéma relationnel
|
||||||
|
|
||||||
|
Transformez votre MCD en **schéma relationnel**. Utilisez la notation :
|
||||||
|
|
||||||
|
```
|
||||||
|
NomTable(#cle_primaire, attribut1, attribut2, cle_etrangere*)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Convention** :
|
||||||
|
- `#` indique la clé primaire
|
||||||
|
- `*` indique une clé étrangère
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : La gestion des visionnages
|
||||||
|
|
||||||
|
### Le problème
|
||||||
|
|
||||||
|
StreamFlix veut permettre aux utilisateurs de **reprendre un contenu là où ils l'ont arrêté**. Il faut donc enregistrer :
|
||||||
|
- Quel utilisateur a regardé quel contenu
|
||||||
|
- À quelle date/heure
|
||||||
|
- Jusqu'à quelle minute du contenu
|
||||||
|
- Si le visionnage est terminé ou non
|
||||||
|
|
||||||
|
### Exercice 6 : Modéliser les visionnages
|
||||||
|
|
||||||
|
1. Créez une entité/table `Visionnage` avec les attributs appropriés.
|
||||||
|
2. Quelles sont les clés étrangères nécessaires ?
|
||||||
|
3. Quelle est la clé primaire de cette table ?
|
||||||
|
|
||||||
|
**Réflexion** : Un utilisateur peut-il regarder le même contenu plusieurs fois ? Comment gérer ce cas ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 5 : Les contraintes d'intégrité
|
||||||
|
|
||||||
|
### Exercice 7 : Identifier les contraintes
|
||||||
|
|
||||||
|
Pour chaque table de votre schéma, identifiez :
|
||||||
|
- Les contraintes d'**intégrité d'entité** (clé primaire unique et non nulle)
|
||||||
|
- Les contraintes d'**intégrité référentielle** (clés étrangères valides)
|
||||||
|
- Les contraintes de **domaine** (types de données, valeurs autorisées)
|
||||||
|
|
||||||
|
**Exemple** :
|
||||||
|
```
|
||||||
|
Table Utilisateur :
|
||||||
|
- ID_Utilisateur : entier, clé primaire, non nul, unique
|
||||||
|
- Email : chaîne, non nul, unique, format email valide
|
||||||
|
- Date_naissance : date, non nul, doit être dans le passé
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice 8 : Que se passe-t-il si...
|
||||||
|
|
||||||
|
Répondez aux questions suivantes en justifiant :
|
||||||
|
|
||||||
|
1. On essaie d'insérer un utilisateur avec un email déjà existant ?
|
||||||
|
2. On essaie de supprimer un film qui a été visionné par des utilisateurs ?
|
||||||
|
3. On essaie d'ajouter un visionnage pour un utilisateur qui n'existe pas ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 6 : Normalisation
|
||||||
|
|
||||||
|
### Exercice 9 : Vérifier la normalisation
|
||||||
|
|
||||||
|
Voici une proposition de table pour gérer les séries :
|
||||||
|
|
||||||
|
| ID_Serie | Titre | Genre | Nb_Saisons | Acteur1 | Acteur2 | Acteur3 |
|
||||||
|
|----------|-------|-------|------------|---------|---------|---------|
|
||||||
|
| 1 | Stranger Things | SF | 5 | Millie Bobby Brown | Finn Wolfhard | Gaten Matarazzo |
|
||||||
|
| 2 | The Crown | Drame | 6 | Claire Foy | Olivia Colman | Imelda Staunton |
|
||||||
|
|
||||||
|
1. Cette table est-elle en **1NF** ? Pourquoi ?
|
||||||
|
2. Proposez un schéma **normalisé** pour gérer les séries et leurs acteurs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 7 : Schéma final
|
||||||
|
|
||||||
|
### Exercice 10 : Synthèse
|
||||||
|
|
||||||
|
Proposez le **schéma relationnel complet** de StreamFlix avec :
|
||||||
|
- Toutes les tables
|
||||||
|
- Toutes les clés (primaires et étrangères)
|
||||||
|
- Les types de données principaux
|
||||||
|
|
||||||
|
### Exemple de format attendu
|
||||||
|
|
||||||
|
```
|
||||||
|
Utilisateurs(
|
||||||
|
#ID_Utilisateur INT,
|
||||||
|
Email VARCHAR(255) UNIQUE NOT NULL,
|
||||||
|
Mot_de_passe VARCHAR(255) NOT NULL,
|
||||||
|
Nom VARCHAR(100),
|
||||||
|
Prénom VARCHAR(100),
|
||||||
|
Date_naissance DATE,
|
||||||
|
Date_inscription DATE DEFAULT CURRENT_DATE
|
||||||
|
)
|
||||||
|
|
||||||
|
Films(
|
||||||
|
#ID_Film INT,
|
||||||
|
Titre VARCHAR(255) NOT NULL,
|
||||||
|
...
|
||||||
|
)
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bonus : Pour aller plus loin
|
||||||
|
|
||||||
|
### Réflexion 1 : L'algorithme de recommandation
|
||||||
|
|
||||||
|
Netflix utilise un algorithme de recommandation basé sur :
|
||||||
|
- Les contenus déjà visionnés
|
||||||
|
- Les notes attribuées
|
||||||
|
- Les contenus similaires (même genre, mêmes acteurs)
|
||||||
|
|
||||||
|
Quelles informations de notre base de données seraient utiles pour cet algorithme ?
|
||||||
|
|
||||||
|
### Réflexion 2 : Passage à l'échelle
|
||||||
|
|
||||||
|
StreamFlix a maintenant 10 millions d'utilisateurs et chacun regarde en moyenne 2 contenus par jour.
|
||||||
|
|
||||||
|
1. Combien d'enregistrements sont ajoutés dans la table `Visionnage` chaque jour ?
|
||||||
|
2. Quels problèmes cela peut-il poser ?
|
||||||
|
3. Avez-vous entendu parler des bases de données **NoSQL** ? Pourquoi pourraient-elles être utiles ici ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Résumé des notions travaillées
|
||||||
|
|
||||||
|
| Notion | Application dans ce TP |
|
||||||
|
|--------|------------------------|
|
||||||
|
| Entité | Utilisateur, Film, Série, etc. |
|
||||||
|
| Attribut | Email, Titre, Durée, etc. |
|
||||||
|
| Association | "regarde", "possède", etc. |
|
||||||
|
| Clé primaire | ID_Utilisateur, ID_Film |
|
||||||
|
| Clé étrangère | ID_Utilisateur dans Visionnage |
|
||||||
|
| Cardinalités | 1-1, 1-n, n-n |
|
||||||
|
| Contraintes d'intégrité | Unicité, référence, domaine |
|
||||||
|
| Normalisation | Éviter la redondance |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Annexe : Schéma relationnel de référence (corrigé)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Cliquez pour afficher le corrigé</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
Utilisateurs(
|
||||||
|
#ID_Utilisateur,
|
||||||
|
Email,
|
||||||
|
Mot_de_passe,
|
||||||
|
Nom,
|
||||||
|
Prenom,
|
||||||
|
Date_naissance,
|
||||||
|
Date_inscription
|
||||||
|
)
|
||||||
|
|
||||||
|
Abonnements(
|
||||||
|
#ID_Abonnement,
|
||||||
|
Type, -- "Basic", "Standard", "Premium"
|
||||||
|
Prix_mensuel,
|
||||||
|
Date_debut,
|
||||||
|
Date_fin,
|
||||||
|
ID_Utilisateur*
|
||||||
|
)
|
||||||
|
|
||||||
|
Films(
|
||||||
|
#ID_Film,
|
||||||
|
Titre,
|
||||||
|
Annee,
|
||||||
|
Duree_minutes,
|
||||||
|
Synopsis,
|
||||||
|
ID_Genre*
|
||||||
|
)
|
||||||
|
|
||||||
|
Series(
|
||||||
|
#ID_Serie,
|
||||||
|
Titre,
|
||||||
|
Annee_debut,
|
||||||
|
Annee_fin,
|
||||||
|
Synopsis,
|
||||||
|
ID_Genre*
|
||||||
|
)
|
||||||
|
|
||||||
|
Episodes(
|
||||||
|
#ID_Episode,
|
||||||
|
Numero_saison,
|
||||||
|
Numero_episode,
|
||||||
|
Titre,
|
||||||
|
Duree_minutes,
|
||||||
|
ID_Serie*
|
||||||
|
)
|
||||||
|
|
||||||
|
Genres(
|
||||||
|
#ID_Genre,
|
||||||
|
Nom_genre
|
||||||
|
)
|
||||||
|
|
||||||
|
Acteurs(
|
||||||
|
#ID_Acteur,
|
||||||
|
Nom,
|
||||||
|
Prenom,
|
||||||
|
Date_naissance
|
||||||
|
)
|
||||||
|
|
||||||
|
Films_Acteurs(
|
||||||
|
#ID_Film*,
|
||||||
|
#ID_Acteur*,
|
||||||
|
Role
|
||||||
|
)
|
||||||
|
|
||||||
|
Series_Acteurs(
|
||||||
|
#ID_Serie*,
|
||||||
|
#ID_Acteur*,
|
||||||
|
Role
|
||||||
|
)
|
||||||
|
|
||||||
|
Visionnages(
|
||||||
|
#ID_Visionnage,
|
||||||
|
Date_heure,
|
||||||
|
Minute_arret,
|
||||||
|
Est_termine,
|
||||||
|
ID_Utilisateur*,
|
||||||
|
ID_Film*, -- NULL si c'est un épisode
|
||||||
|
ID_Episode* -- NULL si c'est un film
|
||||||
|
)
|
||||||
|
|
||||||
|
MaListe(
|
||||||
|
#ID_Utilisateur*,
|
||||||
|
#ID_Film*, -- ou ID_Serie selon le contenu
|
||||||
|
Date_ajout
|
||||||
|
)
|
||||||
|
|
||||||
|
Notes(
|
||||||
|
#ID_Utilisateur*,
|
||||||
|
#ID_Film*, -- ou ID_Serie
|
||||||
|
Note, -- 1 à 5
|
||||||
|
Date_note
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**MCD correspondant** :
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐ 1,1 ┌──────────────┐
|
||||||
|
│ UTILISATEUR │◄────────────┤ ABONNEMENT │
|
||||||
|
├──────────────┤ possède ├──────────────┤
|
||||||
|
│ #ID │ │ #ID │
|
||||||
|
│ Email │ │ Type │
|
||||||
|
│ Nom │ │ Prix │
|
||||||
|
└──────┬───────┘ └──────────────┘
|
||||||
|
│
|
||||||
|
│ 0,n
|
||||||
|
│
|
||||||
|
▼ 0,n ┌──────────────┐
|
||||||
|
REGARDE ─────────────────► │ FILM │
|
||||||
|
│ ├──────────────┤
|
||||||
|
│ │ #ID │
|
||||||
|
│ │ Titre │
|
||||||
|
│ 0,n │ Duree │
|
||||||
|
│ └──────────────┘
|
||||||
|
▼
|
||||||
|
┌──────────────┐
|
||||||
|
│ EPISODE │◄──── appartient ──── SERIE
|
||||||
|
├──────────────┤ 1,n
|
||||||
|
│ #ID │
|
||||||
|
│ Numero │
|
||||||
|
│ Titre │
|
||||||
|
└──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
177
Calculabilité/Exercices.md
Normal file
177
Calculabilité/Exercices.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# Exercices : Calculabilité et Décidabilité
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 1 : Problèmes décidables ou non ?
|
||||||
|
|
||||||
|
Pour chaque problème ci-dessous, indiquez s'il est **décidable** ou **indécidable**. Justifiez votre réponse.
|
||||||
|
|
||||||
|
1. Déterminer si un nombre entier est premier.
|
||||||
|
|
||||||
|
2. Déterminer si une chaîne de caractères est un palindrome.
|
||||||
|
|
||||||
|
3. Déterminer si un programme Python quelconque va s'arrêter.
|
||||||
|
|
||||||
|
4. Déterminer si un nombre est divisible par 7.
|
||||||
|
|
||||||
|
5. Déterminer si deux programmes Python produisent toujours le même résultat pour toutes les entrées possibles.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 2 : Écrire des prédicats
|
||||||
|
|
||||||
|
Un **prédicat** est une fonction qui renvoie un booléen. Écrivez en Python les prédicats suivants :
|
||||||
|
|
||||||
|
### 2.1 Prédicat `est_puissance_de_2`
|
||||||
|
|
||||||
|
```python
|
||||||
|
def est_puissance_de_2(n):
|
||||||
|
"""
|
||||||
|
Renvoie True si n est une puissance de 2, False sinon.
|
||||||
|
Exemples : 1, 2, 4, 8, 16, 32... sont des puissances de 2.
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Prédicat `est_parfait`
|
||||||
|
|
||||||
|
Un nombre parfait est un entier positif égal à la somme de ses diviseurs propres (diviseurs stricts, c'est-à-dire sans compter le nombre lui-même).
|
||||||
|
|
||||||
|
Exemple : 6 = 1 + 2 + 3 est parfait.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def est_parfait(n):
|
||||||
|
"""
|
||||||
|
Renvoie True si n est un nombre parfait, False sinon.
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Prédicat `contient_doublon`
|
||||||
|
|
||||||
|
```python
|
||||||
|
def contient_doublon(liste):
|
||||||
|
"""
|
||||||
|
Renvoie True si la liste contient au moins un élément en double.
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 3 : Thèse de Church-Turing
|
||||||
|
|
||||||
|
### 3.1 Questions de cours
|
||||||
|
|
||||||
|
1. Qui est Alonzo Church et quelle est sa contribution à l'informatique théorique ?
|
||||||
|
|
||||||
|
2. Qu'affirme la thèse de Church-Turing ?
|
||||||
|
|
||||||
|
3. Pourquoi parle-t-on de "thèse" et non de "théorème" ?
|
||||||
|
|
||||||
|
### 3.2 Réflexion
|
||||||
|
|
||||||
|
Si un algorithme fonctionne en Python, peut-il être traduit dans n'importe quel autre langage de programmation ? Justifiez en vous appuyant sur la thèse de Church-Turing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 4 : Le problème de l'arrêt
|
||||||
|
|
||||||
|
### 4.1 Compréhension
|
||||||
|
|
||||||
|
Considérez le programme suivant :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def mystere(n):
|
||||||
|
while n != 1:
|
||||||
|
if n % 2 == 0:
|
||||||
|
n = n // 2
|
||||||
|
else:
|
||||||
|
n = 3 * n + 1
|
||||||
|
return n
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Testez ce programme avec les valeurs 6, 11, 27. Que constatez-vous ?
|
||||||
|
|
||||||
|
2. Ce programme s'arrête-t-il toujours ? (Indice : c'est la conjecture de Syracuse, non résolue à ce jour !)
|
||||||
|
|
||||||
|
3. En quoi cet exemple illustre-t-il le problème de l'arrêt ?
|
||||||
|
|
||||||
|
### 4.2 Démonstration par l'absurde
|
||||||
|
|
||||||
|
Expliquez avec vos propres mots pourquoi le problème de l'arrêt est indécidable. Utilisez le raisonnement par l'absurde vu en cours.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 5 : Machine de Turing
|
||||||
|
|
||||||
|
### 5.1 Addition binaire
|
||||||
|
|
||||||
|
Utilisez l'algorithme de la machine de Turing (activité TURING.md) pour calculer :
|
||||||
|
|
||||||
|
1. 11₂ + 1 = ?
|
||||||
|
2. 111₂ + 1 = ?
|
||||||
|
3. 1111₂ + 1 = ?
|
||||||
|
|
||||||
|
### 5.2 Réflexion
|
||||||
|
|
||||||
|
Que remarquez-vous sur le nombre d'étapes nécessaires quand tous les bits sont à 1 ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 6 : QCM de révision
|
||||||
|
|
||||||
|
### Question 1
|
||||||
|
Un prédicat est une fonction qui :
|
||||||
|
- [ ] A. Prend un booléen en paramètre
|
||||||
|
- [ ] B. Renvoie un booléen
|
||||||
|
- [ ] C. Ne s'arrête jamais
|
||||||
|
- [ ] D. Est toujours récursive
|
||||||
|
|
||||||
|
### Question 2
|
||||||
|
Le problème de l'arrêt est :
|
||||||
|
- [ ] A. Décidable et calculable
|
||||||
|
- [ ] B. Indécidable
|
||||||
|
- [ ] C. Résolu par Alan Turing en 1936
|
||||||
|
- [ ] D. Un problème simple à résoudre
|
||||||
|
|
||||||
|
### Question 3
|
||||||
|
Selon la thèse de Church-Turing :
|
||||||
|
- [ ] A. Python est le meilleur langage de programmation
|
||||||
|
- [ ] B. Tout ce qui est calculable peut l'être par une machine de Turing
|
||||||
|
- [ ] C. Certains langages sont plus puissants que d'autres
|
||||||
|
- [ ] D. Les ordinateurs quantiques peuvent tout calculer
|
||||||
|
|
||||||
|
### Question 4
|
||||||
|
Un problème est dit décidable si :
|
||||||
|
- [ ] A. On peut toujours trouver une solution
|
||||||
|
- [ ] B. Il existe un algorithme qui répond oui ou non en temps fini
|
||||||
|
- [ ] C. Il n'a pas de solution
|
||||||
|
- [ ] D. Il nécessite un supercalculateur
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 7 : Pour aller plus loin
|
||||||
|
|
||||||
|
### Le paradoxe de Russell
|
||||||
|
|
||||||
|
Bertrand Russell a proposé le paradoxe suivant en 1901 :
|
||||||
|
|
||||||
|
> « Dans un village, le barbier rase tous ceux qui ne se rasent pas eux-mêmes, et seulement ceux-là. Qui rase le barbier ? »
|
||||||
|
|
||||||
|
1. Expliquez pourquoi ce paradoxe n'a pas de solution logique.
|
||||||
|
|
||||||
|
2. En quoi ce paradoxe est-il similaire au problème de l'arrêt ?
|
||||||
|
|
||||||
|
3. (Bonus) Recherchez le "paradoxe du menteur" et expliquez son lien avec l'indécidabilité.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
## Calculabilité et décidabilité en informatique ##
|
# Calculabilité et décidabilité en informatique
|
||||||
|
|
||||||
> À chaque problème sa non solution !
|
> À chaque problème sa non solution !
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
#### Introduction à la décidabilité
|
#### Introduction à la décidabilité
|
||||||
|
|
||||||
Il vous est deja arrivé de prendre des décisions en suivant une logique. Dans la vie vous suivez la votre, en prenant des conseils, ou de par ce que vous avez appris à l'école. C'est donc quelque chose général.
|
Il vous est déjà arrivé de prendre des décisions en suivant une logique. Dans la vie vous suivez la votre, en prenant des conseils, ou de par ce que vous avez appris à l'école. C'est donc quelque chose général.
|
||||||
|
|
||||||
Cela vous semble simple de définir la logique, mais en réalité, mise en pratique, cela devient plus complexe à commenter :
|
Cela vous semble simple de définir la logique, mais en réalité, mise en pratique, cela devient plus complexe à commenter :
|
||||||
mathématiquement, la logique n'a pas grand chose à voir avec celle dont on use au quotidien.
|
mathématiquement, la logique n'a pas grand chose à voir avec celle dont on use au quotidien.
|
||||||
@@ -33,22 +33,19 @@ On peut par exemple prendre des problèmes assez élémentaires :
|
|||||||
|
|
||||||
Dans notre premier cas, nous avons en **instance de départ :** un entier naturel, qui, après être traité via le **paramètre** choisi (Si reste = 0 après une division par 2, alors nombre est pair) dans **l'algorithme**, ressortira en une réponse simple : *Oui* ou *Non* selon la **décision** prise.
|
Dans notre premier cas, nous avons en **instance de départ :** un entier naturel, qui, après être traité via le **paramètre** choisi (Si reste = 0 après une division par 2, alors nombre est pair) dans **l'algorithme**, ressortira en une réponse simple : *Oui* ou *Non* selon la **décision** prise.
|
||||||
|
|
||||||
### Définition
|
### Définition : Prédicat
|
||||||
|
|
||||||
En informatique ou mathématiques, on peut parler d'une **fonction algorithmique**. Quand le résultat de celle ci, c'est à dire ce qu'elle *renvoit*, est un boléen, on parle de ***prédicat***.
|
En informatique ou mathématiques, on peut parler d'une **fonction algorithmique**. Quand le résultat de celle-ci, c'est-à-dire ce qu'elle *renvoie*, est un booléen, on parle de ***prédicat***.
|
||||||
|
|
||||||
Un ***prédicat*** est une fonction qui ne prendra que des valeurs booléennes:
|
Un ***prédicat*** est une fonction qui ne prendra que des valeurs booléennes.
|
||||||
|
|
||||||
<p>
|
> La réponse à un problème de décision est donc soit un booléen, soit une valeur qui permet de répondre à un prédicat.
|
||||||
|
>
|
||||||
|
> **Exemple** : Dans un graphe, connaître le nombre de sommets permet de répondre à la question : « Le chemin que j'ai choisi est-il le plus court passant par tous les sommets de ce graphe ? »
|
||||||
|
>
|
||||||
|
> Question dont la réponse est bel et bien un prédicat.
|
||||||
|
|
||||||
La réponse à un problème de décision est donc soit un booléen ou alors une valeur qui permet de répondre à un prédicat
|
### Définition : Décidabilité
|
||||||
|
|
||||||
|
|
||||||
Exemple : Dans un graphe, connaitre le nombre de sommets permet de répondre à la question : le chemin que j'ai choisi est il le plus court passant par tous les sommets de ce graphe ?
|
|
||||||
|
|
||||||
Question dont la réponse est bel et bien un prédicat.
|
|
||||||
|
|
||||||
### Définition
|
|
||||||
|
|
||||||
Si, pour réponse à un problème, on peut en écrire un algorithme permettant la prise de décision et donc amenant à la résolution du dit problème, on parlera de problème ***décidable*** ou de <u>**décidabilité**</u>
|
Si, pour réponse à un problème, on peut en écrire un algorithme permettant la prise de décision et donc amenant à la résolution du dit problème, on parlera de problème ***décidable*** ou de <u>**décidabilité**</u>
|
||||||
|
|
||||||
@@ -58,7 +55,7 @@ Et donc, par extension, si un problème n'est pas soluble, on utilisera le terme
|
|||||||
|
|
||||||
## ~~La calcul habilité~~ La calculabilité
|
## ~~La calcul habilité~~ La calculabilité
|
||||||
|
|
||||||
En informatique, plus précisemment dans la branche dite de programmation, nous utilisons un langage qui nous est propre afin de mettre en place nos idées dans un algorithme.
|
En informatique, plus précisément dans la branche dite de programmation, nous utilisons un langage qui nous est propre afin de mettre en place nos idées dans un algorithme.
|
||||||
|
|
||||||
### Vocabulaire
|
### Vocabulaire
|
||||||
|
|
||||||
@@ -100,7 +97,7 @@ Ici, en NSI, nous utilisons le langage Python, mais nous pourrions parfaitement
|
|||||||
|
|
||||||
Revenons au prédicat : si une fonction renvoie un prédicat, et que celui là est dit calculable, alors la prise de décision est possible. Une fonction calculable permet donc l'emergence de décisions décidables.
|
Revenons au prédicat : si une fonction renvoie un prédicat, et que celui là est dit calculable, alors la prise de décision est possible. Une fonction calculable permet donc l'emergence de décisions décidables.
|
||||||
|
|
||||||
Ce qui signifie qu'il existe un algorithme permettant la résolution de cette fonction, ou d'un problème ammené par la fonction, et si un algorithme existe, alors nous sommes en mesure de programmer la fonction dans n'importe quel langage.
|
Ce qui signifie qu'il existe un algorithme permettant la résolution de cette fonction, ou d'un problème amené par la fonction, et si un algorithme existe, alors nous sommes en mesure de programmer la fonction dans n'importe quel langage.
|
||||||
|
|
||||||
--------
|
--------
|
||||||
|
|
||||||
@@ -116,7 +113,9 @@ Pour un problème, c'est la même chose : si l'on peut écrire un programme qui
|
|||||||
|
|
||||||
### Pourquoi est ce important ?
|
### Pourquoi est ce important ?
|
||||||
|
|
||||||
Imaginez, si votre programme, logiciel ou jeu vidéo favori rencontrait un problème d'execution et tournait en boucle quand vous l'utilisez. <br> Frustrant n'est ce pas ? Une personne qui souhaite développer des programmes doit s'assurer de la décidabilité de son travail.
|
Imaginez, si votre programme, logiciel ou jeu vidéo favori rencontrait un problème d'execution et tournait en boucle quand vous l'utilisez.
|
||||||
|
|
||||||
|
Frustrant n'est ce pas ? Une personne qui souhaite développer des programmes doit s'assurer de la décidabilité de son travail.
|
||||||
|
|
||||||
### Et maintenant, des exemples !
|
### Et maintenant, des exemples !
|
||||||
|
|
||||||
@@ -177,7 +176,7 @@ Nous avons bien la preuve que le problème " Peut-on prouver qu'un nombre est pa
|
|||||||
|
|
||||||
### Tous les problèmes sont ils décidables?
|
### Tous les problèmes sont ils décidables?
|
||||||
|
|
||||||
Et bien non ! Disons le tout de suite, il existe un nombre de problèmes que l'ont ne peut résoudre par un simple algorithme. On parlera de ***Problèmes Indécidables*** !
|
Et bien non ! Disons le tout de suite, il existe un nombre de problèmes que l'on ne peut résoudre par un simple algorithme. On parlera de ***Problèmes Indécidables*** !
|
||||||
|
|
||||||
La question qui se pose à nous est de savoir comment reconnaitre un problème indécidable.
|
La question qui se pose à nous est de savoir comment reconnaitre un problème indécidable.
|
||||||
|
|
||||||
@@ -203,9 +202,9 @@ C'est à dire qu'on va partir d'une hypothèse qu'on supposera vraie, pour essay
|
|||||||
|
|
||||||
Cela nous ramènera donc à des décisions logiques qui en découleront.
|
Cela nous ramènera donc à des décisions logiques qui en découleront.
|
||||||
|
|
||||||
### Mais si le postulat de base est éronné ?
|
### Mais si le postulat de base est erroné ?
|
||||||
|
|
||||||
C'est tout le principe : s'il est éronné, alors il faut tester son exact opposé : celà prend du temps.
|
C'est tout le principe : s'il est erroné, alors il faut tester son exact opposé : cela prend du temps.
|
||||||
|
|
||||||
Et enfin, en testant l'opposé, on pourra savoir si notre problème est décidable ou pas !
|
Et enfin, en testant l'opposé, on pourra savoir si notre problème est décidable ou pas !
|
||||||
|
|
||||||
@@ -215,7 +214,7 @@ Oui mais :En 1936, Alan Turing démontre son indécidabilité.
|
|||||||
|
|
||||||
#### Attendez, le programme qui determine l'indécidabilité est lui même indécidable ??
|
#### Attendez, le programme qui determine l'indécidabilité est lui même indécidable ??
|
||||||
|
|
||||||
Eeh oui, c'est incroyable mais juste : ironique n'est ce pas ? On peut même dire *absurde*
|
Eh oui, c'est incroyable mais juste : ironique n'est ce pas ? On peut même dire *absurde*
|
||||||
|
|
||||||
En effet, on va user d'un raisonnement par l'absurde pour montrer que ce qu'on pense vrai est en réalité faux.
|
En effet, on va user d'un raisonnement par l'absurde pour montrer que ce qu'on pense vrai est en réalité faux.
|
||||||
|
|
||||||
@@ -227,7 +226,7 @@ Supposons que l'on puisse écrire une fonction arret, capable de dire si une fon
|
|||||||
|
|
||||||
Il n'est, dans notre exemple, absolument pas certain qu'une telle fonction existe.
|
Il n'est, dans notre exemple, absolument pas certain qu'une telle fonction existe.
|
||||||
|
|
||||||
Nous allons dans un premier temps, créer une fonction qui nous servira de cobaye dans notre fonction absurbe de programme d'arrêt.
|
Nous allons dans un premier temps, créer une fonction qui nous servira de cobaye dans notre fonction absurde de programme d'arrêt.
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@@ -253,16 +252,23 @@ Maintenant, définissons notre fonction absurde
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def absurde(programme):
|
def absurde(programme):
|
||||||
if arret_programme(programme,x):
|
if arret_programme(programme, programme):
|
||||||
|
# Si le programme s'arrête, on boucle à l'infini
|
||||||
while True:
|
while True:
|
||||||
continue
|
pass
|
||||||
else:
|
else:
|
||||||
return True
|
# Si le programme boucle, on s'arrête
|
||||||
|
return True
|
||||||
```
|
```
|
||||||
|
|
||||||
Si on analyse ce que l'on vient d'écrire : Quand la fonction arret_programme peut s'arrêter, la fonction absurde va lui permettre de boucler à l'infini !
|
Analysons ce que l'on vient d'écrire :
|
||||||
|
- Si `arret_programme(programme, programme)` renvoie `True` (le programme s'arrête), alors `absurde` **boucle à l'infini**
|
||||||
|
- Si `arret_programme(programme, programme)` renvoie `False` (le programme boucle), alors `absurde` **s'arrête** et renvoie `True`
|
||||||
|
|
||||||
Mais si le test se révèle négatif, on renvoit True et arretons donc la fonction!
|
**Le paradoxe** : Que se passe-t-il si on appelle `absurde(absurde)` ?
|
||||||
|
|
||||||
|
- Si `arret_programme(absurde, absurde)` dit que `absurde(absurde)` s'arrête → alors `absurde(absurde)` boucle (contradiction !)
|
||||||
|
- Si `arret_programme(absurde, absurde)` dit que `absurde(absurde)` boucle → alors `absurde(absurde)` s'arrête (contradiction !)
|
||||||
|
|
||||||
Nous venons donc de prouver l'impossible. Pas mal non ?
|
Nous venons donc de prouver l'impossible. Pas mal non ?
|
||||||
|
|
||||||
@@ -277,7 +283,7 @@ Si la machine ne peut nous aider, il vous faudra donc, en tant qu'humain, toujou
|
|||||||
|
|
||||||
On peut retenir deux choses de notre exemple : Le fameux théorème de Kurt Gödel, qui traite de l'indécidabilité, n'est pas vérifiable puisque qu'on ne peut vérifier la décidabilité de tous les sytèmes, dès lors que la fonction qui doit nous y aider n'est elle même pas décidable !
|
On peut retenir deux choses de notre exemple : Le fameux théorème de Kurt Gödel, qui traite de l'indécidabilité, n'est pas vérifiable puisque qu'on ne peut vérifier la décidabilité de tous les sytèmes, dès lors que la fonction qui doit nous y aider n'est elle même pas décidable !
|
||||||
|
|
||||||
Pour comprendre la démonstration de tout cela, il nous aura fallu analyser la démonstration en elle même, et non pas essayer de démontrer ce qu'elle prouve. Ce qui laisse perplexe quant à la veracité de certaines affirmations.
|
Pour comprendre la démonstration de tout cela, il nous aura fallu analyser la démonstration en elle même, et non pas essayer de démontrer ce qu'elle prouve. Ce qui laisse perplexe quant à la véracité de certaines affirmations.
|
||||||
|
|
||||||
Enfin, on peut se demander une chose. Puisque nous avons une donnée en entrée d'une fonction, et une donnée en sortie...Une fonction ne serait-elle pas uniquement que données ?
|
Enfin, on peut se demander une chose. Puisque nous avons une donnée en entrée d'une fonction, et une donnée en sortie...Une fonction ne serait-elle pas uniquement que données ?
|
||||||
|
|
||||||
|
|||||||
289
Calculabilité/TP_Paradoxes.md
Normal file
289
Calculabilité/TP_Paradoxes.md
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
# TP : Le Barbier, le Menteur et la Machine — Paradoxes et Indécidabilité
|
||||||
|
|
||||||
|
> **Thème** : Comprendre l'indécidabilité à travers les paradoxes logiques
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
En 2026, les intelligences artificielles comme ChatGPT, Claude ou Gemini impressionnent par leurs capacités. Mais peuvent-elles *tout* calculer ? Peuvent-elles répondre à *toutes* les questions ?
|
||||||
|
|
||||||
|
Dans ce TP, vous allez découvrir que certaines questions n'ont **pas de réponse calculable**, et ce depuis bien avant l'invention des ordinateurs. Des mathématiciens comme Bertrand Russell, Kurt Gödel et Alan Turing ont prouvé qu'il existe des **limites fondamentales** à ce que les machines peuvent faire.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 1 : Le Paradoxe du Menteur
|
||||||
|
|
||||||
|
### L'énoncé
|
||||||
|
|
||||||
|
Considérez la phrase suivante :
|
||||||
|
|
||||||
|
> « Cette phrase est fausse. »
|
||||||
|
|
||||||
|
### Questions
|
||||||
|
|
||||||
|
1. Si cette phrase est **vraie**, que peut-on en déduire ?
|
||||||
|
|
||||||
|
2. Si cette phrase est **fausse**, que peut-on en déduire ?
|
||||||
|
|
||||||
|
3. Pourquoi dit-on que c'est un paradoxe ?
|
||||||
|
|
||||||
|
### Variante moderne : le tweet impossible
|
||||||
|
|
||||||
|
Imaginez un bot Twitter/X programmé ainsi :
|
||||||
|
|
||||||
|
```
|
||||||
|
SI le tweet dit "Ce tweet est faux" ALORS :
|
||||||
|
SI le tweet est vrai → marquer comme faux
|
||||||
|
SI le tweet est faux → marquer comme vrai
|
||||||
|
```
|
||||||
|
|
||||||
|
Que se passe-t-il quand le bot analyse le tweet « Ce tweet est faux » ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : Le Paradoxe du Barbier
|
||||||
|
|
||||||
|
### L'énoncé (Bertrand Russell, 1901)
|
||||||
|
|
||||||
|
> Dans un village, il y a un barbier qui rase **tous** les hommes qui ne se rasent pas eux-mêmes, et **seulement** ceux-là.
|
||||||
|
>
|
||||||
|
> **Question : Le barbier se rase-t-il lui-même ?**
|
||||||
|
|
||||||
|
### Analyse
|
||||||
|
|
||||||
|
1. **Hypothèse 1** : Le barbier se rase lui-même.
|
||||||
|
- Que peut-on en déduire d'après la règle ?
|
||||||
|
|
||||||
|
2. **Hypothèse 2** : Le barbier ne se rase pas lui-même.
|
||||||
|
- Que peut-on en déduire d'après la règle ?
|
||||||
|
|
||||||
|
3. Conclusion : pourquoi ce paradoxe n'a-t-il pas de solution ?
|
||||||
|
|
||||||
|
### Application en informatique
|
||||||
|
|
||||||
|
Imaginez une fonction Python `barbier(personne)` qui renvoie `True` si le barbier rase cette personne :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def barbier(personne):
|
||||||
|
"""
|
||||||
|
Renvoie True si le barbier rase cette personne.
|
||||||
|
Règle : le barbier rase ceux qui ne se rasent pas eux-mêmes.
|
||||||
|
"""
|
||||||
|
return not se_rase_soi_meme(personne)
|
||||||
|
```
|
||||||
|
|
||||||
|
Que renvoie `barbier("barbier")` si le barbier est une personne du village ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : L'Ensemble de tous les ensembles
|
||||||
|
|
||||||
|
### Le paradoxe de Russell (version mathématique)
|
||||||
|
|
||||||
|
Considérons l'ensemble R défini ainsi :
|
||||||
|
|
||||||
|
> R = { tous les ensembles qui ne se contiennent pas eux-mêmes }
|
||||||
|
|
||||||
|
**Question** : R se contient-il lui-même ?
|
||||||
|
|
||||||
|
### Analyse
|
||||||
|
|
||||||
|
1. Si R ∈ R (R se contient), alors par définition de R, R ne devrait pas se contenir. **Contradiction.**
|
||||||
|
|
||||||
|
2. Si R ∉ R (R ne se contient pas), alors par définition de R, R devrait se contenir. **Contradiction.**
|
||||||
|
|
||||||
|
### Impact historique
|
||||||
|
|
||||||
|
Ce paradoxe a provoqué une **crise des fondements des mathématiques** au début du XXe siècle. Les mathématiciens ont dû repenser la notion même d'ensemble pour éviter ces contradictions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : Du Paradoxe au Problème de l'Arrêt
|
||||||
|
|
||||||
|
### Le lien avec l'informatique
|
||||||
|
|
||||||
|
Alan Turing a utilisé une structure similaire pour prouver que le **problème de l'arrêt** est indécidable.
|
||||||
|
|
||||||
|
### Rappel du problème de l'arrêt
|
||||||
|
|
||||||
|
> Existe-t-il un programme `arret(P, x)` qui, pour tout programme P et toute entrée x, répond :
|
||||||
|
> - `True` si P(x) s'arrête
|
||||||
|
> - `False` si P(x) boucle indéfiniment ?
|
||||||
|
|
||||||
|
### La démonstration de Turing (simplifiée)
|
||||||
|
|
||||||
|
**Étape 1** : Supposons qu'un tel programme `arret` existe.
|
||||||
|
|
||||||
|
**Étape 2** : Construisons le programme `paradoxe` suivant :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def paradoxe(programme):
|
||||||
|
if arret(programme, programme):
|
||||||
|
# Si le programme s'arrête sur lui-même, on boucle
|
||||||
|
while True:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Si le programme boucle sur lui-même, on s'arrête
|
||||||
|
return True
|
||||||
|
```
|
||||||
|
|
||||||
|
**Étape 3** : Que se passe-t-il si on appelle `paradoxe(paradoxe)` ?
|
||||||
|
|
||||||
|
Complétez le raisonnement :
|
||||||
|
|
||||||
|
- Si `arret(paradoxe, paradoxe)` renvoie `True` → ...
|
||||||
|
- Si `arret(paradoxe, paradoxe)` renvoie `False` → ...
|
||||||
|
|
||||||
|
**Étape 4** : Conclusion
|
||||||
|
|
||||||
|
Quelle conclusion peut-on tirer sur l'existence de la fonction `arret` ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 5 : Simulation d'une Machine de Turing en Python
|
||||||
|
|
||||||
|
### Objectif
|
||||||
|
|
||||||
|
Implémenter une machine de Turing simplifiée qui effectue l'addition de 1 à un nombre binaire.
|
||||||
|
|
||||||
|
### Structure de la machine
|
||||||
|
|
||||||
|
```python
|
||||||
|
class MachineDeTuring:
|
||||||
|
def __init__(self, ruban_initial):
|
||||||
|
"""
|
||||||
|
Initialise la machine avec un ruban.
|
||||||
|
Le ruban est une liste de caractères ('0', '1', ou ' ' pour vide).
|
||||||
|
"""
|
||||||
|
self.ruban = list(ruban_initial)
|
||||||
|
self.position = 0 # Position de la tête de lecture
|
||||||
|
self.etat = "chercher_fin" # État initial
|
||||||
|
|
||||||
|
def lire(self):
|
||||||
|
"""Lit le symbole sous la tête de lecture."""
|
||||||
|
if 0 <= self.position < len(self.ruban):
|
||||||
|
return self.ruban[self.position]
|
||||||
|
return ' ' # Case vide
|
||||||
|
|
||||||
|
def ecrire(self, symbole):
|
||||||
|
"""Écrit un symbole sous la tête de lecture."""
|
||||||
|
# Étendre le ruban si nécessaire
|
||||||
|
while self.position >= len(self.ruban):
|
||||||
|
self.ruban.append(' ')
|
||||||
|
while self.position < 0:
|
||||||
|
self.ruban.insert(0, ' ')
|
||||||
|
self.position += 1
|
||||||
|
self.ruban[self.position] = symbole
|
||||||
|
|
||||||
|
def gauche(self):
|
||||||
|
"""Déplace la tête vers la gauche."""
|
||||||
|
self.position -= 1
|
||||||
|
|
||||||
|
def droite(self):
|
||||||
|
"""Déplace la tête vers la droite."""
|
||||||
|
self.position += 1
|
||||||
|
|
||||||
|
def afficher(self):
|
||||||
|
"""Affiche l'état actuel du ruban."""
|
||||||
|
ruban_str = ''.join(self.ruban)
|
||||||
|
curseur = ' ' * self.position + 'V'
|
||||||
|
print(f"État: {self.etat}")
|
||||||
|
print(curseur)
|
||||||
|
print(ruban_str)
|
||||||
|
print()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice : Implémenter l'algorithme d'addition
|
||||||
|
|
||||||
|
Complétez la méthode `ajouter_un` qui implémente l'algorithme d'addition de 1 :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def ajouter_un(self):
|
||||||
|
"""
|
||||||
|
Ajoute 1 au nombre binaire sur le ruban.
|
||||||
|
Algorithme :
|
||||||
|
1. Aller à droite jusqu'à une case vide
|
||||||
|
2. Revenir à gauche et appliquer la règle d'addition avec retenue
|
||||||
|
"""
|
||||||
|
# Étape 1 : Aller à droite jusqu'à une case vide
|
||||||
|
while self.lire() != ' ':
|
||||||
|
self.droite()
|
||||||
|
|
||||||
|
# Étape 2 : Revenir à gauche et ajouter 1
|
||||||
|
self.gauche()
|
||||||
|
|
||||||
|
# À compléter : implémenter la logique d'addition avec retenue
|
||||||
|
# Tant qu'on a une retenue à propager...
|
||||||
|
|
||||||
|
pass # Remplacez par votre code
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Créer une machine avec le nombre binaire 101 (= 5)
|
||||||
|
machine = MachineDeTuring("101")
|
||||||
|
machine.afficher()
|
||||||
|
|
||||||
|
# Ajouter 1
|
||||||
|
machine.ajouter_un()
|
||||||
|
machine.afficher()
|
||||||
|
|
||||||
|
# Résultat attendu : 110 (= 6)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 6 : Réflexion finale
|
||||||
|
|
||||||
|
### Questions de synthèse
|
||||||
|
|
||||||
|
1. **Lien entre les paradoxes** : Quel point commun voyez-vous entre le paradoxe du menteur, le paradoxe du barbier et le problème de l'arrêt ?
|
||||||
|
|
||||||
|
2. **Auto-référence** : Qu'est-ce que l'auto-référence ? Pourquoi pose-t-elle problème ?
|
||||||
|
|
||||||
|
3. **Limites des machines** : Si le problème de l'arrêt est indécidable, cela signifie-t-il que les ordinateurs sont "stupides" ? Justifiez.
|
||||||
|
|
||||||
|
4. **IA et indécidabilité** : Une intelligence artificielle, aussi avancée soit-elle, peut-elle résoudre le problème de l'arrêt ? Pourquoi ?
|
||||||
|
|
||||||
|
### Débat : Les limites de l'IA
|
||||||
|
|
||||||
|
En 2026, les IA génératives sont partout. Mais elles sont soumises aux mêmes limites fondamentales que n'importe quel programme.
|
||||||
|
|
||||||
|
Discutez en groupe :
|
||||||
|
- Une IA peut-elle savoir si elle va boucler indéfiniment sur une tâche ?
|
||||||
|
- Une IA peut-elle vérifier qu'elle n'a pas de bugs ?
|
||||||
|
- Quelles sont les implications pour la sécurité des systèmes autonomes ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Résumé des notions
|
||||||
|
|
||||||
|
| Concept | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| **Paradoxe** | Situation logique contradictoire, sans solution |
|
||||||
|
| **Auto-référence** | Quand un énoncé parle de lui-même |
|
||||||
|
| **Problème de l'arrêt** | Peut-on décider si un programme s'arrête ? (Non !) |
|
||||||
|
| **Indécidabilité** | Existence de problèmes sans algorithme de résolution |
|
||||||
|
| **Machine de Turing** | Modèle théorique de calcul universel |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bonus : Le théorème d'incomplétude de Gödel
|
||||||
|
|
||||||
|
En 1931, Kurt Gödel a prouvé qu'en mathématiques, il existe des énoncés **vrais mais indémontrables**.
|
||||||
|
|
||||||
|
Plus précisément : dans tout système logique assez puissant pour exprimer l'arithmétique, il existe des propositions qui ne peuvent être ni prouvées, ni réfutées.
|
||||||
|
|
||||||
|
C'est une autre facette de l'indécidabilité : même les mathématiques ont leurs limites !
|
||||||
|
|
||||||
|
Recherchez et expliquez avec vos mots ce que signifie ce théorème.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
## Machine de Turing
|
# Activité : Machine de Turing
|
||||||
|
|
||||||
Cette machine est constituée :
|
Cette machine est constituée :
|
||||||
|
|
||||||
@@ -34,24 +34,40 @@ Réalisez pas à pas l'algorithme ci-dessous :
|
|||||||
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
|
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
|
||||||
| | | | | | | | | | | | 1 | 0 | 1 | | | | | | |
|
| | | | | | | | | | | | 1 | 0 | 1 | | | | | | |
|
||||||
|
|
||||||
*Etape 2* : la dernière case lue est ... donc ...
|
*Étape 2* : La dernière case lue est **1**. On se décale à gauche et on écrit **0** (retenue). On doit continuer car on avait un 1.
|
||||||
|
|
||||||
| | | | | | | | | | | | | | V | | | | | | |
|
| | | | | | | | | | | | | | V | | | | | | |
|
||||||
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
|
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
|
||||||
| | | | | | | | | | | | 1 | 0 | 0 | | | | | | |
|
| | | | | | | | | | | | 1 | 0 | 0 | | | | | | |
|
||||||
|
|
||||||
*Etape 4* : ...
|
*Étape 3* : La case courante est **0**. On écrit **1** et on arrête la propagation de la retenue.
|
||||||
|
|
||||||
| | | | | | | | | | | | | V | | | | | | | |
|
| | | | | | | | | | | | | V | | | | | | | |
|
||||||
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
|
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
|
||||||
| | | | | | | | | | | | 1 | 1 | 0 | | | | | | |
|
| | | | | | | | | | | | 1 | 1 | 0 | | | | | | |
|
||||||
|
|
||||||
*Etape 5* : ...
|
*Étape 4* : On retourne à la position initiale. Le calcul est terminé.
|
||||||
|
|
||||||
| | | | | | | | | | | V | | | | | | | | | |
|
| | | | | | | | | | | V | | | | | | | | | |
|
||||||
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
|
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
|
||||||
| | | | | | | | | | | | 1 | 1 | 0 | | | | | | |
|
| | | | | | | | | | | | 1 | 1 | 0 | | | | | | |
|
||||||
|
|
||||||
Vous trouverez [sur ce site](http://zanotti.univ-tln.fr/turing/turing.php) un simulateur d'une machin de de Turing si vous souhaitez aller plus loin.
|
**Résultat** : 101₂ + 1 = 110₂ (soit 5 + 1 = 6 en décimal)
|
||||||
|
|
||||||
ll faut garder à l'esprit que la machine de Turing est un modèle universel de calcul et qu'elle peut calculer tout ce que n'importe quel ordinateur physique peut calculer (aussi puissant soit-il). Inversement, ce qu'elle ne peut pas calculer ne peut l'être non plus par un ordinateur. Elle résume donc de manière saisissante le concept d'*ordinateur* et constitue un support idéal pour raisonner autour de la notion d'*algorithme* de *calcul* ou de *démonstration*. En terminale, nous étudierons plus en détail le concept de calculabilité.
|
Vous trouverez [sur ce site](http://zanotti.univ-tln.fr/turing/turing.php) un simulateur d'une machine de Turing si vous souhaitez aller plus loin.
|
||||||
|
|
||||||
|
Il faut garder à l'esprit que la machine de Turing est un modèle universel de calcul et qu'elle peut calculer tout ce que n'importe quel ordinateur physique peut calculer (aussi puissant soit-il). Inversement, ce qu'elle ne peut pas calculer ne peut l'être non plus par un ordinateur. Elle résume donc de manière saisissante le concept d'*ordinateur* et constitue un support idéal pour raisonner autour de la notion d'*algorithme*, de *calcul* ou de *démonstration*.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice supplémentaire
|
||||||
|
|
||||||
|
Appliquez le même algorithme pour calculer **111₂ + 1**. Combien d'étapes sont nécessaires ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
212
Graphes/Corrige_Exercices.md
Normal file
212
Graphes/Corrige_Exercices.md
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
# Corrigé des Exercices sur les Graphes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 1 : Réseau social (README.md)
|
||||||
|
|
||||||
|
### Rappel du graphe
|
||||||
|
|
||||||
|
```
|
||||||
|
A --- B
|
||||||
|
/|\ |
|
||||||
|
/ | \ |
|
||||||
|
C | D--+
|
||||||
|
\ | /|
|
||||||
|
\|/ |
|
||||||
|
E--F
|
||||||
|
```
|
||||||
|
|
||||||
|
Avec :
|
||||||
|
- A ami avec B, C, D
|
||||||
|
- B ami avec A, D
|
||||||
|
- C ami avec A, E, D
|
||||||
|
- D ami avec A, B, C, E, F
|
||||||
|
- E ami avec C, D, F
|
||||||
|
- F ami avec E, D
|
||||||
|
|
||||||
|
### 1°) Degré des sommets
|
||||||
|
|
||||||
|
| Sommet | Voisins | Degré |
|
||||||
|
|--------|---------|-------|
|
||||||
|
| A | B, C, D | 3 |
|
||||||
|
| B | A, D | 2 |
|
||||||
|
| C | A, E, D | 3 |
|
||||||
|
| D | A, B, C, E, F | 5 |
|
||||||
|
| E | C, D, F | 3 |
|
||||||
|
| F | E, D | 2 |
|
||||||
|
|
||||||
|
**Vérification** : Somme des degrés = 3+2+3+5+3+2 = 18 = 2 × 9 arêtes ✓
|
||||||
|
|
||||||
|
### 2°) Ordre du graphe
|
||||||
|
|
||||||
|
L'ordre du graphe est **6** (il y a 6 sommets : A, B, C, D, E, F).
|
||||||
|
|
||||||
|
### 3°) Ce graphe est-il complet ?
|
||||||
|
|
||||||
|
**Non**, ce graphe n'est pas complet.
|
||||||
|
|
||||||
|
Dans un graphe complet d'ordre 6, chaque sommet serait de degré 5 (relié à tous les autres).
|
||||||
|
|
||||||
|
Contre-exemples :
|
||||||
|
- A n'est pas relié à E et F
|
||||||
|
- B n'est pas relié à C, E et F
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 2 : Matrice d'adjacence du réseau social
|
||||||
|
|
||||||
|
En numérotant les sommets dans l'ordre alphabétique (A, B, C, D, E, F) :
|
||||||
|
|
||||||
|
| | A | B | C | D | E | F |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| A | 0 | 1 | 1 | 1 | 0 | 0 |
|
||||||
|
| B | 1 | 0 | 0 | 1 | 0 | 0 |
|
||||||
|
| C | 1 | 0 | 0 | 1 | 1 | 0 |
|
||||||
|
| D | 1 | 1 | 1 | 0 | 1 | 1 |
|
||||||
|
| E | 0 | 0 | 1 | 1 | 0 | 1 |
|
||||||
|
| F | 0 | 0 | 0 | 1 | 1 | 0 |
|
||||||
|
|
||||||
|
**En Python :**
|
||||||
|
|
||||||
|
```python
|
||||||
|
M = [
|
||||||
|
[0, 1, 1, 1, 0, 0],
|
||||||
|
[1, 0, 0, 1, 0, 0],
|
||||||
|
[1, 0, 0, 1, 1, 0],
|
||||||
|
[1, 1, 1, 0, 1, 1],
|
||||||
|
[0, 0, 1, 1, 0, 1],
|
||||||
|
[0, 0, 0, 1, 1, 0]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Remarque** : La matrice est symétrique car le graphe est non orienté.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 3 : Réseau social d'Arthur (EXERCICES.md)
|
||||||
|
|
||||||
|
### Graphe
|
||||||
|
|
||||||
|
```
|
||||||
|
Arthur ---- Benoit ---- Coralie
|
||||||
|
| / |
|
||||||
|
| / |
|
||||||
|
Elodie ---- Franck ---- David
|
||||||
|
```
|
||||||
|
|
||||||
|
### Représentation par dictionnaire
|
||||||
|
|
||||||
|
```python
|
||||||
|
G = {
|
||||||
|
'Arthur': ['Benoit', 'Elodie'],
|
||||||
|
'Benoit': ['Arthur', 'Coralie'],
|
||||||
|
'Coralie': ['Benoit', 'Franck', 'David'],
|
||||||
|
'David': ['Coralie', 'Franck', 'Elodie'],
|
||||||
|
'Elodie': ['Arthur', 'David', 'Franck'],
|
||||||
|
'Franck': ['Coralie', 'David', 'Elodie']
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Matrice d'adjacence
|
||||||
|
|
||||||
|
En ordre alphabétique : Arthur, Benoit, Coralie, David, Elodie, Franck
|
||||||
|
|
||||||
|
| | Arthur | Benoit | Coralie | David | Elodie | Franck |
|
||||||
|
|---------|--------|--------|---------|-------|--------|--------|
|
||||||
|
| Arthur | 0 | 1 | 0 | 0 | 1 | 0 |
|
||||||
|
| Benoit | 1 | 0 | 1 | 0 | 0 | 0 |
|
||||||
|
| Coralie | 0 | 1 | 0 | 1 | 0 | 1 |
|
||||||
|
| David | 0 | 0 | 1 | 0 | 1 | 1 |
|
||||||
|
| Elodie | 1 | 0 | 0 | 1 | 0 | 1 |
|
||||||
|
| Franck | 0 | 0 | 1 | 1 | 1 | 0 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 4 : Village d'Eva (graphe orienté pondéré)
|
||||||
|
|
||||||
|
### Graphe
|
||||||
|
|
||||||
|
```
|
||||||
|
École
|
||||||
|
/ | \
|
||||||
|
3↓ 4↑ 6↕
|
||||||
|
/ | \
|
||||||
|
Boulangerie ←─── Mairie Salle des fêtes
|
||||||
|
↓ ↕2 ↕7 ↕5
|
||||||
|
4↓ Bureau Église Boucherie
|
||||||
|
↓ de poste
|
||||||
|
Boucherie
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sommets** : Boulangerie, Bureau de poste, École, Boucherie, Église, Mairie, Salle des fêtes
|
||||||
|
|
||||||
|
**Arêtes orientées avec poids** :
|
||||||
|
- Boulangerie ↔ Bureau de poste : 2 min (double sens)
|
||||||
|
- École → Boulangerie : 3 min (sens unique)
|
||||||
|
- Boulangerie → Boucherie : 4 min (sens unique)
|
||||||
|
- École → Église : 3 min (sens unique)
|
||||||
|
- Mairie → École : 4 min (sens unique)
|
||||||
|
- École ↔ Salle des fêtes : 6 min (double sens)
|
||||||
|
- Boucherie ↔ Salle des fêtes : 5 min (double sens)
|
||||||
|
- Mairie ↔ Église : 7 min (double sens)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 5 : Ordre et degrés (img2.PNG)
|
||||||
|
|
||||||
|
*(Réponse basée sur l'image du fichier)*
|
||||||
|
|
||||||
|
Pour déterminer l'ordre : compter le nombre de sommets.
|
||||||
|
|
||||||
|
Pour déterminer le degré de chaque sommet : compter le nombre d'arêtes incidentes.
|
||||||
|
|
||||||
|
**Méthode générale :**
|
||||||
|
```python
|
||||||
|
def ordre(graphe):
|
||||||
|
return len(graphe)
|
||||||
|
|
||||||
|
def degre(graphe, sommet):
|
||||||
|
return len(graphe[sommet])
|
||||||
|
|
||||||
|
def degres(graphe):
|
||||||
|
return {s: len(v) for s, v in graphe.items()}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 6 : Dictionnaire et matrice (img3.PNG)
|
||||||
|
|
||||||
|
*(Réponse basée sur l'image du fichier)*
|
||||||
|
|
||||||
|
**Méthode pour créer le dictionnaire :**
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Lire les sommets et leurs voisins depuis le graphe
|
||||||
|
G = {}
|
||||||
|
# Pour chaque sommet, lister ses voisins
|
||||||
|
# G['A'] = ['B', 'C', ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Méthode pour créer la matrice d'adjacence :**
|
||||||
|
|
||||||
|
```python
|
||||||
|
def creer_matrice(graphe):
|
||||||
|
sommets = list(graphe.keys())
|
||||||
|
n = len(sommets)
|
||||||
|
matrice = [[0] * n for _ in range(n)]
|
||||||
|
|
||||||
|
for i, s1 in enumerate(sommets):
|
||||||
|
for j, s2 in enumerate(sommets):
|
||||||
|
if s2 in graphe[s1]:
|
||||||
|
matrice[i][j] = 1
|
||||||
|
|
||||||
|
return matrice
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -6,14 +6,14 @@ Construire un graphe non orienté du réseau social à partir des informations s
|
|||||||
- **Benoit** est ami avec **Arthur** et **Coralie** ;
|
- **Benoit** est ami avec **Arthur** et **Coralie** ;
|
||||||
- **Coralie** est amie avec **Benoit**, **Franck** et **David** ;
|
- **Coralie** est amie avec **Benoit**, **Franck** et **David** ;
|
||||||
- **David** est ami avec **Coralie**, **Franck** et **Elodie** ;
|
- **David** est ami avec **Coralie**, **Franck** et **Elodie** ;
|
||||||
- **Elodie** est ami avec **Arthur**, **David** et **Franck** ;
|
- **Elodie** est amie avec **Arthur**, **David** et **Franck** ;
|
||||||
- **Franck** est ami avec **Coralie**, **David** et **Elodie**.
|
- **Franck** est ami avec **Coralie**, **David** et **Elodie**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Eva décide de faire un graphe orienté représentant les différentes ruelles de son village. On y trouve une boulangerie, une école, un bureau de poste, une boucherie, une mairie, une église et une salle des fêtes. Certaines ruelles sont à double sens et d'autres à sens unique.
|
Eva décide de faire un graphe orienté représentant les différentes ruelles de son village. On y trouve une boulangerie, une école, un bureau de poste, une boucherie, une mairie, une église et une salle des fêtes. Certaines ruelles sont à double sens et d'autres à sens unique.
|
||||||
|
|
||||||
Eva décide de donner pour chacune des arêtes de son graphe une valeur qui correpond au temps qu'elle met pour traverser la ruelle à pied (chaque arête représente un sens de circulation).
|
Eva décide de donner pour chacune des arêtes de son graphe une valeur qui correspond au temps qu'elle met pour traverser la ruelle à pied (chaque arête représente un sens de circulation).
|
||||||
|
|
||||||
Voici ses données :
|
Voici ses données :
|
||||||
|
|
||||||
@@ -40,3 +40,10 @@ Voici ses données :
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
## Graphes et POO
|
# Graphes et POO
|
||||||
|
|
||||||
> Tout comme les arbres, il nous est possible de représenter les graphes sous la forme d'une classe Python.
|
> Tout comme les arbres, il nous est possible de représenter les graphes sous la forme d'une classe Python.
|
||||||
|
|
||||||
@@ -68,3 +68,11 @@ class Graphe:
|
|||||||
|
|
||||||
|
|
||||||
Merci à [Gilles Lassus](https://glassus.github.io/terminale_nsi/T1_Structures_de_donnees/1.4_Graphes/cours/#3-creation-dune-classe-graphe) pour la source.
|
Merci à [Gilles Lassus](https://glassus.github.io/terminale_nsi/T1_Structures_de_donnees/1.4_Graphes/cours/#3-creation-dune-classe-graphe) pour la source.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
## Les Graphes
|
# Les Graphes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
>
|
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
### Le programme
|
### Le programme
|
||||||
@@ -12,7 +10,7 @@
|
|||||||
|
|
||||||
---------
|
---------
|
||||||
|
|
||||||
### Qu’est-qu’un graphe?
|
### Qu'est-ce qu'un graphe ?
|
||||||
Imaginez un réseau social ayant 6 abonnés (A, B, C, D, E et F) où :
|
Imaginez un réseau social ayant 6 abonnés (A, B, C, D, E et F) où :
|
||||||
* A est ami avec B, C et D
|
* A est ami avec B, C et D
|
||||||
* B est ami avec A et D
|
* B est ami avec A et D
|
||||||
@@ -24,7 +22,7 @@ Imaginez un réseau social ayant 6 abonnés (A, B, C, D, E et F) où :
|
|||||||
|
|
||||||
On peut représenter ce réseau social par un schéma où :
|
On peut représenter ce réseau social par un schéma où :
|
||||||
* Chaque abonné est représenté par un cercle avec son nom.
|
* Chaque abonné est représenté par un cercle avec son nom.
|
||||||
* Chaque relation "X est ami avec Y" par un segment de droite reliant X et Y ("X est amiavec Y" et "Y est ami avec X" étant représenté par le même segment de droite).
|
* Chaque relation "X est ami avec Y" par un segment de droite reliant X et Y ("X est ami avec Y" et "Y est ami avec X" étant représenté par le même segment de droite).
|
||||||
|
|
||||||
Voici ce que cela donne avec le réseau social décrit ci-dessus :
|
Voici ce que cela donne avec le réseau social décrit ci-dessus :
|
||||||
|
|
||||||
@@ -34,13 +32,13 @@ Voici ce que cela donne avec le réseau social décrit ci-dessus :
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
Ce genre de figure s’appelle un graphe. Les graphes sont des objets mathématiquestrès utilisés, notamment en informatique.Les cercles sont appelés des sommets et les segments de droites des arêtes.
|
Ce genre de figure s'appelle un graphe. Les graphes sont des objets mathématiques très utilisés, notamment en informatique. Les cercles sont appelés des **sommets** et les segments de droites des **arêtes**.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Définitions et terminologie
|
### Définitions et terminologie
|
||||||
|
|
||||||
On appelle **graphe** un ensemble de points appelés **sommets** associés à un ensemble de lignes appelées **arrêtes** qui relient certains sommets entre eux.
|
On appelle **graphe** un ensemble de points appelés **sommets** associés à un ensemble de lignes appelées **arêtes** qui relient certains sommets entre eux.
|
||||||
|
|
||||||
**Ordre d’un graphe :**
|
**Ordre d’un graphe :**
|
||||||
L’ordre d’un graphe est le nombre de sommets du graphe.
|
L’ordre d’un graphe est le nombre de sommets du graphe.
|
||||||
@@ -103,7 +101,7 @@ Chaque sommet est de degré 3
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
Un graphe paut être **orienté** ou **non-orienté**.
|
Un graphe peut être **orienté** ou **non-orienté**.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -193,7 +191,7 @@ Ecrire la matrice d'adjacence du réseau social de l'introduction
|
|||||||
### Chaine et cycle
|
### Chaine et cycle
|
||||||
|
|
||||||
**Définition:**
|
**Définition:**
|
||||||
On appelle **chaîne** toute succession d’arêtes dont l’extrémité de l’une (sauf la dernière) estl’origine de la suivante.
|
On appelle **chaîne** toute succession d'arêtes dont l'extrémité de l'une (sauf la dernière) est l'origine de la suivante.
|
||||||
|
|
||||||
* Le nombre d’arêtes qui composent une chaîne est appelé **longueur de la chaîne**.
|
* Le nombre d’arêtes qui composent une chaîne est appelé **longueur de la chaîne**.
|
||||||
* On appelle **chaîne fermée** toute chaîne dont l’origine et l’extrémité coïncident.
|
* On appelle **chaîne fermée** toute chaîne dont l’origine et l’extrémité coïncident.
|
||||||
@@ -201,7 +199,7 @@ On appelle **chaîne** toute succession d’arêtes dont l’extrémité de l’
|
|||||||
|
|
||||||
**Exemple:**
|
**Exemple:**
|
||||||
Dans le graphe ci-dessous:
|
Dans le graphe ci-dessous:
|
||||||
E-A-C-B est un chaîne de longueur 3.
|
E-A-C-B est une chaîne de longueur 3.
|
||||||
|
|
||||||
E-A-C-B-A-E est une chaîne fermée de longueur 5. Ce n’est pas un cycle car l’arête A-E est parcourue deux fois.
|
E-A-C-B-A-E est une chaîne fermée de longueur 5. Ce n’est pas un cycle car l’arête A-E est parcourue deux fois.
|
||||||
|
|
||||||
@@ -209,5 +207,34 @@ D-B-A-C-D est un cycle de longueur 4.
|
|||||||
|
|
||||||
<img src="assets/graphe_8.png" alt="graphe_8" style="zoom:67%;" />
|
<img src="assets/graphe_8.png" alt="graphe_8" style="zoom:67%;" />
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pour aller plus loin
|
||||||
|
|
||||||
|
Les graphes sont une structure fondamentale en informatique. Dans les cours suivants, nous verrons :
|
||||||
|
|
||||||
|
- **Les parcours de graphes** (BFS et DFS) → voir [PARCOURS.md](PARCOURS.md)
|
||||||
|
- **L'implémentation en POO** → voir [POO.md](POO.md)
|
||||||
|
- **Les algorithmes de plus court chemin** (Dijkstra, Bellman-Ford) → voir le chapitre Routage
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Résumé
|
||||||
|
|
||||||
|
| Notion | Définition |
|
||||||
|
|--------|------------|
|
||||||
|
| **Graphe** | Ensemble de sommets reliés par des arêtes |
|
||||||
|
| **Ordre** | Nombre de sommets |
|
||||||
|
| **Degré** | Nombre d'arêtes issues d'un sommet |
|
||||||
|
| **Adjacents** | Deux sommets reliés par une arête |
|
||||||
|
| **Chaîne** | Succession d'arêtes consécutives |
|
||||||
|
| **Cycle** | Chaîne fermée sans répétition d'arêtes |
|
||||||
|
| **Matrice d'adjacence** | Représentation matricielle du graphe |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
|
|||||||
334
Graphes/TP_KevinBacon.md
Normal file
334
Graphes/TP_KevinBacon.md
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
# TP : Les 6 degrés de Kevin Bacon — Le petit monde des réseaux
|
||||||
|
|
||||||
|
> **Thème** : Parcours de graphes et théorie des réseaux sociaux
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
En 2026, les réseaux sociaux connectent des milliards de personnes. TikTok, Instagram, LinkedIn... Mais saviez-vous que vous êtes probablement relié(e) à n'importe quelle célébrité par seulement **6 intermédiaires** ?
|
||||||
|
|
||||||
|
C'est la théorie des **"Six degrés de séparation"**, popularisée par le jeu **"Six Degrees of Kevin Bacon"** : tout acteur d'Hollywood peut être relié à Kevin Bacon en moins de 6 films.
|
||||||
|
|
||||||
|
Dans ce TP, vous allez explorer cette théorie en utilisant les **algorithmes de parcours de graphes**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 1 : Comprendre le problème
|
||||||
|
|
||||||
|
### Le nombre de Bacon
|
||||||
|
|
||||||
|
Le **nombre de Bacon** d'un acteur est la distance minimale (en nombre de films) qui le sépare de Kevin Bacon.
|
||||||
|
|
||||||
|
- Kevin Bacon a un nombre de Bacon de **0**
|
||||||
|
- Un acteur qui a joué avec Kevin Bacon a un nombre de Bacon de **1**
|
||||||
|
- Un acteur qui a joué avec quelqu'un qui a joué avec Kevin Bacon a un nombre de Bacon de **2**
|
||||||
|
- Et ainsi de suite...
|
||||||
|
|
||||||
|
### Exemple
|
||||||
|
|
||||||
|
```
|
||||||
|
Tom Hanks ──[Apollo 13]── Kevin Bacon
|
||||||
|
→ Nombre de Bacon = 1
|
||||||
|
|
||||||
|
Natalie Portman ──[Heat]── Robert De Niro ──[Sleepers]── Kevin Bacon
|
||||||
|
→ Nombre de Bacon = 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Questions préliminaires
|
||||||
|
|
||||||
|
1. Si un acteur A a joué avec un acteur B, quelle est la relation entre eux dans un graphe ?
|
||||||
|
|
||||||
|
2. Quel algorithme de parcours permet de trouver le **plus court chemin** entre deux sommets ?
|
||||||
|
|
||||||
|
3. Pourquoi BFS est-il préférable à DFS pour ce problème ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : Modélisation du problème
|
||||||
|
|
||||||
|
### Le graphe des acteurs
|
||||||
|
|
||||||
|
Nous allons modéliser Hollywood comme un graphe où :
|
||||||
|
- Chaque **sommet** représente un acteur
|
||||||
|
- Chaque **arête** relie deux acteurs qui ont joué dans le même film
|
||||||
|
|
||||||
|
### Mini-base de données
|
||||||
|
|
||||||
|
Voici une petite base de données de films :
|
||||||
|
|
||||||
|
```python
|
||||||
|
films = {
|
||||||
|
"Apollo 13": ["Tom Hanks", "Kevin Bacon", "Bill Paxton"],
|
||||||
|
"Forrest Gump": ["Tom Hanks", "Robin Wright", "Gary Sinise"],
|
||||||
|
"The Dark Knight": ["Christian Bale", "Heath Ledger", "Gary Oldman"],
|
||||||
|
"Inception": ["Leonardo DiCaprio", "Tom Hardy", "Marion Cotillard"],
|
||||||
|
"Interstellar": ["Matthew McConaughey", "Anne Hathaway", "Jessica Chastain"],
|
||||||
|
"Mystic River": ["Sean Penn", "Kevin Bacon", "Tim Robbins"],
|
||||||
|
"X-Men First Class": ["James McAvoy", "Michael Fassbender", "Kevin Bacon"],
|
||||||
|
"The Shawshank Redemption": ["Tim Robbins", "Morgan Freeman"],
|
||||||
|
"Se7en": ["Morgan Freeman", "Brad Pitt", "Kevin Spacey"],
|
||||||
|
"Fight Club": ["Brad Pitt", "Edward Norton", "Helena Bonham Carter"],
|
||||||
|
"Harry Potter": ["Daniel Radcliffe", "Emma Watson", "Gary Oldman"],
|
||||||
|
"Batman Begins": ["Christian Bale", "Liam Neeson", "Gary Oldman"],
|
||||||
|
"Taken": ["Liam Neeson", "Maggie Grace"],
|
||||||
|
"Lost in Translation": ["Bill Murray", "Scarlett Johansson"],
|
||||||
|
"The Avengers": ["Scarlett Johansson", "Robert Downey Jr", "Chris Evans"],
|
||||||
|
"Captain America": ["Chris Evans", "Sebastian Stan", "Robert Downey Jr"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice 1 : Construire le graphe
|
||||||
|
|
||||||
|
Complétez la fonction suivante qui construit le graphe des acteurs à partir de la base de films :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def construire_graphe_acteurs(films):
|
||||||
|
"""
|
||||||
|
Construit un graphe où chaque acteur est un sommet
|
||||||
|
et deux acteurs sont reliés s'ils ont joué dans le même film.
|
||||||
|
|
||||||
|
Paramètre:
|
||||||
|
films (dict): dictionnaire {nom_film: [liste_acteurs]}
|
||||||
|
|
||||||
|
Retourne:
|
||||||
|
dict: graphe sous forme de dictionnaire d'adjacence
|
||||||
|
"""
|
||||||
|
graphe = {}
|
||||||
|
|
||||||
|
for film, acteurs in films.items():
|
||||||
|
# Pour chaque paire d'acteurs dans le film, créer une arête
|
||||||
|
# À compléter...
|
||||||
|
pass
|
||||||
|
|
||||||
|
return graphe
|
||||||
|
```
|
||||||
|
|
||||||
|
**Indice** : Utilisez `itertools.combinations(acteurs, 2)` pour obtenir toutes les paires d'acteurs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : Algorithme BFS pour le nombre de Bacon
|
||||||
|
|
||||||
|
### Rappel : Parcours en largeur (BFS)
|
||||||
|
|
||||||
|
Le BFS explore le graphe **niveau par niveau**. Il est idéal pour trouver le plus court chemin dans un graphe non pondéré.
|
||||||
|
|
||||||
|
### Exercice 2 : Implémenter le calcul du nombre de Bacon
|
||||||
|
|
||||||
|
Complétez la fonction suivante :
|
||||||
|
|
||||||
|
```python
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
def nombre_de_bacon(graphe, acteur, bacon="Kevin Bacon"):
|
||||||
|
"""
|
||||||
|
Calcule le nombre de Bacon d'un acteur.
|
||||||
|
|
||||||
|
Paramètres:
|
||||||
|
graphe (dict): graphe des acteurs
|
||||||
|
acteur (str): nom de l'acteur
|
||||||
|
bacon (str): nom de la référence (Kevin Bacon par défaut)
|
||||||
|
|
||||||
|
Retourne:
|
||||||
|
int: le nombre de Bacon, ou -1 si non connecté
|
||||||
|
"""
|
||||||
|
if acteur == bacon:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if acteur not in graphe or bacon not in graphe:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
# BFS
|
||||||
|
file = deque([(bacon, 0)]) # (sommet, distance)
|
||||||
|
visite = {bacon}
|
||||||
|
|
||||||
|
while file:
|
||||||
|
# À compléter...
|
||||||
|
pass
|
||||||
|
|
||||||
|
return -1 # Non trouvé
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice 3 : Trouver le chemin
|
||||||
|
|
||||||
|
Modifiez l'algorithme pour retourner non seulement la distance, mais aussi le **chemin** (la liste des acteurs intermédiaires) :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def chemin_vers_bacon(graphe, acteur, bacon="Kevin Bacon"):
|
||||||
|
"""
|
||||||
|
Trouve le plus court chemin entre un acteur et Kevin Bacon.
|
||||||
|
|
||||||
|
Retourne:
|
||||||
|
list: liste des acteurs formant le chemin, ou None si non connecté
|
||||||
|
"""
|
||||||
|
# À compléter...
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : Analyse du réseau
|
||||||
|
|
||||||
|
### Exercice 4 : Statistiques globales
|
||||||
|
|
||||||
|
Écrivez des fonctions pour calculer les statistiques suivantes :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def nombre_bacon_moyen(graphe, bacon="Kevin Bacon"):
|
||||||
|
"""Calcule le nombre de Bacon moyen de tous les acteurs."""
|
||||||
|
# À compléter...
|
||||||
|
pass
|
||||||
|
|
||||||
|
def acteur_le_plus_eloigne(graphe, bacon="Kevin Bacon"):
|
||||||
|
"""Trouve l'acteur avec le plus grand nombre de Bacon."""
|
||||||
|
# À compléter...
|
||||||
|
pass
|
||||||
|
|
||||||
|
def acteurs_non_connectes(graphe, bacon="Kevin Bacon"):
|
||||||
|
"""Liste les acteurs qui ne sont pas connectés à Kevin Bacon."""
|
||||||
|
# À compléter...
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice 5 : Visualisation
|
||||||
|
|
||||||
|
Utilisez la bibliothèque `networkx` pour visualiser le graphe :
|
||||||
|
|
||||||
|
```python
|
||||||
|
import networkx as nx
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
def visualiser_graphe(graphe, acteur_central="Kevin Bacon"):
|
||||||
|
"""
|
||||||
|
Affiche le graphe avec des couleurs selon la distance à l'acteur central.
|
||||||
|
"""
|
||||||
|
G = nx.Graph(graphe)
|
||||||
|
|
||||||
|
# Calculer les distances
|
||||||
|
distances = nx.single_source_shortest_path_length(G, acteur_central)
|
||||||
|
|
||||||
|
# Couleurs selon la distance
|
||||||
|
couleurs = [distances.get(node, -1) for node in G.nodes()]
|
||||||
|
|
||||||
|
# Affichage
|
||||||
|
plt.figure(figsize=(12, 8))
|
||||||
|
pos = nx.spring_layout(G, seed=42)
|
||||||
|
nx.draw(G, pos, with_labels=True, node_color=couleurs,
|
||||||
|
cmap=plt.cm.RdYlGn_r, node_size=500, font_size=8)
|
||||||
|
plt.title(f"Graphe des acteurs (distance à {acteur_central})")
|
||||||
|
plt.show()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 5 : Extension — Votre propre réseau
|
||||||
|
|
||||||
|
### Exercice 6 : Réseau social de la classe
|
||||||
|
|
||||||
|
Créez un graphe représentant les relations d'amitié dans votre classe (ou un réseau fictif) et calculez :
|
||||||
|
|
||||||
|
1. Qui est le "Kevin Bacon" de la classe (la personne la plus centrale) ?
|
||||||
|
2. Quel est le diamètre du graphe (la plus grande distance entre deux personnes) ?
|
||||||
|
3. Y a-t-il des personnes isolées ?
|
||||||
|
|
||||||
|
```python
|
||||||
|
def centralite(graphe):
|
||||||
|
"""
|
||||||
|
Trouve la personne la plus centrale du graphe
|
||||||
|
(celle qui minimise la somme des distances aux autres).
|
||||||
|
"""
|
||||||
|
# À compléter...
|
||||||
|
pass
|
||||||
|
|
||||||
|
def diametre(graphe):
|
||||||
|
"""
|
||||||
|
Calcule le diamètre du graphe
|
||||||
|
(la plus grande distance entre deux sommets connectés).
|
||||||
|
"""
|
||||||
|
# À compléter...
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 6 : Réflexion
|
||||||
|
|
||||||
|
### Questions de synthèse
|
||||||
|
|
||||||
|
1. **Six degrés de séparation** : Pourquoi pensez-vous que le nombre 6 revient souvent dans les réseaux sociaux réels ?
|
||||||
|
|
||||||
|
2. **Croissance des connexions** : Si chaque personne connaît en moyenne 100 personnes, combien de personnes peut-on atteindre en 2, 3, puis 6 étapes ? (Ordre de grandeur)
|
||||||
|
|
||||||
|
3. **Limites du modèle** : Notre graphe est non pondéré. Comment pourrait-on le modifier pour tenir compte de la "force" des liens (amis proches vs simples connaissances) ?
|
||||||
|
|
||||||
|
4. **Application réelle** : Citez une application concrète de ces algorithmes dans un réseau social moderne (LinkedIn, Facebook, etc.).
|
||||||
|
|
||||||
|
### Débat : La centralité du pouvoir
|
||||||
|
|
||||||
|
Dans un réseau social, les personnes très centrales ont un "pouvoir" particulier : elles peuvent diffuser rapidement une information à tout le réseau.
|
||||||
|
|
||||||
|
- Est-ce un avantage ou un danger pour la société ?
|
||||||
|
- Comment les plateformes comme TikTok ou Instagram utilisent-elles cette notion de centralité ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bonus : Oracle de Bacon en ligne
|
||||||
|
|
||||||
|
Le site [The Oracle of Bacon](https://oracleofbacon.org/) permet de calculer le nombre de Bacon de n'importe quel acteur dans une base de données de millions de films.
|
||||||
|
|
||||||
|
Testez avec vos acteurs préférés !
|
||||||
|
|
||||||
|
**Question bonus** : L'acteur moyen a un nombre de Bacon d'environ 2.9. Qu'est-ce que cela nous dit sur la structure du réseau des acteurs d'Hollywood ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Résumé des notions
|
||||||
|
|
||||||
|
| Concept | Application dans ce TP |
|
||||||
|
|---------|------------------------|
|
||||||
|
| Graphe non orienté | Réseau d'acteurs |
|
||||||
|
| Sommet | Un acteur |
|
||||||
|
| Arête | Deux acteurs ont joué ensemble |
|
||||||
|
| BFS | Calcul du plus court chemin |
|
||||||
|
| Distance | Nombre de Bacon |
|
||||||
|
| Centralité | L'acteur le plus "connecté" |
|
||||||
|
| Diamètre | La plus grande distance dans le graphe |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Annexe : Solution de l'exercice 1
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Cliquez pour afficher</summary>
|
||||||
|
|
||||||
|
```python
|
||||||
|
from itertools import combinations
|
||||||
|
|
||||||
|
def construire_graphe_acteurs(films):
|
||||||
|
graphe = {}
|
||||||
|
|
||||||
|
for film, acteurs in films.items():
|
||||||
|
# Créer les sommets si nécessaire
|
||||||
|
for acteur in acteurs:
|
||||||
|
if acteur not in graphe:
|
||||||
|
graphe[acteur] = set()
|
||||||
|
|
||||||
|
# Créer les arêtes (toutes les paires d'acteurs)
|
||||||
|
for a1, a2 in combinations(acteurs, 2):
|
||||||
|
graphe[a1].add(a2)
|
||||||
|
graphe[a2].add(a1)
|
||||||
|
|
||||||
|
# Convertir les sets en listes
|
||||||
|
return {k: list(v) for k, v in graphe.items()}
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
306
Modularité/Corrige_TP.md
Normal file
306
Modularité/Corrige_TP.md
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
# Corrigé du TP Modularité
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 1 : Lancer de dé
|
||||||
|
|
||||||
|
### Question 0 : Création du module
|
||||||
|
|
||||||
|
Créer un fichier `de.py`.
|
||||||
|
|
||||||
|
### Question 1 : Fonction de base
|
||||||
|
|
||||||
|
```python
|
||||||
|
# de.py
|
||||||
|
import random
|
||||||
|
|
||||||
|
def de():
|
||||||
|
"""
|
||||||
|
Simule un lancer de dé à 6 faces.
|
||||||
|
|
||||||
|
:return: (int) un entier entre 1 et 6
|
||||||
|
"""
|
||||||
|
return random.randint(1, 6)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Question 2 : Fonction paramétrable
|
||||||
|
|
||||||
|
```python
|
||||||
|
def de(n=6):
|
||||||
|
"""
|
||||||
|
Simule un lancer de dé à n faces.
|
||||||
|
|
||||||
|
:param n: (int) le nombre de faces du dé (6 par défaut)
|
||||||
|
:return: (int) un entier entre 1 et n
|
||||||
|
"""
|
||||||
|
return random.randint(1, n)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Question 3 : Docstring et doctest complets
|
||||||
|
|
||||||
|
```python
|
||||||
|
# de.py
|
||||||
|
import random
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
def de(n=6):
|
||||||
|
"""
|
||||||
|
Simule un lancer de dé à n faces.
|
||||||
|
|
||||||
|
:param n: (int) le nombre de faces du dé (6 par défaut)
|
||||||
|
:return: (int) un entier entre 1 et n
|
||||||
|
:CU: n >= 1
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> de() in [1, 2, 3, 4, 5, 6]
|
||||||
|
True
|
||||||
|
>>> de(12) in range(1, 13)
|
||||||
|
True
|
||||||
|
>>> de(1)
|
||||||
|
1
|
||||||
|
"""
|
||||||
|
return random.randint(1, n)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
doctest.testmod(verbose=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : Année bissextile
|
||||||
|
|
||||||
|
### Question 4 : Création du module
|
||||||
|
|
||||||
|
Créer un fichier `bissextile.py`.
|
||||||
|
|
||||||
|
### Question 5 : Fonction de saisie
|
||||||
|
|
||||||
|
```python
|
||||||
|
def saisir_annee():
|
||||||
|
"""
|
||||||
|
Demande à l'utilisateur de saisir une année.
|
||||||
|
|
||||||
|
:return: (int) l'année saisie par l'utilisateur
|
||||||
|
"""
|
||||||
|
annee = input("Entrez une année : ")
|
||||||
|
return int(annee)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Question 6 : Fonction de vérification
|
||||||
|
|
||||||
|
Une année est bissextile si :
|
||||||
|
- Elle est divisible par 4
|
||||||
|
- MAIS pas par 100
|
||||||
|
- SAUF si elle est divisible par 400
|
||||||
|
|
||||||
|
```python
|
||||||
|
# bissextile.py
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
def verifie(annee):
|
||||||
|
"""
|
||||||
|
Vérifie si une année est bissextile.
|
||||||
|
|
||||||
|
:param annee: (int) l'année à vérifier
|
||||||
|
:return: (bool) True si bissextile, False sinon
|
||||||
|
:CU: annee > 0
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> verifie(2000)
|
||||||
|
True
|
||||||
|
>>> verifie(2022)
|
||||||
|
False
|
||||||
|
>>> verifie(1900)
|
||||||
|
False
|
||||||
|
>>> verifie(2024)
|
||||||
|
True
|
||||||
|
>>> verifie(2100)
|
||||||
|
False
|
||||||
|
"""
|
||||||
|
if annee % 400 == 0:
|
||||||
|
return True
|
||||||
|
if annee % 100 == 0:
|
||||||
|
return False
|
||||||
|
if annee % 4 == 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
doctest.testmod(verbose=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : Fusion de modules
|
||||||
|
|
||||||
|
### Questions 7-8 : Module fusion
|
||||||
|
|
||||||
|
```python
|
||||||
|
# fusion.py
|
||||||
|
import doctest
|
||||||
|
from de import de
|
||||||
|
from bissextile import verifie
|
||||||
|
|
||||||
|
def avance(dep, pas, arr):
|
||||||
|
"""
|
||||||
|
Avance d'année en année selon le pas en paramètre et en partant
|
||||||
|
de l'année de départ jusqu'à l'arrivée et renvoie le nombre
|
||||||
|
d'années bissextiles rencontrées.
|
||||||
|
|
||||||
|
:param dep: (int) l'année de départ
|
||||||
|
:param pas: (int) le pas
|
||||||
|
:param arr: (int) l'année d'arrivée
|
||||||
|
:return: (int) le nombre d'années bissextiles rencontrées
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> avance(2020, 2, 2024)
|
||||||
|
2
|
||||||
|
>>> avance(2018, 4, 2024)
|
||||||
|
0
|
||||||
|
>>> avance(2000, 1, 2010)
|
||||||
|
3
|
||||||
|
"""
|
||||||
|
compteur = 0
|
||||||
|
annee = dep
|
||||||
|
while annee < arr:
|
||||||
|
if verifie(annee):
|
||||||
|
compteur += 1
|
||||||
|
annee += pas
|
||||||
|
return compteur
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
doctest.testmod(verbose=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Explication de l'exemple `avance(2018, 4, 2024)` → 0 :**
|
||||||
|
- On part de 2018, on avance de 4 en 4 : 2018, 2022
|
||||||
|
- 2018 n'est pas bissextile, 2022 non plus
|
||||||
|
- On s'arrête avant 2024, donc on ne compte pas 2024
|
||||||
|
- Résultat : 0
|
||||||
|
|
||||||
|
**Note :** La fonction `de` n'est pas utilisée ici. Elle pourrait servir pour une extension où le pas serait aléatoire.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : Documentations
|
||||||
|
|
||||||
|
### Question 9-10 : Docstrings du module arithmetique
|
||||||
|
|
||||||
|
```python
|
||||||
|
# arithmetique.py
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
def expo(a, b, n):
|
||||||
|
"""
|
||||||
|
Calcule le reste de la division par n de a puissance b.
|
||||||
|
Utilise l'exponentiation modulaire pour plus d'efficacité.
|
||||||
|
|
||||||
|
:param a: (int) la base
|
||||||
|
:param b: (int) l'exposant (positif ou nul)
|
||||||
|
:param n: (int) le modulo (strictement positif)
|
||||||
|
:return: (int) a^b mod n
|
||||||
|
:CU: b >= 0 et n > 0
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> expo(2, 10, 1000)
|
||||||
|
24
|
||||||
|
>>> expo(3, 4, 5)
|
||||||
|
1
|
||||||
|
>>> expo(7, 0, 10)
|
||||||
|
1
|
||||||
|
"""
|
||||||
|
return pow(a, b, n)
|
||||||
|
|
||||||
|
|
||||||
|
def pgcd(a, b):
|
||||||
|
"""
|
||||||
|
Calcule le plus grand diviseur commun entre a et b
|
||||||
|
en utilisant l'algorithme d'Euclide.
|
||||||
|
|
||||||
|
:param a: (int) un entier
|
||||||
|
:param b: (int) un entier
|
||||||
|
:return: (int) le PGCD de a et b
|
||||||
|
:CU: a et b ne sont pas tous les deux nuls
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> pgcd(48, 18)
|
||||||
|
6
|
||||||
|
>>> pgcd(17, 13)
|
||||||
|
1
|
||||||
|
>>> pgcd(100, 25)
|
||||||
|
25
|
||||||
|
>>> pgcd(0, 5)
|
||||||
|
5
|
||||||
|
"""
|
||||||
|
a, b = abs(a), abs(b)
|
||||||
|
while b != 0:
|
||||||
|
a, b = b, a % b
|
||||||
|
return a
|
||||||
|
|
||||||
|
|
||||||
|
def decomposition(n):
|
||||||
|
"""
|
||||||
|
Décompose un entier positif en facteurs premiers.
|
||||||
|
Renvoie une liste de tuples (facteur, multiplicité).
|
||||||
|
|
||||||
|
:param n: (int) un entier strictement positif
|
||||||
|
:return: (list) liste de tuples (facteur, multiplicité)
|
||||||
|
:CU: n > 0
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> decomposition(12)
|
||||||
|
[(2, 2), (3, 1)]
|
||||||
|
>>> decomposition(100)
|
||||||
|
[(2, 2), (5, 2)]
|
||||||
|
>>> decomposition(17)
|
||||||
|
[(17, 1)]
|
||||||
|
>>> decomposition(1)
|
||||||
|
[]
|
||||||
|
"""
|
||||||
|
facteurs = []
|
||||||
|
diviseur = 2
|
||||||
|
while diviseur * diviseur <= n:
|
||||||
|
compteur = 0
|
||||||
|
while n % diviseur == 0:
|
||||||
|
compteur += 1
|
||||||
|
n //= diviseur
|
||||||
|
if compteur > 0:
|
||||||
|
facteurs.append((diviseur, compteur))
|
||||||
|
diviseur += 1
|
||||||
|
if n > 1:
|
||||||
|
facteurs.append((n, 1))
|
||||||
|
return facteurs
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
doctest.testmod(verbose=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Structure finale des fichiers
|
||||||
|
|
||||||
|
```
|
||||||
|
Modularité/
|
||||||
|
├── de.py
|
||||||
|
├── bissextile.py
|
||||||
|
├── fusion.py
|
||||||
|
└── arithmetique.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Chaque module peut être testé individuellement avec :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python de.py
|
||||||
|
python bissextile.py
|
||||||
|
python fusion.py
|
||||||
|
python arithmetique.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
## Débug
|
# Débug
|
||||||
|
|
||||||
> Savoir débugger, c'est savoir sauver son code.
|
> Savoir débugger, c'est savoir sauver son code.
|
||||||
> Comprendre pourquoi il ne fonctionne pas, traquer, chercher la petite erreur, c'est un pan entier du métier de développeur
|
> Comprendre pourquoi il ne fonctionne pas, traquer, chercher la petite erreur, c'est un pan entier du métier de développeur.
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
@@ -16,38 +16,37 @@ Les erreurs, également appelées bugs, peuvent se produire à différents nivea
|
|||||||
|
|
||||||
### Types d'erreurs en Python
|
### Types d'erreurs en Python
|
||||||
|
|
||||||
### Erreurs de syntaxe
|
#### Erreurs de syntaxe
|
||||||
|
|
||||||
Ce sont des erreurs dans la structure du code, telles que des parenthèses manquantes ou une mauvaise indentation.
|
Ce sont des erreurs dans la structure du code, telles que des parenthèses manquantes ou une mauvaise indentation.
|
||||||
|
|
||||||
Exemple :
|
Exemple :
|
||||||
|
|
||||||
```python
|
```python
|
||||||
print("Hello, world"
|
print("Hello, world"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Erreurs d'exécution
|
#### Erreurs d'exécution
|
||||||
|
|
||||||
Ces erreurs se produisent lorsque le programme est en cours d'exécution et provoquent l'arrêt du programme.
|
Ces erreurs se produisent lorsque le programme est en cours d'exécution et provoquent l'arrêt du programme.
|
||||||
|
|
||||||
Exemple :
|
Exemple :
|
||||||
|
|
||||||
```python
|
```python
|
||||||
result = 10 / 0
|
result = 10 / 0
|
||||||
print (result)
|
print(result)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Erreurs logiques
|
#### Erreurs logiques
|
||||||
|
|
||||||
Ces erreurs ne provoquent pas l'arrêt du programme, mais produisent des résultats incorrects.
|
Ces erreurs ne provoquent pas l'arrêt du programme, mais produisent des résultats incorrects.
|
||||||
|
|
||||||
Exemple :
|
Exemple :
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def somme(a, b):
|
def somme(a, b):
|
||||||
|
return a - b # L'erreur est ici, il faut utiliser + au lieu de -
|
||||||
return a - b # L'erreur est ici, il faut utiliser + au lieu de -
|
```
|
||||||
```
|
|
||||||
|
|
||||||
-------
|
-------
|
||||||
|
|
||||||
@@ -65,13 +64,11 @@ Ajoutez des instructions `print` pour afficher la valeur des variables à diffé
|
|||||||
|
|
||||||
Exemple :
|
Exemple :
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def somme(a, b):
|
def somme(a, b):
|
||||||
|
print(f"a = {a}, b = {b}")
|
||||||
print(f"a = {a}, b = {b}")
|
return a + b
|
||||||
|
```
|
||||||
return a + b
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -83,15 +80,12 @@ Utilisez `assert` pour vérifier que certaines conditions sont vraies à différ
|
|||||||
|
|
||||||
Exemple :
|
Exemple :
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def somme(a, b):
|
def somme(a, b):
|
||||||
|
assert isinstance(a, int), "a doit être un entier"
|
||||||
assert isinstance(a, int), "a doit être un entier"
|
assert isinstance(b, int), "b doit être un entier"
|
||||||
|
return a + b
|
||||||
assert isinstance(b, int), "b doit être un entier"
|
```
|
||||||
|
|
||||||
return a + b
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -111,29 +105,25 @@ Programme avec une erreur :
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def moyenne(liste):
|
def moyenne(liste):
|
||||||
|
somme = 0
|
||||||
somme = 0
|
for valeur in liste:
|
||||||
|
somme += valeur
|
||||||
for valeur in liste:
|
return somme / len(liste) - 1 # Erreur : pourquoi -1 ?
|
||||||
|
|
||||||
somme += valeur
|
|
||||||
|
|
||||||
return somme / len(liste)
|
|
||||||
|
|
||||||
notes = [15, 18, 12, 9]
|
notes = [15, 18, 12, 9]
|
||||||
|
|
||||||
print("La moyenne des notes est :", moyenne(notes))
|
print("La moyenne des notes est :", moyenne(notes))
|
||||||
|
# Affiche 12.5 au lieu de 13.5 !
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Exercice** : Identifiez l'erreur et corrigez-la en utilisant les techniques vues ci-dessus.
|
||||||
|
|
||||||
--------
|
--------
|
||||||
|
|
||||||
### Anecdote historique
|
### Anecdote historique
|
||||||
|
|
||||||
Le saviez vous ? Contrairement aux croyances, ce n'est pas Grace Hopper qui a inventé le terme ***bug***, car celui ci était déjà utilisé pour décrire des problèmes de radar durant la seconde guerre mondiale.
|
Le saviez vous ? Contrairement aux croyances, ce n'est pas Grace Hopper qui a inventé le terme ***bug***, car celui ci était déjà utilisé pour décrire des problèmes de radar durant la seconde guerre mondiale.
|
||||||
|
|
||||||
Néanmoins, la postérité aura attribué ce mot à la developpeuse, qui aurait trouvé un insecte dans l'ordinateur de type Mark II.
|
Néanmoins, la postérité aura attribué ce mot à la développeuse, qui aurait trouvé un insecte dans l'ordinateur de type Mark II.
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
## Modularité
|
# Modularité
|
||||||
|
|
||||||
### Le programme
|
### Le programme
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ Il faudra donc prendre de bonnes habitudes :
|
|||||||
- grouper les fonctions par fichier
|
- grouper les fonctions par fichier
|
||||||
- regrouper les fonctions dans des classes (que nous verrons plus tard)
|
- regrouper les fonctions dans des classes (que nous verrons plus tard)
|
||||||
|
|
||||||
Interessons nous au code python ci dessous. Ici, la structure compte plus que le code en lui même.
|
Intéressons-nous au code Python ci-dessous. Ici, la structure compte plus que le code en lui même.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
## importations
|
## importations
|
||||||
@@ -35,10 +35,10 @@ VALEUR = 25
|
|||||||
## fonctions
|
## fonctions
|
||||||
def racine(val):
|
def racine(val):
|
||||||
"""
|
"""
|
||||||
Renvoie la racine carré d'un nombre.
|
Renvoie la racine carrée d'un nombre.
|
||||||
|
|
||||||
:param val: (int) un entier
|
:param val: (int) un entier
|
||||||
:return: (float) la racine carré
|
:return: (float) la racine carrée
|
||||||
:CU: type(val) == int && val >= 0
|
:CU: type(val) == int && val >= 0
|
||||||
|
|
||||||
example:
|
example:
|
||||||
@@ -81,7 +81,7 @@ from math import *
|
|||||||
import doctest
|
import doctest
|
||||||
```
|
```
|
||||||
|
|
||||||
Au début du programme, nous avons une partie *import* où l'on ajoute toute les lignes d'importations. Ces dernières permettent de préciser à python que l'on ajoute à notre programme des élément du module **math** ainsi que du module **doctest**.
|
Au début du programme, nous avons une partie *import* où l'on ajoute toutes les lignes d'importations. Ces dernières permettent de préciser à Python que l'on ajoute à notre programme des éléments du module **math** ainsi que du module **doctest**.
|
||||||
|
|
||||||
Chacun de ces modules a ses propres variables, fonctions et autre éléments. Le module math va donc réaliser des opérations mathématiques comme calculer la racine carrée avec la fonction **sqrt** et le module doctest va s'occuper de réaliser des tests unitaires.
|
Chacun de ces modules a ses propres variables, fonctions et autre éléments. Le module math va donc réaliser des opérations mathématiques comme calculer la racine carrée avec la fonction **sqrt** et le module doctest va s'occuper de réaliser des tests unitaires.
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ Un dernier point très important, c'est la documentation avec ses jeux de tests.
|
|||||||
|
|
||||||
Lorsque l'on utilise un module connu comme **math**, **random** ou encore **tkinter**, il est possible de trouver sa documentation en ligne afin de trouver les informations nous permettant de s'en servir correctement.
|
Lorsque l'on utilise un module connu comme **math**, **random** ou encore **tkinter**, il est possible de trouver sa documentation en ligne afin de trouver les informations nous permettant de s'en servir correctement.
|
||||||
|
|
||||||
Il existe aussi une documentation que l'on peut récupérer via les modules grace à la fonction *help* :
|
Il existe aussi une documentation que l'on peut récupérer via les modules grâce à la fonction *help* :
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import random
|
import random
|
||||||
@@ -114,7 +114,7 @@ import random
|
|||||||
help(random)
|
help(random)
|
||||||
```
|
```
|
||||||
|
|
||||||
La documentation dans le module ne sert pas de décoration mais permet à celui ou celle (voir vous même ! ) comprendre les spécifications.
|
La documentation dans le module ne sert pas de décoration mais permet à celui ou celle qui l'utilise (voire vous-même !) de comprendre les spécifications.
|
||||||
|
|
||||||
Voyons un exemple de **docstring** d'une fonction :
|
Voyons un exemple de **docstring** d'une fonction :
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ if __name__=="__main__":
|
|||||||
doctest.testmod(verbose=True)
|
doctest.testmod(verbose=True)
|
||||||
```
|
```
|
||||||
|
|
||||||
Lors de l'exécution du programme principal, la fonction *testmod* du module **doctest** va parcourir toute les documentations à la recherche de ligne commencant par ```>>>```.
|
Lors de l'exécution du programme principal, la fonction *testmod* du module **doctest** va parcourir toutes les documentations à la recherche de lignes commençant par ```>>>```.
|
||||||
|
|
||||||
Une fois les lignes trouvées, testmod va exécuter la ligne en question puis va comparer son résultat avec ce qui a été mis en dessous.
|
Une fois les lignes trouvées, testmod va exécuter la ligne en question puis va comparer son résultat avec ce qui a été mis en dessous.
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Question 0 - Créer un module ```de.py```.
|
Question 0 - Créer un module ```de.py```.
|
||||||
|
|
||||||
Question 1 - Définir une fonction ```de``` qui simule un lancer de dé à 6 faces. On rappel que la fonction *randint* du module **random** permet le renvoie d'une valeur pseudo-aléatoire.
|
Question 1 - Définir une fonction ```de``` qui simule un lancer de dé à 6 faces. On rappelle que la fonction *randint* du module **random** permet le renvoi d'une valeur pseudo-aléatoire.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
>>> de() in [1, 2, 3, 4, 5, 6]
|
>>> de() in [1, 2, 3, 4, 5, 6]
|
||||||
|
|||||||
569
Modularité/TP_MiniPackage.md
Normal file
569
Modularité/TP_MiniPackage.md
Normal file
@@ -0,0 +1,569 @@
|
|||||||
|
# TP : Créer son premier package Python — MathTools
|
||||||
|
|
||||||
|
> **Thème** : Modularité, documentation et tests
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
En 2026, Python reste le langage le plus populaire au monde grâce à son écosystème de packages. Des millions de développeurs partagent leur code via PyPI (Python Package Index).
|
||||||
|
|
||||||
|
Dans ce TP, vous allez créer votre propre **mini-package** : une bibliothèque d'outils mathématiques appelée **MathTools**. Vous apprendrez à :
|
||||||
|
- Structurer un projet en modules
|
||||||
|
- Documenter proprement avec des docstrings
|
||||||
|
- Tester automatiquement avec doctest
|
||||||
|
- Créer un point d'entrée principal
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 1 : Structure du projet
|
||||||
|
|
||||||
|
### Organisation des fichiers
|
||||||
|
|
||||||
|
Créez la structure de dossiers suivante :
|
||||||
|
|
||||||
|
```
|
||||||
|
mathtools/
|
||||||
|
├── __init__.py
|
||||||
|
├── geometrie.py
|
||||||
|
├── statistiques.py
|
||||||
|
├── conversion.py
|
||||||
|
└── main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Le fichier `__init__.py`
|
||||||
|
|
||||||
|
Ce fichier spécial indique à Python que le dossier est un **package**. Il peut rester vide ou contenir des imports.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# mathtools/__init__.py
|
||||||
|
"""
|
||||||
|
MathTools - Une bibliothèque d'outils mathématiques.
|
||||||
|
|
||||||
|
Modules disponibles :
|
||||||
|
- geometrie : calculs géométriques (aire, périmètre, volume)
|
||||||
|
- statistiques : calculs statistiques (moyenne, médiane, écart-type)
|
||||||
|
- conversion : conversions d'unités (température, distance, poids)
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Votre Nom"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : Module géométrie
|
||||||
|
|
||||||
|
### Exercice 1 : Créer le module `geometrie.py`
|
||||||
|
|
||||||
|
Implémentez les fonctions suivantes avec leur **docstring** et **doctest** :
|
||||||
|
|
||||||
|
```python
|
||||||
|
# mathtools/geometrie.py
|
||||||
|
"""Module de calculs géométriques."""
|
||||||
|
|
||||||
|
import math
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
|
||||||
|
def aire_rectangle(longueur, largeur):
|
||||||
|
"""
|
||||||
|
Calcule l'aire d'un rectangle.
|
||||||
|
|
||||||
|
:param longueur: (float) la longueur du rectangle
|
||||||
|
:param largeur: (float) la largeur du rectangle
|
||||||
|
:return: (float) l'aire du rectangle
|
||||||
|
:CU: longueur > 0 et largeur > 0
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> aire_rectangle(5, 3)
|
||||||
|
15
|
||||||
|
>>> aire_rectangle(2.5, 4)
|
||||||
|
10.0
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def aire_cercle(rayon):
|
||||||
|
"""
|
||||||
|
Calcule l'aire d'un cercle.
|
||||||
|
|
||||||
|
:param rayon: (float) le rayon du cercle
|
||||||
|
:return: (float) l'aire du cercle
|
||||||
|
:CU: rayon > 0
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> round(aire_cercle(1), 4)
|
||||||
|
3.1416
|
||||||
|
>>> round(aire_cercle(2), 4)
|
||||||
|
12.5664
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def perimetre_rectangle(longueur, largeur):
|
||||||
|
"""
|
||||||
|
Calcule le périmètre d'un rectangle.
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> perimetre_rectangle(5, 3)
|
||||||
|
16
|
||||||
|
"""
|
||||||
|
# À compléter (ajoutez la docstring complète)
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def volume_sphere(rayon):
|
||||||
|
"""
|
||||||
|
Calcule le volume d'une sphère.
|
||||||
|
Formule : V = (4/3) * π * r³
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> round(volume_sphere(1), 4)
|
||||||
|
4.1888
|
||||||
|
"""
|
||||||
|
# À compléter (ajoutez la docstring complète)
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def hypotenuse(a, b):
|
||||||
|
"""
|
||||||
|
Calcule l'hypoténuse d'un triangle rectangle.
|
||||||
|
Utilise le théorème de Pythagore : c² = a² + b²
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> hypotenuse(3, 4)
|
||||||
|
5.0
|
||||||
|
>>> hypotenuse(5, 12)
|
||||||
|
13.0
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
doctest.testmod(verbose=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : Module statistiques
|
||||||
|
|
||||||
|
### Exercice 2 : Créer le module `statistiques.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
# mathtools/statistiques.py
|
||||||
|
"""Module de calculs statistiques."""
|
||||||
|
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
|
||||||
|
def moyenne(liste):
|
||||||
|
"""
|
||||||
|
Calcule la moyenne d'une liste de nombres.
|
||||||
|
|
||||||
|
:param liste: (list) une liste de nombres
|
||||||
|
:return: (float) la moyenne
|
||||||
|
:CU: liste non vide
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> moyenne([10, 20, 30])
|
||||||
|
20.0
|
||||||
|
>>> moyenne([15, 18, 12, 9])
|
||||||
|
13.5
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def mediane(liste):
|
||||||
|
"""
|
||||||
|
Calcule la médiane d'une liste de nombres.
|
||||||
|
La médiane est la valeur centrale d'une liste triée.
|
||||||
|
|
||||||
|
:param liste: (list) une liste de nombres
|
||||||
|
:return: (float) la médiane
|
||||||
|
:CU: liste non vide
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> mediane([1, 2, 3, 4, 5])
|
||||||
|
3
|
||||||
|
>>> mediane([1, 2, 3, 4])
|
||||||
|
2.5
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def variance(liste):
|
||||||
|
"""
|
||||||
|
Calcule la variance d'une liste de nombres.
|
||||||
|
Variance = moyenne des carrés des écarts à la moyenne.
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> variance([2, 4, 4, 4, 5, 5, 7, 9])
|
||||||
|
4.0
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def ecart_type(liste):
|
||||||
|
"""
|
||||||
|
Calcule l'écart-type d'une liste de nombres.
|
||||||
|
Écart-type = racine carrée de la variance.
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> ecart_type([2, 4, 4, 4, 5, 5, 7, 9])
|
||||||
|
2.0
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def minimum(liste):
|
||||||
|
"""
|
||||||
|
Retourne le minimum d'une liste (sans utiliser min()).
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> minimum([5, 2, 8, 1, 9])
|
||||||
|
1
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def maximum(liste):
|
||||||
|
"""
|
||||||
|
Retourne le maximum d'une liste (sans utiliser max()).
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> maximum([5, 2, 8, 1, 9])
|
||||||
|
9
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
doctest.testmod(verbose=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : Module conversion
|
||||||
|
|
||||||
|
### Exercice 3 : Créer le module `conversion.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
# mathtools/conversion.py
|
||||||
|
"""Module de conversions d'unités."""
|
||||||
|
|
||||||
|
import doctest
|
||||||
|
|
||||||
|
|
||||||
|
# --- Températures ---
|
||||||
|
|
||||||
|
def celsius_vers_fahrenheit(celsius):
|
||||||
|
"""
|
||||||
|
Convertit des degrés Celsius en Fahrenheit.
|
||||||
|
Formule : F = C × 9/5 + 32
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> celsius_vers_fahrenheit(0)
|
||||||
|
32.0
|
||||||
|
>>> celsius_vers_fahrenheit(100)
|
||||||
|
212.0
|
||||||
|
>>> celsius_vers_fahrenheit(-40)
|
||||||
|
-40.0
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def fahrenheit_vers_celsius(fahrenheit):
|
||||||
|
"""
|
||||||
|
Convertit des degrés Fahrenheit en Celsius.
|
||||||
|
Formule : C = (F - 32) × 5/9
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> fahrenheit_vers_celsius(32)
|
||||||
|
0.0
|
||||||
|
>>> fahrenheit_vers_celsius(212)
|
||||||
|
100.0
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# --- Distances ---
|
||||||
|
|
||||||
|
def km_vers_miles(km):
|
||||||
|
"""
|
||||||
|
Convertit des kilomètres en miles.
|
||||||
|
1 mile = 1.60934 km
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> round(km_vers_miles(10), 2)
|
||||||
|
6.21
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def miles_vers_km(miles):
|
||||||
|
"""
|
||||||
|
Convertit des miles en kilomètres.
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> round(miles_vers_km(10), 2)
|
||||||
|
16.09
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# --- Poids ---
|
||||||
|
|
||||||
|
def kg_vers_livres(kg):
|
||||||
|
"""
|
||||||
|
Convertit des kilogrammes en livres.
|
||||||
|
1 livre = 0.453592 kg
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> round(kg_vers_livres(1), 2)
|
||||||
|
2.2
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# --- Informatique ---
|
||||||
|
|
||||||
|
def octets_vers_kilo(octets):
|
||||||
|
"""
|
||||||
|
Convertit des octets en kilooctets (1 Ko = 1024 octets).
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> octets_vers_kilo(2048)
|
||||||
|
2.0
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def bits_vers_octets(bits):
|
||||||
|
"""
|
||||||
|
Convertit des bits en octets (1 octet = 8 bits).
|
||||||
|
|
||||||
|
Exemple:
|
||||||
|
>>> bits_vers_octets(64)
|
||||||
|
8.0
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
doctest.testmod(verbose=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 5 : Programme principal
|
||||||
|
|
||||||
|
### Exercice 4 : Créer `main.py`
|
||||||
|
|
||||||
|
Ce fichier sera le point d'entrée de votre package. Il propose un menu interactif.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# mathtools/main.py
|
||||||
|
"""Programme principal de MathTools."""
|
||||||
|
|
||||||
|
from geometrie import *
|
||||||
|
from statistiques import *
|
||||||
|
from conversion import *
|
||||||
|
|
||||||
|
|
||||||
|
def menu():
|
||||||
|
"""Affiche le menu principal."""
|
||||||
|
print("\n" + "=" * 40)
|
||||||
|
print(" MATHTOOLS v1.0.0")
|
||||||
|
print("=" * 40)
|
||||||
|
print("1. Géométrie")
|
||||||
|
print("2. Statistiques")
|
||||||
|
print("3. Conversions")
|
||||||
|
print("0. Quitter")
|
||||||
|
print("=" * 40)
|
||||||
|
|
||||||
|
|
||||||
|
def menu_geometrie():
|
||||||
|
"""Sous-menu géométrie."""
|
||||||
|
print("\n--- GÉOMÉTRIE ---")
|
||||||
|
print("1. Aire d'un rectangle")
|
||||||
|
print("2. Aire d'un cercle")
|
||||||
|
print("3. Hypoténuse")
|
||||||
|
print("0. Retour")
|
||||||
|
|
||||||
|
choix = input("Votre choix : ")
|
||||||
|
|
||||||
|
if choix == "1":
|
||||||
|
l = float(input("Longueur : "))
|
||||||
|
L = float(input("Largeur : "))
|
||||||
|
print(f"Aire = {aire_rectangle(l, L)}")
|
||||||
|
elif choix == "2":
|
||||||
|
r = float(input("Rayon : "))
|
||||||
|
print(f"Aire = {round(aire_cercle(r), 4)}")
|
||||||
|
elif choix == "3":
|
||||||
|
a = float(input("Côté a : "))
|
||||||
|
b = float(input("Côté b : "))
|
||||||
|
print(f"Hypoténuse = {hypotenuse(a, b)}")
|
||||||
|
|
||||||
|
|
||||||
|
def menu_statistiques():
|
||||||
|
"""Sous-menu statistiques."""
|
||||||
|
print("\n--- STATISTIQUES ---")
|
||||||
|
print("Entrez vos valeurs séparées par des espaces :")
|
||||||
|
valeurs = input("> ")
|
||||||
|
liste = [float(x) for x in valeurs.split()]
|
||||||
|
|
||||||
|
print(f"Moyenne : {moyenne(liste)}")
|
||||||
|
print(f"Médiane : {mediane(liste)}")
|
||||||
|
print(f"Min : {minimum(liste)}")
|
||||||
|
print(f"Max : {maximum(liste)}")
|
||||||
|
|
||||||
|
|
||||||
|
def menu_conversions():
|
||||||
|
"""Sous-menu conversions."""
|
||||||
|
print("\n--- CONVERSIONS ---")
|
||||||
|
print("1. Celsius → Fahrenheit")
|
||||||
|
print("2. Fahrenheit → Celsius")
|
||||||
|
print("3. Km → Miles")
|
||||||
|
print("0. Retour")
|
||||||
|
|
||||||
|
choix = input("Votre choix : ")
|
||||||
|
|
||||||
|
if choix == "1":
|
||||||
|
c = float(input("Température en °C : "))
|
||||||
|
print(f"{c}°C = {celsius_vers_fahrenheit(c)}°F")
|
||||||
|
elif choix == "2":
|
||||||
|
f = float(input("Température en °F : "))
|
||||||
|
print(f"{f}°F = {fahrenheit_vers_celsius(f)}°C")
|
||||||
|
elif choix == "3":
|
||||||
|
km = float(input("Distance en km : "))
|
||||||
|
print(f"{km} km = {round(km_vers_miles(km), 2)} miles")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Boucle principale."""
|
||||||
|
while True:
|
||||||
|
menu()
|
||||||
|
choix = input("Votre choix : ")
|
||||||
|
|
||||||
|
if choix == "1":
|
||||||
|
menu_geometrie()
|
||||||
|
elif choix == "2":
|
||||||
|
menu_statistiques()
|
||||||
|
elif choix == "3":
|
||||||
|
menu_conversions()
|
||||||
|
elif choix == "0":
|
||||||
|
print("Au revoir !")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("Choix invalide.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 6 : Tests et validation
|
||||||
|
|
||||||
|
### Exercice 5 : Tester tous les modules
|
||||||
|
|
||||||
|
Exécutez chaque module pour vérifier que tous les doctests passent :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mathtools
|
||||||
|
python geometrie.py
|
||||||
|
python statistiques.py
|
||||||
|
python conversion.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Tous les tests doivent afficher `ok`.
|
||||||
|
|
||||||
|
### Exercice 6 : Utiliser le package
|
||||||
|
|
||||||
|
Dans un nouveau fichier `test_mathtools.py` situé **en dehors** du dossier mathtools :
|
||||||
|
|
||||||
|
```python
|
||||||
|
# test_mathtools.py
|
||||||
|
from mathtools.geometrie import aire_cercle, hypotenuse
|
||||||
|
from mathtools.statistiques import moyenne, ecart_type
|
||||||
|
from mathtools.conversion import celsius_vers_fahrenheit
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
print("Aire cercle r=5 :", round(aire_cercle(5), 2))
|
||||||
|
print("Hypoténuse 3,4 :", hypotenuse(3, 4))
|
||||||
|
print("Moyenne [10,20,30] :", moyenne([10, 20, 30]))
|
||||||
|
print("20°C en Fahrenheit :", celsius_vers_fahrenheit(20))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bonus : Documentation avancée
|
||||||
|
|
||||||
|
### Générer une documentation HTML
|
||||||
|
|
||||||
|
Installez `pydoc` (inclus avec Python) et générez la doc :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pydoc -w mathtools.geometrie
|
||||||
|
python -m pydoc -w mathtools.statistiques
|
||||||
|
```
|
||||||
|
|
||||||
|
Cela génère des fichiers HTML consultables dans un navigateur.
|
||||||
|
|
||||||
|
### Ajouter des assertions
|
||||||
|
|
||||||
|
Améliorez vos fonctions avec des vérifications :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def aire_cercle(rayon):
|
||||||
|
assert rayon > 0, "Le rayon doit être strictement positif"
|
||||||
|
return math.pi * rayon ** 2
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Résumé des notions
|
||||||
|
|
||||||
|
| Concept | Application dans ce TP |
|
||||||
|
|---------|------------------------|
|
||||||
|
| Module | Fichier .py contenant des fonctions |
|
||||||
|
| Package | Dossier avec `__init__.py` |
|
||||||
|
| Import | `from module import fonction` |
|
||||||
|
| Docstring | Documentation des fonctions |
|
||||||
|
| Doctest | Tests dans la documentation |
|
||||||
|
| `__name__` | Permet d'exécuter un module seul |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pour aller plus loin
|
||||||
|
|
||||||
|
- **pytest** : Framework de tests plus puissant que doctest
|
||||||
|
- **Sphinx** : Générateur de documentation professionnelle
|
||||||
|
- **PyPI** : Publier son package pour le monde entier
|
||||||
|
- **pip** : Installer des packages depuis PyPI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
24
Modularité/vecteur.py
Normal file
24
Modularité/vecteur.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
""" Ce module contient des fonctions pour faire des calculs sur les vecteurs.
|
||||||
|
Les vecteurs sont exprimés dans des tuples (x,y) """
|
||||||
|
|
||||||
|
def addition(v1: tuple, v2: tuple) -> tuple:
|
||||||
|
""" vectors are tuple (x,y)
|
||||||
|
return the vector v1 + v2 in a tuple
|
||||||
|
>>> addition((1, 1), (2, 2))
|
||||||
|
(3, 3)
|
||||||
|
"""
|
||||||
|
return v1[0] + v2[0], v1[1] + v2[1]
|
||||||
|
|
||||||
|
def soustraction(v1: tuple, v2: tuple) -> tuple:
|
||||||
|
""" vectors are tuple (x,y)
|
||||||
|
return the vector v1 - v2 in a tuple
|
||||||
|
>>> soustraction((1, 1), (2, 2))
|
||||||
|
(-1, -1)
|
||||||
|
"""
|
||||||
|
return v1[0] - v2[0], v1[1] - v2[1]
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
doctest.testmod(optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE, verbose = True)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
## Programmation orientée objet
|
# Programmation orientée objet
|
||||||
|
|
||||||
> Concept fondamental dans la programmation moderne : **la Programmation Orientée Objet**, ou **POO** pour les intimes, est un changement de paradigme par rapport à ce qu’on a vu jusqu’ici.
|
> Concept fondamental dans la programmation moderne : **la Programmation Orientée Objet**, ou **POO** pour les intimes, est un changement de paradigme par rapport à ce qu’on a vu jusqu’ici.
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ dico = {'Nom': 'Dupond','Prenom': 'Bob', 'Spe1' : 'NSI', 'Age': 17}
|
|||||||
|
|
||||||
Ici le dictionnaire contient des informations qui peuvent s'apparenter à un étudiant. Il est possible de changer les clés, les valeurs des clés. Mais en réalité nous manipulons un dictionnaire. Si nous vérifions le type de *dico* il s'agit d'un dictionnaire, non d'un étudiant.
|
Ici le dictionnaire contient des informations qui peuvent s'apparenter à un étudiant. Il est possible de changer les clés, les valeurs des clés. Mais en réalité nous manipulons un dictionnaire. Si nous vérifions le type de *dico* il s'agit d'un dictionnaire, non d'un étudiant.
|
||||||
|
|
||||||
La programmation objet va permettre, de créer un type *Etudiant* qui contiendra des caractéristiques qui lui sont propres (nommées **attributs**) et des fonction appropriées (nommées **méthodes**).
|
La programmation objet va permettre de créer un type *Etudiant* qui contiendra des caractéristiques qui lui sont propres (nommées **attributs**) et des fonctions appropriées (nommées **méthodes**).
|
||||||
|
|
||||||
## Programmation orientée objet
|
## Programmation orientée objet
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ Ces types possèdent des méthodes, par exemple le type str possède la méthode
|
|||||||
|
|
||||||
Le but ici est donc de réaliser la même chose sur un type que nous allons créer. (le type *Etudiant*)
|
Le but ici est donc de réaliser la même chose sur un type que nous allons créer. (le type *Etudiant*)
|
||||||
|
|
||||||
Avant d'écrire la moindre ligne de code, il définir la classe :
|
Avant d'écrire la moindre ligne de code, il faut définir la classe :
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class Etudiant :
|
class Etudiant :
|
||||||
@@ -104,13 +104,13 @@ False
|
|||||||
|
|
||||||
### Méthodes associées à la classe
|
### Méthodes associées à la classe
|
||||||
|
|
||||||
Une fois notre constructeur crée il faut pouvoir manipuler l'objet. Ici nous pouvons accéder à ses attributs. Mais nous allons créer des méthodes permettant de changer, ajouter des attributs.
|
Une fois notre constructeur créé il faut pouvoir manipuler l'objet. Ici nous pouvons accéder à ses attributs. Mais nous allons créer des méthodes permettant de changer, ajouter des attributs.
|
||||||
|
|
||||||
Supposons que l'étudiant change de spécialité. Bob voudrait arrêter de faire NSI (impossible, mais supposons pour l'exemple).
|
Supposons que l'étudiant change de spécialité. Bob voudrait arrêter de faire NSI (impossible, mais supposons pour l'exemple).
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def change_spe(self,nouvelle_spe) :
|
def change_spe(self,nouvelle_spe) :
|
||||||
self.spe = nouvelle_spe
|
self.spe1 = nouvelle_spe
|
||||||
```
|
```
|
||||||
|
|
||||||
<u>Pour appeler la méthode :</u>
|
<u>Pour appeler la méthode :</u>
|
||||||
@@ -144,12 +144,12 @@ def nouvelle_spe(self,new_spe):
|
|||||||
>>> etudiant2 = Etudiant('Timo','Alice','SES',17)
|
>>> etudiant2 = Etudiant('Timo','Alice','SES',17)
|
||||||
>>> etudiant2.spe1
|
>>> etudiant2.spe1
|
||||||
'SES'
|
'SES'
|
||||||
>>> e.nouvelle_spe(self,etudiant2.spe1)
|
>>> e.nouvelle_spe(etudiant2.spe1)
|
||||||
>>> e.spe2
|
>>> e.spe2
|
||||||
'SES'
|
'SES'
|
||||||
```
|
```
|
||||||
|
|
||||||
Ici nous utilisons la spé de l'étudiant 2 (Alice) pour l'étudiant 1. Il y a peu intérêt dans cet exemple, mais il permet de démontrer que c'est possible.
|
Ici nous utilisons la spé de l'étudiant 2 (Alice) pour l'étudiant 1. Il y a peu d'intérêt dans cet exemple, mais il permet de démontrer que c'est possible.
|
||||||
Un objet peut dépendre d'un autre, que cela soit dans un paramètre, un attribut, etc.
|
Un objet peut dépendre d'un autre, que cela soit dans un paramètre, un attribut, etc.
|
||||||
|
|
||||||
### Méthodes particulières
|
### Méthodes particulières
|
||||||
@@ -195,7 +195,7 @@ Actuellement appeler un objet renvoie ceci :
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.nom + ' ' + self.prenon
|
return self.nom + ' ' + self.prenom
|
||||||
```
|
```
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@@ -210,7 +210,7 @@ Dupond Bob
|
|||||||
|
|
||||||
### Pour aller plus loin :
|
### Pour aller plus loin :
|
||||||
|
|
||||||
Il est possible de rendre les attributs privés. Actuellement nous pouvons modifier/ accéder aux attributs d'un objets simplement par son nom.
|
Il est possible de rendre les attributs privés. Actuellement nous pouvons modifier/accéder aux attributs d'un objet simplement par son nom.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
>>> e = Etudiant('Dupond','Bob','NSI',17)
|
>>> e = Etudiant('Dupond','Bob','NSI',17)
|
||||||
@@ -218,7 +218,7 @@ Il est possible de rendre les attributs privés. Actuellement nous pouvons modif
|
|||||||
'Dupond'
|
'Dupond'
|
||||||
```
|
```
|
||||||
|
|
||||||
En utilisant des types privés il n'est plus possible d'accéder directement aux attributs d'un objets. Cette modification se passe dans le constructeur.
|
En utilisant des types privés il n'est plus possible d'accéder directement aux attributs d'un objet. Cette modification se passe dans le constructeur.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class Etudiant :
|
class Etudiant :
|
||||||
@@ -248,7 +248,7 @@ Ces méthodes sont appelées des accesseurs et modificateurs. Elles sont optionn
|
|||||||
|
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
Auteurs : Florian Mathieu, Timothée Decoster, Enzo Frémaux
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
Licence CC BY NC
|
Licence CC BY NC
|
||||||
|
|
||||||
|
|||||||
251
POO/TP/Corrige_TP_POO.md
Normal file
251
POO/TP/Corrige_TP_POO.md
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
# Corrigé du TP Programmation Orientée Objet
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Classe Auteur
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Auteur:
|
||||||
|
def __init__(self, nom, naissance, deces=False):
|
||||||
|
"""
|
||||||
|
Méthode constructeur de Auteur.
|
||||||
|
|
||||||
|
:param nom: (str) Nom de l'auteur
|
||||||
|
:param naissance: (int) Année de naissance de l'auteur
|
||||||
|
:param deces: (int/bool) Année de décès / False si toujours en vie
|
||||||
|
"""
|
||||||
|
self.nom = nom
|
||||||
|
self.annee_naissance = naissance
|
||||||
|
self.deces = deces
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Représentation de l'auteur."""
|
||||||
|
if self.deces:
|
||||||
|
return f"{self.nom} ({self.annee_naissance}-{self.deces})"
|
||||||
|
else:
|
||||||
|
return f"{self.nom} (né en {self.annee_naissance})"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Explications :**
|
||||||
|
- L'attribut `deces` a une valeur par défaut (`False`) pour gérer les auteurs vivants
|
||||||
|
- La méthode `__repr__` est ajoutée pour un affichage lisible
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Classe Livre
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Livre:
|
||||||
|
def __init__(self, titre, genre, auteur):
|
||||||
|
"""
|
||||||
|
Méthode constructeur de Livre.
|
||||||
|
|
||||||
|
:param titre: (str) Titre du livre
|
||||||
|
:param genre: (str) Genre du livre
|
||||||
|
:param auteur: (Auteur) Auteur du livre (objet de type Auteur)
|
||||||
|
"""
|
||||||
|
self.titre = titre
|
||||||
|
self.genre = genre
|
||||||
|
self.auteur = auteur
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Représentation du livre."""
|
||||||
|
return f"{self.titre} - {self.auteur.nom}"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Point important :**
|
||||||
|
- L'attribut `auteur` doit être un objet de type `Auteur`, pas une chaîne de caractères
|
||||||
|
- Cela permet d'accéder aux informations de l'auteur via `livre.auteur.nom`, `livre.auteur.annee_naissance`, etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Classe Bibliothèque
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Bibliotheque:
|
||||||
|
def __init__(self, rayon):
|
||||||
|
"""
|
||||||
|
Méthode constructeur de Bibliothèque.
|
||||||
|
L'ensemble des livres est vide lors de l'initialisation.
|
||||||
|
|
||||||
|
:param rayon: (str) Type de livre que la bibliothèque peut recevoir
|
||||||
|
"""
|
||||||
|
self.rayon = rayon
|
||||||
|
self.ens_livre = []
|
||||||
|
|
||||||
|
def add_livre(self, livre):
|
||||||
|
"""
|
||||||
|
Méthode permettant d'ajouter un livre.
|
||||||
|
|
||||||
|
:param livre: (Livre) Livre à ajouter
|
||||||
|
:return: (str) Précise si le livre est ajouté ou non
|
||||||
|
"""
|
||||||
|
if livre.genre == self.rayon:
|
||||||
|
self.ens_livre.append(livre)
|
||||||
|
return "Livre ajouté"
|
||||||
|
else:
|
||||||
|
return "Impossible d'ajouter le livre (genre incompatible)"
|
||||||
|
|
||||||
|
def est_dispo(self, nom_livre):
|
||||||
|
"""
|
||||||
|
Méthode parcourant l'ensemble des livres de la bibliothèque
|
||||||
|
pour savoir si un livre est disponible.
|
||||||
|
|
||||||
|
:param nom_livre: (str) Nom du livre à rechercher
|
||||||
|
:return: (int) Indice du livre si dispo, -1 sinon
|
||||||
|
"""
|
||||||
|
i = 0
|
||||||
|
while i < len(self.ens_livre):
|
||||||
|
if self.ens_livre[i].titre == nom_livre:
|
||||||
|
return i
|
||||||
|
i += 1
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def prete_livre(self, nom_livre):
|
||||||
|
"""
|
||||||
|
Méthode permettant de prêter un livre s'il est disponible.
|
||||||
|
|
||||||
|
:param nom_livre: (str) Nom du livre à emprunter
|
||||||
|
:return: (Livre/bool) Livre si emprunt possible, False sinon
|
||||||
|
"""
|
||||||
|
indice = self.est_dispo(nom_livre)
|
||||||
|
if indice != -1:
|
||||||
|
livre = self.ens_livre[indice]
|
||||||
|
# Suppression du livre de la liste
|
||||||
|
self.ens_livre = self.ens_livre[:indice] + self.ens_livre[indice+1:]
|
||||||
|
return livre
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Représentation de la bibliothèque."""
|
||||||
|
return f"Bibliothèque [{self.rayon}] - {len(self.ens_livre)} livre(s)"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Explications :**
|
||||||
|
- `add_livre` vérifie que le genre du livre correspond au rayon
|
||||||
|
- `est_dispo` parcourt la liste et renvoie l'indice ou -1
|
||||||
|
- `prete_livre` utilise `est_dispo` et supprime le livre de la liste via les slices
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Création des objets
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Création des auteurs
|
||||||
|
a1 = Auteur('Victor Hugo', 1802, 1885)
|
||||||
|
a2 = Auteur('J.K. Rowling', 1965)
|
||||||
|
a3 = Auteur('Agatha Christie', 1890, 1976)
|
||||||
|
a4 = Auteur('Stephen King', 1947)
|
||||||
|
|
||||||
|
# Création des livres
|
||||||
|
l1 = Livre('Les Misérables', 'Classique', a1)
|
||||||
|
l2 = Livre('Notre-Dame de Paris', 'Classique', a1)
|
||||||
|
l3 = Livre("Harry Potter à l'école des sorciers", 'Fantastique', a2)
|
||||||
|
l4 = Livre('Le Crime de l\'Orient-Express', 'Policier', a3)
|
||||||
|
l5 = Livre('Shining', 'Horreur', a4)
|
||||||
|
l6 = Livre('Ça', 'Horreur', a4)
|
||||||
|
|
||||||
|
# Création des bibliothèques
|
||||||
|
biblio_classique = Bibliotheque('Classique')
|
||||||
|
biblio_horreur = Bibliotheque('Horreur')
|
||||||
|
|
||||||
|
# Ajout des livres
|
||||||
|
print(biblio_classique.add_livre(l1)) # "Livre ajouté"
|
||||||
|
print(biblio_classique.add_livre(l2)) # "Livre ajouté"
|
||||||
|
print(biblio_classique.add_livre(l3)) # "Impossible..." (genre incompatible)
|
||||||
|
|
||||||
|
print(biblio_horreur.add_livre(l5)) # "Livre ajouté"
|
||||||
|
print(biblio_horreur.add_livre(l6)) # "Livre ajouté"
|
||||||
|
|
||||||
|
# Affichage
|
||||||
|
print(biblio_classique) # Bibliothèque [Classique] - 2 livre(s)
|
||||||
|
print(biblio_horreur) # Bibliothèque [Horreur] - 2 livre(s)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Tests complets
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Test de disponibilité
|
||||||
|
print(biblio_classique.est_dispo('Les Misérables')) # 0
|
||||||
|
print(biblio_classique.est_dispo('Livre inexistant')) # -1
|
||||||
|
|
||||||
|
# Test de prêt
|
||||||
|
livre_emprunte = biblio_classique.prete_livre('Les Misérables')
|
||||||
|
print(livre_emprunte) # Les Misérables - Victor Hugo
|
||||||
|
print(biblio_classique) # Bibliothèque [Classique] - 1 livre(s)
|
||||||
|
|
||||||
|
# Le livre n'est plus disponible
|
||||||
|
print(biblio_classique.est_dispo('Les Misérables')) # -1
|
||||||
|
|
||||||
|
# Tentative de prêt d'un livre non disponible
|
||||||
|
print(biblio_classique.prete_livre('Les Misérables')) # False
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Diagramme de classes (UML simplifié)
|
||||||
|
|
||||||
|
```
|
||||||
|
+----------------+ +----------------+ +------------------+
|
||||||
|
| Auteur | | Livre | | Bibliotheque |
|
||||||
|
+----------------+ +----------------+ +------------------+
|
||||||
|
| - nom | | - titre | | - rayon |
|
||||||
|
| - annee_naiss. |<------| - genre | | - ens_livre |
|
||||||
|
| - deces | | - auteur |------>| |
|
||||||
|
+----------------+ +----------------+ +------------------+
|
||||||
|
| + __init__() | | + __init__() | | + __init__() |
|
||||||
|
| + __repr__() | | + __repr__() | | + add_livre() |
|
||||||
|
+----------------+ +----------------+ | + est_dispo() |
|
||||||
|
| + prete_livre() |
|
||||||
|
| + __repr__() |
|
||||||
|
+------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pour aller plus loin
|
||||||
|
|
||||||
|
### Ajout d'une méthode de retour de livre
|
||||||
|
|
||||||
|
```python
|
||||||
|
def retour_livre(self, livre):
|
||||||
|
"""
|
||||||
|
Méthode permettant de retourner un livre emprunté.
|
||||||
|
|
||||||
|
:param livre: (Livre) Livre à retourner
|
||||||
|
:return: (str) Message de confirmation
|
||||||
|
"""
|
||||||
|
if livre.genre == self.rayon:
|
||||||
|
self.ens_livre.append(livre)
|
||||||
|
return f"'{livre.titre}' a été retourné"
|
||||||
|
else:
|
||||||
|
return "Ce livre n'appartient pas à ce rayon"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ajout d'une recherche par auteur
|
||||||
|
|
||||||
|
```python
|
||||||
|
def livres_par_auteur(self, nom_auteur):
|
||||||
|
"""
|
||||||
|
Renvoie tous les livres d'un auteur donné.
|
||||||
|
|
||||||
|
:param nom_auteur: (str) Nom de l'auteur
|
||||||
|
:return: (list) Liste des livres de cet auteur
|
||||||
|
"""
|
||||||
|
resultat = []
|
||||||
|
for livre in self.ens_livre:
|
||||||
|
if livre.auteur.nom == nom_auteur:
|
||||||
|
resultat.append(livre)
|
||||||
|
return resultat
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
358
POO/TP/Corrige_TP_Pokemon.py
Normal file
358
POO/TP/Corrige_TP_Pokemon.py
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
"""
|
||||||
|
Corrigé du TP Pokémon Arena — Programmation Orientée Objet
|
||||||
|
"""
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Dictionnaire des faiblesses (Bonus Exercice 3)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
FAIBLESSES = {
|
||||||
|
"Eau": ["Feu"],
|
||||||
|
"Feu": ["Plante"],
|
||||||
|
"Plante": ["Eau"],
|
||||||
|
"Electrik": ["Eau"],
|
||||||
|
"Roche": ["Eau", "Plante"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 1 : Classe Pokemon
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class Pokemon:
|
||||||
|
def __init__(self, nom, type_pokemon, niveau=1, pv_max=100, attaque=10):
|
||||||
|
"""
|
||||||
|
Constructeur de la classe Pokemon.
|
||||||
|
|
||||||
|
:param nom: (str) Nom du Pokémon
|
||||||
|
:param type_pokemon: (str) Type élémentaire
|
||||||
|
:param niveau: (int) Niveau du Pokémon
|
||||||
|
:param pv_max: (int) Points de vie maximum
|
||||||
|
:param attaque: (int) Puissance d'attaque
|
||||||
|
"""
|
||||||
|
self.nom = nom
|
||||||
|
self.type_pokemon = type_pokemon
|
||||||
|
self.niveau = niveau
|
||||||
|
self.pv_max = pv_max
|
||||||
|
self.pv = pv_max
|
||||||
|
self.attaque = attaque
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Représentation textuelle du Pokémon."""
|
||||||
|
return f"{self.nom} ({self.type_pokemon}) - Nv.{self.niveau} - {self.pv}/{self.pv_max} PV"
|
||||||
|
|
||||||
|
def est_ko(self):
|
||||||
|
"""
|
||||||
|
Vérifie si le Pokémon est KO.
|
||||||
|
|
||||||
|
:return: (bool) True si PV <= 0, False sinon
|
||||||
|
"""
|
||||||
|
return self.pv <= 0
|
||||||
|
|
||||||
|
def subir_degats(self, degats):
|
||||||
|
"""
|
||||||
|
Inflige des dégâts au Pokémon.
|
||||||
|
Les PV ne peuvent pas descendre en dessous de 0.
|
||||||
|
|
||||||
|
:param degats: (int) Nombre de dégâts subis
|
||||||
|
"""
|
||||||
|
self.pv = max(0, self.pv - degats)
|
||||||
|
|
||||||
|
def attaquer(self, cible):
|
||||||
|
"""
|
||||||
|
Attaque un autre Pokémon avec prise en compte des faiblesses.
|
||||||
|
|
||||||
|
:param cible: (Pokemon) Pokémon ciblé par l'attaque
|
||||||
|
"""
|
||||||
|
degats = self.attaque
|
||||||
|
|
||||||
|
# Vérifier si le type de la cible est faible contre notre type
|
||||||
|
if self.type_pokemon in FAIBLESSES:
|
||||||
|
if cible.type_pokemon in FAIBLESSES[self.type_pokemon]:
|
||||||
|
degats *= 2
|
||||||
|
print(f"C'est super efficace !")
|
||||||
|
|
||||||
|
print(f"{self.nom} attaque {cible.nom} et inflige {degats} dégâts !")
|
||||||
|
cible.subir_degats(degats)
|
||||||
|
|
||||||
|
def soigner(self, soin):
|
||||||
|
"""
|
||||||
|
Soigne le Pokémon.
|
||||||
|
Les PV ne peuvent pas dépasser pv_max.
|
||||||
|
|
||||||
|
:param soin: (int) Nombre de PV récupérés
|
||||||
|
"""
|
||||||
|
self.pv = min(self.pv_max, self.pv + soin)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 2 : Classe Dresseur
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class Dresseur:
|
||||||
|
def __init__(self, nom):
|
||||||
|
"""
|
||||||
|
Constructeur de la classe Dresseur.
|
||||||
|
|
||||||
|
:param nom: (str) Nom du dresseur
|
||||||
|
"""
|
||||||
|
self.nom = nom
|
||||||
|
self.equipe = []
|
||||||
|
|
||||||
|
def ajouter_pokemon(self, pokemon):
|
||||||
|
"""
|
||||||
|
Ajoute un Pokémon à l'équipe si possible (max 6).
|
||||||
|
|
||||||
|
:param pokemon: (Pokemon) Pokémon à ajouter
|
||||||
|
:return: (bool) True si ajouté, False sinon
|
||||||
|
"""
|
||||||
|
if len(self.equipe) < 6:
|
||||||
|
self.equipe.append(pokemon)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("L'équipe est complète (6 Pokémon maximum) !")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def pokemon_disponibles(self):
|
||||||
|
"""
|
||||||
|
Renvoie la liste des Pokémon non KO.
|
||||||
|
|
||||||
|
:return: (list) Liste des Pokémon encore en état de combattre
|
||||||
|
"""
|
||||||
|
disponibles = []
|
||||||
|
for pokemon in self.equipe:
|
||||||
|
if not pokemon.est_ko():
|
||||||
|
disponibles.append(pokemon)
|
||||||
|
return disponibles
|
||||||
|
|
||||||
|
def premier_pokemon_dispo(self):
|
||||||
|
"""
|
||||||
|
Renvoie le premier Pokémon non KO.
|
||||||
|
|
||||||
|
:return: (Pokemon/None) Premier Pokémon dispo ou None
|
||||||
|
"""
|
||||||
|
for pokemon in self.equipe:
|
||||||
|
if not pokemon.est_ko():
|
||||||
|
return pokemon
|
||||||
|
return None
|
||||||
|
|
||||||
|
def tous_ko(self):
|
||||||
|
"""
|
||||||
|
Vérifie si tous les Pokémon sont KO.
|
||||||
|
|
||||||
|
:return: (bool) True si tous KO, False sinon
|
||||||
|
"""
|
||||||
|
for pokemon in self.equipe:
|
||||||
|
if not pokemon.est_ko():
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Représentation du dresseur."""
|
||||||
|
result = f"Dresseur {self.nom} - {len(self.equipe)} Pokémon"
|
||||||
|
for pokemon in self.equipe:
|
||||||
|
result += f"\n - {pokemon}"
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 3 : Classe Combat
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class Combat:
|
||||||
|
def __init__(self, dresseur1, dresseur2):
|
||||||
|
"""
|
||||||
|
Constructeur de la classe Combat.
|
||||||
|
|
||||||
|
:param dresseur1: (Dresseur) Premier dresseur
|
||||||
|
:param dresseur2: (Dresseur) Second dresseur
|
||||||
|
"""
|
||||||
|
self.dresseur1 = dresseur1
|
||||||
|
self.dresseur2 = dresseur2
|
||||||
|
self.tour = 0
|
||||||
|
|
||||||
|
def afficher_etat(self):
|
||||||
|
"""Affiche l'état actuel du combat."""
|
||||||
|
print(f"\n{'='*40}")
|
||||||
|
print(f"Tour {self.tour}")
|
||||||
|
print(f"{'='*40}")
|
||||||
|
|
||||||
|
poke1 = self.dresseur1.premier_pokemon_dispo()
|
||||||
|
poke2 = self.dresseur2.premier_pokemon_dispo()
|
||||||
|
|
||||||
|
if poke1:
|
||||||
|
print(f"{self.dresseur1.nom}: {poke1.nom} ({poke1.pv}/{poke1.pv_max} PV)")
|
||||||
|
if poke2:
|
||||||
|
print(f"{self.dresseur2.nom}: {poke2.nom} ({poke2.pv}/{poke2.pv_max} PV)")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def lancer_combat(self):
|
||||||
|
"""
|
||||||
|
Lance le combat tour par tour.
|
||||||
|
Chaque tour, les deux Pokémon actifs s'attaquent.
|
||||||
|
Quand un Pokémon est KO, le suivant prend sa place.
|
||||||
|
Le combat s'arrête quand un dresseur n'a plus de Pokémon.
|
||||||
|
"""
|
||||||
|
print(f"\n*** COMBAT : {self.dresseur1.nom} VS {self.dresseur2.nom} ***")
|
||||||
|
|
||||||
|
while not self.dresseur1.tous_ko() and not self.dresseur2.tous_ko():
|
||||||
|
self.tour += 1
|
||||||
|
self.afficher_etat()
|
||||||
|
|
||||||
|
# Récupérer les Pokémon actifs
|
||||||
|
poke1 = self.dresseur1.premier_pokemon_dispo()
|
||||||
|
poke2 = self.dresseur2.premier_pokemon_dispo()
|
||||||
|
|
||||||
|
# Le Pokémon 1 attaque
|
||||||
|
poke1.attaquer(poke2)
|
||||||
|
|
||||||
|
# Vérifier si Pokémon 2 est KO
|
||||||
|
if poke2.est_ko():
|
||||||
|
print(f"\n{poke2.nom} est KO !")
|
||||||
|
nouveau = self.dresseur2.premier_pokemon_dispo()
|
||||||
|
if nouveau:
|
||||||
|
print(f"{self.dresseur2.nom} envoie {nouveau.nom} !")
|
||||||
|
continue # Passer au tour suivant
|
||||||
|
|
||||||
|
# Le Pokémon 2 attaque
|
||||||
|
poke2.attaquer(poke1)
|
||||||
|
|
||||||
|
# Vérifier si Pokémon 1 est KO
|
||||||
|
if poke1.est_ko():
|
||||||
|
print(f"\n{poke1.nom} est KO !")
|
||||||
|
nouveau = self.dresseur1.premier_pokemon_dispo()
|
||||||
|
if nouveau:
|
||||||
|
print(f"{self.dresseur1.nom} envoie {nouveau.nom} !")
|
||||||
|
|
||||||
|
# Afficher le vainqueur
|
||||||
|
print(f"\n{'='*40}")
|
||||||
|
if self.dresseur1.tous_ko():
|
||||||
|
print(f"Tous les Pokémon de {self.dresseur1.nom} sont KO !")
|
||||||
|
print(f"*** {self.dresseur2.nom.upper()} REMPORTE LE COMBAT ! ***")
|
||||||
|
else:
|
||||||
|
print(f"Tous les Pokémon de {self.dresseur2.nom} sont KO !")
|
||||||
|
print(f"*** {self.dresseur1.nom.upper()} REMPORTE LE COMBAT ! ***")
|
||||||
|
print(f"{'='*40}")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 4 : Centre Pokémon
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class CentrePokemon:
|
||||||
|
def __init__(self, ville):
|
||||||
|
"""
|
||||||
|
:param ville: (str) Ville où se trouve le centre
|
||||||
|
"""
|
||||||
|
self.ville = ville
|
||||||
|
|
||||||
|
def soigner_equipe(self, dresseur):
|
||||||
|
"""
|
||||||
|
Soigne tous les Pokémon d'un dresseur à 100% de leurs PV.
|
||||||
|
|
||||||
|
:param dresseur: (Dresseur) Dresseur dont l'équipe doit être soignée
|
||||||
|
"""
|
||||||
|
print(f"\n*** Centre Pokémon de {self.ville} ***")
|
||||||
|
print(f"Bienvenue {dresseur.nom} !")
|
||||||
|
print("Nous allons soigner vos Pokémon...")
|
||||||
|
|
||||||
|
for pokemon in dresseur.equipe:
|
||||||
|
pokemon.pv = pokemon.pv_max
|
||||||
|
|
||||||
|
print("Vos Pokémon sont en pleine forme !")
|
||||||
|
print("À bientôt !\n")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# BONUS : Classe Capacite
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class Capacite:
|
||||||
|
def __init__(self, nom, puissance, type_capacite, pp_max):
|
||||||
|
"""
|
||||||
|
:param nom: (str) Nom de la capacité
|
||||||
|
:param puissance: (int) Puissance de base
|
||||||
|
:param type_capacite: (str) Type de la capacité
|
||||||
|
:param pp_max: (int) Nombre d'utilisations max
|
||||||
|
"""
|
||||||
|
self.nom = nom
|
||||||
|
self.puissance = puissance
|
||||||
|
self.type_capacite = type_capacite
|
||||||
|
self.pp_max = pp_max
|
||||||
|
self.pp = pp_max
|
||||||
|
|
||||||
|
def utiliser(self):
|
||||||
|
"""Utilise la capacité si PP disponibles."""
|
||||||
|
if self.pp > 0:
|
||||||
|
self.pp -= 1
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"{self.nom} : plus de PP !")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.nom} ({self.type_capacite}) - {self.puissance} DMG - {self.pp}/{self.pp_max} PP"
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=" * 50)
|
||||||
|
print("TEST DES CLASSES POKEMON")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Création des Pokémon
|
||||||
|
pikachu = Pokemon("Pikachu", "Electrik", 25, 80, 15)
|
||||||
|
dracaufeu = Pokemon("Dracaufeu", "Feu", 50, 150, 25)
|
||||||
|
tortank = Pokemon("Tortank", "Eau", 45, 140, 22)
|
||||||
|
onix = Pokemon("Onix", "Roche", 30, 120, 18)
|
||||||
|
racaillou = Pokemon("Racaillou", "Roche", 20, 80, 12)
|
||||||
|
|
||||||
|
print("\nPokémon créés :")
|
||||||
|
print(f" - {pikachu}")
|
||||||
|
print(f" - {dracaufeu}")
|
||||||
|
print(f" - {tortank}")
|
||||||
|
print(f" - {onix}")
|
||||||
|
print(f" - {racaillou}")
|
||||||
|
|
||||||
|
# Test des attaques avec faiblesses
|
||||||
|
print("\n--- Test attaque avec faiblesse ---")
|
||||||
|
print(f"Avant : {onix}")
|
||||||
|
tortank.attaquer(onix) # Eau bat Roche -> x2
|
||||||
|
print(f"Après : {onix}")
|
||||||
|
|
||||||
|
# Création des dresseurs
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("CREATION DES DRESSEURS")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
sacha = Dresseur("Sacha")
|
||||||
|
sacha.ajouter_pokemon(Pokemon("Pikachu", "Electrik", 25, 80, 15))
|
||||||
|
sacha.ajouter_pokemon(Pokemon("Dracaufeu", "Feu", 50, 150, 25))
|
||||||
|
sacha.ajouter_pokemon(Pokemon("Tortank", "Eau", 45, 140, 22))
|
||||||
|
|
||||||
|
pierre = Dresseur("Pierre")
|
||||||
|
pierre.ajouter_pokemon(Pokemon("Onix", "Roche", 30, 120, 18))
|
||||||
|
pierre.ajouter_pokemon(Pokemon("Racaillou", "Roche", 20, 80, 12))
|
||||||
|
|
||||||
|
print(f"\n{sacha}")
|
||||||
|
print(f"\n{pierre}")
|
||||||
|
|
||||||
|
# Lancement du combat
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("LANCEMENT DU COMBAT")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
combat = Combat(sacha, pierre)
|
||||||
|
combat.lancer_combat()
|
||||||
|
|
||||||
|
# Soins au Centre Pokémon
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("SOINS AU CENTRE POKEMON")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
centre = CentrePokemon("Argenta")
|
||||||
|
centre.soigner_equipe(sacha)
|
||||||
|
print(sacha)
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## 1. Introduction
|
## 1. Introduction
|
||||||
|
|
||||||
Le but principal de ce TP est d'implémenter une classe Auteur. Ainsi qu'une classe Livre et Bibliothèque. Il y a volontairement pas/peu d'indication sur le code. Seuls les attributs et méthode **nécessaires** sont indiqués. Leurs implémentations sont libre et dépendent de chacun.
|
Le but principal de ce TP est d'implémenter une classe Auteur, ainsi qu'une classe Livre et Bibliothèque. Il n'y a volontairement pas ou peu d'indications sur le code. Seuls les attributs et méthode **nécessaires** sont indiqués. Leurs implémentations sont libre et dépendent de chacun.
|
||||||
|
|
||||||
## 2. Classe Auteur
|
## 2. Classe Auteur
|
||||||
|
|
||||||
@@ -68,3 +68,11 @@ Créer la classe est bien, maintenant il faut manipuler les objets.
|
|||||||
|
|
||||||
Ici, il est possible d'ajouter n'importe quel attributs et/ou méthodes de votre choix.
|
Ici, il est possible d'ajouter n'importe quel attributs et/ou méthodes de votre choix.
|
||||||
Il faut s'entraîner, essayer d'aller le plus loin possible. Alors essayez, testez !
|
Il faut s'entraîner, essayer d'aller le plus loin possible. Alors essayez, testez !
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
448
POO/TP/TP_Pokemon.md
Normal file
448
POO/TP/TP_Pokemon.md
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
# TP : Pokémon Arena — Programmation Orientée Objet
|
||||||
|
|
||||||
|
> **Thème** : Classes, attributs, méthodes, interactions entre objets
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Depuis 1996, Pokémon est l'une des franchises les plus populaires au monde. En 2026, avec la sortie de Pokémon Legends: Z-A, la licence bat tous les records.
|
||||||
|
|
||||||
|
Dans ce TP, vous allez créer un **simulateur de combat Pokémon** en utilisant la Programmation Orientée Objet. Vous modéliserez des Pokémon, des dresseurs et des combats.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 1 : La classe Pokémon
|
||||||
|
|
||||||
|
### Exercice 1 : Créer la classe de base
|
||||||
|
|
||||||
|
Créez une classe `Pokemon` avec les caractéristiques suivantes :
|
||||||
|
|
||||||
|
**Attributs :**
|
||||||
|
- `nom` : nom du Pokémon (str)
|
||||||
|
- `type_pokemon` : type élémentaire (str) — "Feu", "Eau", "Plante", "Electrik", etc.
|
||||||
|
- `niveau` : niveau du Pokémon (int, par défaut 1)
|
||||||
|
- `pv_max` : points de vie maximum (int, par défaut 100)
|
||||||
|
- `pv` : points de vie actuels (int, initialisé à `pv_max`)
|
||||||
|
- `attaque` : puissance d'attaque (int, par défaut 10)
|
||||||
|
|
||||||
|
**Méthodes :**
|
||||||
|
- `__init__` : constructeur
|
||||||
|
- `__repr__` : affiche le Pokémon sous la forme `"Pikachu (Electrik) - Nv.25 - 80/100 PV"`
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Pokemon:
|
||||||
|
def __init__(self, nom, type_pokemon, niveau=1, pv_max=100, attaque=10):
|
||||||
|
"""
|
||||||
|
Constructeur de la classe Pokemon.
|
||||||
|
|
||||||
|
:param nom: (str) Nom du Pokémon
|
||||||
|
:param type_pokemon: (str) Type élémentaire
|
||||||
|
:param niveau: (int) Niveau du Pokémon
|
||||||
|
:param pv_max: (int) Points de vie maximum
|
||||||
|
:param attaque: (int) Puissance d'attaque
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Représentation textuelle du Pokémon."""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests :**
|
||||||
|
```python
|
||||||
|
>>> pikachu = Pokemon("Pikachu", "Electrik", niveau=25, pv_max=80, attaque=15)
|
||||||
|
>>> print(pikachu)
|
||||||
|
Pikachu (Electrik) - Nv.25 - 80/80 PV
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Exercice 2 : Méthodes de combat
|
||||||
|
|
||||||
|
Ajoutez les méthodes suivantes à la classe `Pokemon` :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def est_ko(self):
|
||||||
|
"""
|
||||||
|
Vérifie si le Pokémon est KO.
|
||||||
|
|
||||||
|
:return: (bool) True si PV <= 0, False sinon
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def subir_degats(self, degats):
|
||||||
|
"""
|
||||||
|
Inflige des dégâts au Pokémon.
|
||||||
|
Les PV ne peuvent pas descendre en dessous de 0.
|
||||||
|
|
||||||
|
:param degats: (int) Nombre de dégâts subis
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def attaquer(self, cible):
|
||||||
|
"""
|
||||||
|
Attaque un autre Pokémon.
|
||||||
|
Affiche un message et inflige des dégâts à la cible.
|
||||||
|
|
||||||
|
:param cible: (Pokemon) Pokémon ciblé par l'attaque
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def soigner(self, soin):
|
||||||
|
"""
|
||||||
|
Soigne le Pokémon.
|
||||||
|
Les PV ne peuvent pas dépasser pv_max.
|
||||||
|
|
||||||
|
:param soin: (int) Nombre de PV récupérés
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests :**
|
||||||
|
```python
|
||||||
|
>>> pikachu = Pokemon("Pikachu", "Electrik", niveau=25, pv_max=80, attaque=15)
|
||||||
|
>>> salameche = Pokemon("Salamèche", "Feu", niveau=20, pv_max=70, attaque=12)
|
||||||
|
>>> pikachu.attaquer(salameche)
|
||||||
|
Pikachu attaque Salamèche et inflige 15 dégâts !
|
||||||
|
>>> print(salameche)
|
||||||
|
Salamèche (Feu) - Nv.20 - 55/70 PV
|
||||||
|
>>> salameche.est_ko()
|
||||||
|
False
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Exercice 3 : Système de faiblesses (Bonus)
|
||||||
|
|
||||||
|
En Pokémon, certains types sont plus efficaces contre d'autres :
|
||||||
|
- **Eau** bat **Feu** (×2 dégâts)
|
||||||
|
- **Feu** bat **Plante** (×2 dégâts)
|
||||||
|
- **Plante** bat **Eau** (×2 dégâts)
|
||||||
|
- **Electrik** bat **Eau** (×2 dégâts)
|
||||||
|
|
||||||
|
Modifiez la méthode `attaquer` pour prendre en compte les faiblesses :
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Dictionnaire des faiblesses : type_attaquant -> liste des types faibles
|
||||||
|
FAIBLESSES = {
|
||||||
|
"Eau": ["Feu"],
|
||||||
|
"Feu": ["Plante"],
|
||||||
|
"Plante": ["Eau"],
|
||||||
|
"Electrik": ["Eau"]
|
||||||
|
}
|
||||||
|
|
||||||
|
def attaquer(self, cible):
|
||||||
|
"""
|
||||||
|
Attaque un autre Pokémon avec prise en compte des faiblesses.
|
||||||
|
"""
|
||||||
|
degats = self.attaque
|
||||||
|
|
||||||
|
# Vérifier si le type de la cible est faible contre notre type
|
||||||
|
if self.type_pokemon in FAIBLESSES:
|
||||||
|
if cible.type_pokemon in FAIBLESSES[self.type_pokemon]:
|
||||||
|
degats *= 2
|
||||||
|
print(f"C'est super efficace !")
|
||||||
|
|
||||||
|
print(f"{self.nom} attaque {cible.nom} et inflige {degats} dégâts !")
|
||||||
|
cible.subir_degats(degats)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : La classe Dresseur
|
||||||
|
|
||||||
|
### Exercice 4 : Créer la classe Dresseur
|
||||||
|
|
||||||
|
Un dresseur possède une équipe de Pokémon (maximum 6).
|
||||||
|
|
||||||
|
**Attributs :**
|
||||||
|
- `nom` : nom du dresseur (str)
|
||||||
|
- `equipe` : liste de Pokémon (list, vide par défaut)
|
||||||
|
|
||||||
|
**Méthodes :**
|
||||||
|
- `__init__` : constructeur
|
||||||
|
- `ajouter_pokemon(pokemon)` : ajoute un Pokémon à l'équipe (max 6)
|
||||||
|
- `pokemon_disponibles()` : renvoie la liste des Pokémon non KO
|
||||||
|
- `premier_pokemon_dispo()` : renvoie le premier Pokémon non KO ou None
|
||||||
|
- `tous_ko()` : renvoie True si tous les Pokémon sont KO
|
||||||
|
- `__repr__` : affiche le dresseur et son équipe
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Dresseur:
|
||||||
|
def __init__(self, nom):
|
||||||
|
"""
|
||||||
|
Constructeur de la classe Dresseur.
|
||||||
|
|
||||||
|
:param nom: (str) Nom du dresseur
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def ajouter_pokemon(self, pokemon):
|
||||||
|
"""
|
||||||
|
Ajoute un Pokémon à l'équipe si possible (max 6).
|
||||||
|
|
||||||
|
:param pokemon: (Pokemon) Pokémon à ajouter
|
||||||
|
:return: (bool) True si ajouté, False sinon
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def pokemon_disponibles(self):
|
||||||
|
"""
|
||||||
|
Renvoie la liste des Pokémon non KO.
|
||||||
|
|
||||||
|
:return: (list) Liste des Pokémon encore en état de combattre
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def premier_pokemon_dispo(self):
|
||||||
|
"""
|
||||||
|
Renvoie le premier Pokémon non KO.
|
||||||
|
|
||||||
|
:return: (Pokemon/None) Premier Pokémon dispo ou None
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tous_ko(self):
|
||||||
|
"""
|
||||||
|
Vérifie si tous les Pokémon sont KO.
|
||||||
|
|
||||||
|
:return: (bool) True si tous KO, False sinon
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Représentation du dresseur."""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests :**
|
||||||
|
```python
|
||||||
|
>>> sacha = Dresseur("Sacha")
|
||||||
|
>>> sacha.ajouter_pokemon(Pokemon("Pikachu", "Electrik", 25, 80, 15))
|
||||||
|
True
|
||||||
|
>>> sacha.ajouter_pokemon(Pokemon("Dracaufeu", "Feu", 50, 150, 25))
|
||||||
|
True
|
||||||
|
>>> print(sacha)
|
||||||
|
Dresseur Sacha - 2 Pokémon
|
||||||
|
- Pikachu (Electrik) - Nv.25 - 80/80 PV
|
||||||
|
- Dracaufeu (Feu) - Nv.50 - 150/150 PV
|
||||||
|
>>> sacha.tous_ko()
|
||||||
|
False
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : La classe Combat
|
||||||
|
|
||||||
|
### Exercice 5 : Simulateur de combat
|
||||||
|
|
||||||
|
Créez une classe `Combat` qui simule un affrontement entre deux dresseurs.
|
||||||
|
|
||||||
|
**Attributs :**
|
||||||
|
- `dresseur1` : premier dresseur (Dresseur)
|
||||||
|
- `dresseur2` : second dresseur (Dresseur)
|
||||||
|
- `tour` : numéro du tour actuel (int)
|
||||||
|
|
||||||
|
**Méthodes :**
|
||||||
|
- `__init__` : constructeur
|
||||||
|
- `lancer_combat()` : lance le combat jusqu'à ce qu'un dresseur n'ait plus de Pokémon
|
||||||
|
- `afficher_etat()` : affiche l'état actuel du combat
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Combat:
|
||||||
|
def __init__(self, dresseur1, dresseur2):
|
||||||
|
"""
|
||||||
|
Constructeur de la classe Combat.
|
||||||
|
|
||||||
|
:param dresseur1: (Dresseur) Premier dresseur
|
||||||
|
:param dresseur2: (Dresseur) Second dresseur
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def afficher_etat(self):
|
||||||
|
"""Affiche l'état actuel du combat."""
|
||||||
|
print(f"\n{'='*40}")
|
||||||
|
print(f"Tour {self.tour}")
|
||||||
|
print(f"{'='*40}")
|
||||||
|
# Afficher les Pokémon actifs
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def lancer_combat(self):
|
||||||
|
"""
|
||||||
|
Lance le combat tour par tour.
|
||||||
|
Chaque tour, les deux Pokémon actifs s'attaquent.
|
||||||
|
Quand un Pokémon est KO, le suivant prend sa place.
|
||||||
|
Le combat s'arrête quand un dresseur n'a plus de Pokémon.
|
||||||
|
"""
|
||||||
|
print(f"\n*** COMBAT : {self.dresseur1.nom} VS {self.dresseur2.nom} ***\n")
|
||||||
|
|
||||||
|
while not self.dresseur1.tous_ko() and not self.dresseur2.tous_ko():
|
||||||
|
self.tour += 1
|
||||||
|
self.afficher_etat()
|
||||||
|
|
||||||
|
# Récupérer les Pokémon actifs
|
||||||
|
poke1 = self.dresseur1.premier_pokemon_dispo()
|
||||||
|
poke2 = self.dresseur2.premier_pokemon_dispo()
|
||||||
|
|
||||||
|
# Le Pokémon 1 attaque
|
||||||
|
# À compléter
|
||||||
|
|
||||||
|
# Vérifier si Pokémon 2 est KO
|
||||||
|
# À compléter
|
||||||
|
|
||||||
|
# Le Pokémon 2 attaque (s'il n'est pas KO)
|
||||||
|
# À compléter
|
||||||
|
|
||||||
|
# Vérifier si Pokémon 1 est KO
|
||||||
|
# À compléter
|
||||||
|
|
||||||
|
# Afficher le vainqueur
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Exemple d'exécution :**
|
||||||
|
```
|
||||||
|
*** COMBAT : Sacha VS Pierre ***
|
||||||
|
|
||||||
|
========================================
|
||||||
|
Tour 1
|
||||||
|
========================================
|
||||||
|
Sacha: Pikachu (80/80 PV)
|
||||||
|
Pierre: Onix (120/120 PV)
|
||||||
|
|
||||||
|
Pikachu attaque Onix et inflige 15 dégâts !
|
||||||
|
Onix attaque Pikachu et inflige 20 dégâts !
|
||||||
|
|
||||||
|
========================================
|
||||||
|
Tour 2
|
||||||
|
========================================
|
||||||
|
Sacha: Pikachu (60/80 PV)
|
||||||
|
Pierre: Onix (105/120 PV)
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
Pikachu est KO !
|
||||||
|
Sacha envoie Dracaufeu !
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
Tous les Pokémon de Pierre sont KO !
|
||||||
|
*** SACHA REMPORTE LE COMBAT ! ***
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : Améliorations
|
||||||
|
|
||||||
|
### Exercice 6 : Centre Pokémon
|
||||||
|
|
||||||
|
Créez une classe `CentrePokemon` qui permet de soigner une équipe :
|
||||||
|
|
||||||
|
```python
|
||||||
|
class CentrePokemon:
|
||||||
|
def __init__(self, ville):
|
||||||
|
"""
|
||||||
|
:param ville: (str) Ville où se trouve le centre
|
||||||
|
"""
|
||||||
|
self.ville = ville
|
||||||
|
|
||||||
|
def soigner_equipe(self, dresseur):
|
||||||
|
"""
|
||||||
|
Soigne tous les Pokémon d'un dresseur à 100% de leurs PV.
|
||||||
|
|
||||||
|
:param dresseur: (Dresseur) Dresseur dont l'équipe doit être soignée
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Exercice 7 : Capacités spéciales (Pour aller plus loin)
|
||||||
|
|
||||||
|
Créez une classe `Capacite` pour donner des attaques spéciales aux Pokémon :
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Capacite:
|
||||||
|
def __init__(self, nom, puissance, type_capacite, pp_max):
|
||||||
|
"""
|
||||||
|
:param nom: (str) Nom de la capacité
|
||||||
|
:param puissance: (int) Puissance de base
|
||||||
|
:param type_capacite: (str) Type de la capacité
|
||||||
|
:param pp_max: (int) Nombre d'utilisations max
|
||||||
|
"""
|
||||||
|
self.nom = nom
|
||||||
|
self.puissance = puissance
|
||||||
|
self.type_capacite = type_capacite
|
||||||
|
self.pp_max = pp_max
|
||||||
|
self.pp = pp_max
|
||||||
|
```
|
||||||
|
|
||||||
|
Modifiez la classe `Pokemon` pour qu'un Pokémon possède une liste de capacités (max 4) et puisse choisir son attaque.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Résumé des classes
|
||||||
|
|
||||||
|
| Classe | Attributs | Méthodes principales |
|
||||||
|
|--------|-----------|----------------------|
|
||||||
|
| `Pokemon` | nom, type, niveau, pv, attaque | attaquer(), subir_degats(), est_ko() |
|
||||||
|
| `Dresseur` | nom, equipe | ajouter_pokemon(), tous_ko() |
|
||||||
|
| `Combat` | dresseur1, dresseur2, tour | lancer_combat() |
|
||||||
|
| `CentrePokemon` | ville | soigner_equipe() |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code de test final
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Création des Pokémon
|
||||||
|
pikachu = Pokemon("Pikachu", "Electrik", 25, 80, 15)
|
||||||
|
dracaufeu = Pokemon("Dracaufeu", "Feu", 50, 150, 25)
|
||||||
|
tortank = Pokemon("Tortank", "Eau", 45, 140, 22)
|
||||||
|
onix = Pokemon("Onix", "Roche", 30, 120, 18)
|
||||||
|
racaillou = Pokemon("Racaillou", "Roche", 20, 80, 12)
|
||||||
|
|
||||||
|
# Création des dresseurs
|
||||||
|
sacha = Dresseur("Sacha")
|
||||||
|
sacha.ajouter_pokemon(pikachu)
|
||||||
|
sacha.ajouter_pokemon(dracaufeu)
|
||||||
|
sacha.ajouter_pokemon(tortank)
|
||||||
|
|
||||||
|
pierre = Dresseur("Pierre")
|
||||||
|
pierre.ajouter_pokemon(onix)
|
||||||
|
pierre.ajouter_pokemon(racaillou)
|
||||||
|
|
||||||
|
# Lancement du combat
|
||||||
|
combat = Combat(sacha, pierre)
|
||||||
|
combat.lancer_combat()
|
||||||
|
|
||||||
|
# Soins au Centre Pokémon
|
||||||
|
centre = CentrePokemon("Argenta")
|
||||||
|
centre.soigner_equipe(sacha)
|
||||||
|
print(sacha)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -70,7 +70,7 @@ class Bibliotheque :
|
|||||||
#Initialisation des valeurs :
|
#Initialisation des valeurs :
|
||||||
a1 = Auteur('Bob',1990)
|
a1 = Auteur('Bob',1990)
|
||||||
a2 = Auteur('Alice', 1998,2009)
|
a2 = Auteur('Alice', 1998,2009)
|
||||||
l1 = Livre('Livre NSI terminale','Cours','Bob')
|
l1 = Livre('Livre NSI terminale','Cours',a1)
|
||||||
l2 = Livre('Livre NSI premiere','Cours',a2)
|
l2 = Livre('Livre NSI premiere','Cours',a2)
|
||||||
l3 = Livre('Les animaux de compagnies','Animaux',a1)
|
l3 = Livre('Les animaux de compagnies','Animaux',a1)
|
||||||
l4 = Livre('Les animaux sauvages','Animaux',a2)
|
l4 = Livre('Les animaux sauvages','Animaux',a2)
|
||||||
|
|||||||
320
Paradigmes/Corrige_Exercice.md
Normal file
320
Paradigmes/Corrige_Exercice.md
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
# Corrigé de l'exercice — Cuisson des pâtes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Énoncé
|
||||||
|
|
||||||
|
Nous voulons faire cuire des pâtes. Écrire la recette sous forme de code Python selon trois paradigmes différents.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version impérative
|
||||||
|
|
||||||
|
Liste d'instructions étape par étape, avec modification d'état.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Version impérative : liste d'instructions étape par étape
|
||||||
|
|
||||||
|
def cuire_pates_imperatif():
|
||||||
|
"""Fait cuire des pâtes de manière impérative."""
|
||||||
|
|
||||||
|
# État initial
|
||||||
|
eau = "froide"
|
||||||
|
pates = "crues"
|
||||||
|
sel = False
|
||||||
|
|
||||||
|
# Étape 1 : Remplir la casserole
|
||||||
|
print("Remplissage de la casserole...")
|
||||||
|
quantite_eau = 1.5 # litres
|
||||||
|
|
||||||
|
# Étape 2 : Faire chauffer l'eau
|
||||||
|
print("Chauffage de l'eau...")
|
||||||
|
eau = "chaude"
|
||||||
|
|
||||||
|
# Étape 3 : Attendre l'ébullition
|
||||||
|
print("Attente de l'ébullition...")
|
||||||
|
eau = "bouillante"
|
||||||
|
|
||||||
|
# Étape 4 : Saler l'eau
|
||||||
|
print("Ajout du sel...")
|
||||||
|
sel = True
|
||||||
|
|
||||||
|
# Étape 5 : Ajouter les pâtes
|
||||||
|
print("Ajout des pâtes...")
|
||||||
|
pates = "en cuisson"
|
||||||
|
|
||||||
|
# Étape 6 : Attendre le temps de cuisson
|
||||||
|
temps_cuisson = 10 # minutes
|
||||||
|
print(f"Cuisson pendant {temps_cuisson} minutes...")
|
||||||
|
|
||||||
|
# Étape 7 : Égoutter
|
||||||
|
print("Égouttage des pâtes...")
|
||||||
|
pates = "cuites"
|
||||||
|
|
||||||
|
print(f"Résultat : pâtes {pates}")
|
||||||
|
return pates
|
||||||
|
|
||||||
|
|
||||||
|
# Exécution
|
||||||
|
resultat = cuire_pates_imperatif()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Caractéristiques :**
|
||||||
|
- Modification d'état (variables `eau`, `pates`, `sel`)
|
||||||
|
- Instructions séquentielles
|
||||||
|
- Effets de bord (print)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version fonctionnelle
|
||||||
|
|
||||||
|
Une fonction pure qui prend un état et retourne un nouvel état, sans modification.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Version fonctionnelle : fonctions pures et composition
|
||||||
|
|
||||||
|
def remplir_casserole(etat):
|
||||||
|
"""Retourne un nouvel état avec la casserole remplie."""
|
||||||
|
return {**etat, "eau": "froide", "quantite_eau": 1.5}
|
||||||
|
|
||||||
|
|
||||||
|
def chauffer_eau(etat):
|
||||||
|
"""Retourne un nouvel état avec l'eau chauffée."""
|
||||||
|
return {**etat, "eau": "chaude"}
|
||||||
|
|
||||||
|
|
||||||
|
def faire_bouillir(etat):
|
||||||
|
"""Retourne un nouvel état avec l'eau bouillante."""
|
||||||
|
return {**etat, "eau": "bouillante"}
|
||||||
|
|
||||||
|
|
||||||
|
def saler(etat):
|
||||||
|
"""Retourne un nouvel état avec le sel ajouté."""
|
||||||
|
return {**etat, "sel": True}
|
||||||
|
|
||||||
|
|
||||||
|
def ajouter_pates(etat):
|
||||||
|
"""Retourne un nouvel état avec les pâtes en cuisson."""
|
||||||
|
return {**etat, "pates": "en cuisson"}
|
||||||
|
|
||||||
|
|
||||||
|
def cuire(etat, duree):
|
||||||
|
"""Retourne un nouvel état après cuisson."""
|
||||||
|
return {**etat, "temps_cuisson": duree, "pates": "cuites"}
|
||||||
|
|
||||||
|
|
||||||
|
def egoutter(etat):
|
||||||
|
"""Retourne un nouvel état avec les pâtes égouttées."""
|
||||||
|
return {**etat, "egouttees": True}
|
||||||
|
|
||||||
|
|
||||||
|
# Composition de fonctions
|
||||||
|
def cuire_pates_fonctionnel(etat_initial):
|
||||||
|
"""
|
||||||
|
Fait cuire des pâtes de manière fonctionnelle.
|
||||||
|
Chaque étape retourne un nouvel état sans modifier l'ancien.
|
||||||
|
"""
|
||||||
|
etat = remplir_casserole(etat_initial)
|
||||||
|
etat = chauffer_eau(etat)
|
||||||
|
etat = faire_bouillir(etat)
|
||||||
|
etat = saler(etat)
|
||||||
|
etat = ajouter_pates(etat)
|
||||||
|
etat = cuire(etat, 10)
|
||||||
|
etat = egoutter(etat)
|
||||||
|
return etat
|
||||||
|
|
||||||
|
|
||||||
|
# Version encore plus fonctionnelle avec reduce
|
||||||
|
import functools
|
||||||
|
|
||||||
|
def cuire_pates_reduce(etat_initial):
|
||||||
|
"""Version avec reduce pour composer les fonctions."""
|
||||||
|
etapes = [
|
||||||
|
remplir_casserole,
|
||||||
|
chauffer_eau,
|
||||||
|
faire_bouillir,
|
||||||
|
saler,
|
||||||
|
ajouter_pates,
|
||||||
|
lambda e: cuire(e, 10),
|
||||||
|
egoutter
|
||||||
|
]
|
||||||
|
return functools.reduce(lambda etat, f: f(etat), etapes, etat_initial)
|
||||||
|
|
||||||
|
|
||||||
|
# Exécution
|
||||||
|
etat_initial = {"pates": "crues", "sel": False}
|
||||||
|
resultat = cuire_pates_fonctionnel(etat_initial)
|
||||||
|
print(f"État final : {resultat}")
|
||||||
|
|
||||||
|
# Vérification : l'état initial n'a pas été modifié
|
||||||
|
print(f"État initial préservé : {etat_initial}")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Caractéristiques :**
|
||||||
|
- Pas de modification d'état (immutabilité)
|
||||||
|
- Fonctions pures (même entrée → même sortie)
|
||||||
|
- Composition de fonctions
|
||||||
|
- L'état initial n'est jamais modifié
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version orientée objet
|
||||||
|
|
||||||
|
Une classe `Pates` avec des attributs et une méthode `cuire()`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Version orientée objet : classe Pates avec méthode cuire()
|
||||||
|
|
||||||
|
class Casserole:
|
||||||
|
"""Représente une casserole pour la cuisson."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.eau = None
|
||||||
|
self.temperature = "froide"
|
||||||
|
self.sel = False
|
||||||
|
|
||||||
|
def remplir(self, quantite=1.5):
|
||||||
|
"""Remplit la casserole d'eau."""
|
||||||
|
self.eau = quantite
|
||||||
|
print(f"Casserole remplie avec {quantite}L d'eau")
|
||||||
|
|
||||||
|
def chauffer(self):
|
||||||
|
"""Chauffe l'eau jusqu'à ébullition."""
|
||||||
|
self.temperature = "chaude"
|
||||||
|
print("Chauffage en cours...")
|
||||||
|
self.temperature = "bouillante"
|
||||||
|
print("L'eau bout !")
|
||||||
|
|
||||||
|
def saler(self):
|
||||||
|
"""Ajoute du sel dans l'eau."""
|
||||||
|
self.sel = True
|
||||||
|
print("Sel ajouté")
|
||||||
|
|
||||||
|
|
||||||
|
class Pates:
|
||||||
|
"""Représente des pâtes à cuire."""
|
||||||
|
|
||||||
|
def __init__(self, type_pates="spaghetti", quantite=500):
|
||||||
|
self.type = type_pates
|
||||||
|
self.quantite = quantite # en grammes
|
||||||
|
self.etat = "crues"
|
||||||
|
self.temps_cuisson = 0
|
||||||
|
|
||||||
|
def cuire(self, casserole, duree=10):
|
||||||
|
"""
|
||||||
|
Fait cuire les pâtes dans une casserole.
|
||||||
|
|
||||||
|
:param casserole: (Casserole) la casserole à utiliser
|
||||||
|
:param duree: (int) temps de cuisson en minutes
|
||||||
|
"""
|
||||||
|
# Vérifications
|
||||||
|
if casserole.eau is None:
|
||||||
|
raise ValueError("La casserole doit contenir de l'eau !")
|
||||||
|
if casserole.temperature != "bouillante":
|
||||||
|
raise ValueError("L'eau doit être bouillante !")
|
||||||
|
|
||||||
|
print(f"Ajout de {self.quantite}g de {self.type} dans la casserole")
|
||||||
|
self.etat = "en cuisson"
|
||||||
|
|
||||||
|
print(f"Cuisson pendant {duree} minutes...")
|
||||||
|
self.temps_cuisson = duree
|
||||||
|
|
||||||
|
self.etat = "cuites"
|
||||||
|
print(f"Les {self.type} sont cuites !")
|
||||||
|
|
||||||
|
def egoutter(self):
|
||||||
|
"""Égoutte les pâtes."""
|
||||||
|
if self.etat != "cuites":
|
||||||
|
raise ValueError("Les pâtes doivent être cuites avant d'être égouttées !")
|
||||||
|
print("Égouttage des pâtes...")
|
||||||
|
self.etat = "prêtes"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Pates({self.type}, {self.quantite}g, état={self.etat})"
|
||||||
|
|
||||||
|
|
||||||
|
# Exécution
|
||||||
|
def preparer_pates_oo():
|
||||||
|
"""Prépare des pâtes en utilisant la POO."""
|
||||||
|
|
||||||
|
# Création des objets
|
||||||
|
casserole = Casserole()
|
||||||
|
pates = Pates("fusilli", 400)
|
||||||
|
|
||||||
|
print(f"Début : {pates}")
|
||||||
|
|
||||||
|
# Préparation de la casserole
|
||||||
|
casserole.remplir(2.0)
|
||||||
|
casserole.chauffer()
|
||||||
|
casserole.saler()
|
||||||
|
|
||||||
|
# Cuisson des pâtes
|
||||||
|
pates.cuire(casserole, duree=12)
|
||||||
|
pates.egoutter()
|
||||||
|
|
||||||
|
print(f"Fin : {pates}")
|
||||||
|
return pates
|
||||||
|
|
||||||
|
|
||||||
|
resultat = preparer_pates_oo()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Caractéristiques :**
|
||||||
|
- Encapsulation (attributs et méthodes regroupés)
|
||||||
|
- Objets avec état interne
|
||||||
|
- Interactions entre objets (Pates et Casserole)
|
||||||
|
- Méthodes qui modifient l'état de l'objet
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tableau comparatif
|
||||||
|
|
||||||
|
| Aspect | Impératif | Fonctionnel | Orienté Objet |
|
||||||
|
|--------|-----------|-------------|---------------|
|
||||||
|
| **État** | Variables modifiées | États immuables | Attributs d'objets |
|
||||||
|
| **Données** | Variables simples | Dictionnaires/tuples | Objets |
|
||||||
|
| **Actions** | Instructions séquentielles | Fonctions composées | Méthodes |
|
||||||
|
| **Modification** | Sur place | Nouvel état retourné | Via méthodes |
|
||||||
|
| **Réutilisabilité** | Faible | Moyenne | Forte |
|
||||||
|
| **Testabilité** | Difficile | Facile | Moyenne |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bonus : Version lambda
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Version ultra-fonctionnelle avec lambdas et reduce
|
||||||
|
|
||||||
|
import functools
|
||||||
|
|
||||||
|
# Chaque étape est une lambda qui transforme l'état
|
||||||
|
etapes = [
|
||||||
|
lambda e: {**e, "eau": 1.5},
|
||||||
|
lambda e: {**e, "temperature": "bouillante"},
|
||||||
|
lambda e: {**e, "sel": True},
|
||||||
|
lambda e: {**e, "pates": "en cuisson"},
|
||||||
|
lambda e: {**e, "temps": 10, "pates": "cuites"},
|
||||||
|
lambda e: {**e, "egouttees": True}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Composition avec reduce
|
||||||
|
cuire_pates_lambda = lambda etat: functools.reduce(
|
||||||
|
lambda e, f: f(e),
|
||||||
|
etapes,
|
||||||
|
etat
|
||||||
|
)
|
||||||
|
|
||||||
|
# Exécution
|
||||||
|
resultat = cuire_pates_lambda({"pates": "crues"})
|
||||||
|
print(resultat)
|
||||||
|
# {'pates': 'cuites', 'eau': 1.5, 'temperature': 'bouillante',
|
||||||
|
# 'sel': True, 'temps': 10, 'egouttees': True}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
433
Paradigmes/Corrige_TP_SpotifyWrapped.md
Normal file
433
Paradigmes/Corrige_TP_SpotifyWrapped.md
Normal file
@@ -0,0 +1,433 @@
|
|||||||
|
# Corrigé du TP Spotify Wrapped
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : Fonctions de base
|
||||||
|
|
||||||
|
### Exercice 1 : Extraction de données avec `map`
|
||||||
|
|
||||||
|
**Question 1.1** :
|
||||||
|
```python
|
||||||
|
get_titre = lambda ecoute: ecoute["titre"]
|
||||||
|
|
||||||
|
titres = list(map(get_titre, ecoutes))
|
||||||
|
print(titres[:3]) # ['Blinding Lights', 'Bohemian Rhapsody', 'Bad Guy']
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 1.2** :
|
||||||
|
```python
|
||||||
|
get_duree_minutes = lambda ecoute: round(ecoute["duree"] / 60, 2)
|
||||||
|
|
||||||
|
durees = list(map(get_duree_minutes, ecoutes))
|
||||||
|
print(durees[:3]) # [3.38, 5.9, 3.23]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 1.3** :
|
||||||
|
```python
|
||||||
|
formater_ecoute = lambda e: f"{e['titre']} - {e['artiste']} ({round(e['duree']/60, 2)}min)"
|
||||||
|
|
||||||
|
formatees = list(map(formater_ecoute, ecoutes))
|
||||||
|
print(formatees[0]) # "Blinding Lights - The Weeknd (3.38min)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Exercice 2 : Filtrage avec `filter`
|
||||||
|
|
||||||
|
**Question 2.1** :
|
||||||
|
```python
|
||||||
|
est_pop = lambda ecoute: ecoute["genre"] == "pop"
|
||||||
|
|
||||||
|
ecoutes_pop = list(filter(est_pop, ecoutes))
|
||||||
|
print(f"Nombre de morceaux pop : {len(ecoutes_pop)}") # 12
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 2.2** :
|
||||||
|
```python
|
||||||
|
est_long = lambda ecoute: ecoute["duree"] > 300
|
||||||
|
|
||||||
|
ecoutes_longues = list(filter(est_long, ecoutes))
|
||||||
|
print(f"Morceaux longs : {len(ecoutes_longues)}") # 5
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 2.3** :
|
||||||
|
```python
|
||||||
|
def filtre_genre(genre):
|
||||||
|
"""
|
||||||
|
Retourne une fonction lambda qui filtre par genre.
|
||||||
|
C'est une fonction d'ordre supérieur !
|
||||||
|
"""
|
||||||
|
return lambda ecoute: ecoute["genre"] == genre
|
||||||
|
|
||||||
|
ecoutes_rock = list(filter(filtre_genre("rock"), ecoutes))
|
||||||
|
ecoutes_rap = list(filter(filtre_genre("rap"), ecoutes))
|
||||||
|
print(f"Rock: {len(ecoutes_rock)}, Rap: {len(ecoutes_rap)}") # Rock: 4, Rap: 2
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Exercice 3 : Agrégation avec `reduce`
|
||||||
|
|
||||||
|
```python
|
||||||
|
import functools
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 3.1** :
|
||||||
|
```python
|
||||||
|
temps_total = functools.reduce(
|
||||||
|
lambda acc, ecoute: acc + ecoute["duree"],
|
||||||
|
ecoutes,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
print(f"Temps total : {temps_total} secondes") # 5301 secondes
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 3.2** :
|
||||||
|
```python
|
||||||
|
heures = temps_total // 3600
|
||||||
|
minutes = (temps_total % 3600) // 60
|
||||||
|
print(f"Temps d'écoute : {heures}h {minutes}min") # 1h 28min
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 3.3** :
|
||||||
|
```python
|
||||||
|
plus_long = functools.reduce(
|
||||||
|
lambda acc, ecoute: ecoute if ecoute["duree"] > acc["duree"] else acc,
|
||||||
|
ecoutes
|
||||||
|
)
|
||||||
|
print(f"Plus long : {plus_long['titre']} ({plus_long['duree']}s)")
|
||||||
|
# Plus long : Stairway to Heaven (482s)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : Statistiques avancées
|
||||||
|
|
||||||
|
### Exercice 4 : Comptage par catégorie
|
||||||
|
|
||||||
|
**Question 4.1** : (fournie dans l'énoncé)
|
||||||
|
```python
|
||||||
|
def compter_par_genre(ecoutes):
|
||||||
|
return functools.reduce(
|
||||||
|
lambda acc, e: {**acc, e["genre"]: acc.get(e["genre"], 0) + 1},
|
||||||
|
ecoutes,
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
stats_genres = compter_par_genre(ecoutes)
|
||||||
|
print(stats_genres) # {'pop': 12, 'rock': 4, 'rap': 2, 'metal': 2}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 4.2** :
|
||||||
|
```python
|
||||||
|
def compter_par_artiste(ecoutes):
|
||||||
|
"""Retourne un dictionnaire {artiste: nombre_ecoutes}."""
|
||||||
|
return functools.reduce(
|
||||||
|
lambda acc, e: {**acc, e["artiste"]: acc.get(e["artiste"], 0) + 1},
|
||||||
|
ecoutes,
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
stats_artistes = compter_par_artiste(ecoutes)
|
||||||
|
print(stats_artistes)
|
||||||
|
# {'The Weeknd': 3, 'Queen': 1, 'Billie Eilish': 1, 'Led Zeppelin': 1, ...}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 4.3** :
|
||||||
|
```python
|
||||||
|
def artiste_prefere(stats_artistes):
|
||||||
|
"""Retourne le nom de l'artiste avec le plus d'écoutes."""
|
||||||
|
return functools.reduce(
|
||||||
|
lambda acc, item: item if item[1] > acc[1] else acc,
|
||||||
|
stats_artistes.items()
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
top_artiste = artiste_prefere(stats_artistes)
|
||||||
|
print(f"Artiste préféré : {top_artiste}") # The Weeknd
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Exercice 5 : Chaînage fonctionnel (Pipeline)
|
||||||
|
|
||||||
|
**Question 5.1** : (fournie dans l'énoncé)
|
||||||
|
```python
|
||||||
|
temps_pop = functools.reduce(
|
||||||
|
lambda acc, e: acc + e["duree"],
|
||||||
|
filter(lambda e: e["genre"] == "pop", ecoutes),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
print(f"Temps pop : {temps_pop // 60} minutes") # 42 minutes
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 5.2** : (fournie dans l'énoncé)
|
||||||
|
```python
|
||||||
|
titres_rock_longs = list(map(
|
||||||
|
lambda e: e["titre"],
|
||||||
|
sorted(
|
||||||
|
filter(
|
||||||
|
lambda e: e["duree"] > 240,
|
||||||
|
filter(
|
||||||
|
lambda e: e["genre"] == "rock",
|
||||||
|
ecoutes
|
||||||
|
)
|
||||||
|
),
|
||||||
|
key=lambda e: e["duree"],
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
))
|
||||||
|
print(titres_rock_longs)
|
||||||
|
# ['Stairway to Heaven', 'Bohemian Rhapsody', 'Smells Like Teen Spirit']
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 5.3** : (fournie dans l'énoncé)
|
||||||
|
```python
|
||||||
|
def pipeline(*fonctions):
|
||||||
|
"""Retourne une fonction qui applique les fonctions en séquence."""
|
||||||
|
return lambda x: functools.reduce(
|
||||||
|
lambda acc, f: f(acc),
|
||||||
|
fonctions,
|
||||||
|
x
|
||||||
|
)
|
||||||
|
|
||||||
|
traitement = pipeline(
|
||||||
|
lambda data: filter(lambda e: e["genre"] == "pop", data),
|
||||||
|
lambda data: map(lambda e: e["titre"], data),
|
||||||
|
list
|
||||||
|
)
|
||||||
|
|
||||||
|
print(traitement(ecoutes)[:3]) # ['Blinding Lights', 'Bad Guy', 'Shape of You']
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : Génération du Wrapped
|
||||||
|
|
||||||
|
### Exercice 6 : Créer le résumé final
|
||||||
|
|
||||||
|
```python
|
||||||
|
import functools
|
||||||
|
|
||||||
|
def compter_par_artiste(ecoutes):
|
||||||
|
return functools.reduce(
|
||||||
|
lambda acc, e: {**acc, e["artiste"]: acc.get(e["artiste"], 0) + 1},
|
||||||
|
ecoutes,
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
def compter_par_genre(ecoutes):
|
||||||
|
return functools.reduce(
|
||||||
|
lambda acc, e: {**acc, e["genre"]: acc.get(e["genre"], 0) + 1},
|
||||||
|
ecoutes,
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
def generer_wrapped(ecoutes):
|
||||||
|
"""
|
||||||
|
Génère un résumé Wrapped complet de manière fonctionnelle.
|
||||||
|
"""
|
||||||
|
# Temps total
|
||||||
|
temps_total = functools.reduce(lambda acc, e: acc + e["duree"], ecoutes, 0)
|
||||||
|
|
||||||
|
# Comptages
|
||||||
|
stats_artistes = compter_par_artiste(ecoutes)
|
||||||
|
stats_genres = compter_par_genre(ecoutes)
|
||||||
|
|
||||||
|
# Top artiste (celui avec le plus d'écoutes)
|
||||||
|
top_artiste = functools.reduce(
|
||||||
|
lambda acc, item: item if item[1] > acc[1] else acc,
|
||||||
|
stats_artistes.items()
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
# Top genre
|
||||||
|
top_genre = functools.reduce(
|
||||||
|
lambda acc, item: item if item[1] > acc[1] else acc,
|
||||||
|
stats_genres.items()
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
# Morceau le plus écouté (celui qui apparaît le plus)
|
||||||
|
stats_titres = functools.reduce(
|
||||||
|
lambda acc, e: {**acc, e["titre"]: acc.get(e["titre"], 0) + 1},
|
||||||
|
ecoutes,
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
top_titre = functools.reduce(
|
||||||
|
lambda acc, item: item if item[1] > acc[1] else acc,
|
||||||
|
stats_titres.items()
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
# Morceau le plus long
|
||||||
|
plus_long = functools.reduce(
|
||||||
|
lambda acc, e: e if e["duree"] > acc["duree"] else acc,
|
||||||
|
ecoutes
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"nombre_ecoutes": len(ecoutes),
|
||||||
|
"temps_total_minutes": round(temps_total / 60, 1),
|
||||||
|
"artiste_prefere": top_artiste,
|
||||||
|
"genre_prefere": top_genre,
|
||||||
|
"titre_prefere": top_titre,
|
||||||
|
"morceau_plus_long": plus_long["titre"],
|
||||||
|
"stats_genres": stats_genres,
|
||||||
|
"top_5_artistes": dict(sorted(
|
||||||
|
stats_artistes.items(),
|
||||||
|
key=lambda x: x[1],
|
||||||
|
reverse=True
|
||||||
|
)[:5])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Exécution
|
||||||
|
wrapped = generer_wrapped(ecoutes)
|
||||||
|
|
||||||
|
print("=" * 40)
|
||||||
|
print(" VOTRE WRAPPED 2026")
|
||||||
|
print("=" * 40)
|
||||||
|
print(f"Écoutes totales : {wrapped['nombre_ecoutes']}")
|
||||||
|
print(f"Temps d'écoute : {wrapped['temps_total_minutes']} minutes")
|
||||||
|
print(f"Artiste préféré : {wrapped['artiste_prefere']}")
|
||||||
|
print(f"Genre préféré : {wrapped['genre_prefere']}")
|
||||||
|
print(f"Titre préféré : {wrapped['titre_prefere']}")
|
||||||
|
print(f"Top 5 artistes : {list(wrapped['top_5_artistes'].keys())}")
|
||||||
|
print("=" * 40)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sortie attendue :**
|
||||||
|
```
|
||||||
|
========================================
|
||||||
|
VOTRE WRAPPED 2026
|
||||||
|
========================================
|
||||||
|
Écoutes totales : 20
|
||||||
|
Temps d'écoute : 88.4 minutes
|
||||||
|
Artiste préféré : The Weeknd
|
||||||
|
Genre préféré : pop
|
||||||
|
Titre préféré : Blinding Lights
|
||||||
|
Top 5 artistes : ['The Weeknd', 'Michael Jackson', 'Metallica', 'Queen', 'Billie Eilish']
|
||||||
|
========================================
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 5 : Bonus — Récursivité
|
||||||
|
|
||||||
|
### Exercice 7 : Implémenter reduce sans boucle
|
||||||
|
|
||||||
|
```python
|
||||||
|
def mon_reduce(fonction, liste, initial):
|
||||||
|
"""
|
||||||
|
Implémentation récursive de reduce.
|
||||||
|
|
||||||
|
:param fonction: (callable) fonction à 2 arguments (acc, elem)
|
||||||
|
:param liste: (list) liste à réduire
|
||||||
|
:param initial: valeur initiale de l'accumulateur
|
||||||
|
:return: résultat de la réduction
|
||||||
|
"""
|
||||||
|
if len(liste) == 0:
|
||||||
|
# Cas de base : liste vide, on retourne l'accumulateur
|
||||||
|
return initial
|
||||||
|
else:
|
||||||
|
# Appel récursif : on applique la fonction au premier élément
|
||||||
|
# puis on continue avec le reste de la liste
|
||||||
|
nouvel_acc = fonction(initial, liste[0])
|
||||||
|
return mon_reduce(fonction, liste[1:], nouvel_acc)
|
||||||
|
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
total = mon_reduce(lambda acc, x: acc + x, [1, 2, 3, 4, 5], 0)
|
||||||
|
print(f"Somme : {total}") # 15
|
||||||
|
|
||||||
|
produit = mon_reduce(lambda acc, x: acc * x, [1, 2, 3, 4, 5], 1)
|
||||||
|
print(f"Produit : {produit}") # 120
|
||||||
|
|
||||||
|
concatenation = mon_reduce(lambda acc, x: acc + x, ["a", "b", "c"], "")
|
||||||
|
print(f"Concaténation : {concatenation}") # "abc"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Explication de la récursivité :**
|
||||||
|
|
||||||
|
Pour `mon_reduce(lambda acc, x: acc + x, [1, 2, 3], 0)` :
|
||||||
|
|
||||||
|
```
|
||||||
|
mon_reduce(f, [1, 2, 3], 0)
|
||||||
|
→ nouvel_acc = f(0, 1) = 1
|
||||||
|
→ mon_reduce(f, [2, 3], 1)
|
||||||
|
→ nouvel_acc = f(1, 2) = 3
|
||||||
|
→ mon_reduce(f, [3], 3)
|
||||||
|
→ nouvel_acc = f(3, 3) = 6
|
||||||
|
→ mon_reduce(f, [], 6)
|
||||||
|
→ return 6 (cas de base)
|
||||||
|
→ return 6
|
||||||
|
→ return 6
|
||||||
|
→ return 6
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code complet fonctionnel
|
||||||
|
|
||||||
|
```python
|
||||||
|
"""
|
||||||
|
Spotify Wrapped - Version fonctionnelle complète
|
||||||
|
"""
|
||||||
|
|
||||||
|
import functools
|
||||||
|
|
||||||
|
# Données
|
||||||
|
ecoutes = [
|
||||||
|
{"titre": "Blinding Lights", "artiste": "The Weeknd", "duree": 203, "genre": "pop", "date": "2026-01-15"},
|
||||||
|
{"titre": "Bohemian Rhapsody", "artiste": "Queen", "duree": 354, "genre": "rock", "date": "2026-01-15"},
|
||||||
|
{"titre": "Bad Guy", "artiste": "Billie Eilish", "duree": 194, "genre": "pop", "date": "2026-01-16"},
|
||||||
|
{"titre": "Stairway to Heaven", "artiste": "Led Zeppelin", "duree": 482, "genre": "rock", "date": "2026-01-16"},
|
||||||
|
{"titre": "Shape of You", "artiste": "Ed Sheeran", "duree": 234, "genre": "pop", "date": "2026-01-17"},
|
||||||
|
{"titre": "Smells Like Teen Spirit", "artiste": "Nirvana", "duree": 279, "genre": "rock", "date": "2026-01-17"},
|
||||||
|
{"titre": "Levitating", "artiste": "Dua Lipa", "duree": 203, "genre": "pop", "date": "2026-01-18"},
|
||||||
|
{"titre": "Back in Black", "artiste": "AC/DC", "duree": 255, "genre": "rock", "date": "2026-01-18"},
|
||||||
|
{"titre": "Blinding Lights", "artiste": "The Weeknd", "duree": 203, "genre": "pop", "date": "2026-01-19"},
|
||||||
|
{"titre": "drivers license", "artiste": "Olivia Rodrigo", "duree": 242, "genre": "pop", "date": "2026-01-19"},
|
||||||
|
{"titre": "Lose Yourself", "artiste": "Eminem", "duree": 326, "genre": "rap", "date": "2026-01-20"},
|
||||||
|
{"titre": "HUMBLE.", "artiste": "Kendrick Lamar", "duree": 177, "genre": "rap", "date": "2026-01-20"},
|
||||||
|
{"titre": "Blinding Lights", "artiste": "The Weeknd", "duree": 203, "genre": "pop", "date": "2026-01-21"},
|
||||||
|
{"titre": "Watermelon Sugar", "artiste": "Harry Styles", "duree": 174, "genre": "pop", "date": "2026-01-21"},
|
||||||
|
{"titre": "Enter Sandman", "artiste": "Metallica", "duree": 332, "genre": "metal", "date": "2026-01-22"},
|
||||||
|
{"titre": "Nothing Else Matters", "artiste": "Metallica", "duree": 388, "genre": "metal", "date": "2026-01-22"},
|
||||||
|
{"titre": "Thriller", "artiste": "Michael Jackson", "duree": 358, "genre": "pop", "date": "2026-01-23"},
|
||||||
|
{"titre": "Billie Jean", "artiste": "Michael Jackson", "duree": 294, "genre": "pop", "date": "2026-01-23"},
|
||||||
|
{"titre": "Anti-Hero", "artiste": "Taylor Swift", "duree": 200, "genre": "pop", "date": "2026-01-24"},
|
||||||
|
{"titre": "Flowers", "artiste": "Miley Cyrus", "duree": 200, "genre": "pop", "date": "2026-01-24"},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Fonctions utilitaires
|
||||||
|
compter = lambda cle: lambda data: functools.reduce(
|
||||||
|
lambda acc, e: {**acc, e[cle]: acc.get(e[cle], 0) + 1},
|
||||||
|
data,
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
top = lambda stats: functools.reduce(
|
||||||
|
lambda acc, item: item if item[1] > acc[1] else acc,
|
||||||
|
stats.items()
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
# Pipeline de génération du Wrapped
|
||||||
|
generer_wrapped = lambda data: {
|
||||||
|
"nombre_ecoutes": len(data),
|
||||||
|
"temps_total_minutes": round(functools.reduce(lambda a, e: a + e["duree"], data, 0) / 60, 1),
|
||||||
|
"artiste_prefere": top(compter("artiste")(data)),
|
||||||
|
"genre_prefere": top(compter("genre")(data)),
|
||||||
|
"titre_prefere": top(compter("titre")(data)),
|
||||||
|
"morceau_plus_long": functools.reduce(lambda a, e: e if e["duree"] > a["duree"] else a, data)["titre"],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Exécution
|
||||||
|
wrapped = generer_wrapped(ecoutes)
|
||||||
|
print(wrapped)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
## Paradigmes de programmation
|
# Paradigmes de programmation
|
||||||
|
|
||||||
> Un paradigme est à la programmation ce qu’un style culinaire est à la cuisine : il impose des règles, des outils, et une manière de faire.
|
> Un paradigme est à la programmation ce qu’un style culinaire est à la cuisine : il impose des règles, des outils, et une manière de faire.
|
||||||
|
|
||||||
@@ -19,14 +19,14 @@ On peut regrouper les différents paradigmes en 2 catégories :
|
|||||||
- la programmation **impérative** ;
|
- la programmation **impérative** ;
|
||||||
- la programmation **déclarative**.
|
- la programmation **déclarative**.
|
||||||
|
|
||||||
Lorsque l'on utilise la programmation impérative, on décrit le comment / la solution. On la retrouve dans plusieurs langage :
|
Lorsque l'on utilise la programmation impérative, on décrit le comment / la solution. On la retrouve dans plusieurs langages :
|
||||||
|
|
||||||
- Structurée (FORTRAN, C)
|
- Structurée (FORTRAN, C)
|
||||||
- Procédurale (FORTRAN, C)
|
- Procédurale (FORTRAN, C)
|
||||||
- Objet (C++, Java, Python)
|
- Objet (C++, Java, Python)
|
||||||
- Modulaire (C++, Java, Python)
|
- Modulaire (C++, Java, Python)
|
||||||
|
|
||||||
Lorsque l'on utilise la programmation déclarative, on décrit le quoi / le problème. On la retrouve dans plusieurs langage :
|
Lorsque l'on utilise la programmation déclarative, on décrit le quoi / le problème. On la retrouve dans plusieurs langages :
|
||||||
|
|
||||||
- Descriptif (HTML, SQL)
|
- Descriptif (HTML, SQL)
|
||||||
- Fonctionnel (LISP, CAML)
|
- Fonctionnel (LISP, CAML)
|
||||||
@@ -40,7 +40,7 @@ Lorsque l'on utilise la programmation déclarative, on décrit le quoi / le prob
|
|||||||
|
|
||||||
La programmation impérative, bien que versatile, conduit rapidement à des programmes difficiles à comprendre et à maintenir, où la démonstration de la correction est impossible.
|
La programmation impérative, bien que versatile, conduit rapidement à des programmes difficiles à comprendre et à maintenir, où la démonstration de la correction est impossible.
|
||||||
|
|
||||||
Ceci est du au fait qu’un traitement de données :
|
Ceci est dû au fait qu'un traitement de données :
|
||||||
|
|
||||||
- dépend de paramètres extérieurs non maîtrisés : variables ;
|
- dépend de paramètres extérieurs non maîtrisés : variables ;
|
||||||
- modifie les données, ou d’autres : affectation ;
|
- modifie les données, ou d’autres : affectation ;
|
||||||
@@ -109,7 +109,7 @@ Une fonction à effet de bord est une fonction qui modifie ou utilise une variab
|
|||||||
|
|
||||||
**__À n’utiliser que lorsque cela est nécessaire !__**
|
**__À n’utiliser que lorsque cela est nécessaire !__**
|
||||||
|
|
||||||
Les effets de bord peuvent être évité facilement grâce au principe de localité des variables et la programmation fonctionnelle.
|
Les effets de bord peuvent être évités facilement grâce au principe de localité des variables et la programmation fonctionnelle.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Fonction avec effets de bord :
|
# Fonction avec effets de bord :
|
||||||
@@ -130,8 +130,9 @@ print("Avec k=3 : ", ajoute_k_a_n(3))
|
|||||||
def ajoute_k_a_n(n, k):
|
def ajoute_k_a_n(n, k):
|
||||||
return n + k
|
return n + k
|
||||||
|
|
||||||
print("Avec k=2 : ", ajoute_k_a_n(2))
|
n = 2
|
||||||
print("Avec k=3 : ", ajoute_k_a_n(3))
|
print("Avec k=2 : ", ajoute_k_a_n(n, 2))
|
||||||
|
print("Avec k=3 : ", ajoute_k_a_n(n, 3))
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Fonctions lambdas
|
#### Fonctions lambdas
|
||||||
@@ -144,7 +145,7 @@ Tout comme la machine de Turing, le λ-calcul peut représenter n’importe quel
|
|||||||
|
|
||||||
La programmation **fonctionnelle** puise son origine du __λ-calcul__ alors que la programmation **impérative** puise son origine de la __machine de Turing__.
|
La programmation **fonctionnelle** puise son origine du __λ-calcul__ alors que la programmation **impérative** puise son origine de la __machine de Turing__.
|
||||||
|
|
||||||
En python la lambda fonction se définie à l'aide du mot clé **lambda** :
|
En Python, la fonction lambda se définit à l'aide du mot-clé **lambda** :
|
||||||
|
|
||||||
```python
|
```python
|
||||||
add_a_b = lambda a,b: a + b
|
add_a_b = lambda a,b: a + b
|
||||||
@@ -191,7 +192,7 @@ maximum = functools.reduce(f_maximum, liste)
|
|||||||
|
|
||||||
__Le filtrage__
|
__Le filtrage__
|
||||||
|
|
||||||
Sélection les éléments d’une liste vérifiant un certain critère.
|
Sélectionne les éléments d'une liste vérifiant un certain critère.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
liste = [1, 3, 5, 6, 2]
|
liste = [1, 3, 5, 6, 2]
|
||||||
@@ -245,7 +246,7 @@ Voici une tâche simple : nous voulons faire cuire des pâtes.
|
|||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
Auteurs : Florian Mathieu, Timothée Decoster, Enzo Frémaux
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
Licence CC BY NC
|
Licence CC BY NC
|
||||||
|
|
||||||
|
|||||||
413
Paradigmes/TP_SpotifyWrapped.md
Normal file
413
Paradigmes/TP_SpotifyWrapped.md
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
# TP : Créer son Spotify Wrapped — Programmation Fonctionnelle
|
||||||
|
|
||||||
|
> **Thème** : Paradigmes de programmation, fonctions lambda, map, filter, reduce
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Chaque fin d'année, **Spotify Wrapped** révèle aux utilisateurs leurs statistiques d'écoute : artistes préférés, genres favoris, minutes écoutées... Ce résumé personnalisé est devenu un phénomène viral sur les réseaux sociaux.
|
||||||
|
|
||||||
|
Dans ce TP, vous allez créer votre propre **mini Wrapped** en utilisant exclusivement la **programmation fonctionnelle**. Vous découvrirez comment les géants du streaming traitent des milliards de données grâce à ce paradigme.
|
||||||
|
|
||||||
|
**Règle d'or du TP** : Aucune boucle `for` ou `while` n'est autorisée ! Uniquement `map`, `filter`, `reduce` et la récursivité.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 1 : Les données
|
||||||
|
|
||||||
|
### Les écoutes
|
||||||
|
|
||||||
|
Voici un extrait de l'historique d'écoute d'un utilisateur. Chaque écoute est représentée par un dictionnaire.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Données d'écoute (à copier dans votre fichier)
|
||||||
|
ecoutes = [
|
||||||
|
{"titre": "Blinding Lights", "artiste": "The Weeknd", "duree": 203, "genre": "pop", "date": "2026-01-15"},
|
||||||
|
{"titre": "Bohemian Rhapsody", "artiste": "Queen", "duree": 354, "genre": "rock", "date": "2026-01-15"},
|
||||||
|
{"titre": "Bad Guy", "artiste": "Billie Eilish", "duree": 194, "genre": "pop", "date": "2026-01-16"},
|
||||||
|
{"titre": "Stairway to Heaven", "artiste": "Led Zeppelin", "duree": 482, "genre": "rock", "date": "2026-01-16"},
|
||||||
|
{"titre": "Shape of You", "artiste": "Ed Sheeran", "duree": 234, "genre": "pop", "date": "2026-01-17"},
|
||||||
|
{"titre": "Smells Like Teen Spirit", "artiste": "Nirvana", "duree": 279, "genre": "rock", "date": "2026-01-17"},
|
||||||
|
{"titre": "Levitating", "artiste": "Dua Lipa", "duree": 203, "genre": "pop", "date": "2026-01-18"},
|
||||||
|
{"titre": "Back in Black", "artiste": "AC/DC", "duree": 255, "genre": "rock", "date": "2026-01-18"},
|
||||||
|
{"titre": "Blinding Lights", "artiste": "The Weeknd", "duree": 203, "genre": "pop", "date": "2026-01-19"},
|
||||||
|
{"titre": "drivers license", "artiste": "Olivia Rodrigo", "duree": 242, "genre": "pop", "date": "2026-01-19"},
|
||||||
|
{"titre": "Lose Yourself", "artiste": "Eminem", "duree": 326, "genre": "rap", "date": "2026-01-20"},
|
||||||
|
{"titre": "HUMBLE.", "artiste": "Kendrick Lamar", "duree": 177, "genre": "rap", "date": "2026-01-20"},
|
||||||
|
{"titre": "Blinding Lights", "artiste": "The Weeknd", "duree": 203, "genre": "pop", "date": "2026-01-21"},
|
||||||
|
{"titre": "Watermelon Sugar", "artiste": "Harry Styles", "duree": 174, "genre": "pop", "date": "2026-01-21"},
|
||||||
|
{"titre": "Enter Sandman", "artiste": "Metallica", "duree": 332, "genre": "metal", "date": "2026-01-22"},
|
||||||
|
{"titre": "Nothing Else Matters", "artiste": "Metallica", "duree": 388, "genre": "metal", "date": "2026-01-22"},
|
||||||
|
{"titre": "Thriller", "artiste": "Michael Jackson", "duree": 358, "genre": "pop", "date": "2026-01-23"},
|
||||||
|
{"titre": "Billie Jean", "artiste": "Michael Jackson", "duree": 294, "genre": "pop", "date": "2026-01-23"},
|
||||||
|
{"titre": "Anti-Hero", "artiste": "Taylor Swift", "duree": 200, "genre": "pop", "date": "2026-01-24"},
|
||||||
|
{"titre": "Flowers", "artiste": "Miley Cyrus", "duree": 200, "genre": "pop", "date": "2026-01-24"},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : Fonctions de base
|
||||||
|
|
||||||
|
### Exercice 1 : Extraction de données avec `map`
|
||||||
|
|
||||||
|
La fonction `map(fonction, liste)` applique une fonction à chaque élément d'une liste.
|
||||||
|
|
||||||
|
**Question 1.1** : Écrivez une fonction lambda `get_titre` qui extrait le titre d'une écoute.
|
||||||
|
|
||||||
|
```python
|
||||||
|
get_titre = lambda ecoute: # À compléter
|
||||||
|
|
||||||
|
# Test
|
||||||
|
titres = list(map(get_titre, ecoutes))
|
||||||
|
print(titres[:3]) # ['Blinding Lights', 'Bohemian Rhapsody', 'Bad Guy']
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 1.2** : Écrivez une fonction lambda `get_duree_minutes` qui convertit la durée (en secondes) en minutes (arrondi à 2 décimales).
|
||||||
|
|
||||||
|
```python
|
||||||
|
get_duree_minutes = lambda ecoute: # À compléter
|
||||||
|
|
||||||
|
# Test
|
||||||
|
durees = list(map(get_duree_minutes, ecoutes))
|
||||||
|
print(durees[:3]) # [3.38, 5.9, 3.23]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 1.3** : Créez une fonction `formater_ecoute` qui retourne une chaîne au format `"Titre - Artiste (Xmin)"`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
formater_ecoute = lambda e: # À compléter
|
||||||
|
|
||||||
|
# Test
|
||||||
|
formatees = list(map(formater_ecoute, ecoutes))
|
||||||
|
print(formatees[0]) # "Blinding Lights - The Weeknd (3.38min)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Exercice 2 : Filtrage avec `filter`
|
||||||
|
|
||||||
|
La fonction `filter(fonction, liste)` conserve uniquement les éléments pour lesquels la fonction retourne `True`.
|
||||||
|
|
||||||
|
**Question 2.1** : Filtrez les écoutes pour ne garder que les morceaux de genre "pop".
|
||||||
|
|
||||||
|
```python
|
||||||
|
est_pop = lambda ecoute: # À compléter
|
||||||
|
|
||||||
|
ecoutes_pop = list(filter(est_pop, ecoutes))
|
||||||
|
print(f"Nombre de morceaux pop : {len(ecoutes_pop)}") # 12
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 2.2** : Filtrez les écoutes de plus de 5 minutes (300 secondes).
|
||||||
|
|
||||||
|
```python
|
||||||
|
est_long = lambda ecoute: # À compléter
|
||||||
|
|
||||||
|
ecoutes_longues = list(filter(est_long, ecoutes))
|
||||||
|
print(f"Morceaux longs : {len(ecoutes_longues)}") # 5
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 2.3** : Créez un filtre paramétrable `filtre_genre(genre)` qui retourne une fonction lambda.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def filtre_genre(genre):
|
||||||
|
"""
|
||||||
|
Retourne une fonction lambda qui filtre par genre.
|
||||||
|
C'est une fonction d'ordre supérieur !
|
||||||
|
"""
|
||||||
|
return lambda ecoute: # À compléter
|
||||||
|
|
||||||
|
# Test
|
||||||
|
ecoutes_rock = list(filter(filtre_genre("rock"), ecoutes))
|
||||||
|
ecoutes_rap = list(filter(filtre_genre("rap"), ecoutes))
|
||||||
|
print(f"Rock: {len(ecoutes_rock)}, Rap: {len(ecoutes_rap)}") # Rock: 4, Rap: 2
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Exercice 3 : Agrégation avec `reduce`
|
||||||
|
|
||||||
|
La fonction `reduce(fonction, liste, valeur_initiale)` combine tous les éléments en une seule valeur.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import functools
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 3.1** : Calculez le temps total d'écoute en secondes.
|
||||||
|
|
||||||
|
```python
|
||||||
|
temps_total = functools.reduce(
|
||||||
|
lambda acc, ecoute: # À compléter,
|
||||||
|
ecoutes,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
print(f"Temps total : {temps_total} secondes") # 5301 secondes
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 3.2** : Convertissez ce temps en heures et minutes.
|
||||||
|
|
||||||
|
```python
|
||||||
|
heures = temps_total // 3600
|
||||||
|
minutes = (temps_total % 3600) // 60
|
||||||
|
print(f"Temps d'écoute : {heures}h {minutes}min") # 1h 28min
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 3.3** : Trouvez le morceau le plus long en utilisant `reduce`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
plus_long = functools.reduce(
|
||||||
|
lambda acc, ecoute: # À compléter,
|
||||||
|
ecoutes
|
||||||
|
)
|
||||||
|
print(f"Plus long : {plus_long['titre']} ({plus_long['duree']}s)")
|
||||||
|
# Plus long : Stairway to Heaven (482s)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : Statistiques avancées
|
||||||
|
|
||||||
|
### Exercice 4 : Comptage par catégorie
|
||||||
|
|
||||||
|
**Question 4.1** : Comptez le nombre d'écoutes par genre en utilisant `reduce`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def compter_par_genre(ecoutes):
|
||||||
|
"""
|
||||||
|
Retourne un dictionnaire {genre: nombre_ecoutes}.
|
||||||
|
Utilise reduce pour accumuler les comptages.
|
||||||
|
"""
|
||||||
|
return functools.reduce(
|
||||||
|
lambda acc, e: {**acc, e["genre"]: acc.get(e["genre"], 0) + 1},
|
||||||
|
ecoutes,
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
stats_genres = compter_par_genre(ecoutes)
|
||||||
|
print(stats_genres) # {'pop': 12, 'rock': 4, 'rap': 2, 'metal': 2}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 4.2** : Adaptez cette fonction pour compter les écoutes par artiste.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def compter_par_artiste(ecoutes):
|
||||||
|
"""Retourne un dictionnaire {artiste: nombre_ecoutes}."""
|
||||||
|
# À compléter (inspirez-vous de compter_par_genre)
|
||||||
|
pass
|
||||||
|
|
||||||
|
stats_artistes = compter_par_artiste(ecoutes)
|
||||||
|
print(stats_artistes)
|
||||||
|
# {'The Weeknd': 3, 'Queen': 1, 'Billie Eilish': 1, ...}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 4.3** : Trouvez l'artiste le plus écouté.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def artiste_prefere(stats_artistes):
|
||||||
|
"""
|
||||||
|
Retourne le nom de l'artiste avec le plus d'écoutes.
|
||||||
|
Utilise reduce sur les items du dictionnaire.
|
||||||
|
"""
|
||||||
|
return functools.reduce(
|
||||||
|
lambda acc, item: # À compléter,
|
||||||
|
stats_artistes.items()
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
top_artiste = artiste_prefere(stats_artistes)
|
||||||
|
print(f"Artiste préféré : {top_artiste}") # The Weeknd
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Exercice 5 : Chaînage fonctionnel (Pipeline)
|
||||||
|
|
||||||
|
L'intérêt de la programmation fonctionnelle est de **chaîner** les opérations.
|
||||||
|
|
||||||
|
**Question 5.1** : Calculez le temps total d'écoute des morceaux pop uniquement.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# En une seule expression chaînée
|
||||||
|
temps_pop = functools.reduce(
|
||||||
|
lambda acc, e: acc + e["duree"],
|
||||||
|
filter(lambda e: e["genre"] == "pop", ecoutes),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
print(f"Temps pop : {temps_pop // 60} minutes")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 5.2** : Listez les titres des morceaux rock de plus de 4 minutes, triés par durée décroissante.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Pipeline : filter -> filter -> map -> sorted
|
||||||
|
titres_rock_longs = list(map(
|
||||||
|
lambda e: e["titre"],
|
||||||
|
sorted(
|
||||||
|
filter(
|
||||||
|
lambda e: e["duree"] > 240,
|
||||||
|
filter(
|
||||||
|
lambda e: e["genre"] == "rock",
|
||||||
|
ecoutes
|
||||||
|
)
|
||||||
|
),
|
||||||
|
key=lambda e: e["duree"],
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
))
|
||||||
|
print(titres_rock_longs)
|
||||||
|
# ['Stairway to Heaven', 'Bohemian Rhapsody', 'Smells Like Teen Spirit']
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 5.3** : Créez une fonction `pipeline` qui compose plusieurs fonctions.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def pipeline(*fonctions):
|
||||||
|
"""
|
||||||
|
Retourne une fonction qui applique les fonctions en séquence.
|
||||||
|
pipeline(f, g, h)(x) équivaut à h(g(f(x)))
|
||||||
|
"""
|
||||||
|
return lambda x: functools.reduce(
|
||||||
|
lambda acc, f: f(acc),
|
||||||
|
fonctions,
|
||||||
|
x
|
||||||
|
)
|
||||||
|
|
||||||
|
# Exemple d'utilisation
|
||||||
|
traitement = pipeline(
|
||||||
|
lambda data: filter(lambda e: e["genre"] == "pop", data),
|
||||||
|
lambda data: map(lambda e: e["titre"], data),
|
||||||
|
list
|
||||||
|
)
|
||||||
|
|
||||||
|
print(traitement(ecoutes)[:3]) # ['Blinding Lights', 'Bad Guy', 'Shape of You']
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : Génération du Wrapped
|
||||||
|
|
||||||
|
### Exercice 6 : Créer le résumé final
|
||||||
|
|
||||||
|
**Question 6** : Créez une fonction `generer_wrapped` qui produit un dictionnaire avec toutes les statistiques.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def generer_wrapped(ecoutes):
|
||||||
|
"""
|
||||||
|
Génère un résumé Wrapped complet de manière fonctionnelle.
|
||||||
|
|
||||||
|
:param ecoutes: (list) liste des écoutes
|
||||||
|
:return: (dict) statistiques complètes
|
||||||
|
"""
|
||||||
|
# Temps total
|
||||||
|
temps_total = functools.reduce(lambda acc, e: acc + e["duree"], ecoutes, 0)
|
||||||
|
|
||||||
|
# Comptages
|
||||||
|
stats_artistes = compter_par_artiste(ecoutes)
|
||||||
|
stats_genres = compter_par_genre(ecoutes)
|
||||||
|
|
||||||
|
# Top artiste
|
||||||
|
top_artiste = # À compléter
|
||||||
|
|
||||||
|
# Top genre
|
||||||
|
top_genre = # À compléter
|
||||||
|
|
||||||
|
# Morceau le plus écouté (celui qui apparaît le plus)
|
||||||
|
stats_titres = functools.reduce(
|
||||||
|
lambda acc, e: {**acc, e["titre"]: acc.get(e["titre"], 0) + 1},
|
||||||
|
ecoutes,
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
top_titre = # À compléter
|
||||||
|
|
||||||
|
# Morceau le plus long
|
||||||
|
plus_long = # À compléter
|
||||||
|
|
||||||
|
return {
|
||||||
|
"nombre_ecoutes": len(ecoutes),
|
||||||
|
"temps_total_minutes": round(temps_total / 60, 1),
|
||||||
|
"artiste_prefere": top_artiste,
|
||||||
|
"genre_prefere": top_genre,
|
||||||
|
"titre_prefere": top_titre,
|
||||||
|
"morceau_plus_long": plus_long["titre"],
|
||||||
|
"stats_genres": stats_genres,
|
||||||
|
"top_5_artistes": dict(sorted(
|
||||||
|
stats_artistes.items(),
|
||||||
|
key=lambda x: x[1],
|
||||||
|
reverse=True
|
||||||
|
)[:5])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Test
|
||||||
|
wrapped = generer_wrapped(ecoutes)
|
||||||
|
|
||||||
|
print("=" * 40)
|
||||||
|
print(" VOTRE WRAPPED 2026")
|
||||||
|
print("=" * 40)
|
||||||
|
print(f"Écoutes totales : {wrapped['nombre_ecoutes']}")
|
||||||
|
print(f"Temps d'écoute : {wrapped['temps_total_minutes']} minutes")
|
||||||
|
print(f"Artiste préféré : {wrapped['artiste_prefere']}")
|
||||||
|
print(f"Genre préféré : {wrapped['genre_prefere']}")
|
||||||
|
print(f"Titre préféré : {wrapped['titre_prefere']}")
|
||||||
|
print(f"Top 5 artistes : {list(wrapped['top_5_artistes'].keys())}")
|
||||||
|
print("=" * 40)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 5 : Bonus — Récursivité
|
||||||
|
|
||||||
|
### Exercice 7 : Implémenter reduce sans boucle
|
||||||
|
|
||||||
|
**Question 7** : Réécrivez la fonction `reduce` en utilisant la récursivité.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def mon_reduce(fonction, liste, initial):
|
||||||
|
"""
|
||||||
|
Implémentation récursive de reduce.
|
||||||
|
|
||||||
|
:param fonction: (callable) fonction à 2 arguments (acc, elem)
|
||||||
|
:param liste: (list) liste à réduire
|
||||||
|
:param initial: valeur initiale de l'accumulateur
|
||||||
|
:return: résultat de la réduction
|
||||||
|
"""
|
||||||
|
if len(liste) == 0:
|
||||||
|
# À compléter : cas de base
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# À compléter : appel récursif
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Test
|
||||||
|
total = mon_reduce(lambda acc, x: acc + x, [1, 2, 3, 4, 5], 0)
|
||||||
|
print(f"Somme : {total}") # 15
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Résumé des notions
|
||||||
|
|
||||||
|
| Fonction | Description | Exemple |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `map(f, lst)` | Applique f à chaque élément | `map(lambda x: x*2, [1,2,3])` → [2,4,6] |
|
||||||
|
| `filter(f, lst)` | Garde les éléments où f est True | `filter(lambda x: x>2, [1,2,3])` → [3] |
|
||||||
|
| `reduce(f, lst, init)` | Combine tous les éléments | `reduce(lambda a,x: a+x, [1,2,3], 0)` → 6 |
|
||||||
|
| `lambda` | Fonction anonyme | `lambda x: x + 1` |
|
||||||
|
| Pipeline | Composition de fonctions | `f(g(h(x)))` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pour aller plus loin
|
||||||
|
|
||||||
|
- **Compréhensions de liste** : Alternative pythonique à map/filter
|
||||||
|
- **Générateurs** : `map` et `filter` retournent des itérateurs (lazy evaluation)
|
||||||
|
- **Bibliothèques** : `itertools`, `toolz`, `fn.py` pour la programmation fonctionnelle avancée
|
||||||
|
- **Langages fonctionnels purs** : Haskell, Elm, Clojure
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
102
Pile_File/README.md
Normal file
102
Pile_File/README.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# Structure de données : Pile et File
|
||||||
|
|
||||||
|
> Structures essentielles pour résoudre des problèmes où l'ordre de traitement des données est crucial, comme la gestion des processus ou la navigation dans des graphes, les piles et files permettent de stocker et manipuler des données selon des règles spécifiques. La pile suit un principe **LIFO** (Last In, First Out), tandis que la file suit un principe **FIFO** (First In, First Out).
|
||||||
|
|
||||||
|
## Le programme
|
||||||
|
|
||||||
|
<img src="Images/bo_1.png" alt="BO_Pile & File" style="zoom:67%;" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<img src="Images/bo_2.png" alt="BO_Pile&File_2" style="zoom:67%;" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 1. Rappel :
|
||||||
|
|
||||||
|
<u>**Structure de données**</u> : Collection d'information dans laquelle il est possible de stocker, traiter, organiser, extraire des données.
|
||||||
|
|
||||||
|
Les structures de données connues à ce jour sont : Les tableaux, les dictionnaires, les chaînes de caractères, les n-uplets. Chacune de ces structures possèdent des caractéristiques propres qui créent des avantages et des inconvénients selon la situation.
|
||||||
|
|
||||||
|
De plus, ces types sont natifs à Python, et aux autres langages selon les cas. Nous n'avons donc ici aucune idée de comment le type *dict* ou *tuple* est créé par exemple.
|
||||||
|
|
||||||
|
Les structures de données Pile et File ne sont pas natives à Python et nous aurons donc besoin de les implémenter.
|
||||||
|
|
||||||
|
## 2. Interface et implémentation :
|
||||||
|
|
||||||
|
Ces deux termes sont particuliers et très importants pour ce chapitre. Alors un petit rappel n'est pas de trop.
|
||||||
|
|
||||||
|
<u>**Interface :**</u> L'interface d'un type est définie par les méthodes qui lui sont associées.
|
||||||
|
|
||||||
|
**<u>Implémentation :</u>** L'implémentation d'un type est la manière dont on va le coder.
|
||||||
|
|
||||||
|
Il faut bien faire la distinction entre les deux termes.
|
||||||
|
|
||||||
|
## 3. Pile
|
||||||
|
|
||||||
|
### 3. 1. Définition
|
||||||
|
|
||||||
|
Une pile est une structure de données dans laquelle les derniers éléments entrant dans la structure seront les premiers à en sortir, nous appelons ce principe **LIFO (Last In First Out)**. Afin d'imager cette structure nous pouvons penser à une pile d'assiettes par exemple.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Ici seul le premier élément est accessible.
|
||||||
|
|
||||||
|
### 3. 2. Interface
|
||||||
|
|
||||||
|
Méthodes associées à la pile :
|
||||||
|
|
||||||
|
- Empile : Méthode permettant d'empiler un élément.
|
||||||
|
- L'élément sera positionné au haut de la pile
|
||||||
|
- Depile : Méthode permettant d'enlever un élément.
|
||||||
|
- L'élément enlevé sera celui en haut de la pile.
|
||||||
|
|
||||||
|
- Est_vide : Permet de savoir si la pile est vide ou non
|
||||||
|
|
||||||
|
On peut ajouter d'autres méthodes :
|
||||||
|
|
||||||
|
- Taille : Permet de savoir le nombre d'éléments de la pile
|
||||||
|
- Top : Permet de connaître l'élément au-dessus de la pile.
|
||||||
|
|
||||||
|
## 4. File
|
||||||
|
|
||||||
|
### 4. 1. Définition
|
||||||
|
|
||||||
|
Une file est une structure de données dans laquelle les premiers éléments entrant dans la structure seront les premiers à en sortir, nous appelons ce principe **FIFO (First In First Out)**. Afin d'imager cette structure nous pouvons penser à une file de voitures sur une route par exemple.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Ici seul le premier élément peut sortir de la structure.
|
||||||
|
|
||||||
|
### 4. 2. Interface
|
||||||
|
|
||||||
|
Méthodes associées à la file :
|
||||||
|
|
||||||
|
- Enfile : Méthode permettant d'enfiler un élément.
|
||||||
|
- L'élément sera positionné en queue de la file
|
||||||
|
- Defile : Méthode permettant d'enlever un élément.
|
||||||
|
- L'élément enlevé sera celui en tête de la file.
|
||||||
|
|
||||||
|
- Est_vide : Permet de savoir si la file est vide ou non
|
||||||
|
|
||||||
|
On peut ajouter d'autres méthodes :
|
||||||
|
|
||||||
|
- Taille : Permet de savoir le nombre d'éléments de la file
|
||||||
|
- Top : Permet de connaître l'élément en tête de la file.
|
||||||
|
|
||||||
|
## 5. Conclusion
|
||||||
|
|
||||||
|
Les deux structures ont une interface très semblable. Seul le principe FIFO / LIFO peut les différencier.
|
||||||
|
|
||||||
|
La suite du chapitre se concentrera sur l'utilisation de ces structures et leurs implémentations.
|
||||||
|
|
||||||
|
---------
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
181
Pile_File/TD/Corrige_TD_Pile_File.md
Normal file
181
Pile_File/TD/Corrige_TD_Pile_File.md
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
# Corrigé du TD Pile et File
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Application de cours
|
||||||
|
|
||||||
|
### 1. 1. Pile
|
||||||
|
|
||||||
|
**État initial de la pile :**
|
||||||
|
```
|
||||||
|
5 ← sommet
|
||||||
|
4
|
||||||
|
3
|
||||||
|
2
|
||||||
|
1 ← fond
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 1** : État après `Depile(), Depile(), Empile(7), Empile(8), Depile()`
|
||||||
|
|
||||||
|
Exécution pas à pas :
|
||||||
|
|
||||||
|
| Opération | État de la pile | Élément retourné |
|
||||||
|
|-----------|-----------------|------------------|
|
||||||
|
| Initial | [1, 2, 3, 4, 5] | - |
|
||||||
|
| Depile() | [1, 2, 3, 4] | 5 |
|
||||||
|
| Depile() | [1, 2, 3] | 4 |
|
||||||
|
| Empile(7) | [1, 2, 3, 7] | - |
|
||||||
|
| Empile(8) | [1, 2, 3, 7, 8] | - |
|
||||||
|
| Depile() | [1, 2, 3, 7] | 8 |
|
||||||
|
|
||||||
|
**État final :**
|
||||||
|
```
|
||||||
|
7 ← sommet
|
||||||
|
3
|
||||||
|
2
|
||||||
|
1 ← fond
|
||||||
|
```
|
||||||
|
|
||||||
|
**`top()` renvoie : 7**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Question 2** : Pour que `Est_vide()` soit vrai, il faut dépiler tous les éléments.
|
||||||
|
|
||||||
|
```
|
||||||
|
Depile() → renvoie 5
|
||||||
|
Depile() → renvoie 4
|
||||||
|
Depile() → renvoie 3
|
||||||
|
Depile() → renvoie 2
|
||||||
|
Depile() → renvoie 1
|
||||||
|
Est_vide() → True
|
||||||
|
```
|
||||||
|
|
||||||
|
**Réponse : 5 appels à Depile()**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Question 3** : Créer une pile contenant 19982018 (1 en bas de pile)
|
||||||
|
|
||||||
|
La pile doit ressembler à :
|
||||||
|
```
|
||||||
|
8 ← sommet
|
||||||
|
1
|
||||||
|
0
|
||||||
|
2
|
||||||
|
8
|
||||||
|
9
|
||||||
|
9
|
||||||
|
1 ← fond
|
||||||
|
```
|
||||||
|
|
||||||
|
**Séquence de méthodes :**
|
||||||
|
```
|
||||||
|
Empile(1)
|
||||||
|
Empile(9)
|
||||||
|
Empile(9)
|
||||||
|
Empile(8)
|
||||||
|
Empile(2)
|
||||||
|
Empile(0)
|
||||||
|
Empile(1)
|
||||||
|
Empile(8)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. 2. File
|
||||||
|
|
||||||
|
**État initial de la file :**
|
||||||
|
```
|
||||||
|
Entrée → 5 | 4 | 3 | 2 | 1 → Sortie
|
||||||
|
↑ ↑
|
||||||
|
queue tête
|
||||||
|
```
|
||||||
|
|
||||||
|
**Question 1** : État après `Defile(), Defile(), Enfile(7), Enfile(8), Defile()`
|
||||||
|
|
||||||
|
Exécution pas à pas :
|
||||||
|
|
||||||
|
| Opération | État de la file | Élément retourné |
|
||||||
|
|-----------|-----------------|------------------|
|
||||||
|
| Initial | [5, 4, 3, 2, 1] | - |
|
||||||
|
| Defile() | [5, 4, 3, 2] | 1 |
|
||||||
|
| Defile() | [5, 4, 3] | 2 |
|
||||||
|
| Enfile(7) | [7, 5, 4, 3] | - |
|
||||||
|
| Enfile(8) | [8, 7, 5, 4, 3] | - |
|
||||||
|
| Defile() | [8, 7, 5, 4] | 3 |
|
||||||
|
|
||||||
|
**État final :**
|
||||||
|
```
|
||||||
|
Entrée → 8 | 7 | 5 | 4 → Sortie
|
||||||
|
```
|
||||||
|
|
||||||
|
**`top()` renvoie : 4** (élément en tête, prêt à sortir)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Question 2** : Pour que `Est_vide()` soit vrai, il faut défiler tous les éléments.
|
||||||
|
|
||||||
|
```
|
||||||
|
Defile() → renvoie 1
|
||||||
|
Defile() → renvoie 2
|
||||||
|
Defile() → renvoie 3
|
||||||
|
Defile() → renvoie 4
|
||||||
|
Defile() → renvoie 5
|
||||||
|
Est_vide() → True
|
||||||
|
```
|
||||||
|
|
||||||
|
**Réponse : 5 appels à Defile()**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Question 3** : Créer une file contenant 19982018 (1 en tête de file)
|
||||||
|
|
||||||
|
La file doit ressembler à :
|
||||||
|
```
|
||||||
|
Entrée → 8 | 1 | 0 | 2 | 8 | 9 | 9 | 1 → Sortie
|
||||||
|
↑
|
||||||
|
tête
|
||||||
|
```
|
||||||
|
|
||||||
|
Pour que 1 soit en tête (premier à sortir), il faut l'enfiler en premier :
|
||||||
|
|
||||||
|
**Séquence de méthodes :**
|
||||||
|
```
|
||||||
|
Enfile(1)
|
||||||
|
Enfile(9)
|
||||||
|
Enfile(9)
|
||||||
|
Enfile(8)
|
||||||
|
Enfile(2)
|
||||||
|
Enfile(0)
|
||||||
|
Enfile(1)
|
||||||
|
Enfile(8)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tableau récapitulatif
|
||||||
|
|
||||||
|
| Structure | Principe | Ajout | Retrait | Accès |
|
||||||
|
|-----------|----------|-------|---------|-------|
|
||||||
|
| **Pile** | LIFO (Last In First Out) | Empile (sommet) | Depile (sommet) | Top (sommet) |
|
||||||
|
| **File** | FIFO (First In First Out) | Enfile (queue) | Defile (tête) | Top (tête) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Analogies pour mémoriser
|
||||||
|
|
||||||
|
| Structure | Analogie | Explication |
|
||||||
|
|-----------|----------|-------------|
|
||||||
|
| **Pile** | Pile d'assiettes | On pose et on prend toujours par le dessus |
|
||||||
|
| **File** | File d'attente | Le premier arrivé est le premier servi |
|
||||||
|
| **Pile** | Historique du navigateur | Le bouton "retour" revient à la page précédente (la dernière visitée) |
|
||||||
|
| **File** | Imprimante | Les documents sont imprimés dans l'ordre d'envoi |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -15,8 +15,8 @@ Le but du TD est de manipuler des piles et des files sans avoir d'implémentatio
|
|||||||
1. Quel sera l'état de la pile après l'utilisation des méthodes suivantes :
|
1. Quel sera l'état de la pile après l'utilisation des méthodes suivantes :
|
||||||
- Depile(), Depile(), Empile(7), Empile(8),Depile()
|
- Depile(), Depile(), Empile(7), Empile(8),Depile()
|
||||||
- Que renvoie la méthode top() ?
|
- Que renvoie la méthode top() ?
|
||||||
2. Reprenons la pile de l'image, que faut t'il faire comme méthode pour que Est_vide() soit vrai ?
|
2. Reprenons la pile de l'image, que faut-il faire comme méthode pour que Est_vide() soit vrai ?
|
||||||
3. En partant de 0 écrire les méthodes permettant de créer une pile contenant les numéros dans cette ordre 19982018. (1 est en bas de pile)
|
3. En partant de 0, écrire les méthodes permettant de créer une pile contenant les numéros dans cet ordre : 19982018. (1 est en bas de pile)
|
||||||
|
|
||||||
### 1. 2. File
|
### 1. 2. File
|
||||||
|
|
||||||
@@ -27,5 +27,13 @@ Le but du TD est de manipuler des piles et des files sans avoir d'implémentatio
|
|||||||
1. Quel sera l'état de la file après l'utilisation des méthodes suivantes :
|
1. Quel sera l'état de la file après l'utilisation des méthodes suivantes :
|
||||||
- Defile(), Defile(), Enfile(7), Enfile(8),Defile()
|
- Defile(), Defile(), Enfile(7), Enfile(8),Defile()
|
||||||
- Que renvoie la méthode top() ?
|
- Que renvoie la méthode top() ?
|
||||||
2. Reprenons la file de l'image, que faut t'il faire comme méthode pour que Est_vide() soit vrai ?
|
2. Reprenons la file de l'image, que faut-il faire comme méthode pour que Est_vide() soit vrai ?
|
||||||
3. En partant de 0 écrire les méthodes permettant de créer une file contenant les numéros dans cette ordre 19982018. (1 est en haut de pile)
|
3. En partant de 0, écrire les méthodes permettant de créer une file contenant les numéros dans cet ordre : 19982018. (1 est en tête de file)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
494
Pile_File/TP/Corrige_TP_Navigateur.py
Normal file
494
Pile_File/TP/Corrige_TP_Navigateur.py
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
"""
|
||||||
|
Corrigé du TP Navigateur — Piles et Files en action
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 1 : Implémentation des structures
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class Pile:
|
||||||
|
"""Structure de données LIFO (Last In First Out)."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialise une pile vide."""
|
||||||
|
self.elements = []
|
||||||
|
|
||||||
|
def empile(self, element):
|
||||||
|
"""Ajoute un élément au sommet de la pile."""
|
||||||
|
self.elements.append(element)
|
||||||
|
|
||||||
|
def depile(self):
|
||||||
|
"""
|
||||||
|
Retire et renvoie l'élément au sommet.
|
||||||
|
Renvoie None si la pile est vide.
|
||||||
|
"""
|
||||||
|
if self.est_vide():
|
||||||
|
return None
|
||||||
|
return self.elements.pop()
|
||||||
|
|
||||||
|
def est_vide(self):
|
||||||
|
"""Renvoie True si la pile est vide."""
|
||||||
|
return len(self.elements) == 0
|
||||||
|
|
||||||
|
def sommet(self):
|
||||||
|
"""
|
||||||
|
Renvoie l'élément au sommet sans le retirer.
|
||||||
|
Renvoie None si la pile est vide.
|
||||||
|
"""
|
||||||
|
if self.est_vide():
|
||||||
|
return None
|
||||||
|
return self.elements[-1]
|
||||||
|
|
||||||
|
def taille(self):
|
||||||
|
"""Renvoie le nombre d'éléments dans la pile."""
|
||||||
|
return len(self.elements)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Affichage de la pile."""
|
||||||
|
if self.est_vide():
|
||||||
|
return "Pile vide"
|
||||||
|
result = "Sommet\n"
|
||||||
|
for i in range(len(self.elements) - 1, -1, -1):
|
||||||
|
result += f" | {self.elements[i]} |\n"
|
||||||
|
result += "Fond"
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class File:
|
||||||
|
"""Structure de données FIFO (First In First Out)."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialise une file vide."""
|
||||||
|
self.elements = []
|
||||||
|
|
||||||
|
def enfile(self, element):
|
||||||
|
"""Ajoute un élément en queue de file."""
|
||||||
|
self.elements.append(element)
|
||||||
|
|
||||||
|
def defile(self):
|
||||||
|
"""
|
||||||
|
Retire et renvoie l'élément en tête de file.
|
||||||
|
Renvoie None si la file est vide.
|
||||||
|
"""
|
||||||
|
if self.est_vide():
|
||||||
|
return None
|
||||||
|
return self.elements.pop(0)
|
||||||
|
|
||||||
|
def est_vide(self):
|
||||||
|
"""Renvoie True si la file est vide."""
|
||||||
|
return len(self.elements) == 0
|
||||||
|
|
||||||
|
def tete(self):
|
||||||
|
"""
|
||||||
|
Renvoie l'élément en tête sans le retirer.
|
||||||
|
Renvoie None si la file est vide.
|
||||||
|
"""
|
||||||
|
if self.est_vide():
|
||||||
|
return None
|
||||||
|
return self.elements[0]
|
||||||
|
|
||||||
|
def taille(self):
|
||||||
|
"""Renvoie le nombre d'éléments dans la file."""
|
||||||
|
return len(self.elements)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Affichage de la file."""
|
||||||
|
if self.est_vide():
|
||||||
|
return "File vide"
|
||||||
|
result = "Tête → "
|
||||||
|
result += " → ".join(str(e) for e in self.elements)
|
||||||
|
result += " → Queue"
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 2 : Simulateur de navigateur web
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class Navigateur:
|
||||||
|
"""Simulateur de navigateur web avec historique."""
|
||||||
|
|
||||||
|
def __init__(self, page_accueil="https://www.google.com"):
|
||||||
|
"""
|
||||||
|
Initialise le navigateur avec une page d'accueil.
|
||||||
|
|
||||||
|
:param page_accueil: (str) URL de la page d'accueil
|
||||||
|
"""
|
||||||
|
self.historique = Pile()
|
||||||
|
self.suivant = Pile()
|
||||||
|
self.historique.empile(page_accueil)
|
||||||
|
|
||||||
|
def page_actuelle(self):
|
||||||
|
"""
|
||||||
|
Renvoie l'URL de la page actuellement affichée.
|
||||||
|
|
||||||
|
:return: (str) URL de la page actuelle ou None
|
||||||
|
"""
|
||||||
|
return self.historique.sommet()
|
||||||
|
|
||||||
|
def visiter(self, url):
|
||||||
|
"""
|
||||||
|
Visite une nouvelle page.
|
||||||
|
- Empile l'URL dans l'historique
|
||||||
|
- Vide la pile 'suivant' (on ne peut plus avancer)
|
||||||
|
|
||||||
|
:param url: (str) URL de la page à visiter
|
||||||
|
"""
|
||||||
|
self.historique.empile(url)
|
||||||
|
# Vider la pile suivant
|
||||||
|
while not self.suivant.est_vide():
|
||||||
|
self.suivant.depile()
|
||||||
|
|
||||||
|
def retour(self):
|
||||||
|
"""
|
||||||
|
Revient à la page précédente (bouton ←).
|
||||||
|
- Dépile l'historique
|
||||||
|
- Empile la page actuelle dans 'suivant'
|
||||||
|
|
||||||
|
:return: (str) URL de la nouvelle page actuelle ou None si impossible
|
||||||
|
"""
|
||||||
|
if not self.peut_reculer():
|
||||||
|
return None
|
||||||
|
|
||||||
|
page_actuelle = self.historique.depile()
|
||||||
|
self.suivant.empile(page_actuelle)
|
||||||
|
return self.page_actuelle()
|
||||||
|
|
||||||
|
def avancer(self):
|
||||||
|
"""
|
||||||
|
Avance à la page suivante (bouton →).
|
||||||
|
- Dépile 'suivant'
|
||||||
|
- Empile dans l'historique
|
||||||
|
|
||||||
|
:return: (str) URL de la nouvelle page actuelle ou None si impossible
|
||||||
|
"""
|
||||||
|
if not self.peut_avancer():
|
||||||
|
return None
|
||||||
|
|
||||||
|
page_suivante = self.suivant.depile()
|
||||||
|
self.historique.empile(page_suivante)
|
||||||
|
return self.page_actuelle()
|
||||||
|
|
||||||
|
def peut_reculer(self):
|
||||||
|
"""Renvoie True si le bouton Retour est actif."""
|
||||||
|
return self.historique.taille() > 1
|
||||||
|
|
||||||
|
def peut_avancer(self):
|
||||||
|
"""Renvoie True si le bouton Suivant est actif."""
|
||||||
|
return not self.suivant.est_vide()
|
||||||
|
|
||||||
|
def afficher_etat(self):
|
||||||
|
"""Affiche l'état actuel du navigateur."""
|
||||||
|
retour = "←" if self.peut_reculer() else "✗"
|
||||||
|
avancer = "→" if self.peut_avancer() else "✗"
|
||||||
|
print(f"[{retour}] [{avancer}] | {self.page_actuelle()}")
|
||||||
|
|
||||||
|
def afficher_historique(self):
|
||||||
|
"""
|
||||||
|
Affiche l'historique complet de navigation.
|
||||||
|
La page actuelle est marquée d'une flèche.
|
||||||
|
"""
|
||||||
|
print("Historique de navigation :")
|
||||||
|
# Copier la pile pour l'afficher sans la modifier
|
||||||
|
temp = Pile()
|
||||||
|
pages = []
|
||||||
|
|
||||||
|
while not self.historique.est_vide():
|
||||||
|
page = self.historique.depile()
|
||||||
|
pages.append(page)
|
||||||
|
temp.empile(page)
|
||||||
|
|
||||||
|
# Restaurer la pile
|
||||||
|
while not temp.est_vide():
|
||||||
|
self.historique.empile(temp.depile())
|
||||||
|
|
||||||
|
# Afficher (inverser pour avoir l'ordre chronologique)
|
||||||
|
pages.reverse()
|
||||||
|
for i, page in enumerate(pages, 1):
|
||||||
|
if i == len(pages):
|
||||||
|
print(f"→ {i}. {page} (page actuelle)")
|
||||||
|
else:
|
||||||
|
print(f" {i}. {page}")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 3 : File d'attente Netflix
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class ListeVisionnage:
|
||||||
|
"""Gestionnaire de file d'attente de visionnage."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialise une liste de visionnage vide."""
|
||||||
|
self.file_attente = File()
|
||||||
|
self.historique = Pile()
|
||||||
|
|
||||||
|
def ajouter(self, titre):
|
||||||
|
"""
|
||||||
|
Ajoute un film/série à la file d'attente.
|
||||||
|
|
||||||
|
:param titre: (str) Titre du contenu à ajouter
|
||||||
|
"""
|
||||||
|
self.file_attente.enfile(titre)
|
||||||
|
|
||||||
|
def regarder_suivant(self):
|
||||||
|
"""
|
||||||
|
Regarde le prochain élément de la file.
|
||||||
|
- Défile le contenu
|
||||||
|
- L'ajoute à l'historique
|
||||||
|
|
||||||
|
:return: (str) Titre du contenu regardé ou None
|
||||||
|
"""
|
||||||
|
if self.file_attente.est_vide():
|
||||||
|
return None
|
||||||
|
|
||||||
|
contenu = self.file_attente.defile()
|
||||||
|
self.historique.empile(contenu)
|
||||||
|
return contenu
|
||||||
|
|
||||||
|
def prochain(self):
|
||||||
|
"""
|
||||||
|
Renvoie le prochain contenu sans le retirer.
|
||||||
|
|
||||||
|
:return: (str) Titre du prochain contenu ou None
|
||||||
|
"""
|
||||||
|
return self.file_attente.tete()
|
||||||
|
|
||||||
|
def revoir_dernier(self):
|
||||||
|
"""
|
||||||
|
Remet le dernier contenu regardé dans la file (en tête).
|
||||||
|
Utile pour revoir un épisode.
|
||||||
|
|
||||||
|
:return: (str) Titre du contenu remis en file ou None
|
||||||
|
"""
|
||||||
|
if self.historique.est_vide():
|
||||||
|
return None
|
||||||
|
|
||||||
|
contenu = self.historique.depile()
|
||||||
|
# Créer une nouvelle file avec ce contenu en tête
|
||||||
|
nouvelle_file = File()
|
||||||
|
nouvelle_file.enfile(contenu)
|
||||||
|
|
||||||
|
# Transvaser l'ancienne file
|
||||||
|
while not self.file_attente.est_vide():
|
||||||
|
nouvelle_file.enfile(self.file_attente.defile())
|
||||||
|
|
||||||
|
self.file_attente = nouvelle_file
|
||||||
|
return contenu
|
||||||
|
|
||||||
|
def ajouter_prioritaire(self, titre):
|
||||||
|
"""
|
||||||
|
Ajoute un contenu en tête de file (sera regardé en premier).
|
||||||
|
|
||||||
|
:param titre: (str) Titre du contenu prioritaire
|
||||||
|
"""
|
||||||
|
nouvelle_file = File()
|
||||||
|
nouvelle_file.enfile(titre)
|
||||||
|
|
||||||
|
while not self.file_attente.est_vide():
|
||||||
|
nouvelle_file.enfile(self.file_attente.defile())
|
||||||
|
|
||||||
|
self.file_attente = nouvelle_file
|
||||||
|
|
||||||
|
def afficher(self):
|
||||||
|
"""Affiche l'état de la liste de visionnage."""
|
||||||
|
print("=== Ma Liste Netflix ===")
|
||||||
|
print(f"À regarder : {self.file_attente.taille()} élément(s)")
|
||||||
|
print(f"Déjà vus : {self.historique.taille()} élément(s)")
|
||||||
|
if not self.file_attente.est_vide():
|
||||||
|
print(f"Prochain : {self.prochain()}")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 4 : Système Undo/Redo
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class EditeurTexte:
|
||||||
|
"""Éditeur de texte avec Undo/Redo."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialise l'éditeur."""
|
||||||
|
self.texte = ""
|
||||||
|
self.historique_undo = Pile()
|
||||||
|
self.historique_redo = Pile()
|
||||||
|
|
||||||
|
def ecrire(self, texte_ajoute):
|
||||||
|
"""
|
||||||
|
Ajoute du texte.
|
||||||
|
Sauvegarde l'état actuel dans l'historique.
|
||||||
|
|
||||||
|
:param texte_ajoute: (str) Texte à ajouter
|
||||||
|
"""
|
||||||
|
self.historique_undo.empile(self.texte)
|
||||||
|
self.texte += texte_ajoute
|
||||||
|
# Vider l'historique redo car nouvelle action
|
||||||
|
while not self.historique_redo.est_vide():
|
||||||
|
self.historique_redo.depile()
|
||||||
|
|
||||||
|
def effacer(self, n=1):
|
||||||
|
"""
|
||||||
|
Efface les n derniers caractères.
|
||||||
|
|
||||||
|
:param n: (int) Nombre de caractères à effacer
|
||||||
|
"""
|
||||||
|
if n > len(self.texte):
|
||||||
|
n = len(self.texte)
|
||||||
|
|
||||||
|
self.historique_undo.empile(self.texte)
|
||||||
|
self.texte = self.texte[:-n] if n > 0 else self.texte
|
||||||
|
# Vider l'historique redo
|
||||||
|
while not self.historique_redo.est_vide():
|
||||||
|
self.historique_redo.depile()
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
"""
|
||||||
|
Annule la dernière action (Ctrl+Z).
|
||||||
|
|
||||||
|
:return: (bool) True si l'annulation a réussi
|
||||||
|
"""
|
||||||
|
if self.historique_undo.est_vide():
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.historique_redo.empile(self.texte)
|
||||||
|
self.texte = self.historique_undo.depile()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def redo(self):
|
||||||
|
"""
|
||||||
|
Refait la dernière action annulée (Ctrl+Y).
|
||||||
|
|
||||||
|
:return: (bool) True si le redo a réussi
|
||||||
|
"""
|
||||||
|
if self.historique_redo.est_vide():
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.historique_undo.empile(self.texte)
|
||||||
|
self.texte = self.historique_redo.depile()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def afficher(self):
|
||||||
|
"""Affiche le texte actuel."""
|
||||||
|
undo_dispo = "Ctrl+Z" if not self.historique_undo.est_vide() else "-----"
|
||||||
|
redo_dispo = "Ctrl+Y" if not self.historique_redo.est_vide() else "-----"
|
||||||
|
print(f"[{undo_dispo}] [{redo_dispo}]")
|
||||||
|
print(f'Texte : "{self.texte}"')
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# BONUS : File avec deux piles (complexité amortie O(1))
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class FileDeuxPiles:
|
||||||
|
"""
|
||||||
|
Implémentation efficace d'une file avec deux piles.
|
||||||
|
Complexité amortie O(1) pour toutes les opérations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialise une file vide."""
|
||||||
|
self.pile_entree = Pile() # Pour les enfilements
|
||||||
|
self.pile_sortie = Pile() # Pour les défilements
|
||||||
|
|
||||||
|
def enfile(self, element):
|
||||||
|
"""Ajoute un élément en queue de file. O(1)"""
|
||||||
|
self.pile_entree.empile(element)
|
||||||
|
|
||||||
|
def _transferer(self):
|
||||||
|
"""Transfère pile_entree vers pile_sortie si nécessaire."""
|
||||||
|
if self.pile_sortie.est_vide():
|
||||||
|
while not self.pile_entree.est_vide():
|
||||||
|
self.pile_sortie.empile(self.pile_entree.depile())
|
||||||
|
|
||||||
|
def defile(self):
|
||||||
|
"""Retire et renvoie l'élément en tête. O(1) amorti."""
|
||||||
|
self._transferer()
|
||||||
|
return self.pile_sortie.depile()
|
||||||
|
|
||||||
|
def tete(self):
|
||||||
|
"""Renvoie l'élément en tête sans le retirer. O(1) amorti."""
|
||||||
|
self._transferer()
|
||||||
|
return self.pile_sortie.sommet()
|
||||||
|
|
||||||
|
def est_vide(self):
|
||||||
|
"""Renvoie True si la file est vide. O(1)"""
|
||||||
|
return self.pile_entree.est_vide() and self.pile_sortie.est_vide()
|
||||||
|
|
||||||
|
def taille(self):
|
||||||
|
"""Renvoie le nombre d'éléments. O(1)"""
|
||||||
|
return self.pile_entree.taille() + self.pile_sortie.taille()
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=" * 60)
|
||||||
|
print("TEST DU NAVIGATEUR")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
nav = Navigateur()
|
||||||
|
nav.afficher_etat()
|
||||||
|
|
||||||
|
nav.visiter("https://www.wikipedia.org")
|
||||||
|
nav.visiter("https://www.youtube.com")
|
||||||
|
nav.visiter("https://www.github.com")
|
||||||
|
nav.afficher_etat()
|
||||||
|
|
||||||
|
print("\nRetour...")
|
||||||
|
nav.retour()
|
||||||
|
nav.afficher_etat()
|
||||||
|
|
||||||
|
print("\nRetour...")
|
||||||
|
nav.retour()
|
||||||
|
nav.afficher_etat()
|
||||||
|
|
||||||
|
print("\nAvancer...")
|
||||||
|
nav.avancer()
|
||||||
|
nav.afficher_etat()
|
||||||
|
|
||||||
|
print("\nVisite nouvelle page...")
|
||||||
|
nav.visiter("https://www.python.org")
|
||||||
|
nav.afficher_etat()
|
||||||
|
|
||||||
|
print("\n")
|
||||||
|
nav.afficher_historique()
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("TEST DE NETFLIX")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
netflix = ListeVisionnage()
|
||||||
|
netflix.ajouter("Stranger Things S5")
|
||||||
|
netflix.ajouter("Wednesday S2")
|
||||||
|
netflix.ajouter("Squid Game S3")
|
||||||
|
netflix.afficher()
|
||||||
|
|
||||||
|
print(f"\nRegardé : {netflix.regarder_suivant()}")
|
||||||
|
print(f"Regardé : {netflix.regarder_suivant()}")
|
||||||
|
netflix.afficher()
|
||||||
|
|
||||||
|
print(f"\nRevoir dernier : {netflix.revoir_dernier()}")
|
||||||
|
netflix.afficher()
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("TEST DE L'EDITEUR")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
editeur = EditeurTexte()
|
||||||
|
editeur.ecrire("Bonjour")
|
||||||
|
editeur.ecrire(" le monde")
|
||||||
|
editeur.afficher()
|
||||||
|
|
||||||
|
print("\nUndo...")
|
||||||
|
editeur.undo()
|
||||||
|
editeur.afficher()
|
||||||
|
|
||||||
|
print("\nRedo...")
|
||||||
|
editeur.redo()
|
||||||
|
editeur.afficher()
|
||||||
|
|
||||||
|
print("\nEffacer 6 caractères...")
|
||||||
|
editeur.effacer(6)
|
||||||
|
editeur.afficher()
|
||||||
@@ -53,8 +53,8 @@ class File1 :
|
|||||||
Fonction qui ajoute un élément, si la taille le permet.
|
Fonction qui ajoute un élément, si la taille le permet.
|
||||||
return (bool): Renvoie True si l'ajout de l'élément a eu lieu, False sinon
|
return (bool): Renvoie True si l'ajout de l'élément a eu lieu, False sinon
|
||||||
"""
|
"""
|
||||||
if file.taille() < 5 :
|
if self.taille() < 7 :
|
||||||
file.enfile(fichier)
|
self.enfile(element)
|
||||||
return True
|
return True
|
||||||
else :
|
else :
|
||||||
return False
|
return False
|
||||||
@@ -63,8 +63,8 @@ class File1 :
|
|||||||
"""
|
"""
|
||||||
Fonction qui vide une file et affiche ces éléments
|
Fonction qui vide une file et affiche ces éléments
|
||||||
"""
|
"""
|
||||||
while file.est_vide() == False :
|
while self.est_vide() == False :
|
||||||
e = file.defile()
|
e = self.defile()
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
# PARTIE 3 :
|
# PARTIE 3 :
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ Supposons que notre file à une taille fixe, disons 7 éléments maximum.
|
|||||||
|
|
||||||
## 3. Deuxième implémentation
|
## 3. Deuxième implémentation
|
||||||
|
|
||||||
Ici nous allons implémenter la file de manière récursive. Elle possèdera deux attributs, le premier sera un élément de la pile nommé tête, le second sera une autre File. Cette seconde file possèdera elle aura un élément et une autre file en attribut. Ainsi de suite.
|
Ici nous allons implémenter la file de manière récursive. Elle possèdera deux attributs, le premier sera un élément de la file nommé tête, le second sera une autre File. Cette seconde file possèdera elle aura un élément et une autre file en attribut. Ainsi de suite.
|
||||||
|
|
||||||
1. Programmez cette classe nommée File2 avec les méthodes suivantes
|
1. Programmez cette classe nommée File2 avec les méthodes suivantes
|
||||||
* **enfile( )**
|
* **enfile( )**
|
||||||
@@ -44,11 +44,11 @@ Chacun tire la carte du dessus de son paquet et la pose sur la table. Celui qui
|
|||||||
|
|
||||||
Lorsqu'il y a "bataille" les joueurs tirent la carte suivante et la posent, face cachée, sur la carte précédente. Puis ils tirent une deuxième carte qu'ils posent cette fois-ci face découverte et c'est cette dernière qui départagera les joueurs.
|
Lorsqu'il y a "bataille" les joueurs tirent la carte suivante et la posent, face cachée, sur la carte précédente. Puis ils tirent une deuxième carte qu'ils posent cette fois-ci face découverte et c'est cette dernière qui départagera les joueurs.
|
||||||
|
|
||||||
Lorsqu'il y a bataille et qu'un des deux joueur à moins de 3 cartes alors il a perdu
|
Lorsqu'il y a bataille et qu'un des deux joueurs a moins de 3 cartes alors il a perdu.
|
||||||
|
|
||||||
Le gagnant est celui qui remporte toutes les cartes.
|
Le gagnant est celui qui remporte toutes les cartes.
|
||||||
|
|
||||||
Le jeu de la bataille peut être facilement coder avec des Files.
|
Le jeu de la bataille peut être facilement codé avec des Files.
|
||||||
|
|
||||||
Un fichier carte.py contient la classe carte et les fonctions suivantes :
|
Un fichier carte.py contient la classe carte et les fonctions suivantes :
|
||||||
|
|
||||||
@@ -69,4 +69,12 @@ Un fichier carte.py contient la classe carte et les fonctions suivantes :
|
|||||||
|
|
||||||
## Pour aller plus loin :
|
## Pour aller plus loin :
|
||||||
|
|
||||||
Ecrire une nouvelle implémentation d'une file, cette fois-ci il faut manipuler la file avec deux piles.
|
Écrire une nouvelle implémentation d'une file, cette fois-ci il faut manipuler la file avec deux piles.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
530
Pile_File/TP/TP_Navigateur.md
Normal file
530
Pile_File/TP/TP_Navigateur.md
Normal file
@@ -0,0 +1,530 @@
|
|||||||
|
# TP : Simulateur de Navigateur Web — Piles et Files en action
|
||||||
|
|
||||||
|
> **Thème** : Structures de données linéaires (Pile, File)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Chaque jour, vous utilisez un navigateur web (Chrome, Firefox, Safari...). Avez-vous déjà remarqué comment fonctionnent les boutons **← Retour** et **→ Suivant** ? Et comment Netflix gère votre file d'attente "**À regarder ensuite**" ?
|
||||||
|
|
||||||
|
Dans ce TP, vous allez recréer ces mécanismes en utilisant les structures **Pile** et **File**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 1 : Implémentation des structures
|
||||||
|
|
||||||
|
### Exercice 1 : La classe Pile
|
||||||
|
|
||||||
|
Implémentez une classe `Pile` avec les méthodes suivantes :
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Pile:
|
||||||
|
"""Structure de données LIFO (Last In First Out)."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialise une pile vide."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def empile(self, element):
|
||||||
|
"""Ajoute un élément au sommet de la pile."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def depile(self):
|
||||||
|
"""
|
||||||
|
Retire et renvoie l'élément au sommet.
|
||||||
|
Renvoie None si la pile est vide.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def est_vide(self):
|
||||||
|
"""Renvoie True si la pile est vide."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def sommet(self):
|
||||||
|
"""
|
||||||
|
Renvoie l'élément au sommet sans le retirer.
|
||||||
|
Renvoie None si la pile est vide.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def taille(self):
|
||||||
|
"""Renvoie le nombre d'éléments dans la pile."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Affichage de la pile."""
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests à effectuer :**
|
||||||
|
```python
|
||||||
|
>>> p = Pile()
|
||||||
|
>>> p.est_vide()
|
||||||
|
True
|
||||||
|
>>> p.empile("A")
|
||||||
|
>>> p.empile("B")
|
||||||
|
>>> p.empile("C")
|
||||||
|
>>> p.sommet()
|
||||||
|
'C'
|
||||||
|
>>> p.depile()
|
||||||
|
'C'
|
||||||
|
>>> p.taille()
|
||||||
|
2
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Exercice 2 : La classe File
|
||||||
|
|
||||||
|
Implémentez une classe `File` avec les méthodes suivantes :
|
||||||
|
|
||||||
|
```python
|
||||||
|
class File:
|
||||||
|
"""Structure de données FIFO (First In First Out)."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialise une file vide."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def enfile(self, element):
|
||||||
|
"""Ajoute un élément en queue de file."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def defile(self):
|
||||||
|
"""
|
||||||
|
Retire et renvoie l'élément en tête de file.
|
||||||
|
Renvoie None si la file est vide.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def est_vide(self):
|
||||||
|
"""Renvoie True si la file est vide."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tete(self):
|
||||||
|
"""
|
||||||
|
Renvoie l'élément en tête sans le retirer.
|
||||||
|
Renvoie None si la file est vide.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def taille(self):
|
||||||
|
"""Renvoie le nombre d'éléments dans la file."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Affichage de la file."""
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests à effectuer :**
|
||||||
|
```python
|
||||||
|
>>> f = File()
|
||||||
|
>>> f.est_vide()
|
||||||
|
True
|
||||||
|
>>> f.enfile("Premier")
|
||||||
|
>>> f.enfile("Deuxième")
|
||||||
|
>>> f.enfile("Troisième")
|
||||||
|
>>> f.tete()
|
||||||
|
'Premier'
|
||||||
|
>>> f.defile()
|
||||||
|
'Premier'
|
||||||
|
>>> f.taille()
|
||||||
|
2
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : Simulateur de navigateur web
|
||||||
|
|
||||||
|
### Principe de fonctionnement
|
||||||
|
|
||||||
|
Un navigateur utilise **deux piles** pour gérer l'historique :
|
||||||
|
|
||||||
|
- **Pile `historique`** : contient les pages visitées (la page actuelle est au sommet)
|
||||||
|
- **Pile `suivant`** : contient les pages "en avant" (après avoir cliqué sur Retour)
|
||||||
|
|
||||||
|
| Action | Effet |
|
||||||
|
|--------|-------|
|
||||||
|
| Visiter une page | Empile dans `historique`, vide `suivant` |
|
||||||
|
| Clic sur ← Retour | Dépile `historique` → empile dans `suivant` |
|
||||||
|
| Clic sur → Suivant | Dépile `suivant` → empile dans `historique` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Exercice 3 : La classe Navigateur
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Navigateur:
|
||||||
|
"""Simulateur de navigateur web avec historique."""
|
||||||
|
|
||||||
|
def __init__(self, page_accueil="https://www.google.com"):
|
||||||
|
"""
|
||||||
|
Initialise le navigateur avec une page d'accueil.
|
||||||
|
|
||||||
|
:param page_accueil: (str) URL de la page d'accueil
|
||||||
|
"""
|
||||||
|
self.historique = Pile()
|
||||||
|
self.suivant = Pile()
|
||||||
|
# À compléter : empiler la page d'accueil
|
||||||
|
|
||||||
|
def page_actuelle(self):
|
||||||
|
"""
|
||||||
|
Renvoie l'URL de la page actuellement affichée.
|
||||||
|
|
||||||
|
:return: (str) URL de la page actuelle ou None
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def visiter(self, url):
|
||||||
|
"""
|
||||||
|
Visite une nouvelle page.
|
||||||
|
- Empile l'URL dans l'historique
|
||||||
|
- Vide la pile 'suivant' (on ne peut plus avancer)
|
||||||
|
|
||||||
|
:param url: (str) URL de la page à visiter
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def retour(self):
|
||||||
|
"""
|
||||||
|
Revient à la page précédente (bouton ←).
|
||||||
|
- Dépile l'historique
|
||||||
|
- Empile la page actuelle dans 'suivant'
|
||||||
|
|
||||||
|
:return: (str) URL de la nouvelle page actuelle ou None si impossible
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def avancer(self):
|
||||||
|
"""
|
||||||
|
Avance à la page suivante (bouton →).
|
||||||
|
- Dépile 'suivant'
|
||||||
|
- Empile dans l'historique
|
||||||
|
|
||||||
|
:return: (str) URL de la nouvelle page actuelle ou None si impossible
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def peut_reculer(self):
|
||||||
|
"""Renvoie True si le bouton Retour est actif."""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def peut_avancer(self):
|
||||||
|
"""Renvoie True si le bouton Suivant est actif."""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def afficher_etat(self):
|
||||||
|
"""Affiche l'état actuel du navigateur."""
|
||||||
|
retour = "←" if self.peut_reculer() else "✗"
|
||||||
|
avancer = "→" if self.peut_avancer() else "✗"
|
||||||
|
print(f"[{retour}] [{avancer}] | {self.page_actuelle()}")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests à effectuer :**
|
||||||
|
```python
|
||||||
|
>>> nav = Navigateur()
|
||||||
|
>>> nav.afficher_etat()
|
||||||
|
[✗] [✗] | https://www.google.com
|
||||||
|
|
||||||
|
>>> nav.visiter("https://www.wikipedia.org")
|
||||||
|
>>> nav.visiter("https://www.youtube.com")
|
||||||
|
>>> nav.visiter("https://www.github.com")
|
||||||
|
>>> nav.afficher_etat()
|
||||||
|
[←] [✗] | https://www.github.com
|
||||||
|
|
||||||
|
>>> nav.retour()
|
||||||
|
'https://www.youtube.com'
|
||||||
|
>>> nav.afficher_etat()
|
||||||
|
[←] [→] | https://www.youtube.com
|
||||||
|
|
||||||
|
>>> nav.retour()
|
||||||
|
'https://www.wikipedia.org'
|
||||||
|
>>> nav.avancer()
|
||||||
|
'https://www.youtube.com'
|
||||||
|
|
||||||
|
>>> nav.visiter("https://www.python.org")
|
||||||
|
>>> nav.afficher_etat()
|
||||||
|
[←] [✗] | https://www.python.org
|
||||||
|
>>> nav.peut_avancer()
|
||||||
|
False # La pile 'suivant' a été vidée
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Exercice 4 : Historique complet
|
||||||
|
|
||||||
|
Ajoutez une méthode pour afficher tout l'historique de navigation :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def afficher_historique(self):
|
||||||
|
"""
|
||||||
|
Affiche l'historique complet de navigation.
|
||||||
|
La page actuelle est marquée d'une flèche.
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Exemple de sortie :**
|
||||||
|
```
|
||||||
|
Historique de navigation :
|
||||||
|
1. https://www.google.com
|
||||||
|
2. https://www.wikipedia.org
|
||||||
|
3. https://www.youtube.com
|
||||||
|
→ 4. https://www.github.com (page actuelle)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : File d'attente Netflix
|
||||||
|
|
||||||
|
### Contexte
|
||||||
|
|
||||||
|
Sur Netflix, vous pouvez ajouter des films à votre liste "**À regarder**". Les films sont regardés dans l'ordre d'ajout (FIFO), mais vous pouvez aussi mettre un film en priorité.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Exercice 5 : La classe ListeVisionnage
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ListeVisionnage:
|
||||||
|
"""Gestionnaire de file d'attente de visionnage."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialise une liste de visionnage vide."""
|
||||||
|
self.file_attente = File()
|
||||||
|
self.historique = Pile() # Films déjà regardés
|
||||||
|
|
||||||
|
def ajouter(self, titre):
|
||||||
|
"""
|
||||||
|
Ajoute un film/série à la file d'attente.
|
||||||
|
|
||||||
|
:param titre: (str) Titre du contenu à ajouter
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def regarder_suivant(self):
|
||||||
|
"""
|
||||||
|
Regarde le prochain élément de la file.
|
||||||
|
- Défile le contenu
|
||||||
|
- L'ajoute à l'historique
|
||||||
|
|
||||||
|
:return: (str) Titre du contenu regardé ou None
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def prochain(self):
|
||||||
|
"""
|
||||||
|
Renvoie le prochain contenu sans le retirer.
|
||||||
|
|
||||||
|
:return: (str) Titre du prochain contenu ou None
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def revoir_dernier(self):
|
||||||
|
"""
|
||||||
|
Remet le dernier contenu regardé dans la file (en tête).
|
||||||
|
Utile pour revoir un épisode.
|
||||||
|
|
||||||
|
:return: (str) Titre du contenu remis en file ou None
|
||||||
|
"""
|
||||||
|
# À compléter (utiliser une file temporaire ou une autre approche)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def afficher(self):
|
||||||
|
"""Affiche l'état de la liste de visionnage."""
|
||||||
|
print("=== Ma Liste Netflix ===")
|
||||||
|
print(f"À regarder : {self.file_attente.taille()} élément(s)")
|
||||||
|
print(f"Déjà vus : {self.historique.taille()} élément(s)")
|
||||||
|
if not self.file_attente.est_vide():
|
||||||
|
print(f"Prochain : {self.prochain()}")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests à effectuer :**
|
||||||
|
```python
|
||||||
|
>>> netflix = ListeVisionnage()
|
||||||
|
>>> netflix.ajouter("Stranger Things S5")
|
||||||
|
>>> netflix.ajouter("Wednesday S2")
|
||||||
|
>>> netflix.ajouter("Squid Game S3")
|
||||||
|
>>> netflix.afficher()
|
||||||
|
=== Ma Liste Netflix ===
|
||||||
|
À regarder : 3 élément(s)
|
||||||
|
Déjà vus : 0 élément(s)
|
||||||
|
Prochain : Stranger Things S5
|
||||||
|
|
||||||
|
>>> netflix.regarder_suivant()
|
||||||
|
'Stranger Things S5'
|
||||||
|
>>> netflix.regarder_suivant()
|
||||||
|
'Wednesday S2'
|
||||||
|
>>> netflix.afficher()
|
||||||
|
=== Ma Liste Netflix ===
|
||||||
|
À regarder : 1 élément(s)
|
||||||
|
Déjà vus : 2 élément(s)
|
||||||
|
Prochain : Squid Game S3
|
||||||
|
|
||||||
|
>>> netflix.revoir_dernier()
|
||||||
|
'Wednesday S2'
|
||||||
|
>>> netflix.prochain()
|
||||||
|
'Wednesday S2'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Exercice 6 : File avec priorité
|
||||||
|
|
||||||
|
Netflix permet aussi de mettre un contenu "en haut de la liste". Ajoutez cette fonctionnalité :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def ajouter_prioritaire(self, titre):
|
||||||
|
"""
|
||||||
|
Ajoute un contenu en tête de file (sera regardé en premier).
|
||||||
|
Nécessite de reconstruire la file.
|
||||||
|
|
||||||
|
:param titre: (str) Titre du contenu prioritaire
|
||||||
|
"""
|
||||||
|
# Indice : créer une nouvelle file, enfiler le titre prioritaire,
|
||||||
|
# puis transvaser l'ancienne file
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test :**
|
||||||
|
```python
|
||||||
|
>>> netflix.ajouter("Film 1")
|
||||||
|
>>> netflix.ajouter("Film 2")
|
||||||
|
>>> netflix.ajouter_prioritaire("Film URGENT")
|
||||||
|
>>> netflix.prochain()
|
||||||
|
'Film URGENT'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : Système Undo/Redo
|
||||||
|
|
||||||
|
### Exercice 7 : Éditeur de texte simplifié
|
||||||
|
|
||||||
|
Créez un éditeur qui permet d'annuler (Ctrl+Z) et de refaire (Ctrl+Y) des actions.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class EditeurTexte:
|
||||||
|
"""Éditeur de texte avec Undo/Redo."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialise l'éditeur."""
|
||||||
|
self.texte = ""
|
||||||
|
self.historique_undo = Pile() # États précédents
|
||||||
|
self.historique_redo = Pile() # États annulés
|
||||||
|
|
||||||
|
def ecrire(self, texte_ajoute):
|
||||||
|
"""
|
||||||
|
Ajoute du texte.
|
||||||
|
Sauvegarde l'état actuel dans l'historique.
|
||||||
|
|
||||||
|
:param texte_ajoute: (str) Texte à ajouter
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def effacer(self, n=1):
|
||||||
|
"""
|
||||||
|
Efface les n derniers caractères.
|
||||||
|
|
||||||
|
:param n: (int) Nombre de caractères à effacer
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
"""
|
||||||
|
Annule la dernière action (Ctrl+Z).
|
||||||
|
|
||||||
|
:return: (bool) True si l'annulation a réussi
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def redo(self):
|
||||||
|
"""
|
||||||
|
Refait la dernière action annulée (Ctrl+Y).
|
||||||
|
|
||||||
|
:return: (bool) True si le redo a réussi
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def afficher(self):
|
||||||
|
"""Affiche le texte actuel."""
|
||||||
|
undo_dispo = "Ctrl+Z" if not self.historique_undo.est_vide() else "-----"
|
||||||
|
redo_dispo = "Ctrl+Y" if not self.historique_redo.est_vide() else "-----"
|
||||||
|
print(f"[{undo_dispo}] [{redo_dispo}]")
|
||||||
|
print(f"Texte : \"{self.texte}\"")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests :**
|
||||||
|
```python
|
||||||
|
>>> editeur = EditeurTexte()
|
||||||
|
>>> editeur.ecrire("Bonjour")
|
||||||
|
>>> editeur.ecrire(" le monde")
|
||||||
|
>>> editeur.afficher()
|
||||||
|
[Ctrl+Z] [-----]
|
||||||
|
Texte : "Bonjour le monde"
|
||||||
|
|
||||||
|
>>> editeur.undo()
|
||||||
|
True
|
||||||
|
>>> editeur.afficher()
|
||||||
|
[Ctrl+Z] [Ctrl+Y]
|
||||||
|
Texte : "Bonjour"
|
||||||
|
|
||||||
|
>>> editeur.redo()
|
||||||
|
True
|
||||||
|
>>> editeur.afficher()
|
||||||
|
[Ctrl+Z] [-----]
|
||||||
|
Texte : "Bonjour le monde"
|
||||||
|
|
||||||
|
>>> editeur.effacer(6)
|
||||||
|
>>> editeur.afficher()
|
||||||
|
[Ctrl+Z] [-----]
|
||||||
|
Texte : "Bonjour le"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bonus : Comparaison des complexités
|
||||||
|
|
||||||
|
| Opération | Pile (liste Python) | File (liste Python) | File (deux piles) |
|
||||||
|
|-----------|---------------------|---------------------|-------------------|
|
||||||
|
| Empile/Enfile | O(1) | O(n)* | O(1) amorti |
|
||||||
|
| Dépile/Défile | O(1) | O(1) | O(1) amorti |
|
||||||
|
| Sommet/Tête | O(1) | O(1) | O(1) |
|
||||||
|
| Est_vide | O(1) | O(1) | O(1) |
|
||||||
|
|
||||||
|
*L'insertion en début de liste Python est en O(n).
|
||||||
|
|
||||||
|
**Exercice bonus** : Implémentez une file efficace en utilisant deux piles.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Résumé des notions
|
||||||
|
|
||||||
|
| Structure | Principe | Analogie | Cas d'usage |
|
||||||
|
|-----------|----------|----------|-------------|
|
||||||
|
| **Pile** | LIFO | Pile d'assiettes | Historique, Undo, Appels de fonctions |
|
||||||
|
| **File** | FIFO | File d'attente | Impression, Streaming, BFS |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -44,7 +44,7 @@ Afin d'implémenter la pile il nous faut utiliser une structure de données perm
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 2. 2. Calculatrice polonaise inverse
|
### 2. 2. Calculatrice polonaise inverse
|
||||||
|
|
||||||
La calculatrice polonaise inverse permet de faire des calculs simple mais pose l'opérateur après les deux opérandes.
|
La calculatrice polonaise inverse permet de faire des calculs simple mais pose l'opérateur après les deux opérandes.
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ La calculatrice polonaise inverse permet de faire des calculs simple mais pose l
|
|||||||
|
|
||||||
Le but ici est de trier une pile. Pour cela nous utiliserons une autre pile temporaire permettant de stocker les éléments.
|
Le but ici est de trier une pile. Pour cela nous utiliserons une autre pile temporaire permettant de stocker les éléments.
|
||||||
|
|
||||||
1. Ecrire une fonction tri_pile( ) prennant en paramètre une pile et renvoyant la pile triée.
|
1. Écrire une fonction tri_pile( ) prenant en paramètre une pile et renvoyant la pile triée.
|
||||||
|
|
||||||
> L'idée ici est d'utiliser seulement deux piles. Afin de comprendre le fonctionnement il faut faire quelques essais à la main.
|
> L'idée ici est d'utiliser seulement deux piles. Afin de comprendre le fonctionnement il faut faire quelques essais à la main.
|
||||||
|
|
||||||
@@ -98,3 +98,10 @@ Le but ici est de trier une pile. Pour cela nous utiliserons une autre pile temp
|
|||||||
9
|
9
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ class Pile :
|
|||||||
|
|
||||||
def empile(self,x) :
|
def empile(self,x) :
|
||||||
"""
|
"""
|
||||||
Méthode qui enpile un élément
|
Méthode qui empile un élément
|
||||||
param x : () Elément x à enpiler
|
param x : () Elément x à empiler
|
||||||
"""
|
"""
|
||||||
self.pile.append(x)
|
self.pile.append(x)
|
||||||
|
|
||||||
@@ -52,8 +52,8 @@ class Pile :
|
|||||||
while pile_tmp.taille() != 0 and pile_tmp.top() < val_tmp:
|
while pile_tmp.taille() != 0 and pile_tmp.top() < val_tmp:
|
||||||
self.empile(pile_tmp.depile())
|
self.empile(pile_tmp.depile())
|
||||||
pile_tmp.empile(val_tmp)
|
pile_tmp.empile(val_tmp)
|
||||||
p.pile = pile_tmp.pile
|
self.pile = pile_tmp.pile
|
||||||
return p
|
return self
|
||||||
|
|
||||||
|
|
||||||
p = Pile()
|
p = Pile()
|
||||||
|
|||||||
@@ -1,16 +1,95 @@
|
|||||||
# TD Gestion des processus et des ressources corrigé
|
# Corrigé des exercices — Gestion des processus
|
||||||
|
|
||||||
------
|
---
|
||||||
|
|
||||||
## 1. QCM
|
## 1. QCM
|
||||||
|
|
||||||
1. a.
|
| Question | Réponse | Explication |
|
||||||
2. a.
|
|----------|---------|-------------|
|
||||||
3. c.
|
| 1 | **a.** | Le terminal (interpréteur de commandes) permet d'exécuter des commandes système. |
|
||||||
4. c.
|
| 2 | **a.** | La commande `ps` (process status) affiche les processus sous Linux. |
|
||||||
5. c.
|
| 3 | **c.** | Un programme peut être exécuté par plusieurs processus simultanément (ex : ouvrir plusieurs fenêtres du même navigateur). |
|
||||||
|
| 4 | **c.** | L'ordonnanceur choisit le processus à exécuter parmi ceux en état "prêt". |
|
||||||
|
| 5 | **c.** | L'interblocage (deadlock) survient quand des processus s'attendent mutuellement pour des ressources. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2. Questions
|
## 2. Questions
|
||||||
|
|
||||||
1. Grâce à l'ordonnancement et le découpage des programmes en processus.
|
### Question 1
|
||||||
2. Un processus élu est en cours d'exécution, un prêt est en file d'attente dans l'ordonnanceur.
|
|
||||||
|
**Expliquer comment il se fait que l'utilisateur d'un ordinateur a l'impression que les programmes sont exécutés en même temps.**
|
||||||
|
|
||||||
|
L'utilisateur a l'impression que les programmes s'exécutent simultanément grâce à deux mécanismes :
|
||||||
|
|
||||||
|
1. **Le découpage en processus** : Chaque programme est divisé en plusieurs processus (unités d'exécution).
|
||||||
|
|
||||||
|
2. **L'ordonnancement** : Le système d'exploitation utilise un ordonnanceur qui :
|
||||||
|
- Gère une file d'attente des processus prêts à s'exécuter
|
||||||
|
- Alloue le processeur à chaque processus pendant un court laps de temps (quantum)
|
||||||
|
- Effectue des changements de contexte très rapides entre les processus
|
||||||
|
|
||||||
|
3. **La rapidité du processeur** : Le processeur exécute des milliards d'instructions par seconde. Les changements de contexte sont si rapides (quelques millisecondes) que l'utilisateur ne perçoit pas les interruptions.
|
||||||
|
|
||||||
|
**Exemple** : Si le quantum est de 10 ms et qu'il y a 10 processus, chaque processus s'exécute 100 fois par seconde. L'œil humain ne peut pas percevoir ces alternances, d'où l'illusion de simultanéité.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Question 2
|
||||||
|
|
||||||
|
**Quelles sont les différences entre les états prêt et élu ?**
|
||||||
|
|
||||||
|
| Critère | État Prêt | État Élu |
|
||||||
|
|---------|-----------|----------|
|
||||||
|
| **Définition** | Le processus attend son tour dans la file d'exécution | Le processus est en cours d'exécution sur le processeur |
|
||||||
|
| **Ressources** | Toutes les ressources nécessaires sont disponibles | Le processeur lui est alloué |
|
||||||
|
| **Position** | Dans la file d'attente de l'ordonnanceur | Sur le processeur (CPU) |
|
||||||
|
| **Nombre** | Plusieurs processus peuvent être prêts | Un seul processus élu par cœur de processeur |
|
||||||
|
| **Transition** | Devient élu quand l'ordonnanceur le choisit | Redevient prêt quand son quantum expire |
|
||||||
|
|
||||||
|
**Schéma des transitions** :
|
||||||
|
```
|
||||||
|
Ordonnanceur choisit
|
||||||
|
[PRÊT] ─────────────────────────────► [ÉLU]
|
||||||
|
▲ │
|
||||||
|
│ Quantum expiré ou │
|
||||||
|
└─────────── interruption ────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Résumé des états d'un processus
|
||||||
|
|
||||||
|
```
|
||||||
|
Création
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────┐
|
||||||
|
│ NOUVEAU │
|
||||||
|
└────┬─────┘
|
||||||
|
│ Admission
|
||||||
|
▼
|
||||||
|
Ressource ┌──────────┐ Ordonnancement
|
||||||
|
disponible ◄────│ PRÊT │────────────────────► ┌──────────┐
|
||||||
|
│ └──────────┘ │ ÉLU │
|
||||||
|
│ ▲ └────┬─────┘
|
||||||
|
│ │ Interruption │
|
||||||
|
│ │ (quantum expiré) │
|
||||||
|
│ ┌────┴─────┐ │
|
||||||
|
└───────────►│ BLOQUÉ │◄──────────────────────────┘
|
||||||
|
└──────────┘ Attente ressource
|
||||||
|
│
|
||||||
|
│ Terminaison
|
||||||
|
▼
|
||||||
|
┌──────────┐
|
||||||
|
│ TERMINÉ │
|
||||||
|
└──────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
|
|||||||
390
Processus/Corrige_TP_Ordonnanceur.py
Normal file
390
Processus/Corrige_TP_Ordonnanceur.py
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
"""
|
||||||
|
Corrigé du TP Simulateur d'Ordonnanceur
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 1 : Classe Processus
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class Processus:
|
||||||
|
"""Représente un processus système."""
|
||||||
|
|
||||||
|
compteur_pid = 0
|
||||||
|
|
||||||
|
def __init__(self, nom, duree_totale, priorite=0):
|
||||||
|
"""
|
||||||
|
Constructeur de la classe Processus.
|
||||||
|
|
||||||
|
:param nom: (str) Nom du processus
|
||||||
|
:param duree_totale: (int) Durée totale d'exécution
|
||||||
|
:param priorite: (int) Priorité (0 = normale)
|
||||||
|
"""
|
||||||
|
Processus.compteur_pid += 1
|
||||||
|
self.pid = Processus.compteur_pid
|
||||||
|
self.nom = nom
|
||||||
|
self.duree_totale = duree_totale
|
||||||
|
self.duree_restante = duree_totale
|
||||||
|
self.etat = "nouveau"
|
||||||
|
self.priorite = priorite
|
||||||
|
self.temps_arrivee = 0
|
||||||
|
self.temps_debut = None
|
||||||
|
self.temps_fin = None
|
||||||
|
|
||||||
|
def executer(self, quantum):
|
||||||
|
"""
|
||||||
|
Exécute le processus pendant quantum unités de temps.
|
||||||
|
|
||||||
|
:param quantum: (int) Temps d'exécution alloué
|
||||||
|
:return: (int) Temps réellement utilisé
|
||||||
|
"""
|
||||||
|
temps_utilise = min(quantum, self.duree_restante)
|
||||||
|
self.duree_restante -= temps_utilise
|
||||||
|
return temps_utilise
|
||||||
|
|
||||||
|
def est_termine(self):
|
||||||
|
"""Vérifie si le processus est terminé."""
|
||||||
|
return self.duree_restante <= 0
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"[PID {self.pid}] {self.nom} ({self.etat}) - {self.duree_restante}/{self.duree_totale}"
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 2 : Ordonnanceur FIFO
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class OrdonnanceurFIFO:
|
||||||
|
"""Ordonnanceur First In First Out."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.file_prets = []
|
||||||
|
self.historique = []
|
||||||
|
self.temps = 0
|
||||||
|
self.processus_termines = []
|
||||||
|
|
||||||
|
def ajouter_processus(self, processus):
|
||||||
|
"""Ajoute un processus à la file."""
|
||||||
|
processus.etat = "pret"
|
||||||
|
processus.temps_arrivee = self.temps
|
||||||
|
self.file_prets.append(processus)
|
||||||
|
print(f"[T={self.temps}] {processus.nom} ajouté (durée: {processus.duree_totale})")
|
||||||
|
|
||||||
|
def executer_suivant(self):
|
||||||
|
"""Exécute le prochain processus jusqu'à la fin."""
|
||||||
|
if len(self.file_prets) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
processus = self.file_prets.pop(0)
|
||||||
|
processus.etat = "elu"
|
||||||
|
processus.temps_debut = self.temps
|
||||||
|
|
||||||
|
print(f"\n[T={self.temps}] >>> {processus.nom} commence")
|
||||||
|
|
||||||
|
while not processus.est_termine():
|
||||||
|
temps_utilise = processus.executer(1)
|
||||||
|
self.temps += temps_utilise
|
||||||
|
self.historique.append(processus.nom[0])
|
||||||
|
|
||||||
|
processus.etat = "termine"
|
||||||
|
processus.temps_fin = self.temps
|
||||||
|
self.processus_termines.append(processus)
|
||||||
|
print(f"[T={self.temps}] <<< {processus.nom} terminé")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def executer_tous(self):
|
||||||
|
"""Exécute tous les processus."""
|
||||||
|
print(f"\n{'='*50}")
|
||||||
|
print("ORDONNANCEUR FIFO")
|
||||||
|
print(f"{'='*50}")
|
||||||
|
|
||||||
|
while self.executer_suivant():
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.afficher_resultats()
|
||||||
|
|
||||||
|
def afficher_resultats(self):
|
||||||
|
"""Affiche les résultats de l'ordonnancement."""
|
||||||
|
print(f"\n{'='*50}")
|
||||||
|
print(f"Temps total : {self.temps}")
|
||||||
|
print(f"Diagramme : {''.join(self.historique)}")
|
||||||
|
print(f"{'='*50}")
|
||||||
|
|
||||||
|
print("\nStatistiques par processus :")
|
||||||
|
temps_attente_total = 0
|
||||||
|
temps_rotation_total = 0
|
||||||
|
|
||||||
|
for p in self.processus_termines:
|
||||||
|
rotation = p.temps_fin - p.temps_arrivee
|
||||||
|
attente = rotation - p.duree_totale
|
||||||
|
temps_attente_total += attente
|
||||||
|
temps_rotation_total += rotation
|
||||||
|
print(f" {p.nom}: rotation={rotation}, attente={attente}")
|
||||||
|
|
||||||
|
n = len(self.processus_termines)
|
||||||
|
print(f"\nMoyennes: rotation={temps_rotation_total/n:.1f}, attente={temps_attente_total/n:.1f}")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 3 : Ordonnanceur Round Robin
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class OrdonnanceurRoundRobin:
|
||||||
|
"""Ordonnanceur Round Robin (tourniquet)."""
|
||||||
|
|
||||||
|
def __init__(self, quantum=2):
|
||||||
|
self.file_prets = []
|
||||||
|
self.quantum = quantum
|
||||||
|
self.temps = 0
|
||||||
|
self.historique = []
|
||||||
|
self.processus_termines = []
|
||||||
|
|
||||||
|
def ajouter_processus(self, processus):
|
||||||
|
"""Ajoute un processus à la file."""
|
||||||
|
processus.etat = "pret"
|
||||||
|
processus.temps_arrivee = self.temps
|
||||||
|
self.file_prets.append(processus)
|
||||||
|
|
||||||
|
def executer_cycle(self):
|
||||||
|
"""Exécute un cycle d'ordonnancement."""
|
||||||
|
if len(self.file_prets) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
processus = self.file_prets.pop(0)
|
||||||
|
processus.etat = "elu"
|
||||||
|
|
||||||
|
if processus.temps_debut is None:
|
||||||
|
processus.temps_debut = self.temps
|
||||||
|
|
||||||
|
temps_utilise = processus.executer(self.quantum)
|
||||||
|
self.temps += temps_utilise
|
||||||
|
|
||||||
|
for _ in range(temps_utilise):
|
||||||
|
self.historique.append(processus.nom[0])
|
||||||
|
|
||||||
|
if processus.est_termine():
|
||||||
|
processus.etat = "termine"
|
||||||
|
processus.temps_fin = self.temps
|
||||||
|
self.processus_termines.append(processus)
|
||||||
|
print(f"[T={self.temps}] {processus.nom} TERMINÉ")
|
||||||
|
else:
|
||||||
|
processus.etat = "pret"
|
||||||
|
self.file_prets.append(processus)
|
||||||
|
print(f"[T={self.temps}] {processus.nom} → file ({processus.duree_restante} restant)")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def executer_tous(self):
|
||||||
|
"""Exécute tous les processus."""
|
||||||
|
print(f"\n{'='*50}")
|
||||||
|
print(f"ORDONNANCEUR ROUND ROBIN (quantum={self.quantum})")
|
||||||
|
print(f"{'='*50}\n")
|
||||||
|
|
||||||
|
while self.executer_cycle():
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.afficher_resultats()
|
||||||
|
|
||||||
|
def afficher_resultats(self):
|
||||||
|
"""Affiche les résultats."""
|
||||||
|
print(f"\n{'='*50}")
|
||||||
|
print(f"Temps total : {self.temps}")
|
||||||
|
print(f"Diagramme : {''.join(self.historique)}")
|
||||||
|
print(f"{'='*50}")
|
||||||
|
|
||||||
|
print("\nStatistiques par processus :")
|
||||||
|
temps_attente_total = 0
|
||||||
|
temps_rotation_total = 0
|
||||||
|
temps_reponse_total = 0
|
||||||
|
|
||||||
|
for p in self.processus_termines:
|
||||||
|
rotation = p.temps_fin - p.temps_arrivee
|
||||||
|
attente = rotation - p.duree_totale
|
||||||
|
reponse = p.temps_debut - p.temps_arrivee
|
||||||
|
temps_attente_total += attente
|
||||||
|
temps_rotation_total += rotation
|
||||||
|
temps_reponse_total += reponse
|
||||||
|
print(f" {p.nom}: rotation={rotation}, attente={attente}, réponse={reponse}")
|
||||||
|
|
||||||
|
n = len(self.processus_termines)
|
||||||
|
print(f"\nMoyennes: rotation={temps_rotation_total/n:.1f}, attente={temps_attente_total/n:.1f}, réponse={temps_reponse_total/n:.1f}")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 4 : Ressources et Interblocage
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class Ressource:
|
||||||
|
"""Représente une ressource partagée."""
|
||||||
|
|
||||||
|
def __init__(self, nom):
|
||||||
|
self.nom = nom
|
||||||
|
self.proprietaire = None
|
||||||
|
|
||||||
|
def est_disponible(self):
|
||||||
|
return self.proprietaire is None
|
||||||
|
|
||||||
|
def acquerir(self, pid):
|
||||||
|
if self.est_disponible():
|
||||||
|
self.proprietaire = pid
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def liberer(self):
|
||||||
|
self.proprietaire = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self.est_disponible():
|
||||||
|
return f"[{self.nom}] LIBRE"
|
||||||
|
return f"[{self.nom}] → PID {self.proprietaire}"
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessusAvecRessources(Processus):
|
||||||
|
"""Processus qui nécessite des ressources."""
|
||||||
|
|
||||||
|
def __init__(self, nom, duree_totale, ressources_requises):
|
||||||
|
super().__init__(nom, duree_totale)
|
||||||
|
self.ressources_requises = ressources_requises
|
||||||
|
self.ressources_obtenues = []
|
||||||
|
|
||||||
|
def demander_ressources(self, ressources_dispo):
|
||||||
|
"""Tente d'obtenir les ressources nécessaires."""
|
||||||
|
for nom_res in self.ressources_requises:
|
||||||
|
if nom_res not in self.ressources_obtenues:
|
||||||
|
ressource = ressources_dispo.get(nom_res)
|
||||||
|
if ressource and ressource.acquerir(self.pid):
|
||||||
|
self.ressources_obtenues.append(nom_res)
|
||||||
|
print(f" ✓ {self.nom} obtient {nom_res}")
|
||||||
|
else:
|
||||||
|
print(f" ✗ {self.nom} BLOQUÉ sur {nom_res}")
|
||||||
|
self.etat = "bloque"
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def liberer_ressources(self, ressources_dispo):
|
||||||
|
"""Libère toutes les ressources."""
|
||||||
|
for nom_res in self.ressources_obtenues:
|
||||||
|
ressources_dispo[nom_res].liberer()
|
||||||
|
print(f" ○ {self.nom} libère {nom_res}")
|
||||||
|
self.ressources_obtenues = []
|
||||||
|
|
||||||
|
|
||||||
|
def detecter_interblocage(processus_list, ressources):
|
||||||
|
"""Détecte un interblocage."""
|
||||||
|
processus_actifs = [p for p in processus_list if p.etat != "termine"]
|
||||||
|
|
||||||
|
if len(processus_actifs) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
tous_bloques = all(p.etat == "bloque" for p in processus_actifs)
|
||||||
|
|
||||||
|
if tous_bloques:
|
||||||
|
print("\n" + "!"*50)
|
||||||
|
print("!!! INTERBLOCAGE DÉTECTÉ !!!")
|
||||||
|
print("!"*50)
|
||||||
|
for p in processus_actifs:
|
||||||
|
attend = [r for r in p.ressources_requises if r not in p.ressources_obtenues]
|
||||||
|
print(f" {p.nom}: possède {p.ressources_obtenues}, attend {attend}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 5 : Comparaison
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def comparer_ordonnanceurs():
|
||||||
|
"""Compare FIFO et Round Robin."""
|
||||||
|
|
||||||
|
processus_test = [
|
||||||
|
("Navigateur", 8),
|
||||||
|
("Editeur", 4),
|
||||||
|
("Terminal", 2),
|
||||||
|
("Musique", 6)
|
||||||
|
]
|
||||||
|
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("COMPARAISON FIFO vs ROUND ROBIN")
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
# Test FIFO
|
||||||
|
Processus.compteur_pid = 0
|
||||||
|
fifo = OrdonnanceurFIFO()
|
||||||
|
for nom, duree in processus_test:
|
||||||
|
fifo.ajouter_processus(Processus(nom, duree))
|
||||||
|
fifo.executer_tous()
|
||||||
|
|
||||||
|
# Test Round Robin
|
||||||
|
Processus.compteur_pid = 0
|
||||||
|
rr = OrdonnanceurRoundRobin(quantum=2)
|
||||||
|
for nom, duree in processus_test:
|
||||||
|
rr.ajouter_processus(Processus(nom, duree))
|
||||||
|
rr.executer_tous()
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
print("="*60)
|
||||||
|
print("TEST 1 : ORDONNANCEUR FIFO")
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
Processus.compteur_pid = 0
|
||||||
|
fifo = OrdonnanceurFIFO()
|
||||||
|
fifo.ajouter_processus(Processus("Firefox", 5))
|
||||||
|
fifo.ajouter_processus(Processus("VSCode", 3))
|
||||||
|
fifo.ajouter_processus(Processus("Spotify", 2))
|
||||||
|
fifo.executer_tous()
|
||||||
|
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("TEST 2 : ORDONNANCEUR ROUND ROBIN")
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
Processus.compteur_pid = 0
|
||||||
|
rr = OrdonnanceurRoundRobin(quantum=2)
|
||||||
|
rr.ajouter_processus(Processus("Firefox", 6))
|
||||||
|
rr.ajouter_processus(Processus("VSCode", 4))
|
||||||
|
rr.ajouter_processus(Processus("Spotify", 2))
|
||||||
|
rr.executer_tous()
|
||||||
|
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("TEST 3 : SIMULATION D'INTERBLOCAGE")
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
ressources = {
|
||||||
|
"A": Ressource("A"),
|
||||||
|
"B": Ressource("B")
|
||||||
|
}
|
||||||
|
|
||||||
|
Processus.compteur_pid = 0
|
||||||
|
p1 = ProcessusAvecRessources("Prog1", 5, ["A", "B"])
|
||||||
|
p2 = ProcessusAvecRessources("Prog2", 5, ["B", "A"])
|
||||||
|
|
||||||
|
print("\nÉtat initial des ressources:")
|
||||||
|
for r in ressources.values():
|
||||||
|
print(f" {r}")
|
||||||
|
|
||||||
|
print("\nTour 1: Prog1 demande ses ressources")
|
||||||
|
p1.demander_ressources(ressources)
|
||||||
|
|
||||||
|
print("\nTour 2: Prog2 demande ses ressources")
|
||||||
|
p2.demander_ressources(ressources)
|
||||||
|
|
||||||
|
print("\nTour 3: Prog1 continue")
|
||||||
|
p1.demander_ressources(ressources)
|
||||||
|
|
||||||
|
print("\nTour 4: Prog2 continue")
|
||||||
|
p2.demander_ressources(ressources)
|
||||||
|
|
||||||
|
detecter_interblocage([p1, p2], ressources)
|
||||||
|
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("TEST 4 : COMPARAISON DES ALGORITHMES")
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
comparer_ordonnanceurs()
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# Exercices — Gestion des processus
|
||||||
|
|
||||||
## 1. QCM
|
## 1. QCM
|
||||||
|
|
||||||
1. Le terminal, interpréteur de commande :
|
1. Le terminal, interpréteur de commande :
|
||||||
@@ -30,7 +32,7 @@
|
|||||||
|
|
||||||
b. Changer l'état des processus
|
b. Changer l'état des processus
|
||||||
|
|
||||||
c. Choisi le processus à exécuter
|
c. Choisit le processus à exécuter
|
||||||
|
|
||||||
5. Un interblocage est :
|
5. Un interblocage est :
|
||||||
|
|
||||||
@@ -42,5 +44,13 @@
|
|||||||
|
|
||||||
## 2. Questions
|
## 2. Questions
|
||||||
|
|
||||||
1. Expliquer comment cela se fait que l'utilisateur d'un ordinateur à l'impression que les programmes sont exécuté en même temps.
|
1. Expliquer comment il se fait que l'utilisateur d'un ordinateur a l'impression que les programmes sont exécutés en même temps.
|
||||||
2. Quels sont les différences entre les états prêt et élu.
|
2. Quelles sont les différences entre les états prêt et élu ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -1,58 +1,56 @@
|
|||||||
## Gestion des processus et des ressources
|
# Gestion des processus et des ressources
|
||||||
|
|
||||||
|
## Le programme
|
||||||
|
|
||||||
### Le programme
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
Nous le savons, un ordinateur possède une architecture de Von Neumann, il y a donc différents composants tels que la mémoire vive, la mémoire 'dure' (Disque dur, SSD...), un processeur...C'est cet élèment ci qui nous intéresse aujourd'hui.
|
Nous le savons, un ordinateur possède une architecture de Von Neumann, il y a donc différents composants tels que la mémoire vive, la mémoire 'dure' (Disque dur, SSD...), un processeur... C'est cet élément-ci qui nous intéresse aujourd'hui.
|
||||||
|
|
||||||
Le processeur est le cerveau de l'ordinateur et c'est lui qui exécute les tâches de la machine.
|
Le processeur est le cerveau de l'ordinateur et c'est lui qui exécute les tâches de la machine.
|
||||||
|
|
||||||
Comment expliquer que notre processeur arrive à ouvrir un navigateur web, un lecteur de musique, télécharger un jeu ou encore envoyer des messages le tout simultanément. (Sans parler des tâches de fond de l'ordinateur)
|
Comment expliquer que notre processeur arrive à ouvrir un navigateur web, un lecteur de musique, télécharger un jeu ou encore envoyer des messages le tout simultanément. (Sans parler des tâches de fond de l'ordinateur)
|
||||||
|
|
||||||
Tout ces programmes sont en réalité des lignes immenses de code et le processeur lui agit seul. Il exécute une à une les instructions de chaque programme, mais donne la sensation de tout gérer en même temps.
|
Tous ces programmes sont en réalité des lignes immenses de code et le processeur, lui, agit seul. Il exécute une à une les instructions de chaque programme, mais donne la sensation de tout gérer en même temps.
|
||||||
|
|
||||||
> Les processeurs multi-cœurs fonctionnent de la même façon à l'exception près qu'il n'y a pas 1 cœur, mais 4 voir 8 cœurs. Mais au vu de la multitude de programmes tournant en même temps sur la machine, le nombre de cœurs est nettement inferieur.
|
> Les processeurs multi-cœurs fonctionnent de la même façon à l'exception près qu'il n'y a pas 1 cœur, mais 4 voire 8 cœurs. Mais au vu de la multitude de programmes tournant en même temps sur la machine, le nombre de cœurs est nettement inférieur.
|
||||||
|
|
||||||
## 1. Notion de processus
|
## 1. Notion de processus
|
||||||
|
|
||||||
Lorsqu'un programme est exécuté, il créé plusieurs processus. En effet, un programme est composé de diverses instructions (lignes de code) qui elles mêmes forment diverses parties (processus) de celui-ci.
|
Lorsqu'un programme est exécuté, il crée plusieurs processus. En effet, un programme est composé de diverses instructions (lignes de code) qui elles-mêmes forment diverses parties (processus) de celui-ci.
|
||||||
|
|
||||||
Ces processus sont donc stocker dans une file (tout cela est géré par l'OS [Rappel 1ère]) et sont exécuté un à un.
|
Ces processus sont donc stockés dans une file (tout cela est géré par l'OS [Rappel 1ère]) et sont exécutés un à un.
|
||||||
|
|
||||||
*Exemple de répartition de deux programmes par l'OS :*
|
*Exemple de répartition de deux programmes par l'OS :*
|
||||||
|
|
||||||
<img src="assets/file.png" alt="File" style="width:50%;" />
|
<img src="assets/file.png" alt="File" style="width:50%;" />
|
||||||
|
|
||||||
Chaque processus possèdent différentes informations stockées en mémoire (dans le PCB (*Process Control Block*)) comme :
|
Chaque processus possède différentes informations stockées en mémoire (dans le PCB (*Process Control Block*)) comme :
|
||||||
|
|
||||||
| Nom | Description |
|
| Nom | Description |
|
||||||
| ---------- | ----------------------------------------------------- |
|
| ---------- | ----------------------------------------------------- |
|
||||||
| PID | Process ID, identifiant du processus |
|
| PID | Process ID, identifiant du processus |
|
||||||
| Etat | Etat du processus |
|
| Etat | Etat du processus |
|
||||||
| Registre | Valeur des registres lors de la dernière interruption |
|
| Registre | Valeur des registres lors de la dernière interruption |
|
||||||
| Mémoire | Emplacement mémoire, allouée par le processeur |
|
| Mémoire | Emplacement mémoire alloué par le système |
|
||||||
| Ressources | Ressources utilisées par le processus. |
|
| Ressources | Ressources utilisées par le processus. |
|
||||||
|
|
||||||
## 2. Gestion des processus
|
## 2. Gestion des processus
|
||||||
|
|
||||||
Nous l'avons vu chaque processus possèdent plusieurs informations. **L'état** d'un processus permet de comprendre comment la file d'exécution est créée et comment celle-ci perdure.
|
Nous l'avons vu, chaque processus possède plusieurs informations. **L'état** d'un processus permet de comprendre comment la file d'exécution est créée et comment celle-ci perdure.
|
||||||
|
|
||||||
Il existe 3 états différents :
|
Il existe 3 états différents :
|
||||||
|
|
||||||
- Prêt : Le processus attend d'être exécuté. Il est dans la file d'exécution.
|
- Prêt : Le processus attend d'être exécuté. Il est dans la file d'exécution.
|
||||||
- Elu : En cours d'exécution
|
- Elu : En cours d'exécution
|
||||||
- Bloqué/En attente : Le processus nécessite une ressource non disponible. Tel qu'un emplacement mémoire, une entré/sortie.
|
- Bloqué/En attente : Le processus nécessite une ressource non disponible, tel qu'un emplacement mémoire ou une entrée/sortie.
|
||||||
Lorsque la ressource sera disponible, le processus repassera en état prêt.
|
Lorsque la ressource sera disponible, le processus repassera en état prêt.
|
||||||
- On peut imaginer que le processus attend l'intervention de l'utilisateur, ou le chargement d'une ressources (donc d'autres processus), etc.
|
- On peut imaginer que le processus attend l'intervention de l'utilisateur, ou le chargement d'une ressource (donc d'autres processus), etc.
|
||||||
|
|
||||||
Il existe aussi deux autres états :
|
Il existe aussi deux autres états :
|
||||||
|
|
||||||
- Nouveau : le processus vient d'être crée, il n'est pas encore dans la file d'exécution
|
- Nouveau : le processus vient d'être créé, il n'est pas encore dans la file d'exécution
|
||||||
- Terminé : l'exécution du processus est finie.
|
- Terminé : l'exécution du processus est finie.
|
||||||
|
|
||||||
<u>Voici un schéma des différents états :</u>
|
<u>Voici un schéma des différents états :</u>
|
||||||
@@ -61,8 +59,8 @@ Il existe aussi deux autres états :
|
|||||||
|
|
||||||
## 3. Interblocage :
|
## 3. Interblocage :
|
||||||
|
|
||||||
L'interblocage intervient lorsque plusieurs processus sont bloqués les un aux autres.
|
L'interblocage intervient lorsque plusieurs processus sont bloqués les uns par les autres.
|
||||||
Imaginons deux programme.
|
Imaginons deux programmes.
|
||||||
|
|
||||||
```
|
```
|
||||||
# Programme 1 :
|
# Programme 1 :
|
||||||
@@ -90,7 +88,7 @@ Imaginons deux programme.
|
|||||||
Accès à la ressource B
|
Accès à la ressource B
|
||||||
```
|
```
|
||||||
|
|
||||||
- Le processus 1 à commencé, via un premier processus. Afin de faire tourner tous les programmes 'en même temps' il repasse dans la file d'exécution
|
- Le processus 1 a commencé, via un premier processus. Afin de faire tourner tous les programmes 'en même temps' il repasse dans la file d'exécution
|
||||||
|
|
||||||
```
|
```
|
||||||
# Programme 2 :
|
# Programme 2 :
|
||||||
@@ -127,14 +125,14 @@ Imaginons deux programme.
|
|||||||
Accès à la ressource A
|
Accès à la ressource A
|
||||||
```
|
```
|
||||||
|
|
||||||
- Le programme 1 créer un premier processus, qui appelle la ressource A
|
- Le programme 1 crée un premier processus, qui appelle la ressource A
|
||||||
|
|
||||||
```
|
```
|
||||||
# Programme 2 :
|
# Programme 2 :
|
||||||
Accès à la ressource B
|
Accès à la ressource B
|
||||||
```
|
```
|
||||||
|
|
||||||
- le programme 2 créer un premier processus, qui appelle la ressource B
|
- Le programme 2 crée un premier processus, qui appelle la ressource B
|
||||||
|
|
||||||
```
|
```
|
||||||
# Programme 1 :
|
# Programme 1 :
|
||||||
@@ -150,13 +148,13 @@ Imaginons deux programme.
|
|||||||
|
|
||||||
- Le programme 2 attend la ressource A utilisée par le programme 1 et passe en état bloqué.
|
- Le programme 2 attend la ressource A utilisée par le programme 1 et passe en état bloqué.
|
||||||
|
|
||||||
Ici les deux programmes sont en état bloqué car chacun attend la ressource de l'autre. Il y a interblocage et donc aucuns des programmes ne peut s'exécuter.
|
Ici les deux programmes sont en état bloqué car chacun attend la ressource de l'autre. Il y a interblocage et donc aucun des programmes ne peut s'exécuter.
|
||||||
|
|
||||||
## 4. En pratique
|
## 4. En pratique
|
||||||
|
|
||||||
### 4. 1. Windows
|
### 4. 1. Windows
|
||||||
|
|
||||||
Sous windows il est possible de voir les processus en cours d'exécution grâce au **gestionnaire de tâche**. Celui-ci est accessible grâce aux touches Ctrl+ Maj+Echap.
|
Sous Windows il est possible de voir les processus en cours d'exécution grâce au **gestionnaire de tâches**. Celui-ci est accessible grâce aux touches Ctrl+Maj+Echap.
|
||||||
Il suffit ensuite d'aller dans l'onglet détail et on obtient tous les processus.
|
Il suffit ensuite d'aller dans l'onglet détail et on obtient tous les processus.
|
||||||
|
|
||||||
<img src="assets/liste_processus_windows.png" style="width:60%;" />
|
<img src="assets/liste_processus_windows.png" style="width:60%;" />
|
||||||
@@ -169,16 +167,16 @@ Sous linux, il suffit d'accéder au terminal. Et d'y écrire la commande **ps**,
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
### 4.3 MacOs
|
### 4. 3. macOS
|
||||||
|
|
||||||
MacOs étant un système Unix, (dérivé de BSD ici) on retrouve les mêmes commandes que sous linux en grande partie.
|
macOS étant un système Unix (dérivé de BSD), on retrouve les mêmes commandes que sous Linux en grande partie.
|
||||||
Pour voir les processus, ici, on utilisera plutôt **TOP** (dispo sous linux également donc).
|
Pour voir les processus, ici, on utilisera plutôt **TOP** (dispo sous linux également donc).
|
||||||
|
|
||||||
<img src="assets/top.png" alt="top" style="zoom:50%;" />
|
<img src="assets/top.png" alt="top" style="zoom:50%;" />
|
||||||
|
|
||||||
---------
|
---------
|
||||||
|
|
||||||
Auteurs : Florian Mathieu, Timothée Decoster, Enzo Frémaux
|
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||||
|
|
||||||
Licence CC BY NC
|
Licence CC BY NC
|
||||||
|
|
||||||
|
|||||||
503
Processus/TP_Ordonnanceur.md
Normal file
503
Processus/TP_Ordonnanceur.md
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
# TP : Simulateur d'Ordonnanceur — Gestion des Processus
|
||||||
|
|
||||||
|
> **Thème** : Processus, états, ordonnancement, interblocage
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Votre système d'exploitation préféré gère en permanence des dizaines de processus : navigateur web, lecteur de musique, mises à jour en arrière-plan... Comment fait-il pour donner l'illusion que tout s'exécute en même temps ?
|
||||||
|
|
||||||
|
Dans ce TP, vous allez créer un **simulateur d'ordonnanceur** qui gère une file de processus et les exécute tour à tour, comme le fait un vrai système d'exploitation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 1 : La classe Processus
|
||||||
|
|
||||||
|
### Exercice 1 : Modéliser un processus
|
||||||
|
|
||||||
|
Créez une classe `Processus` représentant un processus avec ses caractéristiques.
|
||||||
|
|
||||||
|
**Attributs :**
|
||||||
|
- `pid` : identifiant unique du processus (int)
|
||||||
|
- `nom` : nom du processus (str)
|
||||||
|
- `duree_totale` : durée d'exécution totale nécessaire (int, en unités de temps)
|
||||||
|
- `duree_restante` : durée restant à exécuter (int)
|
||||||
|
- `etat` : état actuel du processus (str) — "nouveau", "pret", "elu", "bloque", "termine"
|
||||||
|
- `priorite` : niveau de priorité (int, optionnel, par défaut 0)
|
||||||
|
|
||||||
|
**Méthodes :**
|
||||||
|
- `__init__` : constructeur
|
||||||
|
- `executer(quantum)` : exécute le processus pendant `quantum` unités de temps
|
||||||
|
- `est_termine()` : renvoie True si le processus est terminé
|
||||||
|
- `__repr__` : affichage du processus
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Processus:
|
||||||
|
# Compteur de PID (variable de classe)
|
||||||
|
compteur_pid = 0
|
||||||
|
|
||||||
|
def __init__(self, nom, duree_totale, priorite=0):
|
||||||
|
"""
|
||||||
|
Constructeur de la classe Processus.
|
||||||
|
|
||||||
|
:param nom: (str) Nom du processus
|
||||||
|
:param duree_totale: (int) Durée totale d'exécution
|
||||||
|
:param priorite: (int) Priorité (0 = normale)
|
||||||
|
"""
|
||||||
|
Processus.compteur_pid += 1
|
||||||
|
self.pid = Processus.compteur_pid
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def executer(self, quantum):
|
||||||
|
"""
|
||||||
|
Exécute le processus pendant quantum unités de temps.
|
||||||
|
Met à jour la durée restante.
|
||||||
|
|
||||||
|
:param quantum: (int) Temps d'exécution alloué
|
||||||
|
:return: (int) Temps réellement utilisé
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def est_termine(self):
|
||||||
|
"""
|
||||||
|
Vérifie si le processus est terminé.
|
||||||
|
|
||||||
|
:return: (bool) True si terminé
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Affichage du processus."""
|
||||||
|
return f"[PID {self.pid}] {self.nom} ({self.etat}) - {self.duree_restante}/{self.duree_totale}"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests :**
|
||||||
|
```python
|
||||||
|
>>> p = Processus("Firefox", 10)
|
||||||
|
>>> print(p)
|
||||||
|
[PID 1] Firefox (nouveau) - 10/10
|
||||||
|
>>> p.etat = "elu"
|
||||||
|
>>> p.executer(3)
|
||||||
|
3
|
||||||
|
>>> print(p)
|
||||||
|
[PID 1] Firefox (elu) - 7/10
|
||||||
|
>>> p.est_termine()
|
||||||
|
False
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : L'ordonnanceur FIFO
|
||||||
|
|
||||||
|
### Exercice 2 : Ordonnanceur simple (FIFO)
|
||||||
|
|
||||||
|
L'ordonnanceur **FIFO** (First In, First Out) exécute les processus dans l'ordre d'arrivée, chacun jusqu'à la fin.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class OrdonnanceurFIFO:
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialise l'ordonnanceur avec une file vide."""
|
||||||
|
self.file_prets = [] # File des processus prêts
|
||||||
|
self.historique = [] # Historique d'exécution
|
||||||
|
self.temps = 0 # Temps courant
|
||||||
|
|
||||||
|
def ajouter_processus(self, processus):
|
||||||
|
"""
|
||||||
|
Ajoute un processus à la file des prêts.
|
||||||
|
|
||||||
|
:param processus: (Processus) Processus à ajouter
|
||||||
|
"""
|
||||||
|
processus.etat = "pret"
|
||||||
|
self.file_prets.append(processus)
|
||||||
|
print(f"[T={self.temps}] {processus.nom} ajouté à la file")
|
||||||
|
|
||||||
|
def executer_suivant(self):
|
||||||
|
"""
|
||||||
|
Exécute le prochain processus de la file jusqu'à la fin.
|
||||||
|
"""
|
||||||
|
if len(self.file_prets) == 0:
|
||||||
|
print("File vide, rien à exécuter")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Récupérer le premier processus
|
||||||
|
processus = self.file_prets.pop(0)
|
||||||
|
processus.etat = "elu"
|
||||||
|
|
||||||
|
print(f"\n[T={self.temps}] {processus.nom} commence son exécution")
|
||||||
|
|
||||||
|
# Exécuter jusqu'à la fin
|
||||||
|
while not processus.est_termine():
|
||||||
|
temps_utilise = processus.executer(1)
|
||||||
|
self.temps += temps_utilise
|
||||||
|
self.historique.append((self.temps, processus.pid, processus.nom))
|
||||||
|
|
||||||
|
processus.etat = "termine"
|
||||||
|
print(f"[T={self.temps}] {processus.nom} terminé")
|
||||||
|
|
||||||
|
def executer_tous(self):
|
||||||
|
"""Exécute tous les processus de la file."""
|
||||||
|
print("\n" + "="*50)
|
||||||
|
print("ORDONNANCEUR FIFO - Début de l'exécution")
|
||||||
|
print("="*50)
|
||||||
|
|
||||||
|
while len(self.file_prets) > 0:
|
||||||
|
self.executer_suivant()
|
||||||
|
|
||||||
|
print("\n" + "="*50)
|
||||||
|
print(f"Exécution terminée - Temps total : {self.temps}")
|
||||||
|
print("="*50)
|
||||||
|
|
||||||
|
def afficher_historique(self):
|
||||||
|
"""Affiche l'historique d'exécution."""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests :**
|
||||||
|
```python
|
||||||
|
>>> ordonnanceur = OrdonnanceurFIFO()
|
||||||
|
>>> ordonnanceur.ajouter_processus(Processus("Firefox", 5))
|
||||||
|
>>> ordonnanceur.ajouter_processus(Processus("VSCode", 3))
|
||||||
|
>>> ordonnanceur.ajouter_processus(Processus("Spotify", 2))
|
||||||
|
>>> ordonnanceur.executer_tous()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : L'ordonnanceur Round Robin
|
||||||
|
|
||||||
|
### Exercice 3 : Ordonnanceur à temps partagé
|
||||||
|
|
||||||
|
L'ordonnanceur **Round Robin** (tourniquet) alloue un **quantum** de temps à chaque processus, puis passe au suivant. Si le processus n'est pas terminé, il retourne en fin de file.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class OrdonnanceurRoundRobin:
|
||||||
|
def __init__(self, quantum=2):
|
||||||
|
"""
|
||||||
|
Initialise l'ordonnanceur Round Robin.
|
||||||
|
|
||||||
|
:param quantum: (int) Temps alloué à chaque processus
|
||||||
|
"""
|
||||||
|
self.file_prets = []
|
||||||
|
self.quantum = quantum
|
||||||
|
self.temps = 0
|
||||||
|
self.historique = []
|
||||||
|
|
||||||
|
def ajouter_processus(self, processus):
|
||||||
|
"""Ajoute un processus à la file."""
|
||||||
|
processus.etat = "pret"
|
||||||
|
self.file_prets.append(processus)
|
||||||
|
|
||||||
|
def executer_cycle(self):
|
||||||
|
"""
|
||||||
|
Exécute un cycle : le premier processus pendant quantum unités.
|
||||||
|
S'il n'est pas terminé, il retourne en fin de file.
|
||||||
|
|
||||||
|
:return: (bool) True si un processus a été exécuté
|
||||||
|
"""
|
||||||
|
if len(self.file_prets) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Récupérer le premier processus
|
||||||
|
processus = self.file_prets.pop(0)
|
||||||
|
processus.etat = "elu"
|
||||||
|
|
||||||
|
# Exécuter pendant le quantum
|
||||||
|
temps_utilise = processus.executer(self.quantum)
|
||||||
|
self.temps += temps_utilise
|
||||||
|
|
||||||
|
# Enregistrer dans l'historique
|
||||||
|
for _ in range(temps_utilise):
|
||||||
|
self.historique.append(processus.nom[0]) # Première lettre
|
||||||
|
|
||||||
|
if processus.est_termine():
|
||||||
|
processus.etat = "termine"
|
||||||
|
print(f"[T={self.temps}] {processus.nom} TERMINÉ")
|
||||||
|
else:
|
||||||
|
# Remettre en fin de file
|
||||||
|
processus.etat = "pret"
|
||||||
|
self.file_prets.append(processus)
|
||||||
|
print(f"[T={self.temps}] {processus.nom} interrompu, retour en file ({processus.duree_restante} restant)")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def executer_tous(self):
|
||||||
|
"""Exécute tous les processus jusqu'à la fin."""
|
||||||
|
print(f"\n{'='*50}")
|
||||||
|
print(f"ORDONNANCEUR ROUND ROBIN (quantum={self.quantum})")
|
||||||
|
print(f"{'='*50}\n")
|
||||||
|
|
||||||
|
while self.executer_cycle():
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(f"\n{'='*50}")
|
||||||
|
print(f"Temps total : {self.temps}")
|
||||||
|
print(f"Historique : {''.join(self.historique)}")
|
||||||
|
print(f"{'='*50}")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests :**
|
||||||
|
```python
|
||||||
|
>>> Processus.compteur_pid = 0 # Reset des PID
|
||||||
|
>>> rr = OrdonnanceurRoundRobin(quantum=2)
|
||||||
|
>>> rr.ajouter_processus(Processus("Firefox", 6))
|
||||||
|
>>> rr.ajouter_processus(Processus("VSCode", 4))
|
||||||
|
>>> rr.ajouter_processus(Processus("Spotify", 2))
|
||||||
|
>>> rr.executer_tous()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sortie attendue :**
|
||||||
|
```
|
||||||
|
==================================================
|
||||||
|
ORDONNANCEUR ROUND ROBIN (quantum=2)
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
[T=2] Firefox interrompu, retour en file (4 restant)
|
||||||
|
[T=4] VSCode interrompu, retour en file (2 restant)
|
||||||
|
[T=6] Spotify TERMINÉ
|
||||||
|
[T=8] Firefox interrompu, retour en file (2 restant)
|
||||||
|
[T=10] VSCode TERMINÉ
|
||||||
|
[T=12] Firefox TERMINÉ
|
||||||
|
|
||||||
|
==================================================
|
||||||
|
Temps total : 12
|
||||||
|
Historique : FFVVSSFFVVFF
|
||||||
|
==================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : Simulation d'interblocage
|
||||||
|
|
||||||
|
### Exercice 4 : Processus avec ressources
|
||||||
|
|
||||||
|
Modélisons des processus qui ont besoin de ressources partagées.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Ressource:
|
||||||
|
def __init__(self, nom):
|
||||||
|
"""
|
||||||
|
:param nom: (str) Nom de la ressource
|
||||||
|
"""
|
||||||
|
self.nom = nom
|
||||||
|
self.proprietaire = None # PID du processus qui détient la ressource
|
||||||
|
|
||||||
|
def est_disponible(self):
|
||||||
|
"""Renvoie True si la ressource est libre."""
|
||||||
|
return self.proprietaire is None
|
||||||
|
|
||||||
|
def acquerir(self, pid):
|
||||||
|
"""
|
||||||
|
Tente d'acquérir la ressource.
|
||||||
|
|
||||||
|
:param pid: (int) PID du processus demandeur
|
||||||
|
:return: (bool) True si acquisition réussie
|
||||||
|
"""
|
||||||
|
if self.est_disponible():
|
||||||
|
self.proprietaire = pid
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def liberer(self):
|
||||||
|
"""Libère la ressource."""
|
||||||
|
self.proprietaire = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self.est_disponible():
|
||||||
|
return f"Ressource {self.nom} : LIBRE"
|
||||||
|
else:
|
||||||
|
return f"Ressource {self.nom} : occupée par PID {self.proprietaire}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice 5 : Détection d'interblocage
|
||||||
|
|
||||||
|
Créez une fonction qui détecte un interblocage potentiel.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ProcessusAvecRessources(Processus):
|
||||||
|
def __init__(self, nom, duree_totale, ressources_requises):
|
||||||
|
"""
|
||||||
|
:param ressources_requises: (list) Liste des noms de ressources nécessaires
|
||||||
|
"""
|
||||||
|
super().__init__(nom, duree_totale)
|
||||||
|
self.ressources_requises = ressources_requises
|
||||||
|
self.ressources_obtenues = []
|
||||||
|
|
||||||
|
def demander_ressources(self, ressources_dispo):
|
||||||
|
"""
|
||||||
|
Tente d'obtenir toutes les ressources nécessaires.
|
||||||
|
|
||||||
|
:param ressources_dispo: (dict) {nom: Ressource}
|
||||||
|
:return: (bool) True si toutes les ressources obtenues
|
||||||
|
"""
|
||||||
|
for nom_res in self.ressources_requises:
|
||||||
|
if nom_res not in self.ressources_obtenues:
|
||||||
|
ressource = ressources_dispo.get(nom_res)
|
||||||
|
if ressource and ressource.acquerir(self.pid):
|
||||||
|
self.ressources_obtenues.append(nom_res)
|
||||||
|
print(f" {self.nom} obtient {nom_res}")
|
||||||
|
else:
|
||||||
|
print(f" {self.nom} BLOQUÉ sur {nom_res}")
|
||||||
|
self.etat = "bloque"
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def liberer_ressources(self, ressources_dispo):
|
||||||
|
"""Libère toutes les ressources détenues."""
|
||||||
|
for nom_res in self.ressources_obtenues:
|
||||||
|
ressources_dispo[nom_res].liberer()
|
||||||
|
print(f" {self.nom} libère {nom_res}")
|
||||||
|
self.ressources_obtenues = []
|
||||||
|
|
||||||
|
|
||||||
|
def detecter_interblocage(processus_list, ressources):
|
||||||
|
"""
|
||||||
|
Détecte si un interblocage existe.
|
||||||
|
|
||||||
|
:param processus_list: (list) Liste des processus
|
||||||
|
:param ressources: (dict) Dictionnaire des ressources
|
||||||
|
:return: (bool) True si interblocage détecté
|
||||||
|
"""
|
||||||
|
# Un interblocage existe si tous les processus non terminés sont bloqués
|
||||||
|
processus_actifs = [p for p in processus_list if p.etat != "termine"]
|
||||||
|
|
||||||
|
if len(processus_actifs) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
tous_bloques = all(p.etat == "bloque" for p in processus_actifs)
|
||||||
|
|
||||||
|
if tous_bloques:
|
||||||
|
print("\n*** INTERBLOCAGE DÉTECTÉ ! ***")
|
||||||
|
print("Processus bloqués :")
|
||||||
|
for p in processus_actifs:
|
||||||
|
print(f" - {p.nom} : possède {p.ressources_obtenues}, attend {[r for r in p.ressources_requises if r not in p.ressources_obtenues]}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
```
|
||||||
|
|
||||||
|
**Exemple de simulation d'interblocage :**
|
||||||
|
```python
|
||||||
|
# Ressources
|
||||||
|
ressources = {
|
||||||
|
"A": Ressource("A"),
|
||||||
|
"B": Ressource("B")
|
||||||
|
}
|
||||||
|
|
||||||
|
# Processus (scénario classique d'interblocage)
|
||||||
|
p1 = ProcessusAvecRessources("Programme1", 5, ["A", "B"])
|
||||||
|
p2 = ProcessusAvecRessources("Programme2", 5, ["B", "A"])
|
||||||
|
|
||||||
|
# Simulation manuelle
|
||||||
|
print("=== Simulation d'interblocage ===\n")
|
||||||
|
|
||||||
|
print("Tour 1 : P1 demande A")
|
||||||
|
p1.demander_ressources(ressources)
|
||||||
|
|
||||||
|
print("\nTour 2 : P2 demande B")
|
||||||
|
p2.demander_ressources(ressources)
|
||||||
|
|
||||||
|
print("\nTour 3 : P1 demande B (déjà pris par P2)")
|
||||||
|
p1.demander_ressources(ressources)
|
||||||
|
|
||||||
|
print("\nTour 4 : P2 demande A (déjà pris par P1)")
|
||||||
|
p2.demander_ressources(ressources)
|
||||||
|
|
||||||
|
# Détection
|
||||||
|
detecter_interblocage([p1, p2], ressources)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 5 : Statistiques et comparaison
|
||||||
|
|
||||||
|
### Exercice 6 : Calcul des métriques
|
||||||
|
|
||||||
|
Ajoutez des méthodes pour calculer les statistiques d'ordonnancement :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def calculer_statistiques(self):
|
||||||
|
"""
|
||||||
|
Calcule les statistiques d'ordonnancement.
|
||||||
|
|
||||||
|
:return: (dict) Statistiques
|
||||||
|
"""
|
||||||
|
# Temps de réponse moyen
|
||||||
|
# Temps d'attente moyen
|
||||||
|
# Temps de rotation moyen
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Métriques importantes :**
|
||||||
|
- **Temps de rotation** (turnaround) : temps total entre l'arrivée et la fin
|
||||||
|
- **Temps d'attente** : temps passé en file d'attente
|
||||||
|
- **Temps de réponse** : temps avant la première exécution
|
||||||
|
|
||||||
|
### Exercice 7 : Comparaison FIFO vs Round Robin
|
||||||
|
|
||||||
|
Créez un programme qui compare les deux algorithmes sur le même jeu de processus.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def comparer_ordonnanceurs():
|
||||||
|
"""Compare FIFO et Round Robin."""
|
||||||
|
|
||||||
|
processus_test = [
|
||||||
|
("Navigateur", 8),
|
||||||
|
("Editeur", 4),
|
||||||
|
("Terminal", 2),
|
||||||
|
("Musique", 6)
|
||||||
|
]
|
||||||
|
|
||||||
|
print("="*60)
|
||||||
|
print("COMPARAISON DES ORDONNANCEURS")
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
# Test FIFO
|
||||||
|
Processus.compteur_pid = 0
|
||||||
|
fifo = OrdonnanceurFIFO()
|
||||||
|
for nom, duree in processus_test:
|
||||||
|
fifo.ajouter_processus(Processus(nom, duree))
|
||||||
|
fifo.executer_tous()
|
||||||
|
|
||||||
|
# Test Round Robin
|
||||||
|
Processus.compteur_pid = 0
|
||||||
|
rr = OrdonnanceurRoundRobin(quantum=2)
|
||||||
|
for nom, duree in processus_test:
|
||||||
|
rr.ajouter_processus(Processus(nom, duree))
|
||||||
|
rr.executer_tous()
|
||||||
|
|
||||||
|
# À compléter : afficher la comparaison des statistiques
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Résumé des algorithmes
|
||||||
|
|
||||||
|
| Algorithme | Principe | Avantages | Inconvénients |
|
||||||
|
|------------|----------|-----------|---------------|
|
||||||
|
| **FIFO** | Premier arrivé, premier servi | Simple | Temps d'attente long pour les petits processus |
|
||||||
|
| **Round Robin** | Quantum de temps pour chacun | Équitable, bon temps de réponse | Overhead des changements de contexte |
|
||||||
|
| **Priorité** | Processus prioritaire d'abord | Urgent traité en premier | Famine des processus peu prioritaires |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pour aller plus loin
|
||||||
|
|
||||||
|
- **Ordonnanceur à priorité** : Implémenter un ordonnanceur qui choisit le processus de plus haute priorité
|
||||||
|
- **Ordonnanceur SJF** (Shortest Job First) : Le processus le plus court d'abord
|
||||||
|
- **Vieillissement** (Aging) : Augmenter la priorité des processus qui attendent longtemps
|
||||||
|
- **Interface graphique** : Visualiser l'exécution avec une barre de Gantt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
187
Recherche_textuelle/CORRIGE.md
Normal file
187
Recherche_textuelle/CORRIGE.md
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
# Corrigé des exercices — Recherche textuelle
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. QCM
|
||||||
|
|
||||||
|
| Question | Réponse | Explication |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| 1 | **b.** | On parcourt au maximum tous les caractères du texte une fois : O(n). |
|
||||||
|
| 2 | **c.** | On teste les positions 0, 1, ..., n-m, soit n - m + 1 positions. |
|
||||||
|
| 3 | **b.** | Boyer-Moore compare de droite à gauche (fin du motif vers le début). |
|
||||||
|
| 4 | **b.** | Le pré-traitement permet de savoir de combien décaler après un échec. |
|
||||||
|
| 5 | **c.** | Dans le pire cas, on compare m caractères pour chacune des n-m+1 positions. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Questions de cours
|
||||||
|
|
||||||
|
### Question 1
|
||||||
|
|
||||||
|
Texte : `"abracadabra"` (longueur 11), Motif : `"abra"` (longueur 4)
|
||||||
|
|
||||||
|
**a.** Nombre de positions testées : n - m + 1 = 11 - 4 + 1 = **8 positions** (indices 0 à 7)
|
||||||
|
|
||||||
|
**b.** Le motif `"abra"` est trouvé aux positions :
|
||||||
|
- **Position 0** : `abra`cadabra
|
||||||
|
- **Position 7** : abracad`abra`
|
||||||
|
|
||||||
|
### Question 2
|
||||||
|
|
||||||
|
L'algorithme de Boyer-Moore est plus efficace car :
|
||||||
|
|
||||||
|
1. **Comparaison de droite à gauche** : En cas d'échec sur un caractère qui n'existe pas dans le motif, on peut sauter tout le motif d'un coup.
|
||||||
|
|
||||||
|
2. **Sauts importants** : Plus le motif est long, plus les sauts potentiels sont grands. Par exemple, avec un motif de 10 caractères et un caractère non présent dans le motif, on peut avancer de 10 positions.
|
||||||
|
|
||||||
|
3. **Cas favorable** : Si l'alphabet est grand et le motif long, Boyer-Moore peut avoir une complexité sous-linéaire O(n/m) dans le meilleur cas.
|
||||||
|
|
||||||
|
**Exemple** : Rechercher `"ALGORITHME"` dans un texte. Si on tombe sur un `"Z"` (absent du motif), on saute directement de 10 caractères.
|
||||||
|
|
||||||
|
### Question 3
|
||||||
|
|
||||||
|
**a.** `recherche("bonjour", "jour")` renvoie **3**
|
||||||
|
|
||||||
|
Vérification : `bonjour` → à l'indice 3, on trouve `jour`.
|
||||||
|
|
||||||
|
**b.** `recherche("bonjour", "soir")` renvoie **-1**
|
||||||
|
|
||||||
|
Le motif `"soir"` n'est pas présent dans `"bonjour"`.
|
||||||
|
|
||||||
|
**c.** Modification pour renvoyer toutes les positions :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def recherche_toutes(texte, motif):
|
||||||
|
n = len(texte)
|
||||||
|
m = len(motif)
|
||||||
|
positions = []
|
||||||
|
for j in range(n - m + 1):
|
||||||
|
i = 0
|
||||||
|
while i < m and texte[j + i] == motif[i]:
|
||||||
|
i = i + 1
|
||||||
|
if i == m:
|
||||||
|
positions.append(j)
|
||||||
|
return positions
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> recherche_toutes("abracadabra", "abra")
|
||||||
|
[0, 7]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Exercices de programmation
|
||||||
|
|
||||||
|
### Exercice 1 : Compter les occurrences
|
||||||
|
|
||||||
|
```python
|
||||||
|
def compter_occurrences(texte, motif):
|
||||||
|
"""Compte le nombre d'occurrences du motif dans le texte."""
|
||||||
|
n = len(texte)
|
||||||
|
m = len(motif)
|
||||||
|
compteur = 0
|
||||||
|
for j in range(n - m + 1):
|
||||||
|
i = 0
|
||||||
|
while i < m and texte[j + i] == motif[i]:
|
||||||
|
i = i + 1
|
||||||
|
if i == m:
|
||||||
|
compteur += 1
|
||||||
|
return compteur
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests** :
|
||||||
|
```python
|
||||||
|
>>> compter_occurrences("abracadabra", "a")
|
||||||
|
5
|
||||||
|
>>> compter_occurrences("abracadabra", "abra")
|
||||||
|
2
|
||||||
|
>>> compter_occurrences("aaaa", "aa")
|
||||||
|
3 # Occurrences aux positions 0, 1 et 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice 2 : Recherche insensible à la casse
|
||||||
|
|
||||||
|
```python
|
||||||
|
def recherche_insensible(texte, motif):
|
||||||
|
"""Recherche un motif sans tenir compte des majuscules/minuscules."""
|
||||||
|
texte_min = texte.lower()
|
||||||
|
motif_min = motif.lower()
|
||||||
|
n = len(texte_min)
|
||||||
|
m = len(motif_min)
|
||||||
|
for j in range(n - m + 1):
|
||||||
|
i = 0
|
||||||
|
while i < m and texte_min[j + i] == motif_min[i]:
|
||||||
|
i = i + 1
|
||||||
|
if i == m:
|
||||||
|
return j
|
||||||
|
return -1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests** :
|
||||||
|
```python
|
||||||
|
>>> recherche_insensible("Bonjour tout le Monde", "monde")
|
||||||
|
16
|
||||||
|
>>> recherche_insensible("PYTHON", "python")
|
||||||
|
0
|
||||||
|
>>> recherche_insensible("Hello World", "WORLD")
|
||||||
|
6
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice 3 : Analyse de complexité
|
||||||
|
|
||||||
|
Texte : `"aaaaaaaaaa"` (10 'a'), Motif : `"aaab"` (3 'a' puis 'b')
|
||||||
|
|
||||||
|
**a.** Calcul du nombre de comparaisons :
|
||||||
|
|
||||||
|
- Positions testées : 10 - 4 + 1 = 7 positions (indices 0 à 6)
|
||||||
|
- À chaque position, on compare les 3 premiers 'a' avec succès, puis échec sur 'b'
|
||||||
|
- Soit 4 comparaisons par position
|
||||||
|
|
||||||
|
**Total : 7 × 4 = 28 comparaisons**
|
||||||
|
|
||||||
|
**b.** Ce cas est défavorable car :
|
||||||
|
|
||||||
|
- Le motif partage un long préfixe avec le texte (`"aaa"`)
|
||||||
|
- À chaque tentative, on effectue presque toutes les comparaisons avant l'échec
|
||||||
|
- On ne décale que d'une position à chaque fois
|
||||||
|
- C'est le pire cas : beaucoup de comparaisons pour peu d'avancement
|
||||||
|
|
||||||
|
Ce type de motif (caractères répétés suivis d'un caractère différent) maximise le nombre de comparaisons.
|
||||||
|
|
||||||
|
### Exercice 4 : Table des dernières occurrences
|
||||||
|
|
||||||
|
Pour le motif `"EXEMPLE"` (indices 0 à 6) :
|
||||||
|
|
||||||
|
| Caractère | Dernière position |
|
||||||
|
|-----------|-------------------|
|
||||||
|
| E | 6 |
|
||||||
|
| X | 1 |
|
||||||
|
| M | 3 |
|
||||||
|
| P | 4 |
|
||||||
|
| L | 5 |
|
||||||
|
|
||||||
|
**Note** : Le caractère 'E' apparaît aux positions 0 et 6. On garde la dernière occurrence (6).
|
||||||
|
|
||||||
|
**Construction en Python** :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def table_occurrences(motif):
|
||||||
|
table = {}
|
||||||
|
for i in range(len(motif)):
|
||||||
|
table[motif[i]] = i
|
||||||
|
return table
|
||||||
|
|
||||||
|
>>> table_occurrences("EXEMPLE")
|
||||||
|
{'E': 6, 'X': 1, 'M': 3, 'P': 4, 'L': 5}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Utilisation** : Si on rencontre un caractère 'X' lors d'un échec, on sait que la dernière occurrence de 'X' dans le motif est à la position 1. On peut donc calculer le décalage optimal.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
448
Recherche_textuelle/Corrige_TP_Detecteur_Plagiat.py
Normal file
448
Recherche_textuelle/Corrige_TP_Detecteur_Plagiat.py
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
"""
|
||||||
|
Corrigé du TP Détecteur de Plagiat
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 1 : Recherche naïve
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def recherche_naive(texte, motif):
|
||||||
|
"""
|
||||||
|
Recherche un motif dans un texte avec l'algorithme naïf.
|
||||||
|
|
||||||
|
:param texte: (str) Le texte dans lequel chercher
|
||||||
|
:param motif: (str) Le motif à rechercher
|
||||||
|
:return: (int) Position de la première occurrence, ou -1 si non trouvé
|
||||||
|
"""
|
||||||
|
n = len(texte)
|
||||||
|
m = len(motif)
|
||||||
|
|
||||||
|
for j in range(n - m + 1):
|
||||||
|
i = 0
|
||||||
|
while i < m and texte[j + i] == motif[i]:
|
||||||
|
i += 1
|
||||||
|
if i == m:
|
||||||
|
return j
|
||||||
|
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
|
def recherche_naive_toutes(texte, motif):
|
||||||
|
"""
|
||||||
|
Trouve toutes les occurrences d'un motif dans un texte.
|
||||||
|
|
||||||
|
:param texte: (str) Le texte dans lequel chercher
|
||||||
|
:param motif: (str) Le motif à rechercher
|
||||||
|
:return: (list) Liste des positions de toutes les occurrences
|
||||||
|
"""
|
||||||
|
n = len(texte)
|
||||||
|
m = len(motif)
|
||||||
|
positions = []
|
||||||
|
|
||||||
|
for j in range(n - m + 1):
|
||||||
|
i = 0
|
||||||
|
while i < m and texte[j + i] == motif[i]:
|
||||||
|
i += 1
|
||||||
|
if i == m:
|
||||||
|
positions.append(j)
|
||||||
|
|
||||||
|
return positions
|
||||||
|
|
||||||
|
|
||||||
|
def recherche_naive_compteur(texte, motif):
|
||||||
|
"""
|
||||||
|
Recherche naïve avec compteur de comparaisons.
|
||||||
|
|
||||||
|
:return: (tuple) (position, nombre_de_comparaisons)
|
||||||
|
"""
|
||||||
|
n = len(texte)
|
||||||
|
m = len(motif)
|
||||||
|
comparaisons = 0
|
||||||
|
|
||||||
|
for j in range(n - m + 1):
|
||||||
|
i = 0
|
||||||
|
while i < m:
|
||||||
|
comparaisons += 1
|
||||||
|
if texte[j + i] != motif[i]:
|
||||||
|
break
|
||||||
|
i += 1
|
||||||
|
if i == m:
|
||||||
|
return (j, comparaisons)
|
||||||
|
|
||||||
|
return (-1, comparaisons)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 2 : Algorithme de Boyer-Moore
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def construire_table(motif):
|
||||||
|
"""
|
||||||
|
Construit la table des dernières occurrences pour Boyer-Moore.
|
||||||
|
|
||||||
|
:param motif: (str) Le motif à analyser
|
||||||
|
:return: (dict) Dictionnaire {caractère: dernière_position}
|
||||||
|
"""
|
||||||
|
table = {}
|
||||||
|
for i in range(len(motif)):
|
||||||
|
table[motif[i]] = i
|
||||||
|
return table
|
||||||
|
|
||||||
|
|
||||||
|
def boyer_moore(texte, motif):
|
||||||
|
"""
|
||||||
|
Recherche un motif avec l'algorithme de Boyer-Moore (règle du mauvais caractère).
|
||||||
|
|
||||||
|
:param texte: (str) Le texte dans lequel chercher
|
||||||
|
:param motif: (str) Le motif à rechercher
|
||||||
|
:return: (int) Position de la première occurrence, ou -1 si non trouvé
|
||||||
|
"""
|
||||||
|
n = len(texte)
|
||||||
|
m = len(motif)
|
||||||
|
|
||||||
|
if m == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Pré-traitement : table des dernières occurrences
|
||||||
|
table = construire_table(motif)
|
||||||
|
|
||||||
|
# Recherche
|
||||||
|
s = 0 # Décalage du motif par rapport au texte
|
||||||
|
while s <= n - m:
|
||||||
|
j = m - 1 # On commence par la fin du motif
|
||||||
|
|
||||||
|
# Comparaison de droite à gauche
|
||||||
|
while j >= 0 and motif[j] == texte[s + j]:
|
||||||
|
j -= 1
|
||||||
|
|
||||||
|
if j < 0:
|
||||||
|
return s # Motif trouvé à la position s
|
||||||
|
else:
|
||||||
|
# Calcul du décalage
|
||||||
|
char_texte = texte[s + j]
|
||||||
|
if char_texte in table:
|
||||||
|
decalage = j - table[char_texte]
|
||||||
|
if decalage < 1:
|
||||||
|
decalage = 1
|
||||||
|
else:
|
||||||
|
decalage = j + 1
|
||||||
|
s += decalage
|
||||||
|
|
||||||
|
return -1 # Motif non trouvé
|
||||||
|
|
||||||
|
|
||||||
|
def boyer_moore_compteur(texte, motif):
|
||||||
|
"""
|
||||||
|
Boyer-Moore avec compteur de comparaisons.
|
||||||
|
|
||||||
|
:return: (tuple) (position, nombre_de_comparaisons)
|
||||||
|
"""
|
||||||
|
n = len(texte)
|
||||||
|
m = len(motif)
|
||||||
|
comparaisons = 0
|
||||||
|
|
||||||
|
if m == 0:
|
||||||
|
return (0, 0)
|
||||||
|
|
||||||
|
table = construire_table(motif)
|
||||||
|
|
||||||
|
s = 0
|
||||||
|
while s <= n - m:
|
||||||
|
j = m - 1
|
||||||
|
|
||||||
|
while j >= 0:
|
||||||
|
comparaisons += 1
|
||||||
|
if motif[j] != texte[s + j]:
|
||||||
|
break
|
||||||
|
j -= 1
|
||||||
|
|
||||||
|
if j < 0:
|
||||||
|
return (s, comparaisons)
|
||||||
|
else:
|
||||||
|
char_texte = texte[s + j]
|
||||||
|
if char_texte in table:
|
||||||
|
decalage = j - table[char_texte]
|
||||||
|
if decalage < 1:
|
||||||
|
decalage = 1
|
||||||
|
else:
|
||||||
|
decalage = j + 1
|
||||||
|
s += decalage
|
||||||
|
|
||||||
|
return (-1, comparaisons)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 3 : Comparaison des performances
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def generer_texte(longueur, alphabet="abcdefghijklmnopqrstuvwxyz"):
|
||||||
|
"""Génère un texte aléatoire de la longueur spécifiée."""
|
||||||
|
return ''.join(random.choice(alphabet) for _ in range(longueur))
|
||||||
|
|
||||||
|
|
||||||
|
def benchmark(texte, motif):
|
||||||
|
"""Compare les performances des deux algorithmes."""
|
||||||
|
_, comparaisons_naive = recherche_naive_compteur(texte, motif)
|
||||||
|
_, comparaisons_bm = boyer_moore_compteur(texte, motif)
|
||||||
|
|
||||||
|
print(f"Texte: {len(texte)} caractères, Motif: '{motif}' ({len(motif)} car.)")
|
||||||
|
print(f" Naïf: {comparaisons_naive} comparaisons")
|
||||||
|
print(f" Boyer-Moore: {comparaisons_bm} comparaisons")
|
||||||
|
if comparaisons_naive > 0:
|
||||||
|
gain = (comparaisons_naive - comparaisons_bm) / comparaisons_naive * 100
|
||||||
|
print(f" Gain: {gain:.1f}%")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 4 : Détecteur de plagiat
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class Document:
|
||||||
|
"""Représente un document textuel."""
|
||||||
|
|
||||||
|
def __init__(self, titre, contenu):
|
||||||
|
"""
|
||||||
|
Initialise un document.
|
||||||
|
|
||||||
|
:param titre: (str) Titre du document
|
||||||
|
:param contenu: (str) Contenu textuel
|
||||||
|
"""
|
||||||
|
self.titre = titre
|
||||||
|
self.contenu = contenu
|
||||||
|
|
||||||
|
def normaliser(self):
|
||||||
|
"""
|
||||||
|
Normalise le contenu : minuscules, suppression de la ponctuation.
|
||||||
|
|
||||||
|
:return: (str) Contenu normalisé
|
||||||
|
"""
|
||||||
|
# Convertir en minuscules
|
||||||
|
texte = self.contenu.lower()
|
||||||
|
|
||||||
|
# Supprimer la ponctuation
|
||||||
|
texte_normalise = ""
|
||||||
|
for car in texte:
|
||||||
|
if car.isalnum() or car.isspace():
|
||||||
|
texte_normalise += car
|
||||||
|
|
||||||
|
# Remplacer les espaces multiples par un seul espace
|
||||||
|
while " " in texte_normalise:
|
||||||
|
texte_normalise = texte_normalise.replace(" ", " ")
|
||||||
|
|
||||||
|
return texte_normalise.strip()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Document('{self.titre}', {len(self.contenu)} caractères)"
|
||||||
|
|
||||||
|
|
||||||
|
class DetecteurPlagiat:
|
||||||
|
"""Détecte les passages plagiés entre documents."""
|
||||||
|
|
||||||
|
def __init__(self, taille_minimum=20):
|
||||||
|
"""
|
||||||
|
Initialise le détecteur.
|
||||||
|
|
||||||
|
:param taille_minimum: (int) Taille minimale d'un passage plagié
|
||||||
|
"""
|
||||||
|
self.documents = []
|
||||||
|
self.taille_minimum = taille_minimum
|
||||||
|
|
||||||
|
def ajouter_document(self, document):
|
||||||
|
"""Ajoute un document à la base."""
|
||||||
|
self.documents.append(document)
|
||||||
|
|
||||||
|
def rechercher_plagiat(self, document_suspect):
|
||||||
|
"""
|
||||||
|
Recherche des passages plagiés dans un document.
|
||||||
|
|
||||||
|
:param document_suspect: (Document) Document à analyser
|
||||||
|
:return: (list) Liste des plagiats détectés
|
||||||
|
"""
|
||||||
|
resultats = []
|
||||||
|
texte_suspect = document_suspect.normaliser()
|
||||||
|
|
||||||
|
for doc_reference in self.documents:
|
||||||
|
texte_reference = doc_reference.normaliser()
|
||||||
|
|
||||||
|
# Extraire les passages du document de référence
|
||||||
|
passages = self.extraire_passages(texte_reference, self.taille_minimum)
|
||||||
|
|
||||||
|
for passage in passages:
|
||||||
|
position = boyer_moore(texte_suspect, passage)
|
||||||
|
if position != -1:
|
||||||
|
# Vérifier si ce plagiat n'a pas déjà été détecté
|
||||||
|
deja_detecte = False
|
||||||
|
for r in resultats:
|
||||||
|
if r['source'] == doc_reference.titre:
|
||||||
|
# Vérifier le chevauchement
|
||||||
|
if abs(r['position'] - position) < self.taille_minimum:
|
||||||
|
deja_detecte = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not deja_detecte:
|
||||||
|
resultats.append({
|
||||||
|
'source': doc_reference.titre,
|
||||||
|
'passage': passage,
|
||||||
|
'position': position
|
||||||
|
})
|
||||||
|
|
||||||
|
return resultats
|
||||||
|
|
||||||
|
def extraire_passages(self, texte, taille):
|
||||||
|
"""
|
||||||
|
Extrait tous les passages de taille donnée d'un texte.
|
||||||
|
|
||||||
|
:param texte: (str) Le texte source
|
||||||
|
:param taille: (int) Taille des passages
|
||||||
|
:return: (list) Liste des passages
|
||||||
|
"""
|
||||||
|
passages = []
|
||||||
|
for i in range(len(texte) - taille + 1):
|
||||||
|
passage = texte[i:i + taille]
|
||||||
|
if passage not in passages: # Éviter les doublons
|
||||||
|
passages.append(passage)
|
||||||
|
return passages
|
||||||
|
|
||||||
|
def afficher_resultats(self, resultats):
|
||||||
|
"""Affiche les résultats de la détection."""
|
||||||
|
if len(resultats) == 0:
|
||||||
|
print("Aucun plagiat détecté.")
|
||||||
|
else:
|
||||||
|
print(f"{len(resultats)} plagiat(s) détecté(s) :\n")
|
||||||
|
for i, r in enumerate(resultats, 1):
|
||||||
|
print(f"Plagiat #{i}")
|
||||||
|
print(f" Source: '{r['source']}'")
|
||||||
|
print(f" Passage: '{r['passage'][:50]}...'")
|
||||||
|
print(f" Position dans le suspect: {r['position']}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 5 : Améliorations (Bonus)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def calculer_similarite(doc1, doc2, taille_passage=20):
|
||||||
|
"""
|
||||||
|
Calcule un score de similarité entre deux documents.
|
||||||
|
|
||||||
|
:param doc1: (Document) Premier document
|
||||||
|
:param doc2: (Document) Second document
|
||||||
|
:param taille_passage: (int) Taille des passages à comparer
|
||||||
|
:return: (float) Pourcentage de similarité
|
||||||
|
"""
|
||||||
|
texte1 = doc1.normaliser()
|
||||||
|
texte2 = doc2.normaliser()
|
||||||
|
|
||||||
|
# Extraire les passages du premier document
|
||||||
|
passages = []
|
||||||
|
for i in range(len(texte1) - taille_passage + 1):
|
||||||
|
passages.append(texte1[i:i + taille_passage])
|
||||||
|
|
||||||
|
# Compter les passages présents dans le second document
|
||||||
|
passages_communs = 0
|
||||||
|
for passage in passages:
|
||||||
|
if boyer_moore(texte2, passage) != -1:
|
||||||
|
passages_communs += 1
|
||||||
|
|
||||||
|
if len(passages) == 0:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
return (passages_communs / len(passages)) * 100
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("TEST 1 : RECHERCHE NAÏVE")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
print(f"recherche_naive('bonjour tout le monde', 'tout') = {recherche_naive('bonjour tout le monde', 'tout')}")
|
||||||
|
print(f"recherche_naive('abracadabra', 'abra') = {recherche_naive('abracadabra', 'abra')}")
|
||||||
|
print(f"recherche_naive('python', 'java') = {recherche_naive('python', 'java')}")
|
||||||
|
|
||||||
|
print(f"\nrecherche_naive_toutes('abracadabra', 'abra') = {recherche_naive_toutes('abracadabra', 'abra')}")
|
||||||
|
print(f"recherche_naive_toutes('aaaa', 'aa') = {recherche_naive_toutes('aaaa', 'aa')}")
|
||||||
|
|
||||||
|
print(f"\nrecherche_naive_compteur('abcdefghij', 'hij') = {recherche_naive_compteur('abcdefghij', 'hij')}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("TEST 2 : BOYER-MOORE")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
print(f"construire_table('EXEMPLE') = {construire_table('EXEMPLE')}")
|
||||||
|
print(f"construire_table('abracadabra') = {construire_table('abracadabra')}")
|
||||||
|
|
||||||
|
print(f"\nboyer_moore('voici un exemple de texte', 'exemple') = {boyer_moore('voici un exemple de texte', 'exemple')}")
|
||||||
|
print(f"boyer_moore('abracadabra', 'abra') = {boyer_moore('abracadabra', 'abra')}")
|
||||||
|
print(f"boyer_moore('hello world', 'xyz') = {boyer_moore('hello world', 'xyz')}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("TEST 3 : COMPARAISON DES PERFORMANCES")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Cas 1 : Texte aléatoire avec motif présent
|
||||||
|
texte_test = generer_texte(1000)
|
||||||
|
motif_test = texte_test[500:505] # Motif présent au milieu
|
||||||
|
benchmark(texte_test, motif_test)
|
||||||
|
|
||||||
|
# Cas 2 : Motif absent
|
||||||
|
benchmark(texte_test, "zzzzz")
|
||||||
|
|
||||||
|
# Cas 3 : Cas défavorable pour l'algorithme naïf
|
||||||
|
texte_defavorable = "a" * 100
|
||||||
|
motif_defavorable = "a" * 10 + "b"
|
||||||
|
benchmark(texte_defavorable, motif_defavorable)
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("TEST 4 : DÉTECTEUR DE PLAGIAT")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Documents originaux (base de référence)
|
||||||
|
doc1 = Document("Cours Python", """
|
||||||
|
Python est un langage de programmation interprété, multi-paradigme et
|
||||||
|
multiplateformes. Il favorise la programmation impérative structurée,
|
||||||
|
fonctionnelle et orientée objet. Python est doté d'un typage dynamique fort.
|
||||||
|
""")
|
||||||
|
|
||||||
|
doc2 = Document("Cours Java", """
|
||||||
|
Java est un langage de programmation orienté objet créé par Sun Microsystems.
|
||||||
|
Il est conçu pour être portable et sécurisé. Java utilise une machine virtuelle
|
||||||
|
pour exécuter le bytecode compilé.
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Document suspect
|
||||||
|
suspect = Document("Devoir élève", """
|
||||||
|
Python est un langage de programmation interprété, multi-paradigme et
|
||||||
|
multiplateformes. C'est mon langage préféré car il est simple à apprendre.
|
||||||
|
Il favorise la programmation impérative structurée.
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Détection
|
||||||
|
detecteur = DetecteurPlagiat(taille_minimum=30)
|
||||||
|
detecteur.ajouter_document(doc1)
|
||||||
|
detecteur.ajouter_document(doc2)
|
||||||
|
|
||||||
|
print(f"\nDocument suspect : {suspect}")
|
||||||
|
print(f"Base de référence : {len(detecteur.documents)} documents\n")
|
||||||
|
|
||||||
|
resultats = detecteur.rechercher_plagiat(suspect)
|
||||||
|
detecteur.afficher_resultats(resultats)
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("TEST 5 : SIMILARITÉ")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
similarite = calculer_similarite(doc1, suspect, taille_passage=20)
|
||||||
|
print(f"Similarité entre '{doc1.titre}' et '{suspect.titre}': {similarite:.1f}%")
|
||||||
|
|
||||||
|
similarite2 = calculer_similarite(doc2, suspect, taille_passage=20)
|
||||||
|
print(f"Similarité entre '{doc2.titre}' et '{suspect.titre}': {similarite2:.1f}%")
|
||||||
130
Recherche_textuelle/EXERCICES.md
Normal file
130
Recherche_textuelle/EXERCICES.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# Exercices — Recherche textuelle
|
||||||
|
|
||||||
|
## 1. QCM
|
||||||
|
|
||||||
|
1. Quelle est la complexité de la recherche d'un caractère dans un texte de longueur n ?
|
||||||
|
|
||||||
|
a. O(1)
|
||||||
|
|
||||||
|
b. O(n)
|
||||||
|
|
||||||
|
c. O(n²)
|
||||||
|
|
||||||
|
2. Dans l'algorithme de recherche naïve, si le texte a une longueur n et le motif une longueur m, combien de positions de départ sont testées au maximum ?
|
||||||
|
|
||||||
|
a. n
|
||||||
|
|
||||||
|
b. n - m
|
||||||
|
|
||||||
|
c. n - m + 1
|
||||||
|
|
||||||
|
3. Quelle est la particularité de l'algorithme de Boyer-Moore par rapport à la recherche naïve ?
|
||||||
|
|
||||||
|
a. Il compare les caractères de gauche à droite
|
||||||
|
|
||||||
|
b. Il compare les caractères de droite à gauche
|
||||||
|
|
||||||
|
c. Il ne fait aucune comparaison
|
||||||
|
|
||||||
|
4. À quoi sert le pré-traitement du motif dans l'algorithme de Morris-Pratt ?
|
||||||
|
|
||||||
|
a. À trier les caractères du motif
|
||||||
|
|
||||||
|
b. À calculer les décalages optimaux après un échec
|
||||||
|
|
||||||
|
c. À compter le nombre de caractères différents
|
||||||
|
|
||||||
|
5. Dans le pire cas, quelle est la complexité de l'algorithme de recherche naïve ?
|
||||||
|
|
||||||
|
a. O(n)
|
||||||
|
|
||||||
|
b. O(m)
|
||||||
|
|
||||||
|
c. O(n × m)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Questions de cours
|
||||||
|
|
||||||
|
### Question 1
|
||||||
|
|
||||||
|
On considère le texte `"abracadabra"` et le motif `"abra"`.
|
||||||
|
|
||||||
|
a. Combien de positions de départ sont testées par l'algorithme naïf ?
|
||||||
|
|
||||||
|
b. À quelles positions le motif est-il trouvé ?
|
||||||
|
|
||||||
|
### Question 2
|
||||||
|
|
||||||
|
Expliquez pourquoi l'algorithme de Boyer-Moore peut être plus efficace que la recherche naïve, notamment quand le motif est long.
|
||||||
|
|
||||||
|
### Question 3
|
||||||
|
|
||||||
|
On considère le code suivant :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def recherche(texte, motif):
|
||||||
|
n = len(texte)
|
||||||
|
m = len(motif)
|
||||||
|
for j in range(n - m + 1):
|
||||||
|
i = 0
|
||||||
|
while i < m and texte[j + i] == motif[i]:
|
||||||
|
i = i + 1
|
||||||
|
if i == m:
|
||||||
|
return j
|
||||||
|
return -1
|
||||||
|
```
|
||||||
|
|
||||||
|
a. Que renvoie `recherche("bonjour", "jour")` ?
|
||||||
|
|
||||||
|
b. Que renvoie `recherche("bonjour", "soir")` ?
|
||||||
|
|
||||||
|
c. Modifiez la fonction pour qu'elle renvoie **toutes** les positions où le motif apparaît (sous forme de liste).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Exercices de programmation
|
||||||
|
|
||||||
|
### Exercice 1 : Compter les occurrences
|
||||||
|
|
||||||
|
Écrire une fonction `compter_occurrences(texte, motif)` qui renvoie le nombre de fois où le motif apparaît dans le texte.
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> compter_occurrences("abracadabra", "a")
|
||||||
|
5
|
||||||
|
>>> compter_occurrences("abracadabra", "abra")
|
||||||
|
2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice 2 : Recherche insensible à la casse
|
||||||
|
|
||||||
|
Écrire une fonction `recherche_insensible(texte, motif)` qui effectue une recherche en ignorant les majuscules/minuscules.
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> recherche_insensible("Bonjour tout le Monde", "monde")
|
||||||
|
16
|
||||||
|
>>> recherche_insensible("PYTHON", "python")
|
||||||
|
0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exercice 3 : Analyse de complexité
|
||||||
|
|
||||||
|
On exécute la recherche naïve avec le texte `"aaaaaaaaaa"` (10 fois 'a') et le motif `"aaab"`.
|
||||||
|
|
||||||
|
a. Combien de comparaisons sont effectuées au total ?
|
||||||
|
|
||||||
|
b. Expliquez pourquoi ce cas est défavorable pour l'algorithme naïf.
|
||||||
|
|
||||||
|
### Exercice 4 : Table des dernières occurrences (Boyer-Moore)
|
||||||
|
|
||||||
|
Pour l'algorithme de Boyer-Moore, on construit une table indiquant la dernière position de chaque caractère dans le motif.
|
||||||
|
|
||||||
|
Pour le motif `"EXEMPLE"`, construire cette table.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
> En informatique, il est peut-être utile et interessant de determiner la présence ou non d'un motif au sein d'un texte. Par exemple quand vous cherchez un mot précis dans un long document, si vous voulez vous entrainer pour des chiffres et des lettres...
|
> En informatique, il est peut-être utile et intéressant de déterminer la présence ou non d'un motif au sein d'un texte. Par exemple quand vous cherchez un mot précis dans un long document, si vous voulez vous entraîner pour des chiffres et des lettres...
|
||||||
|
|
||||||
On se donne un texte et un motif représentés en Python par des chaînes de caractères (*type str*). La question est de déterminer la présence ou l'absence de ce motif.
|
On se donne un texte et un motif représentés en Python par des chaînes de caractères (*type str*). La question est de déterminer la présence ou l'absence de ce motif.
|
||||||
|
|
||||||
@@ -27,16 +27,16 @@ def recherche(texte, motif):
|
|||||||
for car in texte:
|
for car in texte:
|
||||||
if car == motif:
|
if car == motif:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
```
|
```
|
||||||
|
|
||||||
Si une des valeur successives de la variable ***car*** est égale à la valeur de la variable ***motif*** alors la fonction renvoie ***True*** et la fonction est interrompue. Si tous les caractères du texte sont examinés et que la bouclle n'est pas interrompue, la fonction renvoie ***False***.
|
Si une des valeurs successives de la variable ***car*** est égale à la valeur de la variable ***motif*** alors la fonction renvoie ***True*** et la fonction est interrompue. Si tous les caractères du texte sont examinés et que la boucle n'est pas interrompue, la fonction renvoie ***False***.
|
||||||
|
|
||||||
Nous remplaçons l'instruction ***return True*** par ***return i*** si nous souhaitons obtenir la place du caractère dans le texte. La fonction renvoie alors la place du caractère s'il a été trouvé et rien sinon (donc la valeur ***None***).
|
Nous remplaçons l'instruction ***return True*** par ***return i*** si nous souhaitons obtenir la place du caractère dans le texte. La fonction renvoie alors la place du caractère s'il a été trouvé et rien sinon (donc la valeur ***None***).
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def recherche (texte, motif):
|
def recherche(texte, motif):
|
||||||
for i in range (len(texte)):
|
for i in range(len(texte)):
|
||||||
if texte[i] == motif:
|
if texte[i] == motif:
|
||||||
return i
|
return i
|
||||||
```
|
```
|
||||||
@@ -47,7 +47,7 @@ Le coût de cette recherche est linéaire en la longueur de la chaîne, en effet
|
|||||||
|
|
||||||
### 2. Recherche naïve d'un motif dans un texte
|
### 2. Recherche naïve d'un motif dans un texte
|
||||||
|
|
||||||
> On parle de recherche naïve car c'est l'une des premières idées qui peut venir à l'esprit
|
> On parle de recherche naïve car c'est l'une des premières idées qui peut venir à l'esprit.
|
||||||
|
|
||||||
- On recherche la présence du premier caractère du motif dans le texte
|
- On recherche la présence du premier caractère du motif dans le texte
|
||||||
- Si on le trouve, on vérifie si les caractères suivants du motif coïncident avec ceux du texte
|
- Si on le trouve, on vérifie si les caractères suivants du motif coïncident avec ceux du texte
|
||||||
@@ -61,9 +61,9 @@ Cet algorithme est implémenté dans le programme suivant, où nous pouvons remp
|
|||||||
def recherche(texte, motif):
|
def recherche(texte, motif):
|
||||||
n = len(texte)
|
n = len(texte)
|
||||||
m = len(motif)
|
m = len(motif)
|
||||||
for j in range(n - m +1):
|
for j in range(n - m + 1):
|
||||||
i = 0
|
i = 0
|
||||||
while i < m and texte [j + i] == motif [i]:
|
while i < m and texte[j + i] == motif[i]:
|
||||||
i = i + 1
|
i = i + 1
|
||||||
if i == m :
|
if i == m :
|
||||||
return j
|
return j
|
||||||
@@ -85,7 +85,7 @@ Si *n* est la longueur du texte et *m* la longueur du motif recherché, alors la
|
|||||||
En effet la recherche du premier caractère du motif s'arrête lorsqu'il ne reste plus assez de place dans le texte pour placer ce motif.
|
En effet la recherche du premier caractère du motif s'arrête lorsqu'il ne reste plus assez de place dans le texte pour placer ce motif.
|
||||||
Dans le pire des cas, la boucle interne *while* est parcourue au plus *m* fois, pour tester chaque caractère du motif.
|
Dans le pire des cas, la boucle interne *while* est parcourue au plus *m* fois, pour tester chaque caractère du motif.
|
||||||
|
|
||||||
> Le nombre total de comparaisons entre caractères est donc majoré par *m(n - m + 1)*. Par exemple, si *m = 5* alors on peut dire que le nombre total de comparaisons est majoré par *5n*. Le nombre *m* est compris entre *0 et n* et on peut donc montrer que la valeur maximale du produit *m(n - m + 1)* est *m<sup>2</sup> + m = (n<sup>2</sup> + 2n) / 4* .
|
> Le nombre total de comparaisons entre caractères est donc majoré par *m(n - m + 1)*. Par exemple, si *m = 5* alors on peut dire que le nombre total de comparaisons est majoré par *5n*. Le nombre *m* est compris entre *0* et *n* et on peut donc montrer que la valeur maximale du produit *m(n - m + 1)* est atteinte pour *m ≈ (n + 1) / 2*, donnant une complexité dans le pire cas en **O(n²)**.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -95,8 +95,8 @@ def recherche2(texte, motif):
|
|||||||
n = len(texte)
|
n = len(texte)
|
||||||
i = 0
|
i = 0
|
||||||
j = 0
|
j = 0
|
||||||
while i < m and j < n :
|
while i < m and j < n:
|
||||||
if motif [i] != texte[j]:
|
if motif[i] != texte[j]:
|
||||||
|
|
||||||
j = j - i + 1
|
j = j - i + 1
|
||||||
i = 0
|
i = 0
|
||||||
@@ -135,7 +135,7 @@ Après six comparaisons de caractères, nous avons un échec (lettres en gras).
|
|||||||
| Motif | | **a** | b | c | a | b | c | | | | |
|
| Motif | | **a** | b | c | a | b | c | | | | |
|
||||||
| Indice i | | 0 | 1 | 2 | 3 | 4 | 5 | | | | |
|
| Indice i | | 0 | 1 | 2 | 3 | 4 | 5 | | | | |
|
||||||
|
|
||||||
Ici, c'est un échec dès la première compraison, il faut donc à nouveau nous décaler vers la droite.
|
Ici, c'est un échec dès la première comparaison, il faut donc à nouveau nous décaler vers la droite.
|
||||||
|
|
||||||
| Indice j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|
| Indice j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|
||||||
| -------- | ---- | ---- | ----- | ---- | ---- | ----- | ---- | ---- | ---- | ---- | ---- |
|
| -------- | ---- | ---- | ----- | ---- | ---- | ----- | ---- | ---- | ---- | ---- | ---- |
|
||||||
@@ -153,7 +153,7 @@ Idem ici, on redécale.
|
|||||||
| Motif | | | | a | b | **c** | | | | | |
|
| Motif | | | | a | b | **c** | | | | | |
|
||||||
| Indice i | | | | 0 | 1 | 2 | 3 | 4 | 5 | | |
|
| Indice i | | | | 0 | 1 | 2 | 3 | 4 | 5 | | |
|
||||||
|
|
||||||
Ici, echec à la troisième comparaison. On décale vers la droite.
|
Ici, échec à la troisième comparaison. On décale vers la droite.
|
||||||
|
|
||||||
| Indice j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|
| Indice j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|
||||||
| -------- | ---- | ---- | ---- | ---- | ----- | ----- | ---- | ---- | ---- | ---- | ---- |
|
| -------- | ---- | ---- | ---- | ---- | ----- | ----- | ---- | ---- | ---- | ---- | ---- |
|
||||||
@@ -181,7 +181,7 @@ Il nous aura donc fallu dix-huit comparaisons pour trouver le motif. Si le derni
|
|||||||
>
|
>
|
||||||
> Par exemple, après les six premières comparaisons, on constate que les cinq premiers caractères du texte sont les cinq premiers du motif. Donc, en décalant, on va forcément être amené à comparer le début du motif avec une autre partie du motif lui même...
|
> Par exemple, après les six premières comparaisons, on constate que les cinq premiers caractères du texte sont les cinq premiers du motif. Donc, en décalant, on va forcément être amené à comparer le début du motif avec une autre partie du motif lui même...
|
||||||
>
|
>
|
||||||
> Les informaticiens Morris et Pratt ont eu séparément l'idée d'effectuer un pré-traitement du motif qui permet de déterminer, à partir de quelle place la recherche doit se poursuivre, et éviter ainsi de controler les mêmes caractères plusieurs fois.
|
> Les informaticiens Morris et Pratt ont eu séparément l'idée d'effectuer un pré-traitement du motif qui permet de déterminer, à partir de quelle place la recherche doit se poursuivre, et éviter ainsi de contrôler les mêmes caractères plusieurs fois.
|
||||||
|
|
||||||
Reprenons le texte "***abcababcabc***" et le motif "***abcabc***"
|
Reprenons le texte "***abcababcabc***" et le motif "***abcabc***"
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ Le motif est donc décalé de trois unités vers la droite.
|
|||||||
|
|
||||||
Les comparaisons commencent donc au troisième caractère pour lequel nous avons un échec ici.
|
Les comparaisons commencent donc au troisième caractère pour lequel nous avons un échec ici.
|
||||||
|
|
||||||
Il n'est pas interessant de décaler uniquement d'une unité vers la droite. Le caractère est un "a" et nous savons parfaitement où se trouve ce caractère dans le motif.
|
Il n'est pas intéressant de décaler uniquement d'une unité vers la droite. Le caractère est un "a" et nous savons parfaitement où se trouve ce caractère dans le motif.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -224,23 +224,40 @@ Le nombre total de comparaisons est de treize au lieu de dix-huit pour le progra
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
La fonction `traitement` construit un tableau de décalages. Pour chaque position `i` du motif, `s[i]` indique la position où reprendre la comparaison après un échec.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def traitement(motif):
|
||||||
|
"""Construit le tableau des décalages pour Morris-Pratt."""
|
||||||
|
m = len(motif)
|
||||||
|
s = [-1] * m # Tableau des décalages
|
||||||
|
j = -1
|
||||||
|
for i in range(1, m):
|
||||||
|
while j >= 0 and motif[j + 1] != motif[i]:
|
||||||
|
j = s[j]
|
||||||
|
if motif[j + 1] == motif[i]:
|
||||||
|
j += 1
|
||||||
|
s[i] = j
|
||||||
|
return s
|
||||||
|
```
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def morris_pratt(texte, motif):
|
def morris_pratt(texte, motif):
|
||||||
m = len (motif)
|
m = len(motif)
|
||||||
n = len(texte)
|
n = len(texte)
|
||||||
i = 0
|
i = 0
|
||||||
j = 0
|
j = 0
|
||||||
sol = [] #tableau pour stocker les solutions
|
sol = [] # Tableau pour stocker les solutions
|
||||||
s = traitement (motif) #modification du programme naïf recherche
|
s = traitement(motif) # Pré-traitement du motif
|
||||||
while i < m and j < n :
|
while i < m and j < n:
|
||||||
if i >= 0 and motif[i] != texte[j] :
|
if i >= 0 and motif[i] != texte[j]:
|
||||||
i = s[i]
|
i = s[i]
|
||||||
else:
|
else:
|
||||||
i += 1
|
i += 1
|
||||||
j += 1
|
j += 1
|
||||||
if i >= m : #Si le motif est présent
|
if i >= m: # Si le motif est présent
|
||||||
sol.append(j-m) # La position dans le texte est ajoutée à la liste
|
sol.append(j - m) # La position dans le texte est ajoutée à la liste
|
||||||
i = 0 # Une nouvelle recherche commence après le motif trouvé
|
i = 0 # Une nouvelle recherche commence après le motif trouvé
|
||||||
return sol
|
return sol
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -262,9 +279,9 @@ Pour la valeur de i, qui permet d'effectuer le bon décalage, on effectue une fo
|
|||||||
|
|
||||||
Le principe général des algorithmes de recherche textuelle est de comparer un motif à certaines parties du texte, le motif se décalant vers la droite après chaque échec.
|
Le principe général des algorithmes de recherche textuelle est de comparer un motif à certaines parties du texte, le motif se décalant vers la droite après chaque échec.
|
||||||
|
|
||||||
Comme cela est pratiqué avec l'algorithme de Morris et Pratt, le calcul du décalage d'obtient par un pré-traitement du motif. La différence fondamentale avec ce que l'on a vu précédémment, est que ***la comparaison entre le motif et une partie du texte se fait de droite à gauche en commençant par la fin du motif***.
|
Comme cela est pratiqué avec l'algorithme de Morris et Pratt, le calcul du décalage s'obtient par un pré-traitement du motif. La différence fondamentale avec ce que l'on a vu précédemment, est que ***la comparaison entre le motif et une partie du texte se fait de droite à gauche en commençant par la fin du motif***.
|
||||||
|
|
||||||
En procédant ainsi, un type de décalage est articulièrement intéresssant : à la premiere différence constatée, on décale le motif vers la droite de manière à faire coïncider le caractère du texte concerné avec un caractère du motif. Si ce n'est pas possible, alors on décale le motif après le caractère.
|
En procédant ainsi, un type de décalage est particulièrement intéressant : à la première différence constatée, on décale le motif vers la droite de manière à faire coïncider le caractère du texte concerné avec un caractère du motif. Si ce n'est pas possible, alors on décale le motif après le caractère.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -333,7 +350,56 @@ Il nous faudra ici six comparaisons finales pour affirmer la présence du motif
|
|||||||
|
|
||||||
Remarque : si la dernière lettre du texte n'était pas un "c", alors nous n'aurions eu besoin que d'une seule comparaison au lieu de six pour affirmer l'absence du motif, soit sept comparaisons au total.
|
Remarque : si la dernière lettre du texte n'était pas un "c", alors nous n'aurions eu besoin que d'une seule comparaison au lieu de six pour affirmer l'absence du motif, soit sept comparaisons au total.
|
||||||
|
|
||||||
|
#### Implémentation simplifiée
|
||||||
|
|
||||||
|
Voici une implémentation de l'algorithme de Boyer-Moore utilisant uniquement la règle du mauvais caractère :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def boyer_moore(texte, motif):
|
||||||
|
"""Recherche un motif dans un texte avec Boyer-Moore (règle du mauvais caractère)."""
|
||||||
|
n = len(texte)
|
||||||
|
m = len(motif)
|
||||||
|
|
||||||
|
if m == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Pré-traitement : table des dernières occurrences
|
||||||
|
derniere_occurrence = {}
|
||||||
|
for i in range(m):
|
||||||
|
derniere_occurrence[motif[i]] = i
|
||||||
|
|
||||||
|
# Recherche
|
||||||
|
s = 0 # Décalage du motif par rapport au texte
|
||||||
|
while s <= n - m:
|
||||||
|
j = m - 1 # On commence par la fin du motif
|
||||||
|
|
||||||
|
# Comparaison de droite à gauche
|
||||||
|
while j >= 0 and motif[j] == texte[s + j]:
|
||||||
|
j -= 1
|
||||||
|
|
||||||
|
if j < 0:
|
||||||
|
return s # Motif trouvé à la position s
|
||||||
|
else:
|
||||||
|
# Calcul du décalage
|
||||||
|
char_texte = texte[s + j]
|
||||||
|
if char_texte in derniere_occurrence:
|
||||||
|
decalage = j - derniere_occurrence[char_texte]
|
||||||
|
if decalage < 1:
|
||||||
|
decalage = 1
|
||||||
|
else:
|
||||||
|
decalage = j + 1
|
||||||
|
s += decalage
|
||||||
|
|
||||||
|
return -1 # Motif non trouvé
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
360
Recherche_textuelle/TP_Detecteur_Plagiat.md
Normal file
360
Recherche_textuelle/TP_Detecteur_Plagiat.md
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
# TP : Détecteur de Plagiat
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Vous travaillez pour une startup EdTech qui développe un outil anti-plagiat pour les établissements scolaires. Votre mission : implémenter le moteur de recherche textuelle qui permettra de détecter les passages copiés entre différents documents.
|
||||||
|
|
||||||
|
Les outils comme **Turnitin**, **Compilatio** ou **Plag.fr** utilisent des algorithmes similaires à ceux que vous allez implémenter.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Objectifs
|
||||||
|
|
||||||
|
- Implémenter l'algorithme de recherche naïve
|
||||||
|
- Implémenter l'algorithme de Boyer-Moore simplifié
|
||||||
|
- Comparer les performances des algorithmes
|
||||||
|
- Construire un détecteur de plagiat fonctionnel
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 1 : Recherche naïve
|
||||||
|
|
||||||
|
### 1.1. Implémentation de base
|
||||||
|
|
||||||
|
Implémenter la fonction `recherche_naive(texte, motif)` qui renvoie la première position du motif dans le texte, ou `-1` si le motif n'est pas trouvé.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def recherche_naive(texte, motif):
|
||||||
|
"""
|
||||||
|
Recherche un motif dans un texte avec l'algorithme naïf.
|
||||||
|
|
||||||
|
:param texte: (str) Le texte dans lequel chercher
|
||||||
|
:param motif: (str) Le motif à rechercher
|
||||||
|
:return: (int) Position de la première occurrence, ou -1 si non trouvé
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests** :
|
||||||
|
```python
|
||||||
|
>>> recherche_naive("bonjour tout le monde", "tout")
|
||||||
|
8
|
||||||
|
>>> recherche_naive("abracadabra", "abra")
|
||||||
|
0
|
||||||
|
>>> recherche_naive("python", "java")
|
||||||
|
-1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2. Toutes les occurrences
|
||||||
|
|
||||||
|
Modifier votre fonction pour créer `recherche_naive_toutes(texte, motif)` qui renvoie la liste de **toutes** les positions où le motif apparaît.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def recherche_naive_toutes(texte, motif):
|
||||||
|
"""
|
||||||
|
Trouve toutes les occurrences d'un motif dans un texte.
|
||||||
|
|
||||||
|
:param texte: (str) Le texte dans lequel chercher
|
||||||
|
:param motif: (str) Le motif à rechercher
|
||||||
|
:return: (list) Liste des positions de toutes les occurrences
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests** :
|
||||||
|
```python
|
||||||
|
>>> recherche_naive_toutes("abracadabra", "abra")
|
||||||
|
[0, 7]
|
||||||
|
>>> recherche_naive_toutes("aaaa", "aa")
|
||||||
|
[0, 1, 2]
|
||||||
|
>>> recherche_naive_toutes("hello", "xyz")
|
||||||
|
[]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3. Compteur de comparaisons
|
||||||
|
|
||||||
|
Créer une version `recherche_naive_compteur(texte, motif)` qui renvoie un tuple `(position, nb_comparaisons)` pour analyser les performances.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def recherche_naive_compteur(texte, motif):
|
||||||
|
"""
|
||||||
|
Recherche naïve avec compteur de comparaisons.
|
||||||
|
|
||||||
|
:return: (tuple) (position, nombre_de_comparaisons)
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test** :
|
||||||
|
```python
|
||||||
|
>>> recherche_naive_compteur("abcdefghij", "hij")
|
||||||
|
(7, 10) # Trouvé en position 7 après 10 comparaisons
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : Algorithme de Boyer-Moore
|
||||||
|
|
||||||
|
### 2.1. Table des dernières occurrences
|
||||||
|
|
||||||
|
L'algorithme de Boyer-Moore utilise une table indiquant la dernière position de chaque caractère dans le motif.
|
||||||
|
|
||||||
|
Implémenter `construire_table(motif)` :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def construire_table(motif):
|
||||||
|
"""
|
||||||
|
Construit la table des dernières occurrences pour Boyer-Moore.
|
||||||
|
|
||||||
|
:param motif: (str) Le motif à analyser
|
||||||
|
:return: (dict) Dictionnaire {caractère: dernière_position}
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests** :
|
||||||
|
```python
|
||||||
|
>>> construire_table("EXEMPLE")
|
||||||
|
{'E': 6, 'X': 1, 'M': 3, 'P': 4, 'L': 5}
|
||||||
|
>>> construire_table("abracadabra")
|
||||||
|
{'a': 10, 'b': 8, 'r': 9, 'c': 4, 'd': 6}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2. Implémentation de Boyer-Moore
|
||||||
|
|
||||||
|
Implémenter `boyer_moore(texte, motif)` en utilisant la règle du "mauvais caractère" :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def boyer_moore(texte, motif):
|
||||||
|
"""
|
||||||
|
Recherche un motif avec l'algorithme de Boyer-Moore (règle du mauvais caractère).
|
||||||
|
|
||||||
|
:param texte: (str) Le texte dans lequel chercher
|
||||||
|
:param motif: (str) Le motif à rechercher
|
||||||
|
:return: (int) Position de la première occurrence, ou -1 si non trouvé
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rappel de l'algorithme** :
|
||||||
|
1. Comparer le motif de **droite à gauche**
|
||||||
|
2. En cas d'échec sur un caractère `c` du texte :
|
||||||
|
- Si `c` est dans le motif, décaler pour aligner `c` avec sa dernière occurrence
|
||||||
|
- Sinon, décaler le motif entièrement après `c`
|
||||||
|
|
||||||
|
**Tests** :
|
||||||
|
```python
|
||||||
|
>>> boyer_moore("voici un exemple de texte", "exemple")
|
||||||
|
9
|
||||||
|
>>> boyer_moore("abracadabra", "abra")
|
||||||
|
0
|
||||||
|
>>> boyer_moore("hello world", "xyz")
|
||||||
|
-1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3. Version avec compteur
|
||||||
|
|
||||||
|
Créer `boyer_moore_compteur(texte, motif)` pour comparer avec la recherche naïve.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : Comparaison des performances
|
||||||
|
|
||||||
|
### 3.1. Générateur de texte
|
||||||
|
|
||||||
|
Créer une fonction qui génère un texte aléatoire pour les tests :
|
||||||
|
|
||||||
|
```python
|
||||||
|
import random
|
||||||
|
|
||||||
|
def generer_texte(longueur, alphabet="abcdefghijklmnopqrstuvwxyz"):
|
||||||
|
"""Génère un texte aléatoire de la longueur spécifiée."""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2. Benchmark
|
||||||
|
|
||||||
|
Comparer les deux algorithmes sur différents cas :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def benchmark(texte, motif):
|
||||||
|
"""Compare les performances des deux algorithmes."""
|
||||||
|
_, comparaisons_naive = recherche_naive_compteur(texte, motif)
|
||||||
|
_, comparaisons_bm = boyer_moore_compteur(texte, motif)
|
||||||
|
|
||||||
|
print(f"Texte: {len(texte)} caractères, Motif: '{motif}' ({len(motif)} car.)")
|
||||||
|
print(f" Naïf: {comparaisons_naive} comparaisons")
|
||||||
|
print(f" Boyer-Moore: {comparaisons_bm} comparaisons")
|
||||||
|
print(f" Gain: {comparaisons_naive - comparaisons_bm} comparaisons économisées")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests à réaliser** :
|
||||||
|
|
||||||
|
1. Texte de 1000 caractères, motif de 5 caractères présent
|
||||||
|
2. Texte de 1000 caractères, motif de 5 caractères absent
|
||||||
|
3. Cas défavorable : texte `"aaa...a"` et motif `"aaab"`
|
||||||
|
4. Cas favorable pour Boyer-Moore : alphabet riche et long motif
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : Détecteur de plagiat
|
||||||
|
|
||||||
|
### 4.1. Classe Document
|
||||||
|
|
||||||
|
Créer une classe représentant un document texte :
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Document:
|
||||||
|
"""Représente un document textuel."""
|
||||||
|
|
||||||
|
def __init__(self, titre, contenu):
|
||||||
|
"""
|
||||||
|
Initialise un document.
|
||||||
|
|
||||||
|
:param titre: (str) Titre du document
|
||||||
|
:param contenu: (str) Contenu textuel
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def normaliser(self):
|
||||||
|
"""
|
||||||
|
Normalise le contenu : minuscules, suppression de la ponctuation.
|
||||||
|
|
||||||
|
:return: (str) Contenu normalisé
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Document('{self.titre}', {len(self.contenu)} caractères)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2. Détecteur de plagiat
|
||||||
|
|
||||||
|
Créer la classe principale :
|
||||||
|
|
||||||
|
```python
|
||||||
|
class DetecteurPlagiat:
|
||||||
|
"""Détecte les passages plagiés entre documents."""
|
||||||
|
|
||||||
|
def __init__(self, taille_minimum=20):
|
||||||
|
"""
|
||||||
|
Initialise le détecteur.
|
||||||
|
|
||||||
|
:param taille_minimum: (int) Taille minimale d'un passage plagié
|
||||||
|
"""
|
||||||
|
self.documents = []
|
||||||
|
self.taille_minimum = taille_minimum
|
||||||
|
|
||||||
|
def ajouter_document(self, document):
|
||||||
|
"""Ajoute un document à la base."""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def rechercher_plagiat(self, document_suspect):
|
||||||
|
"""
|
||||||
|
Recherche des passages plagiés dans un document.
|
||||||
|
|
||||||
|
:param document_suspect: (Document) Document à analyser
|
||||||
|
:return: (list) Liste des plagiats détectés
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def extraire_passages(self, texte, taille):
|
||||||
|
"""
|
||||||
|
Extrait tous les passages de taille donnée d'un texte.
|
||||||
|
|
||||||
|
:param texte: (str) Le texte source
|
||||||
|
:param taille: (int) Taille des passages
|
||||||
|
:return: (list) Liste des passages
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3. Test du détecteur
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Documents originaux (base de référence)
|
||||||
|
doc1 = Document("Cours Python", """
|
||||||
|
Python est un langage de programmation interprété, multi-paradigme et
|
||||||
|
multiplateformes. Il favorise la programmation impérative structurée,
|
||||||
|
fonctionnelle et orientée objet.
|
||||||
|
""")
|
||||||
|
|
||||||
|
doc2 = Document("Cours Java", """
|
||||||
|
Java est un langage de programmation orienté objet créé par Sun Microsystems.
|
||||||
|
Il est conçu pour être portable et sécurisé.
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Document suspect
|
||||||
|
suspect = Document("Devoir élève", """
|
||||||
|
Python est un langage de programmation interprété, multi-paradigme et
|
||||||
|
multiplateformes. C'est mon langage préféré car il est simple à apprendre.
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Détection
|
||||||
|
detecteur = DetecteurPlagiat(taille_minimum=30)
|
||||||
|
detecteur.ajouter_document(doc1)
|
||||||
|
detecteur.ajouter_document(doc2)
|
||||||
|
|
||||||
|
resultats = detecteur.rechercher_plagiat(suspect)
|
||||||
|
for r in resultats:
|
||||||
|
print(r)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sortie attendue** :
|
||||||
|
```
|
||||||
|
Plagiat détecté !
|
||||||
|
Source: 'Cours Python'
|
||||||
|
Passage: 'python est un langage de programmation interprété, multi-paradigme et multiplateformes'
|
||||||
|
Position dans le suspect: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 5 : Améliorations (Bonus)
|
||||||
|
|
||||||
|
### 5.1. Score de similarité
|
||||||
|
|
||||||
|
Ajouter une méthode `calculer_similarite(doc1, doc2)` qui renvoie un pourcentage de similarité entre deux documents.
|
||||||
|
|
||||||
|
### 5.2. Interface utilisateur
|
||||||
|
|
||||||
|
Créer une interface en ligne de commande permettant :
|
||||||
|
- D'ajouter des documents depuis des fichiers
|
||||||
|
- De vérifier un nouveau document
|
||||||
|
- D'afficher un rapport détaillé
|
||||||
|
|
||||||
|
### 5.3. Optimisation
|
||||||
|
|
||||||
|
Implémenter une version utilisant des **n-grammes** (séquences de n mots) pour une détection plus robuste au reformulation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Barème indicatif
|
||||||
|
|
||||||
|
| Partie | Points |
|
||||||
|
|--------|--------|
|
||||||
|
| Partie 1 : Recherche naïve | 4 |
|
||||||
|
| Partie 2 : Boyer-Moore | 5 |
|
||||||
|
| Partie 3 : Comparaison | 3 |
|
||||||
|
| Partie 4 : Détecteur | 6 |
|
||||||
|
| Partie 5 : Bonus | 2 |
|
||||||
|
| **Total** | **20** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
97
Recursivité/Corrigé_Exercices.md
Normal file
97
Recursivité/Corrigé_Exercices.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# Corrigé des exercices de révision sur la récursivité
|
||||||
|
|
||||||
|
## Exercice 1 : Exponentiation rapide
|
||||||
|
|
||||||
|
**Rappel :** On souhaite calculer `a^n` avec les relations de récurrence suivantes :
|
||||||
|
- `a^0 = 1`
|
||||||
|
- Si `n` est pair : `a^n = (a * a)^(n / 2)`
|
||||||
|
- Sinon : `a^n = a * (a * a)^((n - 1) / 2)`
|
||||||
|
|
||||||
|
1. Version récursive
|
||||||
|
|
||||||
|
```python
|
||||||
|
def exponentiation_rapide_recursive(a, n):
|
||||||
|
if n == 0:
|
||||||
|
return 1 # Cas de base
|
||||||
|
elif n % 2 == 0:
|
||||||
|
return exponentiation_rapide_recursive(a * a, n // 2)
|
||||||
|
else:
|
||||||
|
return a * exponentiation_rapide_recursive(a * a, (n - 1) // 2)
|
||||||
|
|
||||||
|
# Exemple de test
|
||||||
|
print(exponentiation_rapide_recursive(2, 10)) # Renvoie 1024
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Version itérative
|
||||||
|
|
||||||
|
```python
|
||||||
|
def exponentiation_rapide_iterative(a, n):
|
||||||
|
result = 1
|
||||||
|
while n > 0:
|
||||||
|
if n % 2 == 1: # Si n est impair
|
||||||
|
result *= a
|
||||||
|
a *= a
|
||||||
|
n //= 2 # Diviser n par 2
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Exemple de test
|
||||||
|
print(exponentiation_rapide_iterative(2, 10)) # Renvoie 1024
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-------
|
||||||
|
|
||||||
|
**Exercice 2 : Génération d’une liste décroissante**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Fonction récursive**
|
||||||
|
|
||||||
|
```python
|
||||||
|
def generate_liste(n):
|
||||||
|
if n == 0:
|
||||||
|
return [0] # Cas de base
|
||||||
|
else:
|
||||||
|
return [n] + generate_liste(n - 1) # Ajoute n à la liste générée pour n-1
|
||||||
|
|
||||||
|
# Exemple de test
|
||||||
|
print(generate_liste(5)) # Renvoie [5, 4, 3, 2, 1, 0]
|
||||||
|
```
|
||||||
|
|
||||||
|
______
|
||||||
|
|
||||||
|
**Exercice 3 : Multiplication égyptienne**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Utiliser la technique pour a = 13 et b = 61**
|
||||||
|
|
||||||
|
| **Étapes de la multiplication égyptienne** |
|
||||||
|
| ------------------------------------------ |
|
||||||
|
| 13 x 61 = 61 |
|
||||||
|
| 6 x 122 |
|
||||||
|
| 3 x 244 = 244 |
|
||||||
|
| 1 x 488 = 488 |
|
||||||
|
| **Total = 793** |
|
||||||
|
|
||||||
|
Fonction récursive :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def multiplication_egyptienne(a, b):
|
||||||
|
if a == 0:
|
||||||
|
return 0 # Cas de base : si a est 0, le produit est 0
|
||||||
|
elif a % 2 == 1:
|
||||||
|
return b + multiplication_egyptienne(a // 2, b * 2) # Ajouter b si a est impair
|
||||||
|
else:
|
||||||
|
return multiplication_egyptienne(a // 2, b * 2) # Continuer sans ajouter si a est pair
|
||||||
|
|
||||||
|
# Exemple de test
|
||||||
|
print(multiplication_egyptienne(13, 61)) # Renvoie 793
|
||||||
|
```
|
||||||
|
|
||||||
|
**Complexité de l’algorithme**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
La complexité de cet algorithme dépend du nombre de divisions de a par 2, ce qui correspond à une complexité logarithmique : **O(log a)**. En effet, chaque étape divise a par 2, ce qui signifie qu’il y aura environ log_2(a) itérations.
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
# Diviser pour régner
|
# Diviser pour régner
|
||||||
|
|
||||||
### Le programme
|
### Le programme
|
||||||
@@ -60,53 +59,60 @@ Prenons une liste d'exemple : `[38, 27, 43, 3, 9, 82, 10]`.
|
|||||||
3. **Combiner** : Enfin, on fusionne les deux sous-listes triées :
|
3. **Combiner** : Enfin, on fusionne les deux sous-listes triées :
|
||||||
- `[27, 38, 43]` et `[3, 9, 10, 82]` sont fusionnées pour donner la liste triée finale : `[3, 9, 10, 27, 38, 43, 82]`.
|
- `[27, 38, 43]` et `[3, 9, 10, 82]` sont fusionnées pour donner la liste triée finale : `[3, 9, 10, 27, 38, 43, 82]`.
|
||||||
|
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
### Implémentation en python
|
### Implémentation en Python
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def tri_fusion(gauche:list, droite:list):
|
def fusionner(gauche: list, droite: list) -> list:
|
||||||
tab_fusion = []
|
"""Fusionne deux listes triées en une seule liste triée."""
|
||||||
l1, l2 = len(gauche), len(droite)
|
resultat = []
|
||||||
i1, i2 = 0,0
|
i, j = 0, 0
|
||||||
while i1 < l1 and i2 < l2:
|
|
||||||
if gauche[i1] < droite[i2]:
|
|
||||||
tab_fusion.append(gauche[i1])
|
|
||||||
i1 += 1
|
|
||||||
else:
|
|
||||||
tab_fusion.append(droite[i2])
|
|
||||||
i2 += 1
|
|
||||||
return tab_fusion + gauche[i1:] + droite[i2:]
|
|
||||||
|
|
||||||
def fusion(tab:list):
|
while i < len(gauche) and j < len(droite):
|
||||||
if len (tab) <=1:
|
if gauche[i] < droite[j]:
|
||||||
return tab
|
resultat.append(gauche[i])
|
||||||
m = len(tab) // 2
|
i += 1
|
||||||
return tri_fusion(fusion(tab[:m]), fusion (tab[m:]))
|
else:
|
||||||
|
resultat.append(droite[j])
|
||||||
|
j += 1
|
||||||
|
|
||||||
|
# Ajouter les éléments restants
|
||||||
|
return resultat + gauche[i:] + droite[j:]
|
||||||
|
|
||||||
|
|
||||||
|
def tri_fusion(tab: list) -> list:
|
||||||
|
"""Trie une liste en utilisant l'algorithme du tri fusion."""
|
||||||
|
# Cas de base : une liste de 0 ou 1 élément est déjà triée
|
||||||
|
if len(tab) <= 1:
|
||||||
|
return tab
|
||||||
|
|
||||||
|
# Diviser : trouver le milieu et séparer en deux sous-listes
|
||||||
|
milieu = len(tab) // 2
|
||||||
|
gauche = tri_fusion(tab[:milieu])
|
||||||
|
droite = tri_fusion(tab[milieu:])
|
||||||
|
|
||||||
|
# Combiner : fusionner les deux sous-listes triées
|
||||||
|
return fusionner(gauche, droite)
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
#### Explication de la fonction `fusionner`
|
||||||
• tab_fusion est une liste vide où seront ajoutés les éléments fusionnés.
|
|
||||||
• l1 et l2 stockent les tailles respectives de gauche et droite.
|
|
||||||
• i1 et i2 sont des indices qui parcourent gauche et droite.
|
|
||||||
2. Boucle principale :
|
|
||||||
• Tant que l’on n’a pas parcouru complètement l’une des deux listes (gauche ou droite), on compare les éléments actuels de chaque liste (c’est-à-dire gauche[i1] et droite[i2]).
|
|
||||||
• Si l’élément de gauche est plus petit, on l’ajoute à tab_fusion, et on incrémente i1.
|
|
||||||
• Sinon, on ajoute l’élément de droite et on incrémente i2.
|
|
||||||
3. Retour des éléments restants :
|
|
||||||
• Une fois que l’une des deux listes est entièrement parcourue, on ajoute les éléments restants de l’autre liste à tab_fusion. C’est ce que fait gauche[i1:] pour la première liste, et droite[i2:] pour la deuxième.
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
- `resultat` est une liste vide où seront ajoutés les éléments fusionnés
|
||||||
Condition de base :
|
- `i` et `j` sont des indices qui parcourent respectivement `gauche` et `droite`
|
||||||
• Si le tableau contient un seul élément (ou aucun), il est déjà trié, donc on le retourne tel quel.
|
- **Boucle principale** : tant que l'on n'a pas parcouru complètement l'une des deux listes, on compare les éléments actuels (`gauche[i]` et `droite[j]`)
|
||||||
2. Diviser :
|
- Si l'élément de gauche est plus petit, on l'ajoute à `resultat` et on incrémente `i`
|
||||||
• On divise le tableau en deux sous-tableaux de taille approximativement égale à l’aide de m = len(tab) // 2.
|
- Sinon, on ajoute l'élément de droite et on incrémente `j`
|
||||||
• tab[:m] correspond à la première moitié du tableau et tab[m:] à la deuxième.
|
- **Éléments restants** : une fois qu'une liste est entièrement parcourue, on ajoute les éléments restants de l'autre liste
|
||||||
3. Récursion et fusion :
|
|
||||||
• On applique récursivement fusion() aux deux moitiés du tableau.
|
#### Explication de la fonction `tri_fusion`
|
||||||
• Une fois que les deux sous-tableaux sont triés, on les combine en utilisant la fonction tri_fusion.
|
|
||||||
```
|
- **Cas de base** : si le tableau contient un seul élément (ou aucun), il est déjà trié, donc on le retourne tel quel
|
||||||
|
- **Diviser** : on divise le tableau en deux sous-tableaux de taille approximativement égale
|
||||||
|
- `tab[:milieu]` correspond à la première moitié
|
||||||
|
- `tab[milieu:]` correspond à la deuxième moitié
|
||||||
|
- **Régner** : on applique récursivement `tri_fusion()` aux deux moitiés du tableau
|
||||||
|
- **Combiner** : une fois les deux sous-tableaux triés, on les fusionne avec la fonction `fusionner`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -133,5 +139,4 @@ Auteur : Florian Mathieu
|
|||||||
|
|
||||||
Licence CC BY NC
|
Licence CC BY NC
|
||||||
|
|
||||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
|
|
||||||
|
|||||||
40
Recursivité/Exercices_Recursivité.md
Normal file
40
Recursivité/Exercices_Recursivité.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Exercices Récursivité
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 1 : Exponentiation rapide
|
||||||
|
|
||||||
|
On rappelle que l'exponentiation rapide permet de calculer le nombre a<sup>n</sup> en utilisant les relations de récurrence suivantes :
|
||||||
|
|
||||||
|
- a<sup>0</sup> = 1
|
||||||
|
- Si n est pair : a<sup>n</sup> = (a × a)<sup>n/2</sup>
|
||||||
|
- Sinon : a<sup>n</sup> = a × (a × a)<sup>(n-1)/2</sup>
|
||||||
|
|
||||||
|
**Questions :**
|
||||||
|
|
||||||
|
1. Écrire une version récursive de l'exponentiation rapide.
|
||||||
|
2. Écrire une version itérative de l'exponentiation rapide.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 2 : Liste décroissante
|
||||||
|
|
||||||
|
Écrire une fonction récursive d'argument `n` qui génère la liste suivante :
|
||||||
|
|
||||||
|
```python
|
||||||
|
[n, n-1, ..., 1, 0]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 3 : Multiplication égyptienne
|
||||||
|
|
||||||
|
La multiplication égyptienne, dite aussi du paysan russe, était utilisée par les scribes dès l'Antiquité. Elle ne nécessite pas la connaissance des tables de multiplication, seulement l'addition et la division par deux.
|
||||||
|
|
||||||
|
On multiplie deux entiers, *a* et *b* de la manière suivante : on divise *a* par 2 tant que c'est possible, en doublant *b*, sinon on décrémente *a* et on ajoute *b* au résultat.
|
||||||
|
|
||||||
|
**Questions :**
|
||||||
|
|
||||||
|
1. Utiliser cette technique pour calculer le produit *a* × *b* avec *a* = 13 et *b* = 61.
|
||||||
|
2. Écrire une fonction récursive qui calcule le produit de *a* et *b* en utilisant la multiplication égyptienne.
|
||||||
|
3. Quelle est la complexité de cet algorithme en fonction de *a* ?
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
## Recursivité
|
# Récursivité
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -17,11 +17,11 @@
|
|||||||
### Un peu de culture
|
### Un peu de culture
|
||||||
|
|
||||||
La récursivité, en informatique, est une pratique qui permet à une fonction de s'appeler elle-même.
|
La récursivité, en informatique, est une pratique qui permet à une fonction de s'appeler elle-même.
|
||||||
On peut comparer cela à la [mise en abyme](https://fr.wikipedia.org/wiki/Mise_en_abyme), procédé littéraire qui décrit une oeuvre incrustée dans elle même
|
On peut comparer cela à la [mise en abyme](https://fr.wikipedia.org/wiki/Mise_en_abyme), procédé littéraire qui décrit une œuvre incrustée dans elle-même.
|
||||||
|
|
||||||
<img src="assets/vache_qui_rit.gif" alt="vache qui rit" style="zoom: 67%;" />
|
<img src="assets/vache_qui_rit.gif" alt="vache qui rit" style="zoom: 67%;" />
|
||||||
|
|
||||||
**La fonction récursive s'appelle elle même dans sa définition.**
|
**La fonction récursive s'appelle elle-même dans sa définition.**
|
||||||
|
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ def fct_recursive() :
|
|||||||
fct_recursive()
|
fct_recursive()
|
||||||
```
|
```
|
||||||
|
|
||||||
Cette fonction s'appelle elle même, mais elle présente un problème.
|
Cette fonction s'appelle elle-même, mais elle présente un problème.
|
||||||
|
|
||||||
Si nous l'appelons, le résultat sera celui-ci :
|
Si nous l'appelons, le résultat sera celui-ci :
|
||||||
|
|
||||||
@@ -55,11 +55,11 @@ Cette fonction s'appelle elle même, mais elle présente un problème.
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
Tout comme les boucles, il faut créer une instruction permettant d'arrêter l'exécution du code, en récursif nous l'appelons **le cas d'arrêt**. Il permettra d'éviter les appels **infinis**
|
Tout comme les boucles, il faut créer une instruction permettant d'arrêter l'exécution du code, en récursif nous l'appelons **le cas de base** (ou cas d'arrêt). Il permettra d'éviter les appels **infinis**.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Le cas d'arrêt
|
### Le cas de base
|
||||||
|
|
||||||
Afin de stopper l'exécution du code il est donc essentiel de créer une instruction n'appelant pas notre fonction.
|
Afin de stopper l'exécution du code il est donc essentiel de créer une instruction n'appelant pas notre fonction.
|
||||||
|
|
||||||
@@ -67,17 +67,22 @@ Par exemple :
|
|||||||
|
|
||||||
Voici la fonction somme(n) se définissant comme :
|
Voici la fonction somme(n) se définissant comme :
|
||||||
|
|
||||||
$`somme(n) = \left\{ \begin{array}{ll} 0 {~si~} n = 0\\ n + somme(n -1){~sinon.} \end{array}\right.`$
|
$$
|
||||||
|
somme(n) = \begin{cases}
|
||||||
|
0 & \text{si } n = 0 \text{ (cas de base)}\\
|
||||||
|
n + somme(n-1) & \text{sinon (cas récursif)}
|
||||||
|
\end{cases}
|
||||||
|
$$
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def somme(n) :
|
def somme(n) :
|
||||||
if n == 0 :
|
if n == 0 :
|
||||||
return 0
|
return 0 # Cas de base
|
||||||
else :
|
else :
|
||||||
return n + somme(n-1)
|
return n + somme(n-1) # Cas récursif
|
||||||
```
|
```
|
||||||
|
|
||||||
Cette fonction s'appelle donc elle même jusqu'à avoir n égal à 0.
|
Cette fonction s'appelle donc elle-même jusqu'à avoir n égal à 0.
|
||||||
|
|
||||||
Mais quel sera le résultat de *somme(3)* ?
|
Mais quel sera le résultat de *somme(3)* ?
|
||||||
|
|
||||||
@@ -92,7 +97,7 @@ somme(0) = 0
|
|||||||
|
|
||||||
Voici les différents appels de fonctions pour somme(3). Mais là, le résultat n'est pas encore obtenu.
|
Voici les différents appels de fonctions pour somme(3). Mais là, le résultat n'est pas encore obtenu.
|
||||||
|
|
||||||
Pour cela il faut pour cela reprendre les appels de fonctions pour revenir jusqu'à somme(3)
|
Pour cela il faut reprendre les appels de fonctions pour revenir jusqu'à somme(3) :
|
||||||
|
|
||||||
```
|
```
|
||||||
somme(0) = 0
|
somme(0) = 0
|
||||||
@@ -105,15 +110,48 @@ Ici nous observons donc 4 appels de fonctions pour résoudre somme(3).
|
|||||||
|
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
### Appel de fonction en python
|
### La pile d'appels
|
||||||
|
|
||||||
En python il existe une limite au nombre d'appel de fonction que l'on peut faire pour une fonction récursive.
|
Lorsqu'une fonction récursive s'exécute, Python utilise une structure de données appelée **pile d'appels** (ou *call stack*) pour gérer les différents appels.
|
||||||
|
|
||||||
|
#### Principe de fonctionnement
|
||||||
|
|
||||||
|
À chaque appel de fonction :
|
||||||
|
1. Python **empile** un nouveau *contexte d'exécution* contenant les paramètres et variables locales
|
||||||
|
2. Lorsque le cas de base est atteint, les appels sont **dépilés** un par un
|
||||||
|
3. Chaque résultat remonte vers l'appel précédent
|
||||||
|
|
||||||
|
#### Visualisation avec somme(3)
|
||||||
|
|
||||||
|
```
|
||||||
|
EMPILEMENT DÉPILEMENT
|
||||||
|
(descente) (remontée)
|
||||||
|
|
||||||
|
┌─────────────┐ ┌─────────────┐
|
||||||
|
│ somme(0) │ ← cas de base │ somme(0) │ → retourne 0
|
||||||
|
├─────────────┤ ├─────────────┤
|
||||||
|
│ somme(1) │ attend somme(0) │ somme(1) │ → retourne 1+0 = 1
|
||||||
|
├─────────────┤ ├─────────────┤
|
||||||
|
│ somme(2) │ attend somme(1) │ somme(2) │ → retourne 2+1 = 3
|
||||||
|
├─────────────┤ ├─────────────┤
|
||||||
|
│ somme(3) │ attend somme(2) │ somme(3) │ → retourne 3+3 = 6
|
||||||
|
└─────────────┘ └─────────────┘
|
||||||
|
BASE BASE
|
||||||
|
```
|
||||||
|
|
||||||
|
> La pile fonctionne selon le principe **LIFO** (Last In, First Out) : le dernier appel empilé est le premier à être résolu.
|
||||||
|
|
||||||
|
-----------
|
||||||
|
|
||||||
|
### Limite de récursion en Python
|
||||||
|
|
||||||
|
En Python il existe une limite au nombre d'appels de fonction que l'on peut faire pour une fonction récursive.
|
||||||
|
|
||||||
En effet, avec notre exemple nous voyons que pour obtenir somme(3) nous faisons 4 appels de fonction.
|
En effet, avec notre exemple nous voyons que pour obtenir somme(3) nous faisons 4 appels de fonction.
|
||||||
|
|
||||||
Ces appels de fonction **s'empilent** en espace mémoire, python peut stocker autour de 1000 appels.
|
Ces appels de fonction **s'empilent** en espace mémoire. Python peut stocker environ **1000 appels** par défaut.
|
||||||
|
|
||||||
Si cette limite est dépassée alors l'erreur **RecursionError** apparaîtra.
|
Si cette limite est dépassée alors l'erreur **RecursionError** apparaîtra :
|
||||||
|
|
||||||
```python
|
```python
|
||||||
>>> somme(1001)
|
>>> somme(1001)
|
||||||
@@ -121,37 +159,195 @@ Si cette limite est dépassée alors l'erreur **RecursionError** apparaîtra.
|
|||||||
RecursionError: maximum recursion depth exceeded in comparison
|
RecursionError: maximum recursion depth exceeded in comparison
|
||||||
```
|
```
|
||||||
|
|
||||||
### Autre type de récursivité
|
> Cette limite existe pour protéger la mémoire de l'ordinateur. On peut la modifier avec `sys.setrecursionlimit()`, mais ce n'est généralement pas recommandé.
|
||||||
|
|
||||||
|
-----------
|
||||||
|
|
||||||
|
### Terminaison d'une fonction récursive
|
||||||
|
|
||||||
|
Une question essentielle se pose : **comment être sûr qu'une fonction récursive s'arrête ?**
|
||||||
|
|
||||||
|
#### Le variant de boucle
|
||||||
|
|
||||||
|
Pour prouver qu'une fonction récursive termine, on utilise un **variant** : une quantité entière qui :
|
||||||
|
- est **positive ou nulle** à chaque appel
|
||||||
|
- **décroît strictement** à chaque appel récursif
|
||||||
|
|
||||||
|
Quand le variant atteint 0 (ou une valeur minimale), on est dans le cas de base.
|
||||||
|
|
||||||
|
#### Exemple avec somme(n)
|
||||||
|
|
||||||
|
Pour la fonction `somme(n)` :
|
||||||
|
- **Variant** : la valeur de `n`
|
||||||
|
- À chaque appel récursif, on appelle `somme(n-1)`, donc `n` décroît de 1
|
||||||
|
- Quand `n = 0`, on atteint le cas de base
|
||||||
|
|
||||||
|
| Appel | Valeur du variant (n) |
|
||||||
|
|-------|----------------------|
|
||||||
|
| somme(3) | 3 |
|
||||||
|
| somme(2) | 2 |
|
||||||
|
| somme(1) | 1 |
|
||||||
|
| somme(0) | 0 → cas de base |
|
||||||
|
|
||||||
|
Le variant décroît strictement et atteint 0 : **la fonction termine**.
|
||||||
|
|
||||||
|
#### Attention aux erreurs
|
||||||
|
|
||||||
|
```python
|
||||||
|
def mauvaise_somme(n):
|
||||||
|
if n == 0:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return n + mauvaise_somme(n + 1) # n augmente !
|
||||||
|
```
|
||||||
|
|
||||||
|
Ici, le paramètre **augmente** au lieu de diminuer : la fonction ne terminera jamais (jusqu'à la `RecursionError`).
|
||||||
|
|
||||||
|
-----------
|
||||||
|
|
||||||
|
### Écrire une fonction récursive : méthodologie
|
||||||
|
|
||||||
|
Pour écrire correctement une fonction récursive, il faut identifier **trois éléments** :
|
||||||
|
|
||||||
|
| Élément | Question à se poser | Exemple avec `somme(n)` |
|
||||||
|
|---------|---------------------|------------------------|
|
||||||
|
| **Cas de base** | Quel est le cas le plus simple, qui ne nécessite pas d'appel récursif ? | `n == 0` → retourne `0` |
|
||||||
|
| **Cas récursif** | Comment décomposer le problème en un sous-problème plus petit ? | `somme(n) = n + somme(n-1)` |
|
||||||
|
| **Variant** | Quelle quantité décroît à chaque appel ? | La valeur de `n` |
|
||||||
|
|
||||||
|
#### Exemple : la factorielle
|
||||||
|
|
||||||
|
On souhaite écrire `factorielle(n)` qui calcule `n! = n × (n-1) × ... × 2 × 1`.
|
||||||
|
|
||||||
|
1. **Cas de base** : `factorielle(0) = 1` (par convention, 0! = 1)
|
||||||
|
2. **Cas récursif** : `factorielle(n) = n × factorielle(n-1)`
|
||||||
|
3. **Variant** : `n` décroît de 1 à chaque appel
|
||||||
|
|
||||||
|
```python
|
||||||
|
def factorielle(n):
|
||||||
|
if n == 0:
|
||||||
|
return 1 # Cas de base
|
||||||
|
else:
|
||||||
|
return n * factorielle(n - 1) # Cas récursif
|
||||||
|
```
|
||||||
|
|
||||||
|
Vérification :
|
||||||
|
```
|
||||||
|
factorielle(4) = 4 × factorielle(3)
|
||||||
|
= 4 × 3 × factorielle(2)
|
||||||
|
= 4 × 3 × 2 × factorielle(1)
|
||||||
|
= 4 × 3 × 2 × 1 × factorielle(0)
|
||||||
|
= 4 × 3 × 2 × 1 × 1
|
||||||
|
= 24
|
||||||
|
```
|
||||||
|
|
||||||
|
-----------
|
||||||
|
|
||||||
|
### Récursif vs Itératif
|
||||||
|
|
||||||
|
Toute fonction récursive peut être réécrite de manière **itérative** (avec des boucles), et inversement.
|
||||||
|
|
||||||
|
#### Comparaison sur l'exemple de la somme
|
||||||
|
|
||||||
|
**Version récursive :**
|
||||||
|
```python
|
||||||
|
def somme_recursive(n):
|
||||||
|
if n == 0:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return n + somme_recursive(n - 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Version itérative :**
|
||||||
|
```python
|
||||||
|
def somme_iterative(n):
|
||||||
|
resultat = 0
|
||||||
|
for i in range(1, n + 1):
|
||||||
|
resultat += i
|
||||||
|
return resultat
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Avantages et inconvénients
|
||||||
|
|
||||||
|
| Critère | Récursif | Itératif |
|
||||||
|
|---------|----------|----------|
|
||||||
|
| **Lisibilité** | Souvent plus élégant et proche de la définition mathématique | Peut être plus verbeux |
|
||||||
|
| **Mémoire** | Utilise la pile d'appels (risque de débordement) | Utilise peu de mémoire |
|
||||||
|
| **Performance** | Peut être plus lent (overhead des appels) | Généralement plus rapide |
|
||||||
|
| **Cas d'usage** | Structures récursives (arbres, graphes), diviser pour régner | Calculs simples, itérations |
|
||||||
|
|
||||||
|
> En pratique, on choisit la récursivité quand elle rend le code **plus clair** ou quand le problème est **naturellement récursif** (parcours d'arbres, fractales, etc.).
|
||||||
|
|
||||||
|
-----------
|
||||||
|
|
||||||
|
### Autres types de récursivité
|
||||||
|
|
||||||
Il existe divers types de récursivité :
|
Il existe divers types de récursivité :
|
||||||
|
|
||||||
- La récursivité multiple
|
- La **récursivité simple** : un seul appel récursif (comme `somme`)
|
||||||
- La récursivité double
|
- La **récursivité multiple** : plusieurs appels récursifs dans la fonction
|
||||||
- La récursivité imbriquée
|
- La **récursivité mutuelle** : deux fonctions qui s'appellent mutuellement
|
||||||
- etc ...
|
- La **récursivité terminale** : l'appel récursif est la dernière opération
|
||||||
|
|
||||||
Ces différents types de récursivité sont quelques peu différents de ce que l'on vient de voir. Mais nous pourrions les rencontrer dans la suite du programme.
|
Ces différents types de récursivité sont quelque peu différents de ce que l'on vient de voir. Mais nous pourrions les rencontrer dans la suite du programme.
|
||||||
|
|
||||||
### Récursivité double
|
### Récursivité multiple : la suite de Fibonacci
|
||||||
|
|
||||||
En effet à la différence de la récursivité dite **simple** il y aura ici plusieurs appels de fonctions dans une même instruction.
|
À la différence de la récursivité dite **simple**, il y aura ici **plusieurs appels de fonctions** dans une même instruction.
|
||||||
|
|
||||||
> En mathématiques, la suite de Fibonacci est une suite de nombres entiers dont chaque terme successif représente la somme des deux termes précédents, et qui commence par 0 puis 1. Ainsi, les dix premiers termes qui la composent sont 0, 1, 1, 2, 3, 5, 8, 13, 21 et 34.
|
> En mathématiques, la suite de Fibonacci est une suite de nombres entiers dont chaque terme successif représente la somme des deux termes précédents, et qui commence par 0 puis 1. Ainsi, les dix premiers termes qui la composent sont 0, 1, 1, 2, 3, 5, 8, 13, 21 et 34.
|
||||||
|
|
||||||
|
$$
|
||||||
|
fib(n) = \begin{cases}
|
||||||
|
0 & \text{si } n = 0\\
|
||||||
|
1 & \text{si } n = 1\\
|
||||||
|
fib(n-1) + fib(n-2) & \text{sinon}
|
||||||
|
\end{cases}
|
||||||
|
$$
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def fibonacci(n):
|
def fibonacci(n):
|
||||||
if n == 0 :
|
if n == 0:
|
||||||
return 0
|
return 0
|
||||||
elif n == 1 :
|
elif n == 1:
|
||||||
return 1
|
return 1
|
||||||
else :
|
else:
|
||||||
return fibonnaci(n-1) + fibonnaci(n-2)
|
return fibonacci(n-1) + fibonacci(n-2)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Arbre des appels pour fibonacci(4)
|
||||||
|
|
||||||
|
```
|
||||||
|
fibonacci(4)
|
||||||
|
/ \
|
||||||
|
fibonacci(3) fibonacci(2)
|
||||||
|
/ \ / \
|
||||||
|
fibonacci(2) fibonacci(1) fibonacci(1) fibonacci(0)
|
||||||
|
/ \ | | |
|
||||||
|
fibonacci(1) fibonacci(0) 1 1 0
|
||||||
|
| |
|
||||||
|
1 0
|
||||||
|
```
|
||||||
|
|
||||||
|
On remarque que `fibonacci(2)` est calculé **deux fois**, et `fibonacci(1)` **trois fois**. Cette redondance rend l'algorithme très inefficace pour de grandes valeurs de `n`.
|
||||||
|
|
||||||
|
> Ce problème sera résolu grâce à la **programmation dynamique**, que nous verrons dans un prochain chapitre.
|
||||||
|
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
### À retenir
|
||||||
|
|
||||||
|
- Une fonction **récursive** s'appelle elle-même
|
||||||
|
- Elle doit avoir un **cas de base** pour s'arrêter
|
||||||
|
- On prouve la **terminaison** avec un **variant** (quantité qui décroît)
|
||||||
|
- Les appels s'empilent dans la **pile d'appels**
|
||||||
|
- Python limite le nombre d'appels récursifs (~1000)
|
||||||
|
- Récursif ≠ toujours meilleur : choisir selon le problème
|
||||||
|
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Auteurs : Florian Mathieu, Timothée Decoster, Enzo Frémeaux
|
Auteurs : Florian Mathieu, Timothée Decoster, Enzo Frémeaux
|
||||||
|
|
||||||
Licence CC BY NC
|
Licence CC BY NC
|
||||||
|
|
||||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ def somme(n) :
|
|||||||
def mystere(i,k):
|
def mystere(i,k):
|
||||||
if i<=k :
|
if i<=k :
|
||||||
print(i)
|
print(i)
|
||||||
mystère(i+1,k)
|
mystere(i+1,k)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 2. Nombre de chiffre d'un nombre :
|
### 2. 2. Nombre de chiffre d'un nombre :
|
||||||
|
|||||||
24
Recursivité/TP/Recherche_dicho.py
Normal file
24
Recursivité/TP/Recherche_dicho.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
def recherche_dichotomique(t, v):
|
||||||
|
debut = 0
|
||||||
|
fin = len(t)-1
|
||||||
|
while debut <= fin :
|
||||||
|
milieu = (debut + fin) // 2
|
||||||
|
print(milieu,debut,fin)
|
||||||
|
if t[milieu] == v :
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
if t[milieu] > v :
|
||||||
|
fin = milieu - 1
|
||||||
|
else :
|
||||||
|
debut = milieu + 1
|
||||||
|
return False
|
||||||
|
|
||||||
|
def recherche_recur(t,v):
|
||||||
|
if len(t) == 1:
|
||||||
|
return t[0] == v
|
||||||
|
else :
|
||||||
|
milieu = len(t)//2
|
||||||
|
if t[milieu] > v :
|
||||||
|
return recherche_recur(t[:milieu],v)
|
||||||
|
else :
|
||||||
|
return recherche_recur(t[milieu:],v)
|
||||||
29
Recursivité/TP/TP.md
Normal file
29
Recursivité/TP/TP.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
## Activité : La récursivité dans les jeux vidéo
|
||||||
|
|
||||||
|
### Contexte :
|
||||||
|
Imaginez un jeu vidéo populaire où le héros doit traverser différents niveaux pour sauver un personnage captif. Chaque niveau est plus difficile que le précédent, et chaque niveau contient des mini-boss qu'il faut vaincre avant de passer au suivant.
|
||||||
|
|
||||||
|
### Objectif de l'activité :
|
||||||
|
Utiliser la récursivité pour simuler le parcours du héros à travers les niveaux du jeu.
|
||||||
|
|
||||||
|
### Instructions :
|
||||||
|
|
||||||
|
### Présentation du problème :
|
||||||
|
- Le héros commence au niveau 1 et doit atteindre le niveau `n`.
|
||||||
|
- À chaque niveau, il y a un certain nombre de mini-boss à vaincre avant de passer au niveau suivant.
|
||||||
|
- Si le héros vainc tous les mini-boss d'un niveau, il passe au niveau suivant, sinon il recommence le même niveau.
|
||||||
|
|
||||||
|
### Modélisation en pseudo-code :
|
||||||
|
Écrire une fonction récursive `traverse_levels` qui prend en entrée le niveau actuel et le nombre total de niveaux.
|
||||||
|
|
||||||
|
- Si le niveau actuel est supérieur au nombre total de niveaux, le héros a gagné.
|
||||||
|
- Sinon, le héros combat les mini-boss et passe au niveau suivant.
|
||||||
|
|
||||||
|
### 3. Exemple de code en Python
|
||||||
|
```python
|
||||||
|
def combat_mini_bosses(level):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def traverse_levels(current_level, total_levels):
|
||||||
|
pass
|
||||||
|
```
|
||||||
349
Recursivité/TP/TP_Recursivite.ipynb
Normal file
349
Recursivité/TP/TP_Recursivite.ipynb
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "66904fb8",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# TP : La Récursivité et le Diviser pour Régner\n",
|
||||||
|
"\n",
|
||||||
|
"## Objectifs :\n",
|
||||||
|
"- Comprendre le principe de la récursivité.\n",
|
||||||
|
"- Appliquer la récursivité pour résoudre des problèmes simples.\n",
|
||||||
|
"- Approfondir la méthode du **diviser pour régner** à travers des exemples concrets.\n",
|
||||||
|
"- Compléter un algorithme de **tri fusion**.\n",
|
||||||
|
"\n",
|
||||||
|
"### Rappel sur la récursivité :\n",
|
||||||
|
"La récursivité est une méthode où une fonction s'appelle elle-même pour résoudre un problème. Chaque appel de la fonction permet de simplifier le problème jusqu'à atteindre une condition de base (appelée **cas de base**) qui permet de terminer la récursion.\n",
|
||||||
|
"\n",
|
||||||
|
"**Exemple classique :** Calcul de la factorielle d'un nombre entier positif `n`.\n",
|
||||||
|
"```python\n",
|
||||||
|
"def factorielle(n):\n",
|
||||||
|
" if n == 0:\n",
|
||||||
|
" return 1 # Cas de base\n",
|
||||||
|
" else:\n",
|
||||||
|
" return n * factorielle(n-1) # Appel récursif\n",
|
||||||
|
"```"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "bb057eb6",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 1 : Fonction récursive `traduire_romain`\n",
|
||||||
|
"**But :** Écrire une fonction récursive `traduire_romain` qui prend en paramètre une chaîne de caractères non vide représentant un nombre écrit en chiffres romains, et qui renvoie son écriture décimale.\n",
|
||||||
|
"\n",
|
||||||
|
"**Consignes :**\n",
|
||||||
|
"- Chaque chiffre romain a une valeur : `I=1, V=5, X=10, L=50, C=100, D=500, M=1000`.\n",
|
||||||
|
"- Si un chiffre plus petit précède un chiffre plus grand (ex : IV), cela signifie qu'il faut soustraire la valeur du chiffre plus petit.\n",
|
||||||
|
" \n",
|
||||||
|
"**Exemple :**\n",
|
||||||
|
"```python\n",
|
||||||
|
"traduire_romain(\"IX\") # Renvoie 9\n",
|
||||||
|
"traduire_romain(\"MCMXCIV\") # Renvoie 1994\n",
|
||||||
|
"```\n",
|
||||||
|
"\n",
|
||||||
|
"**Indication :** Utilisez la récursion pour traiter un caractère à la fois, et soustrayez ou ajoutez en fonction de la position relative des caractères.\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 7,
|
||||||
|
"id": "ff49f2f3",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"None\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"def traduire_romain(chaine):\n",
|
||||||
|
" pass\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"print(traduire_romain(\"IX\")) # Doit afficher 9\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "1aedbf03",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 2 : Compléter un Tri Fusion\n",
|
||||||
|
"**But :** Compléter l'algorithme du tri fusion.\n",
|
||||||
|
"\n",
|
||||||
|
"Le tri fusion est un exemple typique de la méthode de **diviser pour régner**. L'idée est de diviser le tableau en deux, de trier chaque moitié, puis de fusionner les deux moitiés triées.\n",
|
||||||
|
"\n",
|
||||||
|
"Voici un code partiellement écrit que vous devez compléter :\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 8,
|
||||||
|
"id": "31d950ac",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"[38, 27, 43, 3, 9, 82, 10]\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"def tri_fusion(tab):\n",
|
||||||
|
" if len(tab) <= 1:\n",
|
||||||
|
" return tab # Cas de base\n",
|
||||||
|
" milieu = len(tab) // 2\n",
|
||||||
|
" gauche = tri_fusion(tab[:milieu]) # Appel récursif\n",
|
||||||
|
" droite = tri_fusion(tab[milieu:]) # Appel récursif\n",
|
||||||
|
" return fusionner(gauche, droite)\n",
|
||||||
|
"\n",
|
||||||
|
"def fusionner(gauche, droite):\n",
|
||||||
|
" tableau_fusionne = []\n",
|
||||||
|
" i, j = 0, 0\n",
|
||||||
|
" # Complétez la fonction pour fusionner les deux sous-tableaux ici :\n",
|
||||||
|
" # ---------------------------------------------\n",
|
||||||
|
"\n",
|
||||||
|
" # ---------------------------------------------\n",
|
||||||
|
" # Ajoutez ici les éléments restants des sous-tableaux\n",
|
||||||
|
" tableau_fusionne += gauche[i:]\n",
|
||||||
|
" tableau_fusionne += droite[j:]\n",
|
||||||
|
" return tableau_fusionne\n",
|
||||||
|
"\n",
|
||||||
|
"# Test de la fonction tri_fusion\n",
|
||||||
|
"print(tri_fusion([38, 27, 43, 3, 9, 82, 10])) # Doit afficher une liste triée\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "15791934",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 3 : Diviser pour Régner — Recherche d'un élément dans un tableau\n",
|
||||||
|
"**But :** Implémenter la recherche d'un élément dans un tableau trié en utilisant la méthode de **diviser pour régner** (recherche dichotomique).\n",
|
||||||
|
"\n",
|
||||||
|
"La recherche dichotomique consiste à diviser le tableau en deux parties à chaque étape :\n",
|
||||||
|
"- Si l'élément recherché est au milieu, vous avez terminé.\n",
|
||||||
|
"- Si l'élément est plus petit que l'élément du milieu, il se trouve dans la première moitié du tableau.\n",
|
||||||
|
"- Sinon, il se trouve dans la deuxième moitié.\n",
|
||||||
|
"\n",
|
||||||
|
"Voici un squelette à compléter :\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 9,
|
||||||
|
"id": "ab9509b5",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"None\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"def recherche_dichotomique(tab, element, debut=0, fin=None):\n",
|
||||||
|
" if fin is None:\n",
|
||||||
|
" fin = len(tab) - 1\n",
|
||||||
|
" if debut > fin:\n",
|
||||||
|
" return -1 # L'élément n'est pas dans le tableau\n",
|
||||||
|
" milieu = (debut + fin) // 2\n",
|
||||||
|
" if tab[milieu] == element:\n",
|
||||||
|
" return # L'élément est trouvé\n",
|
||||||
|
" # Complétez ici avec les appels récursifs manquants :\n",
|
||||||
|
" # --------------------------------------------------\n",
|
||||||
|
" \n",
|
||||||
|
" # --------------------------------------------------\n",
|
||||||
|
" \n",
|
||||||
|
"# Test de la fonction\n",
|
||||||
|
"tab = [3, 9, 10, 27, 38, 43, 82]\n",
|
||||||
|
"element = 27\n",
|
||||||
|
"print(recherche_dichotomique(tab, element)) # Doit afficher l'index de l'élément trouvé\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "99671e3b",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Exercice 4 : \n",
|
||||||
|
"\n",
|
||||||
|
"On considère des tableaux de nombres dont tous les éléments sont présents\n",
|
||||||
|
"exactement trois fois à la suite, sauf un élément qui est présent une unique fois et\n",
|
||||||
|
"que l'on appelle « l'intrus ». \n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"Voici quelques exemples :\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 10,
|
||||||
|
"id": "f7a2fa7d",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"tab_a = [3, 3, 3, 9, 9, 9, 1, 1, 1, 7, 2, 2, 2, 4, 4, 4, 8, 8, 8, 5,\n",
|
||||||
|
"5, 5]\n",
|
||||||
|
"#l'intrus est 7\n",
|
||||||
|
"tab_b = [8, 5, 5, 5, 9, 9, 9, 18, 18, 18, 3, 3, 3]\n",
|
||||||
|
"#l'intrus est 8\n",
|
||||||
|
"tab_c = [5, 5, 5, 1, 1, 1, 0, 0, 0, 6, 6, 6, 3, 8, 8, 8]\n",
|
||||||
|
"#l'intrus est 3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "f693789b",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"On remarque qu'avec de tels tableaux : \n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
" pour les indices multiples de 3 situés strictement avant l'intrus, l'élément\n",
|
||||||
|
"correspondant et son voisin de droite sont égaux,\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
" pour les indices multiples de 3 situés après l'intrus, l'élément correspondant et\n",
|
||||||
|
"son voisin de droite - s'il existe - sont différents.\n",
|
||||||
|
"\n",
|
||||||
|
"Ce que l'on peut observer ci-dessous en observant les valeurs des paires de voisins\n",
|
||||||
|
"marquées par des caractères ^"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "b0549a7c",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"[3, 3, 3, 9, 9, 9, 1, 1, 1, 7, 2, 2, 2, 4, 4, 4, 8, 8, 8, 5, 5, 5]\n",
|
||||||
|
"^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^\n",
|
||||||
|
"0 3 6 9 12 15 18 21 "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "fbb578a8",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Dans des listes comme celles ci-dessus, un algorithme récursif pour trouver l'intrus\n",
|
||||||
|
"consiste alors à choisir un indice i multiple de 3 situé approximativement au milieu\n",
|
||||||
|
"des indices parmi lesquels se trouve l'intrus.\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"Puis, en fonction des valeurs de l'élément d'indice i et de son voisin de droite, à\n",
|
||||||
|
"appliquer récursivement l'algorithme à la moitié droite ou à la moitié gauche des\n",
|
||||||
|
"indices parmi lesquels se trouve l'intrus."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "b0603315",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Par exemple, si on s’intéresse à l’indice 12, on voit les valeurs 2 et 4 qui sont\n",
|
||||||
|
"différentes : l’intrus est donc à gauche de l’indice 12 (indice 12 compris)\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"En revanche, si on s’intéresse à l’indice 3, on voit les valeurs 9 et 9 qui sont\n",
|
||||||
|
"identiques : l’intrus est donc à droite des indices 3-4-5, donc à partir de l’indice 6."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "bb44af36",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Compléter la fonction récursive **trouver_intrus** proposée page suivante qui met\n",
|
||||||
|
"en œuvre cet algorithme."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 11,
|
||||||
|
"id": "887c8914",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"ename": "SyntaxError",
|
||||||
|
"evalue": "invalid character '–' (U+2013) (617363500.py, line 9)",
|
||||||
|
"output_type": "error",
|
||||||
|
"traceback": [
|
||||||
|
"\u001b[0;36m Cell \u001b[0;32mIn[11], line 9\u001b[0;36m\u001b[0m\n\u001b[0;31m nombre_de_triplets = (d – g) // ...\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid character '–' (U+2013)\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"def trouver_intrus(tab, g, d):\n",
|
||||||
|
" \n",
|
||||||
|
" '''Renvoie la valeur de l'intrus situé entre les indices g et d dans la liste tab où :\n",
|
||||||
|
" tab vérifie les conditions de l'exercice, g et d sont des multiples de 3.\n",
|
||||||
|
" '''\n",
|
||||||
|
" if g == d:\n",
|
||||||
|
" return ...\n",
|
||||||
|
" else:\n",
|
||||||
|
" nombre_de_triplets = (d – g) // ...\n",
|
||||||
|
" indice = g + 3 * (nombre_de_triplets // 2)\n",
|
||||||
|
" if ... :\n",
|
||||||
|
" return ...\n",
|
||||||
|
" else:\n",
|
||||||
|
" return ..."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "679501d0",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"Exemples :"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "d06a36b2",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
">>> trouver_intrus([3, 3, 3, 9, 9, 9, 1, 1, 1, 7, 2, 2, 2, 4,\n",
|
||||||
|
"4, 4, 8, 8, 8, 5, 5, 5], 0, 21)\n",
|
||||||
|
"7\n",
|
||||||
|
">>> trouver_intrus([8, 5, 5, 5, 9, 9, 9, 18, 18, 18, 3, 3, 3],\n",
|
||||||
|
"0, 12)\n",
|
||||||
|
"8\n",
|
||||||
|
">>> trouver_intrus([5, 5, 5, 1, 1, 1, 0, 0, 0, 6, 6, 6, 3, 8,\n",
|
||||||
|
"8, 8], 0, 15)\n",
|
||||||
|
"3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python (myenv)",
|
||||||
|
"language": "python",
|
||||||
|
"name": "myenv"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.12.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
103
Recursivité/TP/TP_recursivite.md
Normal file
103
Recursivité/TP/TP_recursivite.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# TP Récursivité
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
## 1. Courbe de Koch
|
||||||
|
|
||||||
|
> Prérequis : Module turtle
|
||||||
|
|
||||||
|
La courbe de Koch est une figure fractale récursive. Elle fait partie des premières fractales décrites mathématiquement (1904).
|
||||||
|
|
||||||
|
### Principe de construction
|
||||||
|
|
||||||
|
**Ordre 0** : Un simple segment de longueur `cote`.
|
||||||
|
|
||||||
|
```
|
||||||
|
─────────────────────
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ordre 1** : Le segment est divisé en 3 parties égales. La partie centrale est remplacée par deux segments formant un triangle équilatéral sans sa base (un "chapeau" ^).
|
||||||
|
|
||||||
|
```
|
||||||
|
/\
|
||||||
|
/ \
|
||||||
|
──────/ \──────
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ordre 2 et suivants** : On applique la même transformation à chaque segment de la figure précédente.
|
||||||
|
|
||||||
|
```
|
||||||
|
/\
|
||||||
|
/ \ /\
|
||||||
|
/ \ / \
|
||||||
|
─/ \──/ \─
|
||||||
|
```
|
||||||
|
|
||||||
|
### Questions
|
||||||
|
|
||||||
|
1. Écrire de manière récursive le code de la fonction `courbe_koch(n, cote)` permettant de dessiner cette courbe à l'ordre `n` avec des segments de longueur `cote`.
|
||||||
|
|
||||||
|
Nous voulons obtenir maintenant un **flocon de Koch**. Celui-ci est composé de trois courbes de Koch disposées en triangle équilatéral.
|
||||||
|
|
||||||
|
2. Écrire le code de la fonction `flocon(n, cote)` qui dessine le flocon de Koch.
|
||||||
|
|
||||||
|
> **Indice** : Le flocon est formé de 3 courbes de Koch, avec un angle de 120° entre chaque courbe.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Triangle de Pascal
|
||||||
|
|
||||||
|
Le triangle de Pascal représente les coefficients binomiaux sous la forme d'un triangle :
|
||||||
|
|
||||||
|
```
|
||||||
|
1
|
||||||
|
1 1
|
||||||
|
1 2 1
|
||||||
|
1 3 3 1
|
||||||
|
1 4 6 4 1
|
||||||
|
1 5 10 10 5 1
|
||||||
|
```
|
||||||
|
|
||||||
|
La fonction `coef(n, p)` permet de calculer le coefficient à la ligne `n` et colonne `p` :
|
||||||
|
|
||||||
|
$$
|
||||||
|
coef(n, p) = \begin{cases}
|
||||||
|
1 & \text{si } p = 0 \text{ ou } n = p\\
|
||||||
|
coef(n-1, p-1) + coef(n-1, p) & \text{sinon}
|
||||||
|
\end{cases}
|
||||||
|
$$
|
||||||
|
|
||||||
|
### Questions
|
||||||
|
|
||||||
|
1. Écrire le code de la fonction récursive `coef(n, p)`.
|
||||||
|
|
||||||
|
Le sommet du triangle est le résultat de `coef(0, 0)`, la première ligne est représentée par `coef(1, 0)` et `coef(1, 1)`, ainsi de suite pour les autres lignes.
|
||||||
|
|
||||||
|
2. À l'aide de deux boucles, écrire un code permettant d'afficher les 6 premières lignes du triangle.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Recherche dichotomique
|
||||||
|
|
||||||
|
La recherche dichotomique fonctionne sur un tableau trié et permet de trouver un élément de façon optimale en O(log n).
|
||||||
|
|
||||||
|
Voici le code de la recherche dichotomique (programme de 1ère) de manière itérative :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def recherche_dichotomique(t, v):
|
||||||
|
debut = 0
|
||||||
|
fin = len(t) - 1
|
||||||
|
while debut <= fin:
|
||||||
|
milieu = (debut + fin) // 2
|
||||||
|
if t[milieu] == v:
|
||||||
|
return True
|
||||||
|
elif t[milieu] > v:
|
||||||
|
fin = milieu - 1
|
||||||
|
else:
|
||||||
|
debut = milieu + 1
|
||||||
|
return False
|
||||||
|
```
|
||||||
|
|
||||||
|
### Question
|
||||||
|
|
||||||
|
1. Réécrire ce code en version récursive. La fonction prendra en paramètres le tableau `t`, la valeur cherchée `v`, ainsi que les indices `debut` et `fin`.
|
||||||
27
Recursivité/TP/TP_recursivite.py
Normal file
27
Recursivité/TP/TP_recursivite.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import turtle
|
||||||
|
|
||||||
|
def courbe_koch(n, l):
|
||||||
|
if n == 0 :
|
||||||
|
turtle.forward(l)
|
||||||
|
else :
|
||||||
|
courbe_koch(n-1, l/3)
|
||||||
|
turtle.left(60)
|
||||||
|
courbe_koch(n-1, l/3)
|
||||||
|
turtle.left(-120)
|
||||||
|
courbe_koch(n-1, l/3)
|
||||||
|
turtle.left(60)
|
||||||
|
courbe_koch(n-1, l/3)
|
||||||
|
|
||||||
|
def flocon(n,l,i=3):
|
||||||
|
if i != 0:
|
||||||
|
courbe_koch(n,l)
|
||||||
|
turtle.left(-120)
|
||||||
|
flocon(n,l,i-1)
|
||||||
|
|
||||||
|
turtle.setheading(0) # orientation intiale de la tête : vers la droite de l'écran
|
||||||
|
#turtle.hideturtle() # on cache la tortue
|
||||||
|
turtle.speed(0) # on accélère la tortue
|
||||||
|
turtle.color('green')
|
||||||
|
#flocon(1,300)
|
||||||
|
#courbe_koch(,400)
|
||||||
|
print(8)
|
||||||
10
Recursivité/TP/Triangle_Pascal.py
Normal file
10
Recursivité/TP/Triangle_Pascal.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
def coef(n,p):
|
||||||
|
if p==0 or n == p :
|
||||||
|
return 1
|
||||||
|
else :
|
||||||
|
return coef(n-1,p-1) + coef(n-1,p)
|
||||||
|
|
||||||
|
for i in range(19):
|
||||||
|
for j in range(i+1):
|
||||||
|
print(coef(i,j),' ',end='')
|
||||||
|
print()
|
||||||
@@ -3,34 +3,7 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": "# Récursivité et Programmation Dynamique\n\n# La reproduction des lapins\n\nNous allons utiliser 2 grandes méthodes de programmation que nous allons appliquer à un problème célèbre et récurrent dans le milieu de la programmation.\n\nEn 1202, Leonardo Fibonacci publie un ouvrage dans lequel il traite d'un problème simple et concret : la croissance d'une population de lapins. Il s'agit de savoir comment contrôler la population des clapiers et quand vendre ses lapins, etc.\n\nVoici le fonctionnement simplifié de cette reproduction :\n\n1. On compte les lapins par **couple**\n2. **Chaque mois** chaque couple de lapins matures donne naissance à 1 couple de jeunes lapins immatures\n3. Après 2 mois les jeunes couples deviennent matures et engendrent à leur tour un jeune couple immature\n4. On étudie la croissance de la population tous les mois : n est le nombre de mois\n\n| n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |\n|:------------------------------:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:--:|:--:|:--:|:--:|\n| nb couples matures | 0 | 0 | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 |\n| nb couples immatures 1er mois | 0 | 1 | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 |\n| nb couples immatures 2ème mois | 0 | 0 | 1 | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 |\n| nb couples | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 | 55 |\n\n- Complétez les mois manquants en respectant les règles de reproduction.\n\n- En vous inspirant de la méthode que vous avez employée pour compléter le tableau ci-dessus, écrivez une fonction `lapinoux(n)` qui retourne le nombre de couples de lapin obtenus au mois n.\n- Testez votre fonction pour des valeurs de n entre 0 et 10."
|
||||||
"# Récursivité et Programmation Dynamique\n",
|
|
||||||
"\n",
|
|
||||||
"# La reproduction des lapins\n",
|
|
||||||
"\n",
|
|
||||||
"Nous allons utiliser 2 grandes méthodes de programmation que nous allons appliquer à un problème célèbre et récurrent dans le milieu de la programmation.\n",
|
|
||||||
"\n",
|
|
||||||
"En 1202, Leonardo Fibonacci publie un ouvrage dans lequel il traite d'un problème simple et concret : la croissance d'une population de lapins. Il s'agit de savoir comment contrôler la population des clapiers et quand vendre ses lapins, etc.\n",
|
|
||||||
"\n",
|
|
||||||
"Voici le fonctionnement simplifié de cette reproduction :\n",
|
|
||||||
"\n",
|
|
||||||
"1. On compte les lapins par **couple**\n",
|
|
||||||
"2. **Chaque mois** chaque couple de lapins matures donne naissance à 1 couple de jeunes lapins immatures\n",
|
|
||||||
"3. Après 2 mois les jeunes couples devienent matures et engendrent à leur tour un jeune couple immature\n",
|
|
||||||
"4. On étudie la croissance de la population tous les mois : n est le nombre de mois\n",
|
|
||||||
"\n",
|
|
||||||
"| n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |\n",
|
|
||||||
"|:------------------------------:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:--:|:--:|:--:|:--:|\n",
|
|
||||||
"| nb couples matures | 0 | 0 | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 |\n",
|
|
||||||
"| nb couples immatures 1er mois | 0 | 1 | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 |\n",
|
|
||||||
"| nb couples immatures 2ème mois | 0 | 0 | 1 | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 |\n",
|
|
||||||
"| nb couples | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 | 55 |\n",
|
|
||||||
"\n",
|
|
||||||
"- Complétez les mois manquants en respectant les règles de reproduction.\n",
|
|
||||||
"\n",
|
|
||||||
"- En vous inspirant de la méthode que vous avez employée pour compléter le tableau ci-dessus, écrivez une fonction `lapinoux(n)` qui retourne le nombre de couples de lapin obtenus au mois n.\n",
|
|
||||||
"- Testez votre fonction pour des valeurs de n entre 0 et 10."
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
|
|||||||
@@ -3,34 +3,7 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": "# Récursivité et Programmation Dynamique\n\n# La reproduction des lapins\n\nNous allons utiliser 2 grandes méthodes de programmation que nous allons appliquer à un problème célèbre et récurrent dans le milieu de la programmation.\n\nEn 1202, Leonardo Fibonacci publie un ouvrage dans lequel il traite d'un problème simple et concret : la croissance d'une population de lapins. Il s'agit de savoir comment contrôler la population des clapiers et quand vendre ses lapins, etc.\n\nVoici le fonctionnement simplifié de cette reproduction :\n\n1. On compte les lapins par **couple**\n2. **Chaque mois** chaque couple de lapins matures donne naissance à 1 couple de jeunes lapins immatures\n3. Après 2 mois les jeunes couples deviennent matures et engendrent à leur tour un jeune couple immature\n4. On étudie la croissance de la population tous les mois : n est le nombre de mois\n\n| n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |\n|:------------------------------:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:--:|:--:|:--:|:--:|\n| nb couples matures | 0 | 0 | 0 | 1 | | | | | | | |\n| nb couples immatures 1er mois | 0 | 1 | 0 | 1 | | | | | | | |\n| nb couples immatures 2ème mois | 0 | 0 | 1 | 0 | | | | | | | |\n| nb couples | 0 | 1 | 1 | 2 | | | | | | | |\n\n- Complétez les mois manquants en respectant les règles de reproduction.\n\n- En vous inspirant de la méthode que vous avez employée pour compléter le tableau ci-dessus, écrivez une fonction `lapinoux(n)` qui retourne le nombre de couples de lapin obtenus au mois n.\n- Testez votre fonction pour des valeurs de n entre 0 et 10."
|
||||||
"# Récursivité et Programmation Dynamique\n",
|
|
||||||
"\n",
|
|
||||||
"# La reproduction des lapins\n",
|
|
||||||
"\n",
|
|
||||||
"Nous allons utiliser 2 grandes méthodes de programmation que nous allons appliquer à un problème célèbre et récurrent dans le milieu de la programmation.\n",
|
|
||||||
"\n",
|
|
||||||
"En 1202, Leonardo Fibonacci publie un ouvrage dans lequel il traite d'un problème simple et concret : la croissance d'une population de lapins. Il s'agit de savoir comment contrôler la population des clapiers et quand vendre ses lapins, etc.\n",
|
|
||||||
"\n",
|
|
||||||
"Voici le fonctionnement simplifié de cette reproduction :\n",
|
|
||||||
"\n",
|
|
||||||
"1. On compte les lapins par **couple**\n",
|
|
||||||
"2. **Chaque mois** chaque couple de lapins matures donne naissance à 1 couple de jeunes lapins immatures\n",
|
|
||||||
"3. Après 2 mois les jeunes couples devienent matures et engendrent à leur tour un jeune couple immature\n",
|
|
||||||
"4. On étudie la croissance de la population tous les mois : n est le nombre de mois\n",
|
|
||||||
"\n",
|
|
||||||
"| n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |\n",
|
|
||||||
"|:------------------------------:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:--:|:--:|:--:|:--:|\n",
|
|
||||||
"| nb couples matures | 0 | 0 | 0 | 1 | | | | | | | |\n",
|
|
||||||
"| nb couples immatures 1er mois | 0 | 1 | 0 | 1 | | | | | | | |\n",
|
|
||||||
"| nb couples immatures 2ème mois | 0 | 0 | 1 | 0 | | | | | | | |\n",
|
|
||||||
"| nb couples | 0 | 1 | 1 | 2 | | | | | | | |\n",
|
|
||||||
"\n",
|
|
||||||
"- Complétez les mois manquants en respectant les règles de reproduction.\n",
|
|
||||||
"\n",
|
|
||||||
"- En vous inspirant de la méthode que vous avez employée pour compléter le tableau ci-dessus, écrivez une fonction `lapinoux(n)` qui retourne le nombre de couples de lapin obtenus au mois n.\n",
|
|
||||||
"- Testez votre fonction pour des valeurs de n entre 0 et 10."
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
|
|||||||
15
Recursivité/sources/combinaisons.py
Normal file
15
Recursivité/sources/combinaisons.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
def combinaisons(n,p):
|
||||||
|
result = 1
|
||||||
|
if n>p and p>0:
|
||||||
|
result = combinaisons(n-1,p-1) + combinaisons(n-1,p)
|
||||||
|
return result
|
||||||
|
|
||||||
|
print(combinaisons(32,5))
|
||||||
|
|
||||||
|
def combinaisons2(n,p):
|
||||||
|
if n>p and p>0:
|
||||||
|
return combinaisons(n-1,p-1) + combinaisons(n-1,p)
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print(combinaisons2(32,5))
|
||||||
31
Recursivité/sources/escalier.py
Normal file
31
Recursivité/sources/escalier.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from functools import lru_cache
|
||||||
|
from functools import cache
|
||||||
|
|
||||||
|
@cache
|
||||||
|
def steps_to(stair):
|
||||||
|
if stair == 1:
|
||||||
|
# You can reach the first stair with only a single step from the floor.
|
||||||
|
return 1
|
||||||
|
|
||||||
|
elif stair == 2:
|
||||||
|
# You can reach the second stair by jumping from the floor with a single two-stair hop or by jumping a single stair a couple of times.
|
||||||
|
return 2
|
||||||
|
|
||||||
|
elif stair == 3:
|
||||||
|
# You can reach the third stair using four possible combinations:
|
||||||
|
# 1. Jumping all the way from the floor
|
||||||
|
# 2. Jumping two stairs, then one
|
||||||
|
# 3. Jumping one stair, then two
|
||||||
|
# 4. Jumping one stair three times
|
||||||
|
return 4
|
||||||
|
|
||||||
|
else:
|
||||||
|
# You can reach your current stair from three different places:
|
||||||
|
# 1. From three stairs down
|
||||||
|
# 2. From two stairs down
|
||||||
|
# 2. From one stair down
|
||||||
|
# If you add up the number of ways of getting to those
|
||||||
|
# those three positions, then you should have your solution.
|
||||||
|
return steps_to(stair - 3) + steps_to(stair - 2) + steps_to(stair - 1)
|
||||||
|
|
||||||
|
print(steps_to(35))
|
||||||
30
Recursivité/sources/fib5.dot
Normal file
30
Recursivité/sources/fib5.dot
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
digraph Fibonnacci {
|
||||||
|
#bgcolor="transparent"
|
||||||
|
"fib(6)" -> {fib4 [label="fib(4)"] fib5 [label="fib(5)"]}
|
||||||
|
|
||||||
|
subgraph cluster1 {
|
||||||
|
style="rounded,dashed";
|
||||||
|
fib4 -> {fib2 [label="fib(2)"] fib3 [label="fib(3)"]}
|
||||||
|
subgraph cluster1 {
|
||||||
|
style="rounded,dotted";
|
||||||
|
fib3 -> {fib1 [label="fib(1)"] fib22 [label="fib(2)"]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fib5 -> {fib33 [label="fib(3)"] fib44 [label="fib(4)"]};
|
||||||
|
|
||||||
|
subgraph cluster2 {
|
||||||
|
style="rounded,dotted";
|
||||||
|
fib33 -> {fib11 [label="fib(1)"] fib222 [label="fib(2)"]};
|
||||||
|
}
|
||||||
|
|
||||||
|
subgraph cluster3 {
|
||||||
|
style="rounded,dashed";
|
||||||
|
fib44 -> {fib2222 [label="fib(2)"] fib333 [label="fib(3)"]}
|
||||||
|
subgraph cluster4 {
|
||||||
|
style="rounded,dotted";
|
||||||
|
fib333 -> {fib111 [label="fib(1)"] fib22222 [label="fib(2)"]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
41
Recursivité/sources/lumiere.py
Normal file
41
Recursivité/sources/lumiere.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from random import randint
|
||||||
|
|
||||||
|
nb_interrupteurs = 10
|
||||||
|
etats_interrupteurs = [_%2==1 for _ in range(nb_interrupteurs)]
|
||||||
|
etats_interrupteurs = [False for _ in range(nb_interrupteurs)]
|
||||||
|
etats_interrupteurs = [randint(0,1)==0 for _ in range(nb_interrupteurs)]
|
||||||
|
|
||||||
|
commutations = []
|
||||||
|
|
||||||
|
print(etats_interrupteurs)
|
||||||
|
|
||||||
|
def inverse_interrupteur(n: int):
|
||||||
|
global etats_interrupteurs
|
||||||
|
global commutations
|
||||||
|
#print("On inverse l'interrupteur", n)
|
||||||
|
commutations.append(n)
|
||||||
|
etats_interrupteurs[n-1] = not etats_interrupteurs[n-1]
|
||||||
|
|
||||||
|
def islight() -> bool:
|
||||||
|
global etats_interrupteurs
|
||||||
|
result = True
|
||||||
|
for etat_interrupteur in etats_interrupteurs:
|
||||||
|
if not etat_interrupteur:
|
||||||
|
result = False
|
||||||
|
break
|
||||||
|
return result
|
||||||
|
|
||||||
|
def cherche_commutations(n):
|
||||||
|
#print("commute", n)
|
||||||
|
if n == 1 and not islight():
|
||||||
|
inverse_interrupteur(n)
|
||||||
|
elif n > 1 and not islight():
|
||||||
|
cherche_commutations(n-1)
|
||||||
|
if not islight():
|
||||||
|
inverse_interrupteur(n)
|
||||||
|
if not islight():
|
||||||
|
cherche_commutations(n-1)
|
||||||
|
|
||||||
|
cherche_commutations(nb_interrupteurs)
|
||||||
|
print(etats_interrupteurs)
|
||||||
|
print(len(commutations))
|
||||||
38
Recursivité/sources/mult_egypt.py
Normal file
38
Recursivité/sources/mult_egypt.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
def multiplication_egyptienne(n: int, m: int) -> int:
|
||||||
|
assert n>=1 and m>=1
|
||||||
|
result = m
|
||||||
|
if n > 1:
|
||||||
|
if n % 2 == 0:
|
||||||
|
result = multiplication_egyptienne(n//2, m*2)
|
||||||
|
else:
|
||||||
|
result = m + multiplication_egyptienne(n-1, m)
|
||||||
|
return result
|
||||||
|
|
||||||
|
print(multiplication_egyptienne(7,5))
|
||||||
|
|
||||||
|
def multiplication_egyptienne2(n: int, m: int) -> int:
|
||||||
|
assert n>=1 and m>=1
|
||||||
|
if n == 1:
|
||||||
|
return m
|
||||||
|
elif n % 2 == 0:
|
||||||
|
return multiplication_egyptienne(n//2, m*2)
|
||||||
|
else:
|
||||||
|
return m + multiplication_egyptienne(n-1, m)
|
||||||
|
|
||||||
|
print(multiplication_egyptienne2(7,5))
|
||||||
|
|
||||||
|
def multiplication_egyptienne_iterative(n: int, m: int) -> int:
|
||||||
|
assert n > m
|
||||||
|
assert n>=1 and m>=1
|
||||||
|
result = 0
|
||||||
|
while n > 1:
|
||||||
|
if n % 2 != 0:
|
||||||
|
result += m
|
||||||
|
n = n-1
|
||||||
|
else :
|
||||||
|
n = n//2
|
||||||
|
m = m*2
|
||||||
|
result += m
|
||||||
|
return result
|
||||||
|
|
||||||
|
print(multiplication_egyptienne_iterative(7,5))
|
||||||
1324
Recursivité/sources/nsi_tle_02_02_exos_recursivite_correction.md
Normal file
1324
Recursivité/sources/nsi_tle_02_02_exos_recursivite_correction.md
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
207
Recursivité/sources/out.svg
Normal file
207
Recursivité/sources/out.svg
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
<?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 2.44.0 (0)
|
||||||
|
-->
|
||||||
|
<!-- Title: Fibonnacci Pages: 1 -->
|
||||||
|
<svg width="683pt" height="356pt"
|
||||||
|
viewBox="0.00 0.00 683.00 356.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 352)">
|
||||||
|
<title>Fibonnacci</title>
|
||||||
|
<polygon fill="white" stroke="transparent" points="-4,4 -4,-352 679,-352 679,4 -4,4"/>
|
||||||
|
<g id="clust2" class="cluster">
|
||||||
|
<title>cluster1</title>
|
||||||
|
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M20,-80C20,-80 239,-80 239,-80 245,-80 251,-86 251,-92 251,-92 251,-272 251,-272 251,-278 245,-284 239,-284 239,-284 20,-284 20,-284 14,-284 8,-278 8,-272 8,-272 8,-92 8,-92 8,-86 14,-80 20,-80"/>
|
||||||
|
</g>
|
||||||
|
<g id="clust4" class="cluster">
|
||||||
|
<title>cluster1</title>
|
||||||
|
<path fill="none" stroke="black" stroke-dasharray="1,5" d="M98,-88C98,-88 231,-88 231,-88 237,-88 243,-94 243,-100 243,-100 243,-200 243,-200 243,-206 237,-212 231,-212 231,-212 98,-212 98,-212 92,-212 86,-206 86,-200 86,-200 86,-100 86,-100 86,-94 92,-88 98,-88"/>
|
||||||
|
</g>
|
||||||
|
<g id="clust7" class="cluster">
|
||||||
|
<title>cluster2</title>
|
||||||
|
<path fill="none" stroke="black" stroke-dasharray="1,5" d="M271,-88C271,-88 404,-88 404,-88 410,-88 416,-94 416,-100 416,-100 416,-200 416,-200 416,-206 410,-212 404,-212 404,-212 271,-212 271,-212 265,-212 259,-206 259,-200 259,-200 259,-100 259,-100 259,-94 265,-88 271,-88"/>
|
||||||
|
</g>
|
||||||
|
<g id="clust9" class="cluster">
|
||||||
|
<title>cluster3</title>
|
||||||
|
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M436,-8C436,-8 655,-8 655,-8 661,-8 667,-14 667,-20 667,-20 667,-200 667,-200 667,-206 661,-212 655,-212 655,-212 436,-212 436,-212 430,-212 424,-206 424,-200 424,-200 424,-20 424,-20 424,-14 430,-8 436,-8"/>
|
||||||
|
</g>
|
||||||
|
<g id="clust11" class="cluster">
|
||||||
|
<title>cluster4</title>
|
||||||
|
<path fill="none" stroke="black" stroke-dasharray="1,5" d="M514,-16C514,-16 647,-16 647,-16 653,-16 659,-22 659,-28 659,-28 659,-128 659,-128 659,-134 653,-140 647,-140 647,-140 514,-140 514,-140 508,-140 502,-134 502,-128 502,-128 502,-28 502,-28 502,-22 508,-16 514,-16"/>
|
||||||
|
</g>
|
||||||
|
<!-- fib(6) -->
|
||||||
|
<g id="node1" class="node">
|
||||||
|
<title>fib(6)</title>
|
||||||
|
<ellipse fill="none" stroke="black" cx="231" cy="-330" rx="30.59" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="231" y="-326.3" font-family="Times,serif" font-size="14.00">fib(6)</text>
|
||||||
|
</g>
|
||||||
|
<!-- fib4 -->
|
||||||
|
<g id="node2" class="node">
|
||||||
|
<title>fib4</title>
|
||||||
|
<ellipse fill="none" stroke="black" cx="165" cy="-258" rx="30.59" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="165" y="-254.3" font-family="Times,serif" font-size="14.00">fib(4)</text>
|
||||||
|
</g>
|
||||||
|
<!-- fib(6)->fib4 -->
|
||||||
|
<g id="edge1" class="edge">
|
||||||
|
<title>fib(6)->fib4</title>
|
||||||
|
<path fill="none" stroke="black" d="M216.68,-313.81C207.77,-304.36 196.15,-292.04 186.19,-281.48"/>
|
||||||
|
<polygon fill="black" stroke="black" points="188.7,-279.03 179.29,-274.16 183.61,-283.84 188.7,-279.03"/>
|
||||||
|
</g>
|
||||||
|
<!-- fib5 -->
|
||||||
|
<g id="node3" class="node">
|
||||||
|
<title>fib5</title>
|
||||||
|
<ellipse fill="none" stroke="black" cx="337" cy="-258" rx="30.59" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="337" y="-254.3" font-family="Times,serif" font-size="14.00">fib(5)</text>
|
||||||
|
</g>
|
||||||
|
<!-- fib(6)->fib5 -->
|
||||||
|
<g id="edge2" class="edge">
|
||||||
|
<title>fib(6)->fib5</title>
|
||||||
|
<path fill="none" stroke="black" d="M250.94,-315.83C267.33,-305.01 290.74,-289.55 309.01,-277.49"/>
|
||||||
|
<polygon fill="black" stroke="black" points="311.09,-280.3 317.51,-271.87 307.24,-274.46 311.09,-280.3"/>
|
||||||
|
</g>
|
||||||
|
<!-- fib2 -->
|
||||||
|
<g id="node4" class="node">
|
||||||
|
<title>fib2</title>
|
||||||
|
<ellipse fill="none" stroke="black" cx="47" cy="-186" rx="30.59" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="47" y="-182.3" font-family="Times,serif" font-size="14.00">fib(2)</text>
|
||||||
|
</g>
|
||||||
|
<!-- fib4->fib2 -->
|
||||||
|
<g id="edge3" class="edge">
|
||||||
|
<title>fib4->fib2</title>
|
||||||
|
<path fill="none" stroke="black" d="M142.38,-245.7C125.45,-237.01 101.88,-224.42 82,-212 79.21,-210.26 76.35,-208.38 73.52,-206.46"/>
|
||||||
|
<polygon fill="black" stroke="black" points="75.44,-203.53 65.24,-200.67 71.43,-209.27 75.44,-203.53"/>
|
||||||
|
</g>
|
||||||
|
<!-- fib3 -->
|
||||||
|
<g id="node5" class="node">
|
||||||
|
<title>fib3</title>
|
||||||
|
<ellipse fill="none" stroke="black" cx="165" cy="-186" rx="30.59" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="165" y="-182.3" font-family="Times,serif" font-size="14.00">fib(3)</text>
|
||||||
|
</g>
|
||||||
|
<!-- fib4->fib3 -->
|
||||||
|
<g id="edge4" class="edge">
|
||||||
|
<title>fib4->fib3</title>
|
||||||
|
<path fill="none" stroke="black" d="M165,-239.7C165,-231.98 165,-222.71 165,-214.11"/>
|
||||||
|
<polygon fill="black" stroke="black" points="168.5,-214.1 165,-204.1 161.5,-214.1 168.5,-214.1"/>
|
||||||
|
</g>
|
||||||
|
<!-- fib33 -->
|
||||||
|
<g id="node8" class="node">
|
||||||
|
<title>fib33</title>
|
||||||
|
<ellipse fill="none" stroke="black" cx="337" cy="-186" rx="30.59" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="337" y="-182.3" font-family="Times,serif" font-size="14.00">fib(3)</text>
|
||||||
|
</g>
|
||||||
|
<!-- fib5->fib33 -->
|
||||||
|
<g id="edge7" class="edge">
|
||||||
|
<title>fib5->fib33</title>
|
||||||
|
<path fill="none" stroke="black" d="M337,-239.7C337,-231.98 337,-222.71 337,-214.11"/>
|
||||||
|
<polygon fill="black" stroke="black" points="340.5,-214.1 337,-204.1 333.5,-214.1 340.5,-214.1"/>
|
||||||
|
</g>
|
||||||
|
<!-- fib44 -->
|
||||||
|
<g id="node9" class="node">
|
||||||
|
<title>fib44</title>
|
||||||
|
<ellipse fill="none" stroke="black" cx="463" cy="-186" rx="30.59" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="463" y="-182.3" font-family="Times,serif" font-size="14.00">fib(4)</text>
|
||||||
|
</g>
|
||||||
|
<!-- fib5->fib44 -->
|
||||||
|
<g id="edge8" class="edge">
|
||||||
|
<title>fib5->fib44</title>
|
||||||
|
<path fill="none" stroke="black" d="M358.94,-245.33C375.73,-236.35 399.42,-223.56 420,-212 424.29,-209.59 428.8,-207.01 433.23,-204.46"/>
|
||||||
|
<polygon fill="black" stroke="black" points="435.18,-207.38 442.08,-199.33 431.67,-201.32 435.18,-207.38"/>
|
||||||
|
</g>
|
||||||
|
<!-- fib1 -->
|
||||||
|
<g id="node6" class="node">
|
||||||
|
<title>fib1</title>
|
||||||
|
<ellipse fill="none" stroke="black" cx="125" cy="-114" rx="30.59" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="125" y="-110.3" font-family="Times,serif" font-size="14.00">fib(1)</text>
|
||||||
|
</g>
|
||||||
|
<!-- fib3->fib1 -->
|
||||||
|
<g id="edge5" class="edge">
|
||||||
|
<title>fib3->fib1</title>
|
||||||
|
<path fill="none" stroke="black" d="M155.72,-168.76C150.95,-160.4 145.01,-150.02 139.63,-140.61"/>
|
||||||
|
<polygon fill="black" stroke="black" points="142.51,-138.58 134.5,-131.63 136.43,-142.05 142.51,-138.58"/>
|
||||||
|
</g>
|
||||||
|
<!-- fib22 -->
|
||||||
|
<g id="node7" class="node">
|
||||||
|
<title>fib22</title>
|
||||||
|
<ellipse fill="none" stroke="black" cx="204" cy="-114" rx="30.59" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="204" y="-110.3" font-family="Times,serif" font-size="14.00">fib(2)</text>
|
||||||
|
</g>
|
||||||
|
<!-- fib3->fib22 -->
|
||||||
|
<g id="edge6" class="edge">
|
||||||
|
<title>fib3->fib22</title>
|
||||||
|
<path fill="none" stroke="black" d="M174.05,-168.76C178.7,-160.4 184.49,-150.02 189.73,-140.61"/>
|
||||||
|
<polygon fill="black" stroke="black" points="192.92,-142.07 194.73,-131.63 186.81,-138.67 192.92,-142.07"/>
|
||||||
|
</g>
|
||||||
|
<!-- fib11 -->
|
||||||
|
<g id="node10" class="node">
|
||||||
|
<title>fib11</title>
|
||||||
|
<ellipse fill="none" stroke="black" cx="298" cy="-114" rx="30.59" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="298" y="-110.3" font-family="Times,serif" font-size="14.00">fib(1)</text>
|
||||||
|
</g>
|
||||||
|
<!-- fib33->fib11 -->
|
||||||
|
<g id="edge9" class="edge">
|
||||||
|
<title>fib33->fib11</title>
|
||||||
|
<path fill="none" stroke="black" d="M327.95,-168.76C323.3,-160.4 317.51,-150.02 312.27,-140.61"/>
|
||||||
|
<polygon fill="black" stroke="black" points="315.19,-138.67 307.27,-131.63 309.08,-142.07 315.19,-138.67"/>
|
||||||
|
</g>
|
||||||
|
<!-- fib222 -->
|
||||||
|
<g id="node11" class="node">
|
||||||
|
<title>fib222</title>
|
||||||
|
<ellipse fill="none" stroke="black" cx="377" cy="-114" rx="30.59" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="377" y="-110.3" font-family="Times,serif" font-size="14.00">fib(2)</text>
|
||||||
|
</g>
|
||||||
|
<!-- fib33->fib222 -->
|
||||||
|
<g id="edge10" class="edge">
|
||||||
|
<title>fib33->fib222</title>
|
||||||
|
<path fill="none" stroke="black" d="M346.28,-168.76C351.05,-160.4 356.99,-150.02 362.37,-140.61"/>
|
||||||
|
<polygon fill="black" stroke="black" points="365.57,-142.05 367.5,-131.63 359.49,-138.58 365.57,-142.05"/>
|
||||||
|
</g>
|
||||||
|
<!-- fib2222 -->
|
||||||
|
<g id="node12" class="node">
|
||||||
|
<title>fib2222</title>
|
||||||
|
<ellipse fill="none" stroke="black" cx="463" cy="-114" rx="30.59" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="463" y="-110.3" font-family="Times,serif" font-size="14.00">fib(2)</text>
|
||||||
|
</g>
|
||||||
|
<!-- fib44->fib2222 -->
|
||||||
|
<g id="edge11" class="edge">
|
||||||
|
<title>fib44->fib2222</title>
|
||||||
|
<path fill="none" stroke="black" d="M463,-167.7C463,-159.98 463,-150.71 463,-142.11"/>
|
||||||
|
<polygon fill="black" stroke="black" points="466.5,-142.1 463,-132.1 459.5,-142.1 466.5,-142.1"/>
|
||||||
|
</g>
|
||||||
|
<!-- fib333 -->
|
||||||
|
<g id="node13" class="node">
|
||||||
|
<title>fib333</title>
|
||||||
|
<ellipse fill="none" stroke="black" cx="542" cy="-114" rx="30.59" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="542" y="-110.3" font-family="Times,serif" font-size="14.00">fib(3)</text>
|
||||||
|
</g>
|
||||||
|
<!-- fib44->fib333 -->
|
||||||
|
<g id="edge12" class="edge">
|
||||||
|
<title>fib44->fib333</title>
|
||||||
|
<path fill="none" stroke="black" d="M479.37,-170.5C490.49,-160.64 505.39,-147.44 517.87,-136.38"/>
|
||||||
|
<polygon fill="black" stroke="black" points="520.49,-138.74 525.65,-129.49 515.85,-133.5 520.49,-138.74"/>
|
||||||
|
</g>
|
||||||
|
<!-- fib111 -->
|
||||||
|
<g id="node14" class="node">
|
||||||
|
<title>fib111</title>
|
||||||
|
<ellipse fill="none" stroke="black" cx="541" cy="-42" rx="30.59" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="541" y="-38.3" font-family="Times,serif" font-size="14.00">fib(1)</text>
|
||||||
|
</g>
|
||||||
|
<!-- fib333->fib111 -->
|
||||||
|
<g id="edge13" class="edge">
|
||||||
|
<title>fib333->fib111</title>
|
||||||
|
<path fill="none" stroke="black" d="M541.75,-95.7C541.64,-87.98 541.51,-78.71 541.39,-70.11"/>
|
||||||
|
<polygon fill="black" stroke="black" points="544.89,-70.05 541.24,-60.1 537.89,-70.15 544.89,-70.05"/>
|
||||||
|
</g>
|
||||||
|
<!-- fib22222 -->
|
||||||
|
<g id="node15" class="node">
|
||||||
|
<title>fib22222</title>
|
||||||
|
<ellipse fill="none" stroke="black" cx="620" cy="-42" rx="30.59" ry="18"/>
|
||||||
|
<text text-anchor="middle" x="620" y="-38.3" font-family="Times,serif" font-size="14.00">fib(2)</text>
|
||||||
|
</g>
|
||||||
|
<!-- fib333->fib22222 -->
|
||||||
|
<g id="edge14" class="edge">
|
||||||
|
<title>fib333->fib22222</title>
|
||||||
|
<path fill="none" stroke="black" d="M558.16,-98.5C569.14,-88.64 583.85,-75.44 596.17,-64.38"/>
|
||||||
|
<polygon fill="black" stroke="black" points="598.75,-66.77 603.86,-57.49 594.08,-61.56 598.75,-66.77"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 10 KiB |
245
Routage/CORRIGE.md
Normal file
245
Routage/CORRIGE.md
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
# Corrigé des exercices — Routage
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 1 : Tables de routage
|
||||||
|
|
||||||
|
Soit le réseau suivant :
|
||||||
|
|
||||||
|
```
|
||||||
|
A ---1--- B ---2--- E
|
||||||
|
| |
|
||||||
|
3 5
|
||||||
|
| |
|
||||||
|
C ---4--- D ---1--- F
|
||||||
|
\ /
|
||||||
|
2 1
|
||||||
|
\ /
|
||||||
|
G
|
||||||
|
```
|
||||||
|
|
||||||
|
### Question 1 : Compléter la table de routage du routeur A
|
||||||
|
|
||||||
|
| Destination | Passerelle | Distance (sauts) |
|
||||||
|
|-------------|------------|------------------|
|
||||||
|
| B | B | 1 |
|
||||||
|
| C | C | 1 |
|
||||||
|
| D | C | 2 |
|
||||||
|
| E | B | 2 |
|
||||||
|
| F | C | 3 |
|
||||||
|
| G | C | 2 |
|
||||||
|
|
||||||
|
**Explication** : Depuis A, pour atteindre :
|
||||||
|
- B : directement connecté (1 saut)
|
||||||
|
- C : directement connecté (1 saut)
|
||||||
|
- D : via C (A→C→D = 2 sauts)
|
||||||
|
- E : via B (A→B→E = 2 sauts)
|
||||||
|
- F : via C puis D (A→C→D→F = 3 sauts)
|
||||||
|
- G : via C (A→C→G = 2 sauts)
|
||||||
|
|
||||||
|
### Question 2 : Compléter la table de routage du routeur D
|
||||||
|
|
||||||
|
| Destination | Passerelle | Distance (sauts) |
|
||||||
|
|-------------|------------|------------------|
|
||||||
|
| A | C | 2 |
|
||||||
|
| B | B | 1 |
|
||||||
|
| C | C | 1 |
|
||||||
|
| E | B | 2 |
|
||||||
|
| F | F | 1 |
|
||||||
|
| G | G | 1 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 2 : Protocole RIP
|
||||||
|
|
||||||
|
### Question 1 : Principe de mise à jour
|
||||||
|
|
||||||
|
Un routeur R reçoit de son voisin V la table suivante :
|
||||||
|
|
||||||
|
| Destination | Distance |
|
||||||
|
|-------------|----------|
|
||||||
|
| X | 2 |
|
||||||
|
| Y | 4 |
|
||||||
|
| Z | 1 |
|
||||||
|
|
||||||
|
Si R est à distance 1 de V, quelle sera la mise à jour de la table de R ?
|
||||||
|
|
||||||
|
**Réponse** :
|
||||||
|
|
||||||
|
R ajoute 1 (la distance vers V) à chaque entrée :
|
||||||
|
|
||||||
|
| Destination | Distance via V |
|
||||||
|
|-------------|----------------|
|
||||||
|
| X | 2 + 1 = 3 |
|
||||||
|
| Y | 4 + 1 = 5 |
|
||||||
|
| Z | 1 + 1 = 2 |
|
||||||
|
|
||||||
|
R ne mettra à jour sa table que si ces distances sont **inférieures** aux distances actuellement connues.
|
||||||
|
|
||||||
|
### Question 2 : Limite de RIP
|
||||||
|
|
||||||
|
Pourquoi RIP est-il limité à 15 sauts maximum ?
|
||||||
|
|
||||||
|
**Réponse** :
|
||||||
|
- Au-delà de 15 sauts, la destination est considérée comme inaccessible (distance = 16 = infini)
|
||||||
|
- Cette limite évite les boucles infinies de routage
|
||||||
|
- Elle limite aussi la taille des réseaux pouvant utiliser RIP
|
||||||
|
- C'est pourquoi RIP est adapté aux petits réseaux seulement
|
||||||
|
|
||||||
|
### Question 3 : Convergence
|
||||||
|
|
||||||
|
Combien de temps faut-il au minimum pour qu'une information de routage traverse 5 routeurs avec RIP ?
|
||||||
|
|
||||||
|
**Réponse** :
|
||||||
|
- RIP envoie des mises à jour toutes les 30 secondes
|
||||||
|
- Pour traverser 5 routeurs : 5 × 30 = **150 secondes** (2 min 30 s) minimum
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 3 : Algorithme de Dijkstra
|
||||||
|
|
||||||
|
### Question 1 : Exécution manuelle
|
||||||
|
|
||||||
|
Soit le graphe pondéré suivant :
|
||||||
|
|
||||||
|
```
|
||||||
|
4
|
||||||
|
A -----> B
|
||||||
|
| |
|
||||||
|
2| |1
|
||||||
|
v v
|
||||||
|
C -----> D -----> E
|
||||||
|
1 3
|
||||||
|
```
|
||||||
|
|
||||||
|
Appliquer l'algorithme de Dijkstra depuis A.
|
||||||
|
|
||||||
|
**Résolution pas à pas** :
|
||||||
|
|
||||||
|
| Étape | Sommet traité | d(A) | d(B) | d(C) | d(D) | d(E) |
|
||||||
|
|-------|---------------|------|------|------|------|------|
|
||||||
|
| Init | - | 0 | ∞ | ∞ | ∞ | ∞ |
|
||||||
|
| 1 | A | 0 | 4 | 2 | ∞ | ∞ |
|
||||||
|
| 2 | C | 0 | 4 | 2 | 3 | ∞ |
|
||||||
|
| 3 | D | 0 | 4 | 2 | 3 | 6 |
|
||||||
|
| 4 | B | 0 | 4 | 2 | 3 | 6 |
|
||||||
|
| 5 | E | 0 | 4 | 2 | 3 | 6 |
|
||||||
|
|
||||||
|
**Résultat final** :
|
||||||
|
- A → A : 0
|
||||||
|
- A → B : 4 (chemin direct)
|
||||||
|
- A → C : 2 (chemin direct)
|
||||||
|
- A → D : 3 (via C)
|
||||||
|
- A → E : 6 (via C puis D)
|
||||||
|
|
||||||
|
### Question 2 : Reconstruction du chemin
|
||||||
|
|
||||||
|
Quel est le plus court chemin de A vers E ?
|
||||||
|
|
||||||
|
**Réponse** : A → C → D → E (coût total : 6)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 4 : Protocole OSPF
|
||||||
|
|
||||||
|
### Question 1 : Calcul du coût d'une liaison
|
||||||
|
|
||||||
|
La formule OSPF est : `coût = 10^8 / débit`
|
||||||
|
|
||||||
|
Calculer le coût des liaisons suivantes :
|
||||||
|
|
||||||
|
| Type de liaison | Débit | Coût |
|
||||||
|
|-----------------|-------|------|
|
||||||
|
| Ethernet 10 Mbps | 10 × 10^6 bps | 10^8 / 10^7 = **10** |
|
||||||
|
| Fast Ethernet 100 Mbps | 100 × 10^6 bps | 10^8 / 10^8 = **1** |
|
||||||
|
| Gigabit Ethernet | 10^9 bps | 10^8 / 10^9 = **0.1 ≈ 1** |
|
||||||
|
| Liaison série 2 Mbps | 2 × 10^6 bps | 10^8 / (2×10^6) = **50** |
|
||||||
|
|
||||||
|
**Note** : En pratique, le coût minimum est souvent fixé à 1.
|
||||||
|
|
||||||
|
### Question 2 : Choix du meilleur chemin
|
||||||
|
|
||||||
|
Soit un réseau avec deux chemins possibles de A vers Z :
|
||||||
|
- Chemin 1 : A → B → Z avec liaisons à 100 Mbps et 10 Mbps
|
||||||
|
- Chemin 2 : A → C → D → Z avec trois liaisons à 100 Mbps
|
||||||
|
|
||||||
|
Quel chemin OSPF choisira-t-il ?
|
||||||
|
|
||||||
|
**Calcul** :
|
||||||
|
- Chemin 1 : coût = 1 + 10 = **11**
|
||||||
|
- Chemin 2 : coût = 1 + 1 + 1 = **3**
|
||||||
|
|
||||||
|
**Réponse** : OSPF choisira le **chemin 2** car son coût (3) est inférieur à celui du chemin 1 (11), même s'il traverse plus de routeurs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 5 : Comparaison RIP vs OSPF
|
||||||
|
|
||||||
|
### Question : Différences fondamentales
|
||||||
|
|
||||||
|
| Critère | RIP | OSPF |
|
||||||
|
|---------|-----|------|
|
||||||
|
| Type d'algorithme | Vecteur de distance (Bellman-Ford) | État de liens (Dijkstra) |
|
||||||
|
| Métrique | Nombre de sauts | Coût (basé sur le débit) |
|
||||||
|
| Vision du réseau | Locale (voisins uniquement) | Globale (carte complète) |
|
||||||
|
| Limite | 15 sauts max | Pas de limite de sauts |
|
||||||
|
| Convergence | Lente (30s entre mises à jour) | Rapide |
|
||||||
|
| Complexité | Simple | Plus complexe |
|
||||||
|
| Adapté pour | Petits réseaux | Grands réseaux |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 6 : Implémentation Python de Dijkstra
|
||||||
|
|
||||||
|
```python
|
||||||
|
def dijkstra(graphe, source):
|
||||||
|
"""
|
||||||
|
Algorithme de Dijkstra pour trouver les plus courts chemins.
|
||||||
|
|
||||||
|
:param graphe: dict {sommet: [(voisin, poids), ...]}
|
||||||
|
:param source: sommet de départ
|
||||||
|
:return: dict des distances minimales
|
||||||
|
"""
|
||||||
|
# Initialisation
|
||||||
|
distances = {sommet: float('inf') for sommet in graphe}
|
||||||
|
distances[source] = 0
|
||||||
|
non_visites = set(graphe.keys())
|
||||||
|
|
||||||
|
while non_visites:
|
||||||
|
# Sélectionner le sommet non visité avec la plus petite distance
|
||||||
|
courant = min(non_visites, key=lambda s: distances[s])
|
||||||
|
non_visites.remove(courant)
|
||||||
|
|
||||||
|
# Si la distance est infinie, les sommets restants sont inaccessibles
|
||||||
|
if distances[courant] == float('inf'):
|
||||||
|
break
|
||||||
|
|
||||||
|
# Mettre à jour les distances des voisins
|
||||||
|
for voisin, poids in graphe[courant]:
|
||||||
|
nouvelle_distance = distances[courant] + poids
|
||||||
|
if nouvelle_distance < distances[voisin]:
|
||||||
|
distances[voisin] = nouvelle_distance
|
||||||
|
|
||||||
|
return distances
|
||||||
|
|
||||||
|
|
||||||
|
# Test
|
||||||
|
graphe = {
|
||||||
|
'A': [('B', 4), ('C', 2)],
|
||||||
|
'B': [('D', 1)],
|
||||||
|
'C': [('D', 1)],
|
||||||
|
'D': [('E', 3)],
|
||||||
|
'E': []
|
||||||
|
}
|
||||||
|
|
||||||
|
print(dijkstra(graphe, 'A'))
|
||||||
|
# Résultat : {'A': 0, 'B': 4, 'C': 2, 'D': 3, 'E': 6}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
415
Routage/Corrige_TP_GPS_Navigator.py
Normal file
415
Routage/Corrige_TP_GPS_Navigator.py
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
"""
|
||||||
|
Corrigé du TP GPS Navigator — Itinéraires optimaux
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 1 : Modélisation du réseau
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class ReseauRoutier:
|
||||||
|
"""Représente un réseau routier sous forme de graphe."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialise un réseau vide."""
|
||||||
|
self.graphe = {}
|
||||||
|
|
||||||
|
def ajouter_ville(self, ville):
|
||||||
|
"""
|
||||||
|
Ajoute une ville (sommet) au réseau.
|
||||||
|
|
||||||
|
:param ville: (str) Nom de la ville
|
||||||
|
"""
|
||||||
|
if ville not in self.graphe:
|
||||||
|
self.graphe[ville] = []
|
||||||
|
|
||||||
|
def ajouter_route(self, ville1, ville2, distance):
|
||||||
|
"""
|
||||||
|
Ajoute une route bidirectionnelle entre deux villes.
|
||||||
|
|
||||||
|
:param ville1: (str) Première ville
|
||||||
|
:param ville2: (str) Deuxième ville
|
||||||
|
:param distance: (int) Distance en km
|
||||||
|
"""
|
||||||
|
# S'assurer que les villes existent
|
||||||
|
self.ajouter_ville(ville1)
|
||||||
|
self.ajouter_ville(ville2)
|
||||||
|
|
||||||
|
# Ajouter la route dans les deux sens
|
||||||
|
self.graphe[ville1].append((ville2, distance))
|
||||||
|
self.graphe[ville2].append((ville1, distance))
|
||||||
|
|
||||||
|
def get_voisins(self, ville):
|
||||||
|
"""
|
||||||
|
Retourne la liste des voisins d'une ville avec les distances.
|
||||||
|
|
||||||
|
:param ville: (str) Nom de la ville
|
||||||
|
:return: (list) Liste de tuples (voisin, distance)
|
||||||
|
"""
|
||||||
|
return self.graphe.get(ville, [])
|
||||||
|
|
||||||
|
def afficher(self):
|
||||||
|
"""Affiche le réseau."""
|
||||||
|
for ville, voisins in self.graphe.items():
|
||||||
|
print(f"{ville} -> {voisins}")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 2 : Algorithme de Dijkstra
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def selectionner_minimum(distances, non_visites):
|
||||||
|
"""
|
||||||
|
Sélectionne le sommet non visité avec la distance minimale.
|
||||||
|
|
||||||
|
:param distances: (dict) Distances actuelles
|
||||||
|
:param non_visites: (set) Ensemble des sommets non visités
|
||||||
|
:return: (str) Le sommet avec la distance minimale
|
||||||
|
"""
|
||||||
|
minimum = None
|
||||||
|
distance_min = float('inf')
|
||||||
|
|
||||||
|
for sommet in non_visites:
|
||||||
|
if distances[sommet] < distance_min:
|
||||||
|
distance_min = distances[sommet]
|
||||||
|
minimum = sommet
|
||||||
|
|
||||||
|
return minimum
|
||||||
|
|
||||||
|
|
||||||
|
def dijkstra(reseau, depart):
|
||||||
|
"""
|
||||||
|
Algorithme de Dijkstra pour trouver les plus courts chemins.
|
||||||
|
|
||||||
|
:param reseau: (ReseauRoutier) Le réseau routier
|
||||||
|
:param depart: (str) Ville de départ
|
||||||
|
:return: (dict) Distances minimales depuis le départ
|
||||||
|
"""
|
||||||
|
# Initialisation
|
||||||
|
distances = {ville: float('inf') for ville in reseau.graphe}
|
||||||
|
distances[depart] = 0
|
||||||
|
non_visites = set(reseau.graphe.keys())
|
||||||
|
|
||||||
|
while non_visites:
|
||||||
|
# Sélectionner le sommet avec la distance minimale
|
||||||
|
courant = selectionner_minimum(distances, non_visites)
|
||||||
|
|
||||||
|
if courant is None or distances[courant] == float('inf'):
|
||||||
|
break
|
||||||
|
|
||||||
|
non_visites.remove(courant)
|
||||||
|
|
||||||
|
# Mettre à jour les distances des voisins
|
||||||
|
for voisin, poids in reseau.get_voisins(courant):
|
||||||
|
nouvelle_distance = distances[courant] + poids
|
||||||
|
if nouvelle_distance < distances[voisin]:
|
||||||
|
distances[voisin] = nouvelle_distance
|
||||||
|
|
||||||
|
return distances
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 3 : Reconstruction du chemin
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def dijkstra_avec_chemin(reseau, depart):
|
||||||
|
"""
|
||||||
|
Dijkstra avec mémorisation des prédécesseurs.
|
||||||
|
|
||||||
|
:param reseau: (ReseauRoutier) Le réseau routier
|
||||||
|
:param depart: (str) Ville de départ
|
||||||
|
:return: (tuple) (distances, predecesseurs)
|
||||||
|
"""
|
||||||
|
distances = {ville: float('inf') for ville in reseau.graphe}
|
||||||
|
predecesseurs = {ville: None for ville in reseau.graphe}
|
||||||
|
distances[depart] = 0
|
||||||
|
non_visites = set(reseau.graphe.keys())
|
||||||
|
|
||||||
|
while non_visites:
|
||||||
|
courant = selectionner_minimum(distances, non_visites)
|
||||||
|
|
||||||
|
if courant is None or distances[courant] == float('inf'):
|
||||||
|
break
|
||||||
|
|
||||||
|
non_visites.remove(courant)
|
||||||
|
|
||||||
|
for voisin, poids in reseau.get_voisins(courant):
|
||||||
|
nouvelle_distance = distances[courant] + poids
|
||||||
|
if nouvelle_distance < distances[voisin]:
|
||||||
|
distances[voisin] = nouvelle_distance
|
||||||
|
predecesseurs[voisin] = courant
|
||||||
|
|
||||||
|
return distances, predecesseurs
|
||||||
|
|
||||||
|
|
||||||
|
def reconstruire_chemin(predecesseurs, depart, arrivee):
|
||||||
|
"""
|
||||||
|
Reconstruit le chemin du départ à l'arrivée.
|
||||||
|
|
||||||
|
:param predecesseurs: (dict) Dictionnaire des prédécesseurs
|
||||||
|
:param depart: (str) Ville de départ
|
||||||
|
:param arrivee: (str) Ville d'arrivée
|
||||||
|
:return: (list) Chemin sous forme de liste de villes
|
||||||
|
"""
|
||||||
|
chemin = []
|
||||||
|
courant = arrivee
|
||||||
|
|
||||||
|
while courant is not None:
|
||||||
|
chemin.append(courant)
|
||||||
|
courant = predecesseurs[courant]
|
||||||
|
|
||||||
|
chemin.reverse()
|
||||||
|
|
||||||
|
# Vérifier que le chemin commence bien par le départ
|
||||||
|
if chemin[0] != depart:
|
||||||
|
return [] # Pas de chemin trouvé
|
||||||
|
|
||||||
|
return chemin
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 4 : GPS Navigator
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class GPSNavigator:
|
||||||
|
"""Système de navigation GPS."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.reseau = ReseauRoutier()
|
||||||
|
|
||||||
|
def charger_carte(self, donnees):
|
||||||
|
"""
|
||||||
|
Charge une carte depuis des données.
|
||||||
|
|
||||||
|
:param donnees: (dict) {"villes": [...], "routes": [...]}
|
||||||
|
"""
|
||||||
|
for ville in donnees.get("villes", []):
|
||||||
|
self.reseau.ajouter_ville(ville)
|
||||||
|
|
||||||
|
for route in donnees.get("routes", []):
|
||||||
|
self.reseau.ajouter_route(route[0], route[1], route[2])
|
||||||
|
|
||||||
|
def calculer_itineraire(self, depart, arrivee):
|
||||||
|
"""
|
||||||
|
Calcule l'itinéraire optimal entre deux villes.
|
||||||
|
|
||||||
|
:param depart: (str) Ville de départ
|
||||||
|
:param arrivee: (str) Ville d'arrivée
|
||||||
|
:return: (dict) Informations sur l'itinéraire
|
||||||
|
"""
|
||||||
|
if depart not in self.reseau.graphe:
|
||||||
|
return {"erreur": f"Ville inconnue : {depart}"}
|
||||||
|
if arrivee not in self.reseau.graphe:
|
||||||
|
return {"erreur": f"Ville inconnue : {arrivee}"}
|
||||||
|
|
||||||
|
distances, predecesseurs = dijkstra_avec_chemin(self.reseau, depart)
|
||||||
|
|
||||||
|
if distances[arrivee] == float('inf'):
|
||||||
|
return {"erreur": "Aucun itinéraire trouvé"}
|
||||||
|
|
||||||
|
chemin = reconstruire_chemin(predecesseurs, depart, arrivee)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"depart": depart,
|
||||||
|
"arrivee": arrivee,
|
||||||
|
"distance": distances[arrivee],
|
||||||
|
"chemin": chemin,
|
||||||
|
"nb_etapes": len(chemin) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
def afficher_itineraire(self, itineraire):
|
||||||
|
"""Affiche l'itinéraire de manière lisible."""
|
||||||
|
if "erreur" in itineraire:
|
||||||
|
print(f"Erreur : {itineraire['erreur']}")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n{'='*50}")
|
||||||
|
print(f"ITINÉRAIRE : {itineraire['depart']} → {itineraire['arrivee']}")
|
||||||
|
print(f"{'='*50}")
|
||||||
|
print(f"Distance totale : {itineraire['distance']} km")
|
||||||
|
print(f"Nombre d'étapes : {itineraire['nb_etapes']}")
|
||||||
|
print(f"\nChemin :")
|
||||||
|
for i, ville in enumerate(itineraire['chemin']):
|
||||||
|
if i < len(itineraire['chemin']) - 1:
|
||||||
|
print(f" {i+1}. {ville} →")
|
||||||
|
else:
|
||||||
|
print(f" {i+1}. {ville} (arrivée)")
|
||||||
|
print(f"{'='*50}\n")
|
||||||
|
|
||||||
|
def toutes_les_distances(self, depart):
|
||||||
|
"""
|
||||||
|
Affiche les distances vers toutes les villes depuis un point de départ.
|
||||||
|
|
||||||
|
:param depart: (str) Ville de départ
|
||||||
|
"""
|
||||||
|
distances = dijkstra(self.reseau, depart)
|
||||||
|
print(f"\nDistances depuis {depart} :")
|
||||||
|
for ville, dist in sorted(distances.items(), key=lambda x: x[1]):
|
||||||
|
if dist == float('inf'):
|
||||||
|
print(f" {ville} : inaccessible")
|
||||||
|
else:
|
||||||
|
print(f" {ville} : {dist} km")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 5 : Améliorations (Bonus)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class GPSNavigatorAvance(GPSNavigator):
|
||||||
|
"""GPS avec fonctionnalités avancées."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.vitesses = {} # Vitesses moyennes par route
|
||||||
|
|
||||||
|
def ajouter_route_avec_vitesse(self, ville1, ville2, distance, vitesse):
|
||||||
|
"""Ajoute une route avec sa vitesse moyenne."""
|
||||||
|
self.reseau.ajouter_route(ville1, ville2, distance)
|
||||||
|
cle = tuple(sorted([ville1, ville2]))
|
||||||
|
self.vitesses[cle] = vitesse
|
||||||
|
|
||||||
|
def calculer_temps(self, ville1, ville2, distance):
|
||||||
|
"""Calcule le temps de trajet en minutes."""
|
||||||
|
cle = tuple(sorted([ville1, ville2]))
|
||||||
|
vitesse = self.vitesses.get(cle, 50) # 50 km/h par défaut
|
||||||
|
return (distance / vitesse) * 60 # En minutes
|
||||||
|
|
||||||
|
def calculer_itineraire_via(self, depart, arrivee, via):
|
||||||
|
"""
|
||||||
|
Calcule un itinéraire passant par des points intermédiaires.
|
||||||
|
|
||||||
|
:param via: (list) Liste des villes intermédiaires dans l'ordre
|
||||||
|
"""
|
||||||
|
etapes = [depart] + via + [arrivee]
|
||||||
|
chemin_complet = []
|
||||||
|
distance_totale = 0
|
||||||
|
|
||||||
|
for i in range(len(etapes) - 1):
|
||||||
|
itineraire = self.calculer_itineraire(etapes[i], etapes[i + 1])
|
||||||
|
if "erreur" in itineraire:
|
||||||
|
return itineraire
|
||||||
|
|
||||||
|
if chemin_complet:
|
||||||
|
chemin_complet.extend(itineraire['chemin'][1:])
|
||||||
|
else:
|
||||||
|
chemin_complet = itineraire['chemin']
|
||||||
|
|
||||||
|
distance_totale += itineraire['distance']
|
||||||
|
|
||||||
|
return {
|
||||||
|
"depart": depart,
|
||||||
|
"arrivee": arrivee,
|
||||||
|
"via": via,
|
||||||
|
"distance": distance_totale,
|
||||||
|
"chemin": chemin_complet,
|
||||||
|
"nb_etapes": len(chemin_complet) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 6 : Réponses aux questions
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
"""
|
||||||
|
RÉPONSES AUX QUESTIONS DE SYNTHÈSE
|
||||||
|
|
||||||
|
1. Complexité temporelle de Dijkstra :
|
||||||
|
- Avec notre implémentation : O(V²) où V est le nombre de sommets
|
||||||
|
- La recherche du minimum parcourt tous les sommets non visités
|
||||||
|
- Pour chaque sommet, on parcourt ses voisins
|
||||||
|
|
||||||
|
2. Amélioration pour les grands graphes :
|
||||||
|
- Utiliser un tas binaire (heap) pour la sélection du minimum : O((V+E)log V)
|
||||||
|
- Utiliser un tas de Fibonacci : O(E + V log V)
|
||||||
|
- Utiliser A* (A-star) qui est une variante avec heuristique
|
||||||
|
|
||||||
|
3. Pourquoi pas de poids négatifs :
|
||||||
|
- Dijkstra suppose qu'une fois un sommet visité, sa distance est définitive
|
||||||
|
- Un poids négatif pourrait permettre de trouver un chemin plus court
|
||||||
|
en passant par un sommet déjà visité
|
||||||
|
- Pour les poids négatifs, utiliser Bellman-Ford
|
||||||
|
|
||||||
|
4. Chemin passant par tous les sommets :
|
||||||
|
- C'est le problème du voyageur de commerce (TSP)
|
||||||
|
- Problème NP-complet, pas de solution polynomiale connue
|
||||||
|
- On utilise des heuristiques ou des algorithmes d'approximation
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("TEST 1 : CRÉATION DU RÉSEAU")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
gps = GPSNavigator()
|
||||||
|
|
||||||
|
# Ajout des villes de la Côte d'Azur
|
||||||
|
villes = ["Nice", "Monaco", "Menton", "Cannes", "Antibes", "Grasse", "Vence"]
|
||||||
|
for ville in villes:
|
||||||
|
gps.reseau.ajouter_ville(ville)
|
||||||
|
|
||||||
|
# Ajout des routes (distances en km)
|
||||||
|
routes = [
|
||||||
|
("Nice", "Monaco", 20),
|
||||||
|
("Monaco", "Menton", 10),
|
||||||
|
("Nice", "Cannes", 33),
|
||||||
|
("Nice", "Antibes", 23),
|
||||||
|
("Cannes", "Antibes", 11),
|
||||||
|
("Cannes", "Grasse", 17),
|
||||||
|
("Nice", "Grasse", 40),
|
||||||
|
("Nice", "Vence", 22),
|
||||||
|
("Grasse", "Vence", 15)
|
||||||
|
]
|
||||||
|
for v1, v2, dist in routes:
|
||||||
|
gps.reseau.ajouter_route(v1, v2, dist)
|
||||||
|
|
||||||
|
print("\nRéseau routier :")
|
||||||
|
gps.reseau.afficher()
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("TEST 2 : DIJKSTRA SIMPLE")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
distances = dijkstra(gps.reseau, "Nice")
|
||||||
|
print("\nDistances depuis Nice :")
|
||||||
|
for ville, dist in sorted(distances.items(), key=lambda x: x[1]):
|
||||||
|
print(f" {ville} : {dist} km")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("TEST 3 : CALCUL D'ITINÉRAIRE")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
itineraire = gps.calculer_itineraire("Menton", "Grasse")
|
||||||
|
gps.afficher_itineraire(itineraire)
|
||||||
|
|
||||||
|
itineraire2 = gps.calculer_itineraire("Monaco", "Vence")
|
||||||
|
gps.afficher_itineraire(itineraire2)
|
||||||
|
|
||||||
|
itineraire3 = gps.calculer_itineraire("Cannes", "Menton")
|
||||||
|
gps.afficher_itineraire(itineraire3)
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("TEST 4 : TOUTES LES DISTANCES")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
gps.toutes_les_distances("Cannes")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("TEST 5 : ITINÉRAIRE VIA (BONUS)")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
gps_avance = GPSNavigatorAvance()
|
||||||
|
for ville in villes:
|
||||||
|
gps_avance.reseau.ajouter_ville(ville)
|
||||||
|
for v1, v2, dist in routes:
|
||||||
|
gps_avance.reseau.ajouter_route(v1, v2, dist)
|
||||||
|
|
||||||
|
itineraire_via = gps_avance.calculer_itineraire_via("Menton", "Cannes", via=["Nice"])
|
||||||
|
print(f"\nItinéraire Menton → Nice → Cannes :")
|
||||||
|
print(f" Distance : {itineraire_via['distance']} km")
|
||||||
|
print(f" Chemin : {' → '.join(itineraire_via['chemin'])}")
|
||||||
@@ -28,7 +28,7 @@ L’acheminement efficace des données est crucial pour garantir la rapidité, l
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Mais dis moi Jamy, qu'est ce qu'un routeur ?
|
### Mais dis-moi Jamy, qu'est-ce qu'un routeur ?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ L’acheminement efficace des données est crucial pour garantir la rapidité, l
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
Un routeur est en quelque chose un hôte, un ordinateur, qui possède deux périphériques réseau, qui disposent d'une adresse IP chacun.
|
Un routeur est en quelque sorte un hôte, un ordinateur, qui possède deux périphériques réseau, qui disposent d'une adresse IP chacun.
|
||||||
|
|
||||||
Lorsque des données transitent sur le réseau, chaque routeur va en recevoir, et pourra décider de les transmettre à telle ou telle machine.
|
Lorsque des données transitent sur le réseau, chaque routeur va en recevoir, et pourra décider de les transmettre à telle ou telle machine.
|
||||||
Pour ne pas se tromper et faire le bon choix, le routeur se servira de **tables de routage.**
|
Pour ne pas se tromper et faire le bon choix, le routeur se servira de **tables de routage.**
|
||||||
@@ -91,13 +91,13 @@ Comme on peut le voir, on dispose de plusieurs informations :
|
|||||||
|
|
||||||
Depuis le routeur X, pour atteindre une destination, on va indiquer par quel routeur on passera, et la distance / le coût pour atteindre notre destination.
|
Depuis le routeur X, pour atteindre une destination, on va indiquer par quel routeur on passera, et la distance / le coût pour atteindre notre destination.
|
||||||
|
|
||||||
On parle alors de **passerelle** quand un routeur permet d'accéder à un sous-réseau différent du notre.
|
On parle alors de **passerelle** quand un routeur permet d'accéder à un sous-réseau différent du nôtre.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Les algorithmes de routage
|
### Les algorithmes de routage
|
||||||
|
|
||||||
Il paraît évident qu'un réseau aussi grand qu'internet ne peut pas reposer sur des tables de routages manuelles. On a donc décidé d'automatiser cette tâche à l'aide d'algorithmes.
|
Il paraît évident qu'un réseau aussi grand qu'Internet ne peut pas reposer sur des tables de routage manuelles. On a donc décidé d'automatiser cette tâche à l'aide d'algorithmes.
|
||||||
Ainsi, ceux-ci permettent aux routeurs de choisir le chemin optimal pour transmettre les données. On distingue deux grandes catégories :
|
Ainsi, ceux-ci permettent aux routeurs de choisir le chemin optimal pour transmettre les données. On distingue deux grandes catégories :
|
||||||
|
|
||||||
- **Les algorithmes à état de liens (Link-State)** : Chaque routeur construit une carte du réseau et calcule les meilleurs chemins avec des algorithmes comme **Dijkstra**.
|
- **Les algorithmes à état de liens (Link-State)** : Chaque routeur construit une carte du réseau et calcule les meilleurs chemins avec des algorithmes comme **Dijkstra**.
|
||||||
@@ -108,11 +108,11 @@ Ainsi, ceux-ci permettent aux routeurs de choisir le chemin optimal pour transme
|
|||||||
Ici, chaque routeur va attribuer une distance la plus courte possible à chaque destination accessible.
|
Ici, chaque routeur va attribuer une distance la plus courte possible à chaque destination accessible.
|
||||||
Cette distance sera indiquée en unité de sauts (hop), avec un saut = un routeur traversé.
|
Cette distance sera indiquée en unité de sauts (hop), avec un saut = un routeur traversé.
|
||||||
|
|
||||||
Pour commencer, chaque routeur va écrire dans sa table les plus proches voisins, c'est à dire les routeurs accessibles à 0 de distance.
|
Pour commencer, chaque routeur va écrire dans sa table les plus proches voisins, c'est-à-dire les routeurs accessibles à 1 saut de distance.
|
||||||
|
|
||||||
Puis, toutes les 30 secondes, chaque routeur va propager les informations de sa table vers ses voisins immédiats, qui mettront leur propre table à jour avec ces informations, notamment si un chemin plus court vers une destination existe !
|
Puis, toutes les 30 secondes, chaque routeur va propager les informations de sa table vers ses voisins immédiats, qui mettront leur propre table à jour avec ces informations, notamment si un chemin plus court vers une destination existe !
|
||||||
|
|
||||||
Puis que l'on dispose d'un routeur comme **direction**, et d'une **distance**, on a donc un **vecteur**.
|
Puisque l'on dispose d'un routeur comme **direction**, et d'une **distance**, on a donc un **vecteur**.
|
||||||
|
|
||||||
**Attention** !
|
**Attention** !
|
||||||
|
|
||||||
@@ -123,11 +123,11 @@ Si un routeur (et donc un réseau) n'est plus accessible passé 3 minutes, il se
|
|||||||
À retenir :
|
À retenir :
|
||||||
|
|
||||||
- Distance en terme de sauts (ou hop en anglais)
|
- Distance en terme de sauts (ou hop en anglais)
|
||||||
- Chaque routeur ne possède les informations que sur son voisinnage direct. On parle de ***routing by rumor*** : aucune vision globale du réseau, on fait confiance aux voisins pour savoir ce qu'il en est.
|
- Chaque routeur ne possède les informations que sur son voisinage direct. On parle de ***routing by rumor*** : aucune vision globale du réseau, on fait confiance aux voisins pour savoir ce qu'il en est.
|
||||||
- Au delà de 15 sauts, on considère un routeur inaccessible.
|
- Au-delà de 15 sauts, on considère un routeur inaccessible.
|
||||||
- Utilise l’algorithme à vecteur de distance de Bellman-Ford et est simple à configurer, mais limité en performance.
|
- Utilise l’algorithme à vecteur de distance de Bellman-Ford et est simple à configurer, mais limité en performance.
|
||||||
- Convient aux petits réseaux
|
- Convient aux petits réseaux
|
||||||
- Premiere algorithme de routage de l'histoire
|
- Premier algorithme de routage de l'histoire
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ $$
|
|||||||
où d est le débit nominal en bits/seconde.
|
où d est le débit nominal en bits/seconde.
|
||||||
|
|
||||||
- Chaque réseau pourra être représenté sous forme de graphe.
|
- Chaque réseau pourra être représenté sous forme de graphe.
|
||||||
- On recherchera les routes les moins couteuses, et sans cycle.
|
- On recherchera les routes les moins coûteuses, et sans cycle.
|
||||||
- Chaque routeur devient alors la racine d'un arbre contenant les meilleurs routes.
|
- Chaque routeur devient alors la racine d'un arbre contenant les meilleurs routes.
|
||||||
|
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ Enfin, il reste un algorithme :
|
|||||||
|
|
||||||
Que nous ne verrons pas cette année.
|
Que nous ne verrons pas cette année.
|
||||||
|
|
||||||
Merci jamy !
|
Merci Jamy !
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
391
Routage/TP_GPS_Navigator.md
Normal file
391
Routage/TP_GPS_Navigator.md
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
# TP : GPS Navigator — Itinéraires optimaux
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Vous êtes développeur chez une startup qui souhaite concurrencer Google Maps et Waze. Votre mission : implémenter le moteur de calcul d'itinéraires en utilisant l'algorithme de **Dijkstra** pour trouver les chemins les plus courts dans un réseau routier.
|
||||||
|
|
||||||
|
Saviez-vous que Google Maps effectue des milliards de calculs Dijkstra chaque jour ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Objectifs
|
||||||
|
|
||||||
|
- Modéliser un réseau routier sous forme de graphe
|
||||||
|
- Implémenter l'algorithme de Dijkstra
|
||||||
|
- Calculer les itinéraires optimaux (distance ou temps)
|
||||||
|
- Reconstruire le chemin complet entre deux points
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 1 : Modélisation du réseau
|
||||||
|
|
||||||
|
### 1.1. Représentation d'un graphe
|
||||||
|
|
||||||
|
Un réseau routier peut être modélisé par un **graphe pondéré** :
|
||||||
|
- Les **sommets** représentent les intersections/villes
|
||||||
|
- Les **arêtes** représentent les routes
|
||||||
|
- Les **poids** représentent les distances ou temps de trajet
|
||||||
|
|
||||||
|
Nous utiliserons un dictionnaire d'adjacence :
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Exemple : réseau simplifié de la Côte d'Azur
|
||||||
|
reseau = {
|
||||||
|
"Nice": [("Monaco", 20), ("Cannes", 33), ("Grasse", 40)],
|
||||||
|
"Monaco": [("Nice", 20), ("Menton", 10)],
|
||||||
|
"Cannes": [("Nice", 33), ("Grasse", 17), ("Antibes", 11)],
|
||||||
|
"Grasse": [("Nice", 40), ("Cannes", 17)],
|
||||||
|
"Antibes": [("Cannes", 11), ("Nice", 23)],
|
||||||
|
"Menton": [("Monaco", 10)]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2. Création du graphe
|
||||||
|
|
||||||
|
Implémenter une classe `ReseauRoutier` pour gérer le graphe :
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ReseauRoutier:
|
||||||
|
"""Représente un réseau routier sous forme de graphe."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialise un réseau vide."""
|
||||||
|
self.graphe = {}
|
||||||
|
|
||||||
|
def ajouter_ville(self, ville):
|
||||||
|
"""
|
||||||
|
Ajoute une ville (sommet) au réseau.
|
||||||
|
|
||||||
|
:param ville: (str) Nom de la ville
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def ajouter_route(self, ville1, ville2, distance):
|
||||||
|
"""
|
||||||
|
Ajoute une route bidirectionnelle entre deux villes.
|
||||||
|
|
||||||
|
:param ville1: (str) Première ville
|
||||||
|
:param ville2: (str) Deuxième ville
|
||||||
|
:param distance: (int) Distance en km
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_voisins(self, ville):
|
||||||
|
"""
|
||||||
|
Retourne la liste des voisins d'une ville avec les distances.
|
||||||
|
|
||||||
|
:param ville: (str) Nom de la ville
|
||||||
|
:return: (list) Liste de tuples (voisin, distance)
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def afficher(self):
|
||||||
|
"""Affiche le réseau."""
|
||||||
|
for ville, voisins in self.graphe.items():
|
||||||
|
print(f"{ville} -> {voisins}")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test** :
|
||||||
|
```python
|
||||||
|
reseau = ReseauRoutier()
|
||||||
|
reseau.ajouter_ville("Nice")
|
||||||
|
reseau.ajouter_ville("Monaco")
|
||||||
|
reseau.ajouter_route("Nice", "Monaco", 20)
|
||||||
|
reseau.afficher()
|
||||||
|
# Nice -> [('Monaco', 20)]
|
||||||
|
# Monaco -> [('Nice', 20)]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : Algorithme de Dijkstra
|
||||||
|
|
||||||
|
### 2.1. Principe de l'algorithme
|
||||||
|
|
||||||
|
L'algorithme de Dijkstra trouve le plus court chemin depuis une source vers tous les autres sommets :
|
||||||
|
|
||||||
|
1. Initialiser toutes les distances à l'infini, sauf la source à 0
|
||||||
|
2. Marquer tous les sommets comme non visités
|
||||||
|
3. Tant qu'il reste des sommets non visités :
|
||||||
|
- Sélectionner le sommet non visité avec la plus petite distance
|
||||||
|
- Pour chaque voisin, mettre à jour la distance si on trouve un chemin plus court
|
||||||
|
- Marquer le sommet comme visité
|
||||||
|
|
||||||
|
### 2.2. Implémentation
|
||||||
|
|
||||||
|
```python
|
||||||
|
def dijkstra(reseau, depart):
|
||||||
|
"""
|
||||||
|
Algorithme de Dijkstra pour trouver les plus courts chemins.
|
||||||
|
|
||||||
|
:param reseau: (ReseauRoutier) Le réseau routier
|
||||||
|
:param depart: (str) Ville de départ
|
||||||
|
:return: (dict) Distances minimales depuis le départ
|
||||||
|
"""
|
||||||
|
# Initialisation
|
||||||
|
distances = {ville: float('inf') for ville in reseau.graphe}
|
||||||
|
distances[depart] = 0
|
||||||
|
non_visites = set(reseau.graphe.keys())
|
||||||
|
|
||||||
|
# À compléter : boucle principale de l'algorithme
|
||||||
|
|
||||||
|
return distances
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test** :
|
||||||
|
```python
|
||||||
|
>>> dijkstra(reseau, "Nice")
|
||||||
|
{'Nice': 0, 'Monaco': 20, 'Cannes': 33, 'Grasse': 40, 'Antibes': 44, 'Menton': 30}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3. Sélection du minimum
|
||||||
|
|
||||||
|
Implémenter une fonction auxiliaire pour sélectionner le sommet non visité avec la plus petite distance :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def selectionner_minimum(distances, non_visites):
|
||||||
|
"""
|
||||||
|
Sélectionne le sommet non visité avec la distance minimale.
|
||||||
|
|
||||||
|
:param distances: (dict) Distances actuelles
|
||||||
|
:param non_visites: (set) Ensemble des sommets non visités
|
||||||
|
:return: (str) Le sommet avec la distance minimale
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : Reconstruction du chemin
|
||||||
|
|
||||||
|
### 3.1. Mémorisation des prédécesseurs
|
||||||
|
|
||||||
|
Pour reconstruire le chemin, il faut mémoriser par quel sommet on est passé. Modifier `dijkstra` pour retourner aussi les prédécesseurs :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def dijkstra_avec_chemin(reseau, depart):
|
||||||
|
"""
|
||||||
|
Dijkstra avec mémorisation des prédécesseurs.
|
||||||
|
|
||||||
|
:return: (tuple) (distances, predecesseurs)
|
||||||
|
"""
|
||||||
|
distances = {ville: float('inf') for ville in reseau.graphe}
|
||||||
|
predecesseurs = {ville: None for ville in reseau.graphe}
|
||||||
|
distances[depart] = 0
|
||||||
|
non_visites = set(reseau.graphe.keys())
|
||||||
|
|
||||||
|
while non_visites:
|
||||||
|
courant = selectionner_minimum(distances, non_visites)
|
||||||
|
non_visites.remove(courant)
|
||||||
|
|
||||||
|
if distances[courant] == float('inf'):
|
||||||
|
break
|
||||||
|
|
||||||
|
for voisin, poids in reseau.get_voisins(courant):
|
||||||
|
nouvelle_distance = distances[courant] + poids
|
||||||
|
if nouvelle_distance < distances[voisin]:
|
||||||
|
distances[voisin] = nouvelle_distance
|
||||||
|
predecesseurs[voisin] = courant # Mémoriser le prédécesseur
|
||||||
|
|
||||||
|
return distances, predecesseurs
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2. Reconstruction
|
||||||
|
|
||||||
|
Implémenter la fonction de reconstruction du chemin :
|
||||||
|
|
||||||
|
```python
|
||||||
|
def reconstruire_chemin(predecesseurs, depart, arrivee):
|
||||||
|
"""
|
||||||
|
Reconstruit le chemin du départ à l'arrivée.
|
||||||
|
|
||||||
|
:param predecesseurs: (dict) Dictionnaire des prédécesseurs
|
||||||
|
:param depart: (str) Ville de départ
|
||||||
|
:param arrivee: (str) Ville d'arrivée
|
||||||
|
:return: (list) Chemin sous forme de liste de villes
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test** :
|
||||||
|
```python
|
||||||
|
>>> distances, pred = dijkstra_avec_chemin(reseau, "Nice")
|
||||||
|
>>> reconstruire_chemin(pred, "Nice", "Menton")
|
||||||
|
['Nice', 'Monaco', 'Menton']
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : GPS Navigator
|
||||||
|
|
||||||
|
### 4.1. Classe GPS
|
||||||
|
|
||||||
|
Créer une classe complète de navigation :
|
||||||
|
|
||||||
|
```python
|
||||||
|
class GPSNavigator:
|
||||||
|
"""Système de navigation GPS."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.reseau = ReseauRoutier()
|
||||||
|
|
||||||
|
def charger_carte(self, fichier):
|
||||||
|
"""Charge une carte depuis un fichier."""
|
||||||
|
# Bonus : à implémenter
|
||||||
|
pass
|
||||||
|
|
||||||
|
def calculer_itineraire(self, depart, arrivee):
|
||||||
|
"""
|
||||||
|
Calcule l'itinéraire optimal entre deux villes.
|
||||||
|
|
||||||
|
:param depart: (str) Ville de départ
|
||||||
|
:param arrivee: (str) Ville d'arrivée
|
||||||
|
:return: (dict) Informations sur l'itinéraire
|
||||||
|
"""
|
||||||
|
distances, predecesseurs = dijkstra_avec_chemin(self.reseau, depart)
|
||||||
|
|
||||||
|
if distances[arrivee] == float('inf'):
|
||||||
|
return {"erreur": "Aucun itinéraire trouvé"}
|
||||||
|
|
||||||
|
chemin = reconstruire_chemin(predecesseurs, depart, arrivee)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"depart": depart,
|
||||||
|
"arrivee": arrivee,
|
||||||
|
"distance": distances[arrivee],
|
||||||
|
"chemin": chemin,
|
||||||
|
"nb_etapes": len(chemin) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
def afficher_itineraire(self, itineraire):
|
||||||
|
"""Affiche l'itinéraire de manière lisible."""
|
||||||
|
if "erreur" in itineraire:
|
||||||
|
print(itineraire["erreur"])
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n{'='*50}")
|
||||||
|
print(f"ITINÉRAIRE : {itineraire['depart']} → {itineraire['arrivee']}")
|
||||||
|
print(f"{'='*50}")
|
||||||
|
print(f"Distance totale : {itineraire['distance']} km")
|
||||||
|
print(f"Nombre d'étapes : {itineraire['nb_etapes']}")
|
||||||
|
print(f"\nChemin :")
|
||||||
|
for i, ville in enumerate(itineraire['chemin']):
|
||||||
|
if i < len(itineraire['chemin']) - 1:
|
||||||
|
print(f" {i+1}. {ville} →")
|
||||||
|
else:
|
||||||
|
print(f" {i+1}. {ville} (arrivée)")
|
||||||
|
print(f"{'='*50}\n")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2. Test complet
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Création du GPS
|
||||||
|
gps = GPSNavigator()
|
||||||
|
|
||||||
|
# Ajout des villes de la Côte d'Azur
|
||||||
|
villes = ["Nice", "Monaco", "Menton", "Cannes", "Antibes", "Grasse", "Vence"]
|
||||||
|
for ville in villes:
|
||||||
|
gps.reseau.ajouter_ville(ville)
|
||||||
|
|
||||||
|
# Ajout des routes (distances en km)
|
||||||
|
routes = [
|
||||||
|
("Nice", "Monaco", 20),
|
||||||
|
("Monaco", "Menton", 10),
|
||||||
|
("Nice", "Cannes", 33),
|
||||||
|
("Nice", "Antibes", 23),
|
||||||
|
("Cannes", "Antibes", 11),
|
||||||
|
("Cannes", "Grasse", 17),
|
||||||
|
("Nice", "Grasse", 40),
|
||||||
|
("Nice", "Vence", 22),
|
||||||
|
("Grasse", "Vence", 15)
|
||||||
|
]
|
||||||
|
for v1, v2, dist in routes:
|
||||||
|
gps.reseau.ajouter_route(v1, v2, dist)
|
||||||
|
|
||||||
|
# Calcul d'itinéraires
|
||||||
|
itineraire = gps.calculer_itineraire("Menton", "Grasse")
|
||||||
|
gps.afficher_itineraire(itineraire)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sortie attendue** :
|
||||||
|
```
|
||||||
|
==================================================
|
||||||
|
ITINÉRAIRE : Menton → Grasse
|
||||||
|
==================================================
|
||||||
|
Distance totale : 70 km
|
||||||
|
Nombre d'étapes : 3
|
||||||
|
|
||||||
|
Chemin :
|
||||||
|
1. Menton →
|
||||||
|
2. Monaco →
|
||||||
|
3. Nice →
|
||||||
|
4. Grasse (arrivée)
|
||||||
|
==================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 5 : Améliorations (Bonus)
|
||||||
|
|
||||||
|
### 5.1. Itinéraire par temps de trajet
|
||||||
|
|
||||||
|
Modifier le système pour calculer l'itinéraire le plus rapide au lieu du plus court :
|
||||||
|
- Stocker aussi la vitesse moyenne de chaque route
|
||||||
|
- Calculer le temps de trajet : `temps = distance / vitesse`
|
||||||
|
|
||||||
|
### 5.2. Points d'intérêt
|
||||||
|
|
||||||
|
Ajouter la possibilité de passer par des points intermédiaires :
|
||||||
|
```python
|
||||||
|
gps.calculer_itineraire_via("Nice", "Menton", via=["Monaco"])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3. Éviter certaines routes
|
||||||
|
|
||||||
|
Permettre d'éviter des routes (travaux, péages...) :
|
||||||
|
```python
|
||||||
|
gps.calculer_itineraire("Nice", "Cannes", eviter=["autoroute"])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4. Visualisation
|
||||||
|
|
||||||
|
Utiliser `matplotlib` ou `networkx` pour afficher graphiquement le réseau et l'itinéraire calculé.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 6 : Questions de synthèse
|
||||||
|
|
||||||
|
1. Quelle est la complexité temporelle de l'algorithme de Dijkstra avec notre implémentation ?
|
||||||
|
|
||||||
|
2. Comment améliorer les performances pour de très grands graphes (millions de sommets) ?
|
||||||
|
|
||||||
|
3. Pourquoi Dijkstra ne fonctionne-t-il pas avec des poids négatifs ?
|
||||||
|
|
||||||
|
4. Quel algorithme utiliseriez-vous pour trouver le chemin passant par tous les sommets exactement une fois ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Barème indicatif
|
||||||
|
|
||||||
|
| Partie | Points |
|
||||||
|
|--------|--------|
|
||||||
|
| Partie 1 : Modélisation | 3 |
|
||||||
|
| Partie 2 : Dijkstra | 6 |
|
||||||
|
| Partie 3 : Reconstruction | 4 |
|
||||||
|
| Partie 4 : GPS Navigator | 5 |
|
||||||
|
| Partie 5 : Bonus | 2 |
|
||||||
|
| **Total** | **20** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
55
Routage/animation_dijkstra.py
Normal file
55
Routage/animation_dijkstra.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import networkx as nx
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import matplotlib.animation as animation
|
||||||
|
|
||||||
|
# Création du graphe
|
||||||
|
G = nx.DiGraph()
|
||||||
|
edges = [
|
||||||
|
(0, 1, 4), (0, 2, 1),
|
||||||
|
(2, 1, 2), (1, 3, 1),
|
||||||
|
(2, 3, 5), (3, 4, 3)
|
||||||
|
]
|
||||||
|
G.add_weighted_edges_from(edges)
|
||||||
|
|
||||||
|
# Position des nœuds pour affichage
|
||||||
|
pos = nx.spring_layout(G, seed=42)
|
||||||
|
labels = {n: str(n) for n in G.nodes()}
|
||||||
|
|
||||||
|
# Initialisation de la figure
|
||||||
|
fig, ax = plt.subplots(figsize=(6, 6))
|
||||||
|
|
||||||
|
# Algorithme de Dijkstra avec animation
|
||||||
|
def dijkstra_animation(start_node=0):
|
||||||
|
distances = {node: float('inf') for node in G.nodes()}
|
||||||
|
distances[start_node] = 0
|
||||||
|
visited = set()
|
||||||
|
steps = []
|
||||||
|
|
||||||
|
while len(visited) < len(G.nodes()):
|
||||||
|
min_node = min((node for node in distances if node not in visited), key=lambda x: distances[x])
|
||||||
|
visited.add(min_node)
|
||||||
|
|
||||||
|
for neighbor in G.neighbors(min_node):
|
||||||
|
new_distance = distances[min_node] + G[min_node][neighbor]["weight"]
|
||||||
|
if new_distance < distances[neighbor]:
|
||||||
|
distances[neighbor] = new_distance
|
||||||
|
|
||||||
|
steps.append((visited.copy(), [(min_node, v) for v in G.neighbors(min_node)]))
|
||||||
|
|
||||||
|
return steps
|
||||||
|
|
||||||
|
steps = dijkstra_animation()
|
||||||
|
|
||||||
|
def update(frame):
|
||||||
|
ax.clear()
|
||||||
|
visited, edges = steps[frame]
|
||||||
|
node_colors = ["green" if n in visited else "lightblue" for n in G.nodes()]
|
||||||
|
edge_colors = ["red" if (u, v) in edges else "black" for u, v in G.edges()]
|
||||||
|
|
||||||
|
nx.draw(G, pos, with_labels=True, labels=labels, node_size=800,
|
||||||
|
node_color=node_colors, edge_color=edge_colors, font_size=14, font_weight="bold", ax=ax)
|
||||||
|
edge_labels = {(u, v): G[u][v]["weight"] for u, v in G.edges()}
|
||||||
|
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=12, ax=ax)
|
||||||
|
|
||||||
|
ani = animation.FuncAnimation(fig, update, frames=len(steps), repeat=False, interval=1000)
|
||||||
|
plt.show()
|
||||||
@@ -24,13 +24,13 @@ def dijkstra(g, s):
|
|||||||
res[IND[s]] = 0
|
res[IND[s]] = 0
|
||||||
a_faire = [e for e in g[0]]
|
a_faire = [e for e in g[0]]
|
||||||
while a_faire != []:
|
while a_faire != []:
|
||||||
ind_courrant = select_min(res, a_faire)
|
ind_courant = select_min(res, a_faire)
|
||||||
sommet_courrant = g[0][ind_courrant]
|
sommet_courant = g[0][ind_courant]
|
||||||
a_faire.remove(sommet_courrant)
|
a_faire.remove(sommet_courant)
|
||||||
cout_courrant = res[ind_courrant]
|
cout_courant = res[ind_courant]
|
||||||
for l in g[1]:
|
for l in g[1]:
|
||||||
if l[0]==sommet_courrant:
|
if l[0]==sommet_courant:
|
||||||
cout_tmp=l[2]+cout_courrant
|
cout_tmp=l[2]+cout_courant
|
||||||
if res[IND[l[1]]]>=cout_tmp:
|
if res[IND[l[1]]]>=cout_tmp:
|
||||||
res[IND[l[1]]] = cout_tmp
|
res[IND[l[1]]] = cout_tmp
|
||||||
return res
|
return res
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ Si vous n'êtes pas à l'aise avec le chiffre magique, une autre solution existe
|
|||||||
- Changez tous les `1` en `0` et tous les `0` en `1` pour obtenir le masque de sous-réseau inversé.
|
- Changez tous les `1` en `0` et tous les `0` en `1` pour obtenir le masque de sous-réseau inversé.
|
||||||
3. **Effectuer une opération OU (OR) bit à bit** :
|
3. **Effectuer une opération OU (OR) bit à bit** :
|
||||||
- Ajoutez chaque bit de l'adresse IP au bit correspondant du masque inversé.
|
- Ajoutez chaque bit de l'adresse IP au bit correspondant du masque inversé.
|
||||||
- Cela revient à conserver les bits de l'adresse IP là où le masque inversé a des `0`, et à mettre des `1`ailleurs.
|
- Cela revient à conserver les bits de l'adresse IP là où le masque inversé a des `0`, et à mettre des `1` ailleurs.
|
||||||
4. **Convertir le résultat en décimal** :
|
4. **Convertir le résultat en décimal** :
|
||||||
- Le résultat de l'opération OU donne l'adresse de broadcast en binaire.
|
- Le résultat de l'opération OU donne l'adresse de broadcast en binaire.
|
||||||
- Convertir cette adresse binaire en décimal pour obtenir l'adresse de broadcast.
|
- Convertir cette adresse binaire en décimal pour obtenir l'adresse de broadcast.
|
||||||
@@ -131,3 +131,10 @@ Déterminez l’adresse réseau et l’adresse de broadcast des adresses suivant
|
|||||||
2. `172.16.100.200/22`
|
2. `172.16.100.200/22`
|
||||||
3. `192.168.0.250/27`
|
3. `192.168.0.250/27`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
|
|||||||
245
Réseau/CORRIGE.md
Normal file
245
Réseau/CORRIGE.md
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
# Corrigé des exercices — Réseau
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercices du cours principal (README.md)
|
||||||
|
|
||||||
|
### Exercice d'application
|
||||||
|
|
||||||
|
**1. Trouvez votre adresse IP locale**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Windows
|
||||||
|
ipconfig
|
||||||
|
|
||||||
|
# Linux/Mac
|
||||||
|
ifconfig
|
||||||
|
# ou
|
||||||
|
ip a
|
||||||
|
```
|
||||||
|
|
||||||
|
L'adresse IP locale apparaît généralement sous la forme `192.168.x.x` ou `10.x.x.x` dans la section de l'interface réseau active (Ethernet ou Wi-Fi).
|
||||||
|
|
||||||
|
**2. Testez une requête DNS**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nslookup www.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Résultat typique :
|
||||||
|
```
|
||||||
|
Serveur : dns.google
|
||||||
|
Address: 8.8.8.8
|
||||||
|
|
||||||
|
Réponse ne faisant pas autorité :
|
||||||
|
Nom : www.example.com
|
||||||
|
Address: 93.184.216.34
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Comparez TCP et UDP**
|
||||||
|
|
||||||
|
| Usage | Protocole | Justification |
|
||||||
|
|-------|-----------|---------------|
|
||||||
|
| Envoyer un e-mail | **TCP** | Fiabilité nécessaire : tous les caractères doivent arriver dans l'ordre |
|
||||||
|
| Jouer en ligne | **UDP** | Rapidité prioritaire : une perte de paquet est acceptable (on perd une frame) |
|
||||||
|
| Regarder une vidéo en streaming | **UDP** | Rapidité prioritaire : un pixel perdu n'est pas critique |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercices de calcul d'adresses IP (CALCUL.md)
|
||||||
|
|
||||||
|
### Exercice 1 : Application directe
|
||||||
|
|
||||||
|
#### 1. `10.0.5.150/20`
|
||||||
|
|
||||||
|
**Méthode magique :**
|
||||||
|
- Masque /20 = `255.255.240.0`
|
||||||
|
- Octet variable : 240 (3e octet)
|
||||||
|
- Incrément magique : 256 - 240 = **16**
|
||||||
|
- Valeur du 3e octet de l'IP : 5
|
||||||
|
- Multiple de 16 inférieur ou égal à 5 : **0**
|
||||||
|
|
||||||
|
**Résultats :**
|
||||||
|
- Adresse réseau : `10.0.0.0`
|
||||||
|
- Adresse de broadcast : `10.0.15.255` (0 + 16 - 1 = 15 pour le 3e octet, 255 pour le 4e)
|
||||||
|
|
||||||
|
**Vérification en binaire :**
|
||||||
|
- IP : `00001010.00000000.00000101.10010110`
|
||||||
|
- Masque : `11111111.11111111.11110000.00000000`
|
||||||
|
- AND : `00001010.00000000.00000000.00000000` = `10.0.0.0` ✓
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2. `172.16.100.200/22`
|
||||||
|
|
||||||
|
**Méthode magique :**
|
||||||
|
- Masque /22 = `255.255.252.0`
|
||||||
|
- Octet variable : 252 (3e octet)
|
||||||
|
- Incrément magique : 256 - 252 = **4**
|
||||||
|
- Valeur du 3e octet de l'IP : 100
|
||||||
|
- Multiple de 4 inférieur ou égal à 100 : **100** (100 = 25 × 4)
|
||||||
|
|
||||||
|
**Résultats :**
|
||||||
|
- Adresse réseau : `172.16.100.0`
|
||||||
|
- Adresse de broadcast : `172.16.103.255` (100 + 4 - 1 = 103 pour le 3e octet)
|
||||||
|
|
||||||
|
**Vérification en binaire :**
|
||||||
|
- IP : `10101100.00010000.01100100.11001000`
|
||||||
|
- Masque : `11111111.11111111.11111100.00000000`
|
||||||
|
- AND : `10101100.00010000.01100100.00000000` = `172.16.100.0` ✓
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 3. `192.168.0.250/27`
|
||||||
|
|
||||||
|
**Méthode magique :**
|
||||||
|
- Masque /27 = `255.255.255.224`
|
||||||
|
- Octet variable : 224 (4e octet)
|
||||||
|
- Incrément magique : 256 - 224 = **32**
|
||||||
|
- Valeur du 4e octet de l'IP : 250
|
||||||
|
- Multiples de 32 : 0, 32, 64, 96, 128, 160, 192, **224**, 256
|
||||||
|
- Multiple de 32 inférieur ou égal à 250 : **224**
|
||||||
|
|
||||||
|
**Résultats :**
|
||||||
|
- Adresse réseau : `192.168.0.224`
|
||||||
|
- Adresse de broadcast : `192.168.0.255` (224 + 32 - 1 = 255)
|
||||||
|
|
||||||
|
**Vérification en binaire :**
|
||||||
|
- IP : `11000000.10101000.00000000.11111010`
|
||||||
|
- Masque : `11111111.11111111.11111111.11100000`
|
||||||
|
- AND : `11000000.10101000.00000000.11100000` = `192.168.0.224` ✓
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercices TCP (tcp/README.md)
|
||||||
|
|
||||||
|
### Questions de réflexion
|
||||||
|
|
||||||
|
**1. Qu'est-ce que le routage des paquets engendre comme contrainte ?**
|
||||||
|
|
||||||
|
Le routage des paquets engendre plusieurs contraintes :
|
||||||
|
- **Latence** : Les paquets peuvent emprunter des chemins différents, certains plus longs que d'autres
|
||||||
|
- **Ordre d'arrivée** : Les paquets peuvent arriver dans le désordre (nécessite une numérotation)
|
||||||
|
- **Fiabilité** : Certains paquets peuvent se perdre en route (nécessite un accusé de réception)
|
||||||
|
- **Overhead** : Chaque paquet doit contenir des informations de routage (en-têtes)
|
||||||
|
|
||||||
|
**2. Pourquoi UDP est-il plus rapide que TCP ?**
|
||||||
|
|
||||||
|
UDP est plus rapide car :
|
||||||
|
- **Pas de connexion préalable** : TCP nécessite un "handshake" en 3 étapes (SYN, SYN-ACK, ACK)
|
||||||
|
- **Pas de numérotation** : Pas besoin d'ordonner les paquets
|
||||||
|
- **Pas d'accusé de réception** : Pas d'attente de confirmation
|
||||||
|
- **Pas de retransmission** : Si un paquet est perdu, on continue sans lui
|
||||||
|
- **En-têtes plus légers** : Moins de données de contrôle
|
||||||
|
|
||||||
|
**3. Services TCP vs UDP**
|
||||||
|
|
||||||
|
| Service | Protocole | Raison |
|
||||||
|
|---------|-----------|--------|
|
||||||
|
| Fichiers sur cloud | **TCP** | Intégrité des données critique |
|
||||||
|
| Streaming vidéo | **UDP** | Fluidité prioritaire |
|
||||||
|
|
||||||
|
**4. Que faire si un paquet se perd ?**
|
||||||
|
- **TCP** : Le protocole détecte la perte (pas d'ACK reçu) et retransmet automatiquement
|
||||||
|
- **UDP** : Le paquet est perdu définitivement, l'application doit gérer (ou ignorer)
|
||||||
|
|
||||||
|
**5. Que faire si un paquet arrive en double ?**
|
||||||
|
- **TCP** : Grâce à la numérotation, le doublon est détecté et ignoré
|
||||||
|
- **UDP** : L'application reçoit les deux copies
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercices DNS (dns/README.md)
|
||||||
|
|
||||||
|
### Questions sur les URL
|
||||||
|
|
||||||
|
**1. De combien de parties est composée une URL ?**
|
||||||
|
|
||||||
|
Une URL est composée de **5 parties** :
|
||||||
|
1. Protocole
|
||||||
|
2. Sous-domaine
|
||||||
|
3. Domaine principal
|
||||||
|
4. Domaine de deuxième niveau (TLD)
|
||||||
|
5. Répertoire (chemin)
|
||||||
|
|
||||||
|
**2. Définitions personnelles**
|
||||||
|
|
||||||
|
| Partie | Définition |
|
||||||
|
|--------|------------|
|
||||||
|
| Protocole | Règle de communication utilisée (HTTP, HTTPS, FTP...) |
|
||||||
|
| Sous-domaine | Subdivision du domaine principal (www, fr, blog...) |
|
||||||
|
| Domaine principal | Nom identifiant le site (google, wikipedia...) |
|
||||||
|
| TLD | Extension indiquant le type ou pays (.fr, .com, .org...) |
|
||||||
|
| Répertoire | Chemin vers la ressource demandée sur le serveur |
|
||||||
|
|
||||||
|
**3. Si une partie de l'URL est incorrecte ?**
|
||||||
|
|
||||||
|
- **Protocole incorrect** : Erreur de connexion ou redirection
|
||||||
|
- **Sous-domaine incorrect** : Erreur 404 ou DNS non résolu
|
||||||
|
- **Domaine incorrect** : DNS non résolu (NXDOMAIN)
|
||||||
|
- **TLD incorrect** : Site différent ou inexistant
|
||||||
|
- **Répertoire incorrect** : Erreur 404 (page non trouvée)
|
||||||
|
|
||||||
|
### Analyse de l'URL Wikipedia
|
||||||
|
|
||||||
|
URL : `https://fr.wikipedia.org/wiki/Ada_Lovelace`
|
||||||
|
|
||||||
|
| Partie | Type | Valeur |
|
||||||
|
|--------|------|--------|
|
||||||
|
| https | Protocole | Communication sécurisée |
|
||||||
|
| fr | Sous-domaine | Version française |
|
||||||
|
| wikipedia | Domaine Principal | Nom du site |
|
||||||
|
| org | Domaine de deuxième niveau | Organisation à but non lucratif |
|
||||||
|
| wiki/Ada_Lovelace | Répertoire | Article sur Ada Lovelace |
|
||||||
|
|
||||||
|
### Analyse de l'URL du lycée
|
||||||
|
|
||||||
|
URL : `https://www.lyc-thierry-maulnier.ac-nice.fr`
|
||||||
|
|
||||||
|
**Informations visibles :**
|
||||||
|
- Site sécurisé (HTTPS)
|
||||||
|
- Site web classique (www)
|
||||||
|
- Lycée Thierry Maulnier
|
||||||
|
- Académie de Nice
|
||||||
|
- Domaine français (.fr)
|
||||||
|
|
||||||
|
**Décomposition :**
|
||||||
|
|
||||||
|
| Partie | Valeur |
|
||||||
|
|--------|--------|
|
||||||
|
| Protocole | https |
|
||||||
|
| Sous-domaine | www |
|
||||||
|
| Domaine | lyc-thierry-maulnier |
|
||||||
|
| Sous-domaine académique | ac-nice |
|
||||||
|
| TLD | fr |
|
||||||
|
|
||||||
|
### Manipulation nslookup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nslookup www.google.fr
|
||||||
|
```
|
||||||
|
|
||||||
|
**Résultat typique :**
|
||||||
|
```
|
||||||
|
Nom : www.google.fr
|
||||||
|
Addresses: 142.250.179.99
|
||||||
|
2a00:1450:4007:818::2003
|
||||||
|
```
|
||||||
|
|
||||||
|
**Principe de fonctionnement :**
|
||||||
|
- `nslookup` interroge un serveur DNS
|
||||||
|
- Le serveur DNS traduit le nom de domaine en adresse IP
|
||||||
|
- L'adresse IPv4 et/ou IPv6 est retournée
|
||||||
|
|
||||||
|
**Constat en utilisant l'IP directement :**
|
||||||
|
- Le site fonctionne avec l'adresse IP
|
||||||
|
- Cela prouve que le DNS n'est qu'un "annuaire" de traduction
|
||||||
|
- Les serveurs web peuvent gérer plusieurs domaines sur une même IP (virtual hosts)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
424
Réseau/Corrige_TP_MiniChat.py
Normal file
424
Réseau/Corrige_TP_MiniChat.py
Normal file
@@ -0,0 +1,424 @@
|
|||||||
|
"""
|
||||||
|
Corrigé du TP Mini-Chat en Python
|
||||||
|
"""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 1 : Découverte des sockets
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def afficher_info_reseau():
|
||||||
|
"""Affiche les informations réseau de la machine."""
|
||||||
|
nom_machine = socket.gethostname()
|
||||||
|
adresse_ip = socket.gethostbyname(nom_machine)
|
||||||
|
|
||||||
|
print(f"Nom de la machine : {nom_machine}")
|
||||||
|
print(f"Adresse IP locale : {adresse_ip}")
|
||||||
|
|
||||||
|
|
||||||
|
def resoudre_dns(domaine):
|
||||||
|
"""
|
||||||
|
Résout un nom de domaine en adresse IP.
|
||||||
|
|
||||||
|
:param domaine: (str) Le nom de domaine à résoudre
|
||||||
|
:return: (str) L'adresse IP correspondante
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
adresse_ip = socket.gethostbyname(domaine)
|
||||||
|
return adresse_ip
|
||||||
|
except socket.gaierror:
|
||||||
|
return f"Erreur : impossible de résoudre {domaine}"
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 2 : Serveur TCP simple
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def creer_serveur_tcp(port):
|
||||||
|
"""
|
||||||
|
Crée un serveur TCP qui attend une connexion et affiche le message reçu.
|
||||||
|
|
||||||
|
:param port: (int) Le port d'écoute
|
||||||
|
"""
|
||||||
|
# Créer le socket TCP
|
||||||
|
serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
|
# Permettre la réutilisation de l'adresse
|
||||||
|
serveur.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
|
||||||
|
# Lier à toutes les interfaces sur le port spécifié
|
||||||
|
serveur.bind(('', port))
|
||||||
|
|
||||||
|
# Écouter (max 1 connexion en attente)
|
||||||
|
serveur.listen(1)
|
||||||
|
|
||||||
|
print(f"Serveur en écoute sur le port {port}...")
|
||||||
|
|
||||||
|
# Accepter une connexion
|
||||||
|
connexion, adresse = serveur.accept()
|
||||||
|
print(f"Connexion acceptée de {adresse[0]}:{adresse[1]}")
|
||||||
|
|
||||||
|
# Recevoir le message
|
||||||
|
donnees = connexion.recv(1024)
|
||||||
|
message = donnees.decode('utf-8')
|
||||||
|
print(f"Message reçu : {message}")
|
||||||
|
|
||||||
|
# Fermer les connexions
|
||||||
|
connexion.close()
|
||||||
|
serveur.close()
|
||||||
|
|
||||||
|
|
||||||
|
def creer_client_tcp(adresse_serveur, port, message):
|
||||||
|
"""
|
||||||
|
Crée un client TCP qui envoie un message au serveur.
|
||||||
|
|
||||||
|
:param adresse_serveur: (str) L'adresse IP du serveur
|
||||||
|
:param port: (int) Le port du serveur
|
||||||
|
:param message: (str) Le message à envoyer
|
||||||
|
"""
|
||||||
|
# Créer le socket TCP
|
||||||
|
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
|
# Se connecter au serveur
|
||||||
|
client.connect((adresse_serveur, port))
|
||||||
|
print(f"Connecté à {adresse_serveur}:{port}")
|
||||||
|
|
||||||
|
# Envoyer le message
|
||||||
|
client.send(message.encode('utf-8'))
|
||||||
|
print(f"Message envoyé : {message}")
|
||||||
|
|
||||||
|
# Fermer la connexion
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 3 : Chat bidirectionnel
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def serveur_chat(port):
|
||||||
|
"""
|
||||||
|
Serveur de chat interactif.
|
||||||
|
"""
|
||||||
|
serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
serveur.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
serveur.bind(('', port))
|
||||||
|
serveur.listen(1)
|
||||||
|
|
||||||
|
print(f"Serveur de chat en attente sur le port {port}...")
|
||||||
|
|
||||||
|
connexion, adresse = serveur.accept()
|
||||||
|
print(f"Connexion de {adresse[0]}:{adresse[1]}")
|
||||||
|
print("Chat démarré ! (le client envoie 'quit' pour terminer)\n")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# Recevoir un message du client
|
||||||
|
donnees = connexion.recv(1024)
|
||||||
|
if not donnees:
|
||||||
|
break
|
||||||
|
|
||||||
|
message_client = donnees.decode('utf-8')
|
||||||
|
print(f"[Client] {message_client}")
|
||||||
|
|
||||||
|
# Vérifier si le client veut quitter
|
||||||
|
if message_client.lower() == "quit":
|
||||||
|
print("Le client a quitté.")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Envoyer une réponse
|
||||||
|
reponse = input("[Serveur] Votre réponse : ")
|
||||||
|
connexion.send(reponse.encode('utf-8'))
|
||||||
|
|
||||||
|
connexion.close()
|
||||||
|
serveur.close()
|
||||||
|
|
||||||
|
|
||||||
|
def client_chat(adresse_serveur, port):
|
||||||
|
"""
|
||||||
|
Client de chat interactif.
|
||||||
|
"""
|
||||||
|
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
client.connect((adresse_serveur, port))
|
||||||
|
|
||||||
|
print(f"Connecté au serveur {adresse_serveur}:{port}")
|
||||||
|
print("Tapez 'quit' pour quitter.\n")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# Envoyer un message
|
||||||
|
message = input("[Vous] ")
|
||||||
|
client.send(message.encode('utf-8'))
|
||||||
|
|
||||||
|
# Quitter si demandé
|
||||||
|
if message.lower() == "quit":
|
||||||
|
print("Déconnexion...")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Recevoir la réponse du serveur
|
||||||
|
reponse = client.recv(1024).decode('utf-8')
|
||||||
|
print(f"[Serveur] {reponse}")
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 4 : Comparaison TCP vs UDP
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def serveur_udp(port):
|
||||||
|
"""
|
||||||
|
Serveur UDP simple.
|
||||||
|
"""
|
||||||
|
serveur = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # DGRAM = UDP
|
||||||
|
serveur.bind(('', port))
|
||||||
|
|
||||||
|
print(f"Serveur UDP en écoute sur le port {port}...")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# recvfrom retourne les données ET l'adresse de l'expéditeur
|
||||||
|
donnees, adresse = serveur.recvfrom(1024)
|
||||||
|
message = donnees.decode('utf-8')
|
||||||
|
print(f"[{adresse[0]}:{adresse[1]}] {message}")
|
||||||
|
|
||||||
|
if message.lower() == "quit":
|
||||||
|
break
|
||||||
|
|
||||||
|
serveur.close()
|
||||||
|
print("Serveur UDP arrêté.")
|
||||||
|
|
||||||
|
|
||||||
|
def client_udp(adresse_serveur, port):
|
||||||
|
"""
|
||||||
|
Client UDP simple.
|
||||||
|
"""
|
||||||
|
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
|
||||||
|
print(f"Client UDP prêt à envoyer vers {adresse_serveur}:{port}")
|
||||||
|
print("Tapez 'quit' pour quitter.\n")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
message = input("Message : ")
|
||||||
|
# sendto envoie directement sans connexion préalable
|
||||||
|
client.sendto(message.encode('utf-8'), (adresse_serveur, port))
|
||||||
|
|
||||||
|
if message.lower() == "quit":
|
||||||
|
break
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
print("Client UDP arrêté.")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 5 : Mini-serveur multi-clients (Bonus)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
clients = []
|
||||||
|
clients_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def gerer_client(connexion, adresse):
|
||||||
|
"""Gère la communication avec un client."""
|
||||||
|
print(f"[+] Nouveau client : {adresse}")
|
||||||
|
|
||||||
|
with clients_lock:
|
||||||
|
clients.append(connexion)
|
||||||
|
|
||||||
|
# Envoyer un message de bienvenue
|
||||||
|
connexion.send("Bienvenue sur le chat ! Tapez 'quit' pour quitter.\n".encode('utf-8'))
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
message = connexion.recv(1024).decode('utf-8')
|
||||||
|
if not message or message.lower() == "quit":
|
||||||
|
break
|
||||||
|
|
||||||
|
print(f"[{adresse[0]}:{adresse[1]}] {message}")
|
||||||
|
|
||||||
|
# Diffuser le message à tous les autres clients
|
||||||
|
with clients_lock:
|
||||||
|
for client in clients:
|
||||||
|
if client != connexion:
|
||||||
|
try:
|
||||||
|
client.send(f"[{adresse[0]}:{adresse[1]}] {message}".encode('utf-8'))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
|
||||||
|
with clients_lock:
|
||||||
|
if connexion in clients:
|
||||||
|
clients.remove(connexion)
|
||||||
|
|
||||||
|
connexion.close()
|
||||||
|
print(f"[-] Client déconnecté : {adresse}")
|
||||||
|
|
||||||
|
|
||||||
|
def serveur_multi_clients(port):
|
||||||
|
"""Serveur acceptant plusieurs clients."""
|
||||||
|
serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
serveur.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
serveur.bind(('', port))
|
||||||
|
serveur.listen(5)
|
||||||
|
|
||||||
|
print(f"Serveur multi-clients sur le port {port}")
|
||||||
|
print("En attente de connexions... (Ctrl+C pour arrêter)\n")
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
connexion, adresse = serveur.accept()
|
||||||
|
thread = threading.Thread(target=gerer_client, args=(connexion, adresse))
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nArrêt du serveur...")
|
||||||
|
finally:
|
||||||
|
serveur.close()
|
||||||
|
|
||||||
|
|
||||||
|
def client_multi(adresse_serveur, port, pseudo):
|
||||||
|
"""Client pour le serveur multi-clients avec réception asynchrone."""
|
||||||
|
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
client.connect((adresse_serveur, port))
|
||||||
|
|
||||||
|
print(f"Connecté au serveur {adresse_serveur}:{port} en tant que {pseudo}")
|
||||||
|
|
||||||
|
def recevoir():
|
||||||
|
"""Thread de réception des messages."""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
message = client.recv(1024).decode('utf-8')
|
||||||
|
if message:
|
||||||
|
print(f"\r{message}\n[{pseudo}] ", end='')
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Lancer le thread de réception
|
||||||
|
thread_reception = threading.Thread(target=recevoir)
|
||||||
|
thread_reception.daemon = True
|
||||||
|
thread_reception.start()
|
||||||
|
|
||||||
|
# Boucle d'envoi
|
||||||
|
while True:
|
||||||
|
message = input(f"[{pseudo}] ")
|
||||||
|
if message.lower() == "quit":
|
||||||
|
client.send("quit".encode('utf-8'))
|
||||||
|
break
|
||||||
|
client.send(f"{pseudo}: {message}".encode('utf-8'))
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 6 : Réponses aux questions de synthèse
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
"""
|
||||||
|
RÉPONSES AUX QUESTIONS DE SYNTHÈSE
|
||||||
|
|
||||||
|
1. Différence entre SOCK_STREAM (TCP) et SOCK_DGRAM (UDP) :
|
||||||
|
- SOCK_STREAM (TCP) : Communication orientée connexion, fiable, ordonnée.
|
||||||
|
Les données arrivent dans l'ordre, sans perte ni duplication.
|
||||||
|
- SOCK_DGRAM (UDP) : Communication sans connexion, non fiable.
|
||||||
|
Les datagrammes peuvent arriver dans le désordre, être perdus ou dupliqués.
|
||||||
|
|
||||||
|
2. Pourquoi bind() pour le serveur et connect() pour le client ?
|
||||||
|
- Le serveur utilise bind() pour s'attacher à une adresse/port spécifique
|
||||||
|
et attendre les connexions entrantes (il écoute).
|
||||||
|
- Le client utilise connect() pour initier activement une connexion
|
||||||
|
vers un serveur connu (il se connecte à quelque chose qui existe déjà).
|
||||||
|
|
||||||
|
3. Que se passe-t-il si deux programmes utilisent le même port ?
|
||||||
|
- Le second programme recevra une erreur "Address already in use".
|
||||||
|
- Un port ne peut être utilisé que par un seul processus à la fois
|
||||||
|
(sauf configuration spéciale comme SO_REUSEADDR).
|
||||||
|
|
||||||
|
4. Pourquoi 127.0.0.1 (localhost) pour les tests ?
|
||||||
|
- C'est l'adresse de loopback qui pointe vers la machine locale.
|
||||||
|
- Les paquets ne quittent jamais la machine, idéal pour les tests.
|
||||||
|
- Pas besoin de configuration réseau ni de pare-feu.
|
||||||
|
|
||||||
|
5. Quand préférer UDP à TCP pour un chat ?
|
||||||
|
- Pour un chat vocal/vidéo en temps réel : la latence est critique,
|
||||||
|
perdre quelques paquets est acceptable (on n'entend pas un mot).
|
||||||
|
- Pour un chat textuel : TCP est préférable car chaque caractère compte.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TABLEAU COMPARATIF TCP vs UDP
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
"""
|
||||||
|
| Critère | TCP | UDP |
|
||||||
|
|------------------------|--------------------------|-------------------------|
|
||||||
|
| Connexion préalable | Oui (connect/accept) | Non (envoi direct) |
|
||||||
|
| Fonction d'envoi | send() | sendto() |
|
||||||
|
| Garantie de livraison | Oui (retransmission) | Non |
|
||||||
|
| Ordre des messages | Garanti | Non garanti |
|
||||||
|
| Utilisation typique | Web, email, fichiers | Streaming, jeux, VoIP |
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("CORRIGÉ DU TP MINI-CHAT")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
print("\n--- Partie 1 : Découverte des sockets ---\n")
|
||||||
|
|
||||||
|
print("Test afficher_info_reseau():")
|
||||||
|
afficher_info_reseau()
|
||||||
|
|
||||||
|
print("\nTest resoudre_dns():")
|
||||||
|
print(f"www.google.fr -> {resoudre_dns('www.google.fr')}")
|
||||||
|
print(f"www.wikipedia.org -> {resoudre_dns('www.wikipedia.org')}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("Pour tester les parties 2 à 5, lancez le script avec un argument :")
|
||||||
|
print(" python Corrige_TP_MiniChat.py serveur_tcp")
|
||||||
|
print(" python Corrige_TP_MiniChat.py client_tcp")
|
||||||
|
print(" python Corrige_TP_MiniChat.py serveur_chat")
|
||||||
|
print(" python Corrige_TP_MiniChat.py client_chat")
|
||||||
|
print(" python Corrige_TP_MiniChat.py serveur_udp")
|
||||||
|
print(" python Corrige_TP_MiniChat.py client_udp")
|
||||||
|
print(" python Corrige_TP_MiniChat.py serveur_multi")
|
||||||
|
print(" python Corrige_TP_MiniChat.py client_multi <pseudo>")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
commande = sys.argv[1]
|
||||||
|
|
||||||
|
if commande == "serveur_tcp":
|
||||||
|
creer_serveur_tcp(12345)
|
||||||
|
|
||||||
|
elif commande == "client_tcp":
|
||||||
|
creer_client_tcp('127.0.0.1', 12345, "Bonjour serveur !")
|
||||||
|
|
||||||
|
elif commande == "serveur_chat":
|
||||||
|
serveur_chat(12345)
|
||||||
|
|
||||||
|
elif commande == "client_chat":
|
||||||
|
client_chat('127.0.0.1', 12345)
|
||||||
|
|
||||||
|
elif commande == "serveur_udp":
|
||||||
|
serveur_udp(12345)
|
||||||
|
|
||||||
|
elif commande == "client_udp":
|
||||||
|
client_udp('127.0.0.1', 12345)
|
||||||
|
|
||||||
|
elif commande == "serveur_multi":
|
||||||
|
serveur_multi_clients(12345)
|
||||||
|
|
||||||
|
elif commande == "client_multi":
|
||||||
|
pseudo = sys.argv[2] if len(sys.argv) > 2 else "Anonyme"
|
||||||
|
client_multi('127.0.0.1', 12345, pseudo)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f"Commande inconnue : {commande}")
|
||||||
@@ -18,7 +18,7 @@ Le modèle OSI (Open Systems Interconnection) est un modèle théorique en 7 cou
|
|||||||
| 4 | Transport | Transport fiable des données (ex : TCP, UDP) : Divise le message en plusieurs segments, puis le reconstitue à l'arrivée |
|
| 4 | Transport | Transport fiable des données (ex : TCP, UDP) : Divise le message en plusieurs segments, puis le reconstitue à l'arrivée |
|
||||||
| 3 | Réseau | Routage des paquets (ex : IP) : S'occupe du trajet des paquets, en s'assurant de la bonne destination. |
|
| 3 | Réseau | Routage des paquets (ex : IP) : S'occupe du trajet des paquets, en s'assurant de la bonne destination. |
|
||||||
| 2 | Liaison | Adressage physique (ex : Ethernet, Wi-Fi, MAC) : Adresses physiques et réseau local |
|
| 2 | Liaison | Adressage physique (ex : Ethernet, Wi-Fi, MAC) : Adresses physiques et réseau local |
|
||||||
| 1 | Physique | Transmission des bits (ex : câbles, ondes radio) : Transmets les bits de manière effective. |
|
| 1 | Physique | Transmission des bits (ex : câbles, ondes radio) : Transmet les bits de manière effective. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ Le modèle TCP/IP est une simplification du modèle OSI, plus proche de l'implé
|
|||||||
|
|
||||||
Chaque machine d'un réseau a une adresse unique appelée **adresse IP**.
|
Chaque machine d'un réseau a une adresse unique appelée **adresse IP**.
|
||||||
|
|
||||||
### 2.1 Adresses IPv4
|
### Adresses IPv4
|
||||||
Une adresse IPv4 est constituée de 4 nombres entre 0 et 255, séparés par des points (ex : `192.168.1.1`).
|
Une adresse IPv4 est constituée de 4 nombres entre 0 et 255, séparés par des points (ex : `192.168.1.1`).
|
||||||
|
|
||||||
Les adresses IPv4 sont divisées en **adresses publiques** (utilisées sur Internet) et **adresses privées** (utilisées dans les réseaux locaux).
|
Les adresses IPv4 sont divisées en **adresses publiques** (utilisées sur Internet) et **adresses privées** (utilisées dans les réseaux locaux).
|
||||||
@@ -116,7 +116,7 @@ Les réseaux sont la base d'Internet et des systèmes informatiques modernes. Co
|
|||||||
### 🔍 Exercice d'application
|
### 🔍 Exercice d'application
|
||||||
1. Trouvez votre adresse IP locale avec la commande :
|
1. Trouvez votre adresse IP locale avec la commande :
|
||||||
```sh
|
```sh
|
||||||
ipconfig (Windows) ou ifconfig/ip a (Linux/Mac)
|
ipconfig (Windows) ou ifconfig / ip a (Linux/Mac)
|
||||||
```
|
```
|
||||||
2. Testez une requête DNS avec :
|
2. Testez une requête DNS avec :
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
380
Réseau/TP_MiniChat.md
Normal file
380
Réseau/TP_MiniChat.md
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
# TP : Mini-Chat en Python
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Vous êtes développeur chez une startup qui souhaite créer une alternative légère à Discord. Votre mission : implémenter un système de messagerie instantanée en utilisant les **sockets Python** pour comprendre concrètement le fonctionnement des protocoles TCP et UDP.
|
||||||
|
|
||||||
|
Ce TP vous permettra de manipuler les concepts vus en cours : adresses IP, ports, protocoles de transport, et modèle client-serveur.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Objectifs
|
||||||
|
|
||||||
|
- Comprendre le fonctionnement des sockets réseau
|
||||||
|
- Implémenter une communication client-serveur en TCP
|
||||||
|
- Comparer TCP et UDP en pratique
|
||||||
|
- Manipuler les adresses IP et les ports
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prérequis
|
||||||
|
|
||||||
|
```python
|
||||||
|
import socket
|
||||||
|
```
|
||||||
|
|
||||||
|
Le module `socket` est inclus dans Python, aucune installation nécessaire.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 1 : Découverte des sockets
|
||||||
|
|
||||||
|
### 1.1. Qu'est-ce qu'un socket ?
|
||||||
|
|
||||||
|
Un **socket** est un point de terminaison pour envoyer ou recevoir des données sur un réseau. C'est comme une prise électrique : vous branchez votre programme dessus pour communiquer.
|
||||||
|
|
||||||
|
Un socket est identifié par :
|
||||||
|
- Une **adresse IP** (quelle machine)
|
||||||
|
- Un **port** (quelle application sur cette machine)
|
||||||
|
|
||||||
|
### 1.2. Récupérer son adresse IP
|
||||||
|
|
||||||
|
Créer un programme qui affiche votre nom d'hôte et votre adresse IP locale.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import socket
|
||||||
|
|
||||||
|
def afficher_info_reseau():
|
||||||
|
"""Affiche les informations réseau de la machine."""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Test
|
||||||
|
afficher_info_reseau()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fonctions utiles :**
|
||||||
|
- `socket.gethostname()` : retourne le nom de la machine
|
||||||
|
- `socket.gethostbyname(nom)` : retourne l'adresse IP associée au nom
|
||||||
|
|
||||||
|
**Sortie attendue :**
|
||||||
|
```
|
||||||
|
Nom de la machine : MonPC
|
||||||
|
Adresse IP locale : 192.168.1.42
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3. Résolution DNS
|
||||||
|
|
||||||
|
Créer une fonction qui résout un nom de domaine en adresse IP (comme `nslookup`).
|
||||||
|
|
||||||
|
```python
|
||||||
|
def resoudre_dns(domaine):
|
||||||
|
"""
|
||||||
|
Résout un nom de domaine en adresse IP.
|
||||||
|
|
||||||
|
:param domaine: (str) Le nom de domaine à résoudre
|
||||||
|
:return: (str) L'adresse IP correspondante
|
||||||
|
"""
|
||||||
|
# À compléter
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
print(resoudre_dns("www.google.fr"))
|
||||||
|
print(resoudre_dns("www.wikipedia.org"))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : Serveur TCP simple
|
||||||
|
|
||||||
|
### 2.1. Création du serveur
|
||||||
|
|
||||||
|
Un serveur TCP fonctionne en plusieurs étapes :
|
||||||
|
1. Créer un socket
|
||||||
|
2. Lier le socket à une adresse et un port (`bind`)
|
||||||
|
3. Écouter les connexions (`listen`)
|
||||||
|
4. Accepter une connexion (`accept`)
|
||||||
|
5. Recevoir/Envoyer des données
|
||||||
|
6. Fermer la connexion
|
||||||
|
|
||||||
|
```python
|
||||||
|
import socket
|
||||||
|
|
||||||
|
def creer_serveur_tcp(port):
|
||||||
|
"""
|
||||||
|
Crée un serveur TCP qui attend une connexion et affiche le message reçu.
|
||||||
|
|
||||||
|
:param port: (int) Le port d'écoute
|
||||||
|
"""
|
||||||
|
# Créer le socket TCP
|
||||||
|
serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
|
# Permettre la réutilisation de l'adresse
|
||||||
|
serveur.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
|
||||||
|
# Lier à toutes les interfaces sur le port spécifié
|
||||||
|
serveur.bind(('', port))
|
||||||
|
|
||||||
|
# Écouter (max 1 connexion en attente)
|
||||||
|
serveur.listen(1)
|
||||||
|
|
||||||
|
print(f"Serveur en écoute sur le port {port}...")
|
||||||
|
|
||||||
|
# À compléter : accepter une connexion et recevoir un message
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Lancer le serveur sur le port 12345
|
||||||
|
creer_serveur_tcp(12345)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fonctions à utiliser :**
|
||||||
|
- `connexion, adresse = serveur.accept()` : accepte une connexion
|
||||||
|
- `donnees = connexion.recv(1024)` : reçoit jusqu'à 1024 octets
|
||||||
|
- `donnees.decode('utf-8')` : convertit les octets en texte
|
||||||
|
- `connexion.close()` : ferme la connexion
|
||||||
|
|
||||||
|
### 2.2. Création du client
|
||||||
|
|
||||||
|
```python
|
||||||
|
import socket
|
||||||
|
|
||||||
|
def creer_client_tcp(adresse_serveur, port, message):
|
||||||
|
"""
|
||||||
|
Crée un client TCP qui envoie un message au serveur.
|
||||||
|
|
||||||
|
:param adresse_serveur: (str) L'adresse IP du serveur
|
||||||
|
:param port: (int) Le port du serveur
|
||||||
|
:param message: (str) Le message à envoyer
|
||||||
|
"""
|
||||||
|
# Créer le socket TCP
|
||||||
|
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
|
# À compléter : se connecter et envoyer le message
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Envoyer un message au serveur local
|
||||||
|
creer_client_tcp('127.0.0.1', 12345, "Bonjour serveur !")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fonctions à utiliser :**
|
||||||
|
- `client.connect((adresse, port))` : se connecte au serveur
|
||||||
|
- `client.send(message.encode('utf-8'))` : envoie le message
|
||||||
|
|
||||||
|
### 2.3. Test
|
||||||
|
|
||||||
|
1. Ouvrir deux terminaux
|
||||||
|
2. Dans le premier, lancer le serveur
|
||||||
|
3. Dans le second, lancer le client
|
||||||
|
4. Observer le message reçu par le serveur
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : Chat bidirectionnel
|
||||||
|
|
||||||
|
### 3.1. Serveur de chat
|
||||||
|
|
||||||
|
Modifier le serveur pour qu'il puisse :
|
||||||
|
- Recevoir un message du client
|
||||||
|
- Répondre au client
|
||||||
|
- Continuer la conversation jusqu'à ce que le client envoie "quit"
|
||||||
|
|
||||||
|
```python
|
||||||
|
def serveur_chat(port):
|
||||||
|
"""
|
||||||
|
Serveur de chat interactif.
|
||||||
|
"""
|
||||||
|
serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
serveur.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
serveur.bind(('', port))
|
||||||
|
serveur.listen(1)
|
||||||
|
|
||||||
|
print(f"Serveur de chat en attente sur le port {port}...")
|
||||||
|
|
||||||
|
connexion, adresse = serveur.accept()
|
||||||
|
print(f"Connexion de {adresse[0]}:{adresse[1]}")
|
||||||
|
|
||||||
|
# À compléter : boucle de conversation
|
||||||
|
# - Recevoir un message
|
||||||
|
# - Si le message est "quit", terminer
|
||||||
|
# - Sinon, demander une réponse et l'envoyer
|
||||||
|
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2. Client de chat
|
||||||
|
|
||||||
|
```python
|
||||||
|
def client_chat(adresse_serveur, port):
|
||||||
|
"""
|
||||||
|
Client de chat interactif.
|
||||||
|
"""
|
||||||
|
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
client.connect((adresse_serveur, port))
|
||||||
|
|
||||||
|
print(f"Connecté au serveur {adresse_serveur}:{port}")
|
||||||
|
print("Tapez 'quit' pour quitter.\n")
|
||||||
|
|
||||||
|
# À compléter : boucle de conversation
|
||||||
|
# - Demander un message à l'utilisateur
|
||||||
|
# - Envoyer le message
|
||||||
|
# - Si "quit", terminer
|
||||||
|
# - Sinon, attendre et afficher la réponse
|
||||||
|
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : Comparaison TCP vs UDP
|
||||||
|
|
||||||
|
### 4.1. Serveur UDP
|
||||||
|
|
||||||
|
UDP est "connectionless" : pas besoin d'établir une connexion avant d'envoyer des données.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def serveur_udp(port):
|
||||||
|
"""
|
||||||
|
Serveur UDP simple.
|
||||||
|
"""
|
||||||
|
serveur = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # DGRAM = UDP
|
||||||
|
serveur.bind(('', port))
|
||||||
|
|
||||||
|
print(f"Serveur UDP en écoute sur le port {port}...")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# recvfrom retourne les données ET l'adresse de l'expéditeur
|
||||||
|
donnees, adresse = serveur.recvfrom(1024)
|
||||||
|
message = donnees.decode('utf-8')
|
||||||
|
print(f"[{adresse[0]}:{adresse[1]}] {message}")
|
||||||
|
|
||||||
|
if message.lower() == "quit":
|
||||||
|
break
|
||||||
|
|
||||||
|
serveur.close()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2. Client UDP
|
||||||
|
|
||||||
|
```python
|
||||||
|
def client_udp(adresse_serveur, port):
|
||||||
|
"""
|
||||||
|
Client UDP simple.
|
||||||
|
"""
|
||||||
|
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
message = input("Message (quit pour quitter) : ")
|
||||||
|
# sendto envoie directement sans connexion préalable
|
||||||
|
client.sendto(message.encode('utf-8'), (adresse_serveur, port))
|
||||||
|
|
||||||
|
if message.lower() == "quit":
|
||||||
|
break
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3. Analyse comparative
|
||||||
|
|
||||||
|
Compléter le tableau suivant après avoir testé les deux versions :
|
||||||
|
|
||||||
|
| Critère | TCP | UDP |
|
||||||
|
|---------|-----|-----|
|
||||||
|
| Connexion préalable | ... | ... |
|
||||||
|
| Fonction d'envoi | ... | ... |
|
||||||
|
| Garantie de livraison | ... | ... |
|
||||||
|
| Ordre des messages | ... | ... |
|
||||||
|
| Utilisation typique | ... | ... |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 5 : Mini-serveur multi-clients (Bonus)
|
||||||
|
|
||||||
|
### 5.1. Problème
|
||||||
|
|
||||||
|
Le serveur actuel ne peut gérer qu'un seul client. Pour accepter plusieurs clients simultanément, il faut utiliser les **threads**.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
|
||||||
|
clients = []
|
||||||
|
|
||||||
|
def gerer_client(connexion, adresse):
|
||||||
|
"""Gère la communication avec un client."""
|
||||||
|
print(f"[+] Nouveau client : {adresse}")
|
||||||
|
clients.append(connexion)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
message = connexion.recv(1024).decode('utf-8')
|
||||||
|
if not message or message.lower() == "quit":
|
||||||
|
break
|
||||||
|
|
||||||
|
# Diffuser le message à tous les clients
|
||||||
|
for client in clients:
|
||||||
|
if client != connexion:
|
||||||
|
client.send(f"[{adresse[0]}] {message}".encode('utf-8'))
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
|
||||||
|
clients.remove(connexion)
|
||||||
|
connexion.close()
|
||||||
|
print(f"[-] Client déconnecté : {adresse}")
|
||||||
|
|
||||||
|
|
||||||
|
def serveur_multi_clients(port):
|
||||||
|
"""Serveur acceptant plusieurs clients."""
|
||||||
|
serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
serveur.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
serveur.bind(('', port))
|
||||||
|
serveur.listen(5)
|
||||||
|
|
||||||
|
print(f"Serveur multi-clients sur le port {port}")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
connexion, adresse = serveur.accept()
|
||||||
|
thread = threading.Thread(target=gerer_client, args=(connexion, adresse))
|
||||||
|
thread.start()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2. Client amélioré
|
||||||
|
|
||||||
|
Créer un client qui peut recevoir des messages tout en permettant d'en envoyer (utiliser un thread pour la réception).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 6 : Questions de synthèse
|
||||||
|
|
||||||
|
1. **Expliquer** la différence entre `SOCK_STREAM` (TCP) et `SOCK_DGRAM` (UDP).
|
||||||
|
|
||||||
|
2. **Pourquoi** le serveur utilise-t-il `bind()` alors que le client utilise `connect()` ?
|
||||||
|
|
||||||
|
3. **Que se passe-t-il** si deux programmes essaient d'utiliser le même port simultanément ?
|
||||||
|
|
||||||
|
4. **Pourquoi** utilise-t-on `127.0.0.1` (localhost) pour les tests ?
|
||||||
|
|
||||||
|
5. **Dans quel cas** préféreriez-vous UDP à TCP pour une application de chat ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Barème indicatif
|
||||||
|
|
||||||
|
| Partie | Points |
|
||||||
|
|--------|--------|
|
||||||
|
| Partie 1 : Découverte | 3 |
|
||||||
|
| Partie 2 : Serveur/Client TCP | 5 |
|
||||||
|
| Partie 3 : Chat bidirectionnel | 4 |
|
||||||
|
| Partie 4 : Comparaison TCP/UDP | 4 |
|
||||||
|
| Partie 5 : Multi-clients (Bonus) | 2 |
|
||||||
|
| Partie 6 : Questions | 2 |
|
||||||
|
| **Total** | **20** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
> Et que dire de Bob, qui fait f5 toutes les 10 min sur 46.33.191.5 ?
|
> Et que dire de Bob, qui fait f5 toutes les 10 min sur 46.33.191.5 ?
|
||||||
|
|
||||||
|
|
||||||
Cela vous parait idiot ? Pourtant les adresses IP citées au dessus sont bel et bien utilisées quotidiennement par des milliers de personnes. Néanmoins, vous les connaissez probablement mieux sous leur petit nom *__google__*, *__tiktok__* et *__pronote__* !
|
Cela vous paraît idiot ? Pourtant les adresses IP citées au-dessus sont bel et bien utilisées quotidiennement par des milliers de personnes. Néanmoins, vous les connaissez probablement mieux sous leur petit nom *__google__*, *__tiktok__* et *__pronote__* !
|
||||||
|
|
||||||
Comme nous l'avons vu avec l'adresse IP, chaque machine possède un identifiant sur le réseau. Tout comme chaque personne possède une adresse physique : lorsque vous allez voir tatie Monique, vous vous rendez là où elle vit, et bien c'est pareil avec un site internet.
|
Comme nous l'avons vu avec l'adresse IP, chaque machine possède un identifiant sur le réseau. Tout comme chaque personne possède une adresse physique : lorsque vous allez voir tatie Monique, vous vous rendez là où elle vit, et bien c'est pareil avec un site internet.
|
||||||
|
|
||||||
@@ -20,9 +20,9 @@ Si, à l'époque, on stockait les adresses des sites dans un fichier HOST.TXT, c
|
|||||||
Très rapidement, cette solution devint ingérable au vu de la croissance exponentielle du nombre de sites mis en ligne.
|
Très rapidement, cette solution devint ingérable au vu de la croissance exponentielle du nombre de sites mis en ligne.
|
||||||
En 1983, le DNS voit le jour. Et très rapidement, ce système montra son utilité : quatre années plus tard, on enregistrait plus de 20 000 enregistrements de nom de domaine.
|
En 1983, le DNS voit le jour. Et très rapidement, ce système montra son utilité : quatre années plus tard, on enregistrait plus de 20 000 enregistrements de nom de domaine.
|
||||||
|
|
||||||
Pour nous autres, êtres humains, il est complexe de retenir toutes les adresses IP des sites que l'on souhaite visiter. Et à moins de tout noter dans un repertoire, jamais vous ne vous rappelerez de tous ces nombres.
|
Pour nous autres, êtres humains, il est complexe de retenir toutes les adresses IP des sites que l'on souhaite visiter. Et à moins de tout noter dans un répertoire, jamais vous ne vous rappellerez de tous ces nombres.
|
||||||
|
|
||||||
C'est pour cela que le __protocole__ *Domain Name System* a été crée.
|
C'est pour cela que le __protocole__ *Domain Name System* a été créé.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ Toutes ces adresses vont être enregistrées auprès d'un organisme, *L'ICANN*.
|
|||||||
> Vous l'aurez compris, le principe du DNS est de faire le lien entre l'adresse IP et l'URL d'un site.
|
> Vous l'aurez compris, le principe du DNS est de faire le lien entre l'adresse IP et l'URL d'un site.
|
||||||
Mais c'est quoi, une URL ?
|
Mais c'est quoi, une URL ?
|
||||||
|
|
||||||
Une URL, ou Uniform Ressource Locator, est composée de 5 parties; le protocole, le sous-domaine, le nom de domaine principal, le domaine de deuxième niveau et le répertoire.
|
Une URL, ou Uniform Resource Locator, est composée de 5 parties : le protocole, le sous-domaine, le nom de domaine principal, le domaine de deuxième niveau et le répertoire.
|
||||||
|
|
||||||
Par exemple, dans cette adresse, que peut on retrouver ?
|
Par exemple, dans cette adresse, que peut on retrouver ?
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ Répondez aux questions suivantes :
|
|||||||
|
|
||||||
- De combien de parties est composée une url ?
|
- De combien de parties est composée une url ?
|
||||||
- Pour chaque partie, donner votre propre définition
|
- Pour chaque partie, donner votre propre définition
|
||||||
- Si une partie de l'URL est incorrecte, que se passe -il ?
|
- Si une partie de l'URL est incorrecte, que se passe-t-il ?
|
||||||
|
|
||||||
Voici une autre url : https://fr.wikipedia.org/wiki/Ada_Lovelace
|
Voici une autre url : https://fr.wikipedia.org/wiki/Ada_Lovelace
|
||||||
|
|
||||||
@@ -115,9 +115,9 @@ subgraph Sous-domaines de wikipedia
|
|||||||
|
|
||||||
--------
|
--------
|
||||||
|
|
||||||
### Resolution de nom
|
### Résolution de nom
|
||||||
|
|
||||||
Que se passe t-il lorsque l'on tape dans son navigateur web *__ViveLaSNT.fr__* ?
|
Que se passe-t-il lorsque l'on tape dans son navigateur web *__ViveLaSNT.fr__* ?
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=av0zX-dr8o8)
|
[](https://www.youtube.com/watch?v=av0zX-dr8o8)
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Dans le merveilleux monde d'Internet, c'est la même chose, grâce à l'adresse
|
|||||||
|
|
||||||
Arobase souhaite passer une commande auprès d'une célèbre chaine de magasin de meuble. Hélas, le paquet est trop gros pour être livré en une fois, en effet, le poids maximum d'un colis est de 1,5 kg.
|
Arobase souhaite passer une commande auprès d'une célèbre chaine de magasin de meuble. Hélas, le paquet est trop gros pour être livré en une fois, en effet, le poids maximum d'un colis est de 1,5 kg.
|
||||||
|
|
||||||
*Quelle solution le commerçant va t-il choisir pour expédier les différents colis ?*
|
*Quelle solution le commerçant va-t-il choisir pour expédier les différents colis ?*
|
||||||
|
|
||||||
*__Problématiques__* :
|
*__Problématiques__* :
|
||||||
|
|
||||||
@@ -23,17 +23,17 @@ Arobase souhaite passer une commande auprès d'une célèbre chaine de magasin d
|
|||||||
|
|
||||||
__En informatique__
|
__En informatique__
|
||||||
|
|
||||||
Cela fonctionne de la même manière : Arobase souhaite envoyer télécharger un gros fichier depuis un serveur https. Les problèmatiques restent les mêmes :
|
Cela fonctionne de la même manière : Arobase souhaite télécharger un gros fichier depuis un serveur HTTPS. Les problématiques restent les mêmes :
|
||||||
|
|
||||||
- Comment découper le fichier ?
|
- Comment découper le fichier ?
|
||||||
- Comment s'assurer que tous les "morceaux" de fichiers parviennent à destination ?
|
- Comment s'assurer que tous les "morceaux" de fichiers parviennent à destination ?
|
||||||
|
|
||||||
|
|
||||||
## Deux méthodes d'envoie
|
## Deux méthodes d'envoi
|
||||||
|
|
||||||
Arobase a le choix entre deux méthodes de livraison :
|
Arobase a le choix entre deux méthodes de livraison :
|
||||||
|
|
||||||
- Soit l'ensemble des paquets sera livrée en trois jours, avec une numérotation précise de chaque colis
|
- Soit l'ensemble des paquets sera livré en trois jours, avec une numérotation précise de chaque colis
|
||||||
- Soit la commande arrivera dès le lendemain, mais sans aucune garantie de fiabilité
|
- Soit la commande arrivera dès le lendemain, mais sans aucune garantie de fiabilité
|
||||||
|
|
||||||
On peut rapprocher ces deux méthodes à deux protocoles de transmission de données :
|
On peut rapprocher ces deux méthodes à deux protocoles de transmission de données :
|
||||||
@@ -43,7 +43,7 @@ On peut rapprocher ces deux méthodes à deux protocoles de transmission de donn
|
|||||||
|
|
||||||
### TCP
|
### TCP
|
||||||
|
|
||||||
TCP assure une qualité de service, c'est à dire qu'il assure le découpage du fichier en plus petits paquets, en permettant le routage des données par quelques chemins que cela soit tout en promettant une reconstitution des fichiers demandés dans le bon ordre, grâce à une numérotation précise des données.
|
TCP assure une qualité de service, c'est-à-dire qu'il assure le découpage du fichier en plus petits paquets, en permettant le routage des données quels que soient les chemins empruntés, tout en promettant une reconstitution des fichiers demandés dans le bon ordre, grâce à une numérotation précise des données.
|
||||||
|
|
||||||
*__Selon vous, qu'est ce que le routage des paquets (c'est à dire le fait de pouvoir disperser les données à travers Internet tout en s'assurant qu'elles parviennent à destination) engendre comme contrainte ?__*
|
*__Selon vous, qu'est ce que le routage des paquets (c'est à dire le fait de pouvoir disperser les données à travers Internet tout en s'assurant qu'elles parviennent à destination) engendre comme contrainte ?__*
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ On suppose qu'Arobase souhaite envoyer des fichiers personnels sur son cloud Bob
|
|||||||
- Que faire si un paquet se perd ?
|
- Que faire si un paquet se perd ?
|
||||||
- Que faire si un paquet arrive en double ?
|
- Que faire si un paquet arrive en double ?
|
||||||
|
|
||||||
## Mais au fait, comment se font ces échanges tcp ?
|
## Mais au fait, comment se font ces échanges TCP ?
|
||||||
|
|
||||||
Voilà comment se déroule un échange entre un client et un serveur TCP.
|
Voilà comment se déroule un échange entre un client et un serveur TCP.
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ Hélas on voit ici que le message était trop long, et que le canal s'est referm
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
Et que se passe t-il dans ce cas ...?
|
Et que se passe-t-il dans ce cas... ?
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
464
SQL/CORRIGE.md
Normal file
464
SQL/CORRIGE.md
Normal file
@@ -0,0 +1,464 @@
|
|||||||
|
# Corrigé des exercices — SQL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 1 : Requêtes SELECT simples
|
||||||
|
|
||||||
|
1. **Tous les livres de la table LIVRES.**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM LIVRES;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Les titres et années de publication de tous les livres.**
|
||||||
|
```sql
|
||||||
|
SELECT titre, ann_publi FROM LIVRES;
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Les noms et prénoms de tous les auteurs.**
|
||||||
|
```sql
|
||||||
|
SELECT nom, prenom FROM AUTEURS;
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Les titres des livres publiés après 1950.**
|
||||||
|
```sql
|
||||||
|
SELECT titre FROM LIVRES WHERE ann_publi > 1950;
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Les titres des livres du genre "dystopie".**
|
||||||
|
```sql
|
||||||
|
SELECT titre FROM LIVRES WHERE genre = 'dystopie';
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Les auteurs de nationalité française.**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM AUTEURS WHERE nationalite = 'français';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 2 : Filtres et conditions
|
||||||
|
|
||||||
|
1. **Les livres publiés entre 1940 et 1960 (inclus).**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM LIVRES WHERE ann_publi BETWEEN 1940 AND 1960;
|
||||||
|
-- ou
|
||||||
|
SELECT * FROM LIVRES WHERE ann_publi >= 1940 AND ann_publi <= 1960;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Les livres ayant plus de 300 pages.**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM LIVRES WHERE nb_pages > 300;
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Les auteurs nés après 1900 et de nationalité américaine.**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM AUTEURS
|
||||||
|
WHERE ann_naissance > 1900 AND nationalite = 'américain';
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Les livres dont le titre contient le mot "Terre".**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM LIVRES WHERE titre LIKE '%Terre%';
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Les livres qui ne sont pas du genre "SF".**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM LIVRES WHERE genre <> 'SF';
|
||||||
|
-- ou
|
||||||
|
SELECT * FROM LIVRES WHERE genre != 'SF';
|
||||||
|
-- ou
|
||||||
|
SELECT * FROM LIVRES WHERE NOT genre = 'SF';
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Les auteurs dont le prénom commence par la lettre "R".**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM AUTEURS WHERE prenom LIKE 'R%';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 3 : Tri et limitation
|
||||||
|
|
||||||
|
1. **Tous les livres triés par année de publication (du plus ancien au plus récent).**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM LIVRES ORDER BY ann_publi ASC;
|
||||||
|
-- ou simplement
|
||||||
|
SELECT * FROM LIVRES ORDER BY ann_publi;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Les 5 livres les plus récents.**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM LIVRES ORDER BY ann_publi DESC LIMIT 5;
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Les auteurs triés par année de naissance décroissante.**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM AUTEURS ORDER BY ann_naissance DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Les 3 livres ayant le plus de pages.**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM LIVRES ORDER BY nb_pages DESC LIMIT 3;
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Les livres triés par genre, puis par titre alphabétique.**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM LIVRES ORDER BY genre ASC, titre ASC;
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **La liste des genres sans doublons.**
|
||||||
|
```sql
|
||||||
|
SELECT DISTINCT genre FROM LIVRES;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 4 : Fonctions d'agrégation
|
||||||
|
|
||||||
|
1. **Le nombre total de livres dans la base.**
|
||||||
|
```sql
|
||||||
|
SELECT COUNT(*) FROM LIVRES;
|
||||||
|
-- Résultat : 12
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Le nombre de pages du livre le plus long.**
|
||||||
|
```sql
|
||||||
|
SELECT MAX(nb_pages) FROM LIVRES;
|
||||||
|
-- Résultat : 896 (Dune)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **L'année de publication du livre le plus ancien.**
|
||||||
|
```sql
|
||||||
|
SELECT MIN(ann_publi) FROM LIVRES;
|
||||||
|
-- Résultat : 1865 (De la Terre à la Lune)
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Le nombre moyen de pages des livres.**
|
||||||
|
```sql
|
||||||
|
SELECT AVG(nb_pages) FROM LIVRES;
|
||||||
|
-- Résultat : environ 333.25
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Le nombre de livres par genre.**
|
||||||
|
```sql
|
||||||
|
SELECT genre, COUNT(*) AS nb_livres
|
||||||
|
FROM LIVRES
|
||||||
|
GROUP BY genre;
|
||||||
|
```
|
||||||
|
Résultat :
|
||||||
|
| genre | nb_livres |
|
||||||
|
|-------|-----------|
|
||||||
|
| aventure | 2 |
|
||||||
|
| dystopie | 3 |
|
||||||
|
| post-apo | 1 |
|
||||||
|
| SF | 4 |
|
||||||
|
| space opera | 2 |
|
||||||
|
|
||||||
|
6. **Le nombre total de pages de tous les livres de Jules Verne (id_auteur = 8).**
|
||||||
|
```sql
|
||||||
|
SELECT SUM(nb_pages) FROM LIVRES WHERE id_auteur = 8;
|
||||||
|
-- Résultat : 672 (192 + 480)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 5 : GROUP BY et HAVING
|
||||||
|
|
||||||
|
1. **Le nombre de livres écrits par chaque auteur.**
|
||||||
|
```sql
|
||||||
|
SELECT id_auteur, COUNT(*) AS nb_livres
|
||||||
|
FROM LIVRES
|
||||||
|
GROUP BY id_auteur;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Le nombre moyen de pages par genre.**
|
||||||
|
```sql
|
||||||
|
SELECT genre, AVG(nb_pages) AS moy_pages
|
||||||
|
FROM LIVRES
|
||||||
|
GROUP BY genre;
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Les genres ayant plus de 2 livres.**
|
||||||
|
```sql
|
||||||
|
SELECT genre, COUNT(*) AS nb_livres
|
||||||
|
FROM LIVRES
|
||||||
|
GROUP BY genre
|
||||||
|
HAVING COUNT(*) > 2;
|
||||||
|
```
|
||||||
|
Résultat : dystopie (3), SF (4)
|
||||||
|
|
||||||
|
4. **Les auteurs ayant écrit au moins 2 livres.**
|
||||||
|
```sql
|
||||||
|
SELECT id_auteur, COUNT(*) AS nb_livres
|
||||||
|
FROM LIVRES
|
||||||
|
GROUP BY id_auteur
|
||||||
|
HAVING COUNT(*) >= 2;
|
||||||
|
```
|
||||||
|
Résultat : id_auteur 3, 5, 7, 8 (Asimov, Bradbury, Barjavel, Verne)
|
||||||
|
|
||||||
|
5. **L'année de publication du premier livre de chaque auteur.**
|
||||||
|
```sql
|
||||||
|
SELECT id_auteur, MIN(ann_publi) AS premier_livre
|
||||||
|
FROM LIVRES
|
||||||
|
GROUP BY id_auteur;
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Les genres dont la moyenne de pages dépasse 250.**
|
||||||
|
```sql
|
||||||
|
SELECT genre, AVG(nb_pages) AS moy_pages
|
||||||
|
FROM LIVRES
|
||||||
|
GROUP BY genre
|
||||||
|
HAVING AVG(nb_pages) > 250;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 6 : Jointures
|
||||||
|
|
||||||
|
1. **Les titres des livres avec le nom et prénom de leur auteur.**
|
||||||
|
```sql
|
||||||
|
SELECT L.titre, A.nom, A.prenom
|
||||||
|
FROM LIVRES L
|
||||||
|
INNER JOIN AUTEURS A ON L.id_auteur = A.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Les titres des livres écrits par des auteurs français.**
|
||||||
|
```sql
|
||||||
|
SELECT L.titre
|
||||||
|
FROM LIVRES L
|
||||||
|
INNER JOIN AUTEURS A ON L.id_auteur = A.id
|
||||||
|
WHERE A.nationalite = 'français';
|
||||||
|
```
|
||||||
|
Résultat : La nuit des temps, Ravage, De la Terre à la Lune, Vingt mille lieues sous les mers
|
||||||
|
|
||||||
|
3. **Les titres et auteurs des livres du genre "dystopie", triés par année.**
|
||||||
|
```sql
|
||||||
|
SELECT L.titre, A.nom, A.prenom, L.ann_publi
|
||||||
|
FROM LIVRES L
|
||||||
|
INNER JOIN AUTEURS A ON L.id_auteur = A.id
|
||||||
|
WHERE L.genre = 'dystopie'
|
||||||
|
ORDER BY L.ann_publi;
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Le nom des auteurs ayant écrit des livres de plus de 400 pages.**
|
||||||
|
```sql
|
||||||
|
SELECT DISTINCT A.nom, A.prenom
|
||||||
|
FROM AUTEURS A
|
||||||
|
INNER JOIN LIVRES L ON A.id = L.id_auteur
|
||||||
|
WHERE L.nb_pages > 400;
|
||||||
|
```
|
||||||
|
Résultat : Herbert Frank, Verne Jules
|
||||||
|
|
||||||
|
5. **Les livres avec le nom de l'auteur, pour les auteurs américains nés après 1910.**
|
||||||
|
```sql
|
||||||
|
SELECT L.titre, A.nom
|
||||||
|
FROM LIVRES L
|
||||||
|
INNER JOIN AUTEURS A ON L.id_auteur = A.id
|
||||||
|
WHERE A.nationalite = 'américain' AND A.ann_naissance > 1910;
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Pour chaque nationalité, le nombre de livres écrits.**
|
||||||
|
```sql
|
||||||
|
SELECT A.nationalite, COUNT(*) AS nb_livres
|
||||||
|
FROM AUTEURS A
|
||||||
|
INNER JOIN LIVRES L ON A.id = L.id_auteur
|
||||||
|
GROUP BY A.nationalite;
|
||||||
|
```
|
||||||
|
Résultat :
|
||||||
|
| nationalite | nb_livres |
|
||||||
|
|-------------|-----------|
|
||||||
|
| américain | 6 |
|
||||||
|
| britannique | 2 |
|
||||||
|
| français | 4 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 7 : Requêtes complexes
|
||||||
|
|
||||||
|
1. **Le titre du livre le plus long de chaque auteur, avec le nom de l'auteur.**
|
||||||
|
```sql
|
||||||
|
SELECT A.nom, A.prenom, L.titre, L.nb_pages
|
||||||
|
FROM LIVRES L
|
||||||
|
INNER JOIN AUTEURS A ON L.id_auteur = A.id
|
||||||
|
WHERE L.nb_pages = (
|
||||||
|
SELECT MAX(nb_pages)
|
||||||
|
FROM LIVRES
|
||||||
|
WHERE id_auteur = L.id_auteur
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Les auteurs n'ayant écrit qu'un seul livre.**
|
||||||
|
```sql
|
||||||
|
SELECT A.nom, A.prenom, L.titre
|
||||||
|
FROM AUTEURS A
|
||||||
|
INNER JOIN LIVRES L ON A.id = L.id_auteur
|
||||||
|
WHERE A.id IN (
|
||||||
|
SELECT id_auteur
|
||||||
|
FROM LIVRES
|
||||||
|
GROUP BY id_auteur
|
||||||
|
HAVING COUNT(*) = 1
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Le nombre moyen de pages des livres par nationalité d'auteur.**
|
||||||
|
```sql
|
||||||
|
SELECT A.nationalite, AVG(L.nb_pages) AS moy_pages
|
||||||
|
FROM AUTEURS A
|
||||||
|
INNER JOIN LIVRES L ON A.id = L.id_auteur
|
||||||
|
GROUP BY A.nationalite;
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Les genres pour lesquels tous les livres ont été publiés après 1940.**
|
||||||
|
```sql
|
||||||
|
SELECT genre
|
||||||
|
FROM LIVRES
|
||||||
|
GROUP BY genre
|
||||||
|
HAVING MIN(ann_publi) > 1940;
|
||||||
|
```
|
||||||
|
Résultat : dystopie (min 1931... non!), SF (min 1950), space opera (min 1951)
|
||||||
|
|
||||||
|
Note : En vérifiant les données, "Le meilleur des mondes" (dystopie) est de 1931, donc dystopie ne correspond pas. Les genres valides sont : SF, space opera, post-apo.
|
||||||
|
|
||||||
|
5. **Le nom complet des auteurs et le nombre total de pages qu'ils ont écrites.**
|
||||||
|
```sql
|
||||||
|
SELECT A.prenom || ' ' || A.nom AS nom_complet, SUM(L.nb_pages) AS total_pages
|
||||||
|
FROM AUTEURS A
|
||||||
|
INNER JOIN LIVRES L ON A.id = L.id_auteur
|
||||||
|
GROUP BY A.id
|
||||||
|
ORDER BY total_pages DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 8 : INSERT, UPDATE, DELETE
|
||||||
|
|
||||||
|
1. **Ajouter l'auteur Ursula K. Le Guin.**
|
||||||
|
```sql
|
||||||
|
INSERT INTO AUTEURS (id, nom, prenom, nationalite, ann_naissance)
|
||||||
|
VALUES (9, 'Le Guin', 'Ursula K.', 'américain', 1929);
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Ajouter le livre "La Main gauche de la nuit".**
|
||||||
|
```sql
|
||||||
|
INSERT INTO LIVRES (id, titre, id_auteur, ann_publi, genre, nb_pages)
|
||||||
|
VALUES (13, 'La Main gauche de la nuit', 9, 1969, 'SF', 304);
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Changer le genre de "Dune" en "SF".**
|
||||||
|
```sql
|
||||||
|
UPDATE LIVRES
|
||||||
|
SET genre = 'SF'
|
||||||
|
WHERE titre = 'Dune';
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Augmenter de 10% le nombre de pages de tous les livres de Jules Verne.**
|
||||||
|
```sql
|
||||||
|
UPDATE LIVRES
|
||||||
|
SET nb_pages = nb_pages * 1.1
|
||||||
|
WHERE id_auteur = 8;
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Supprimer tous les livres publiés avant 1900.**
|
||||||
|
```sql
|
||||||
|
DELETE FROM LIVRES
|
||||||
|
WHERE ann_publi < 1900;
|
||||||
|
```
|
||||||
|
Note : Cela supprimera "De la Terre à la Lune" (1865) et "Vingt mille lieues sous les mers" (1870).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 9 : Création de tables
|
||||||
|
|
||||||
|
1. **Créer la table EMPRUNTS.**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE EMPRUNTS (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
id_livre INTEGER,
|
||||||
|
id_adherent INTEGER,
|
||||||
|
date_emprunt DATE,
|
||||||
|
date_retour DATE,
|
||||||
|
FOREIGN KEY (id_livre) REFERENCES LIVRES(id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Créer la table EDITEURS.**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE EDITEURS (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
nom TEXT,
|
||||||
|
pays TEXT,
|
||||||
|
ann_creation INTEGER
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Ajouter une colonne id_editeur à la table LIVRES.**
|
||||||
|
```sql
|
||||||
|
ALTER TABLE LIVRES ADD COLUMN id_editeur INTEGER REFERENCES EDITEURS(id);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 10 : Analyse de requêtes
|
||||||
|
|
||||||
|
1. **Requête avec sous-requête MIN :**
|
||||||
|
```sql
|
||||||
|
SELECT titre FROM LIVRES WHERE ann_publi = (SELECT MIN(ann_publi) FROM LIVRES);
|
||||||
|
```
|
||||||
|
**Explication :** Cette requête retourne le titre du livre le plus ancien de la base. La sous-requête trouve l'année minimale, puis la requête principale sélectionne le(s) livre(s) publié(s) cette année-là.
|
||||||
|
|
||||||
|
**Résultat :** "De la Terre à la Lune" (1865)
|
||||||
|
|
||||||
|
2. **Requête complexe avec double sous-requête :**
|
||||||
|
```sql
|
||||||
|
SELECT A.nom, COUNT(*)
|
||||||
|
FROM AUTEURS A
|
||||||
|
INNER JOIN LIVRES L ON A.id = L.id_auteur
|
||||||
|
GROUP BY A.id
|
||||||
|
HAVING COUNT(*) = (SELECT MAX(cnt) FROM (SELECT COUNT(*) as cnt FROM LIVRES GROUP BY id_auteur));
|
||||||
|
```
|
||||||
|
**Explication :** Cette requête retourne le(s) auteur(s) ayant écrit le plus grand nombre de livres. La sous-requête interne compte les livres par auteur, la sous-requête externe trouve le maximum, et la requête principale affiche les auteurs atteignant ce maximum.
|
||||||
|
|
||||||
|
**Résultat :** Les auteurs avec 2 livres chacun (Asimov, Bradbury, Barjavel, Verne)
|
||||||
|
|
||||||
|
3. **Requête avec NOT IN :**
|
||||||
|
```sql
|
||||||
|
SELECT DISTINCT genre FROM LIVRES WHERE genre NOT IN (SELECT genre FROM LIVRES WHERE ann_publi < 1950);
|
||||||
|
```
|
||||||
|
**Explication :** Cette requête retourne les genres pour lesquels AUCUN livre n'a été publié avant 1950. La sous-requête trouve les genres ayant au moins un livre avant 1950, puis la requête principale exclut ces genres.
|
||||||
|
|
||||||
|
**Résultat :** Les genres dont tous les livres sont de 1950 ou après (SF, space opera, potentiellement post-apo selon les données)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 11 : QCM
|
||||||
|
|
||||||
|
1. **Quelle clause permet de filtrer les résultats d'un GROUP BY ?**
|
||||||
|
- **Réponse : c) HAVING**
|
||||||
|
|
||||||
|
2. **Quelle est la différence entre WHERE et HAVING ?**
|
||||||
|
- **Réponse : b) WHERE filtre avant GROUP BY, HAVING filtre après**
|
||||||
|
|
||||||
|
Explication : WHERE filtre les lignes individuelles avant le regroupement. HAVING filtre les groupes après le regroupement et permet d'utiliser des fonctions d'agrégation.
|
||||||
|
|
||||||
|
3. **Que retourne `SELECT COUNT(*) FROM LIVRES WHERE 1=0;` ?**
|
||||||
|
- **Réponse : c) 0**
|
||||||
|
|
||||||
|
Explication : La condition `1=0` est toujours fausse, donc aucune ligne n'est sélectionnée. COUNT(*) sur un ensemble vide retourne 0 (pas NULL, pas d'erreur).
|
||||||
|
|
||||||
|
4. **Quel type de jointure retourne tous les enregistrements de la table de gauche ?**
|
||||||
|
- **Réponse : b) LEFT JOIN**
|
||||||
|
|
||||||
|
Explication : LEFT JOIN retourne toutes les lignes de la table de gauche, même si elles n'ont pas de correspondance dans la table de droite (avec NULL pour les colonnes de droite).
|
||||||
|
|
||||||
|
5. **Quelle fonction d'agrégation ignore les valeurs NULL ?**
|
||||||
|
- **Réponse : b) COUNT(colonne)**
|
||||||
|
|
||||||
|
Explication : `COUNT(*)` compte toutes les lignes, y compris celles avec des NULL. `COUNT(colonne)` ne compte que les valeurs non NULL de cette colonne.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
407
SQL/Corrige_TP_Streaming_Musical.md
Normal file
407
SQL/Corrige_TP_Streaming_Musical.md
Normal file
@@ -0,0 +1,407 @@
|
|||||||
|
# Corrigé du TP : Base de données d'un service de streaming musical
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 1 : Schéma relationnel
|
||||||
|
|
||||||
|
```
|
||||||
|
ARTISTES ALBUMS
|
||||||
|
+----+---------+ +----+--------+-----------+
|
||||||
|
| id | nom | | id | titre | id_artiste|
|
||||||
|
| PK | ... |<─────────────────| PK | ... | FK |
|
||||||
|
+----+---------+ +----+--------+-----------+
|
||||||
|
│
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
TITRES
|
||||||
|
+----+------+----------+
|
||||||
|
| id | nom | id_album |
|
||||||
|
| PK | ... | FK |
|
||||||
|
+----+------+----------+
|
||||||
|
│
|
||||||
|
│
|
||||||
|
UTILISATEURS │
|
||||||
|
+----+--------+ │
|
||||||
|
| id | pseudo | │
|
||||||
|
| PK | ... | │
|
||||||
|
+----+--------+ │
|
||||||
|
│ │
|
||||||
|
│ ECOUTES │
|
||||||
|
│ +----+--------------+-----+
|
||||||
|
└────────>| id | id_utilisateur| id_titre |
|
||||||
|
| PK | FK | FK |
|
||||||
|
+----+--------------+----------+
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : Requêtes de base
|
||||||
|
|
||||||
|
**1. Tous les artistes**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM ARTISTES;
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Artistes français**
|
||||||
|
```sql
|
||||||
|
SELECT nom, pays FROM ARTISTES WHERE pays = 'France';
|
||||||
|
```
|
||||||
|
Résultat : Daft Punk, PNL, Aya Nakamura
|
||||||
|
|
||||||
|
**3. Albums sortis après 2015**
|
||||||
|
```sql
|
||||||
|
SELECT titre FROM ALBUMS WHERE ann_sortie > 2015;
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. Utilisateurs premium**
|
||||||
|
```sql
|
||||||
|
SELECT pseudo FROM UTILISATEURS WHERE type_abo = 'premium';
|
||||||
|
```
|
||||||
|
Résultat : music_lover_33, kevin_hiphop, thomas_electro, jp_kpop
|
||||||
|
|
||||||
|
**5. Titres de plus de 4 minutes**
|
||||||
|
```sql
|
||||||
|
SELECT nom, duree_sec FROM TITRES WHERE duree_sec > 240;
|
||||||
|
```
|
||||||
|
|
||||||
|
**6. Artistes Hip-Hop ou Pop**
|
||||||
|
```sql
|
||||||
|
SELECT * FROM ARTISTES WHERE genre_principal IN ('Hip-Hop', 'Pop');
|
||||||
|
-- ou
|
||||||
|
SELECT * FROM ARTISTES WHERE genre_principal = 'Hip-Hop' OR genre_principal = 'Pop';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : Requêtes avec jointures
|
||||||
|
|
||||||
|
**7. Titres avec nom d'album**
|
||||||
|
```sql
|
||||||
|
SELECT T.nom AS titre, A.titre AS album
|
||||||
|
FROM TITRES T
|
||||||
|
INNER JOIN ALBUMS A ON T.id_album = A.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
**8. Titres avec nom d'artiste**
|
||||||
|
```sql
|
||||||
|
SELECT T.nom AS titre, AR.nom AS artiste
|
||||||
|
FROM TITRES T
|
||||||
|
INNER JOIN ALBUMS AL ON T.id_album = AL.id
|
||||||
|
INNER JOIN ARTISTES AR ON AL.id_artiste = AR.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
**9. Albums de Daft Punk**
|
||||||
|
```sql
|
||||||
|
SELECT AL.titre, AL.ann_sortie
|
||||||
|
FROM ALBUMS AL
|
||||||
|
INNER JOIN ARTISTES AR ON AL.id_artiste = AR.id
|
||||||
|
WHERE AR.nom = 'Daft Punk';
|
||||||
|
```
|
||||||
|
Résultat : Random Access Memories (2013), Discovery (2001)
|
||||||
|
|
||||||
|
**10. Écoutes avec pseudo et titre**
|
||||||
|
```sql
|
||||||
|
SELECT U.pseudo, T.nom AS titre, E.date_ecoute
|
||||||
|
FROM ECOUTES E
|
||||||
|
INNER JOIN UTILISATEURS U ON E.id_utilisateur = U.id
|
||||||
|
INNER JOIN TITRES T ON E.id_titre = T.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
**11. Titres des artistes français**
|
||||||
|
```sql
|
||||||
|
SELECT T.nom AS titre, AR.nom AS artiste
|
||||||
|
FROM TITRES T
|
||||||
|
INNER JOIN ALBUMS AL ON T.id_album = AL.id
|
||||||
|
INNER JOIN ARTISTES AR ON AL.id_artiste = AR.id
|
||||||
|
WHERE AR.pays = 'France';
|
||||||
|
```
|
||||||
|
|
||||||
|
**12. Écoutes complètes**
|
||||||
|
```sql
|
||||||
|
SELECT U.pseudo, T.nom AS titre, AL.titre AS album, AR.nom AS artiste
|
||||||
|
FROM ECOUTES E
|
||||||
|
INNER JOIN UTILISATEURS U ON E.id_utilisateur = U.id
|
||||||
|
INNER JOIN TITRES T ON E.id_titre = T.id
|
||||||
|
INNER JOIN ALBUMS AL ON T.id_album = AL.id
|
||||||
|
INNER JOIN ARTISTES AR ON AL.id_artiste = AR.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : Agrégations
|
||||||
|
|
||||||
|
**13. Nombre de titres**
|
||||||
|
```sql
|
||||||
|
SELECT COUNT(*) AS nb_titres FROM TITRES;
|
||||||
|
```
|
||||||
|
Résultat : 43
|
||||||
|
|
||||||
|
**14. Durée moyenne des titres**
|
||||||
|
```sql
|
||||||
|
SELECT AVG(duree_sec) AS duree_moyenne FROM TITRES;
|
||||||
|
```
|
||||||
|
Résultat : environ 227 secondes
|
||||||
|
|
||||||
|
**15. Titre le plus long et le plus court**
|
||||||
|
```sql
|
||||||
|
-- Le plus long
|
||||||
|
SELECT nom, duree_sec FROM TITRES WHERE duree_sec = (SELECT MAX(duree_sec) FROM TITRES);
|
||||||
|
-- Résultat : Get Lucky (369 sec)
|
||||||
|
|
||||||
|
-- Le plus court
|
||||||
|
SELECT nom, duree_sec FROM TITRES WHERE duree_sec = (SELECT MIN(duree_sec) FROM TITRES);
|
||||||
|
-- Résultat : SMS (164 sec)
|
||||||
|
```
|
||||||
|
|
||||||
|
**16. Nombre d'albums par artiste**
|
||||||
|
```sql
|
||||||
|
SELECT AR.nom, COUNT(*) AS nb_albums
|
||||||
|
FROM ARTISTES AR
|
||||||
|
INNER JOIN ALBUMS AL ON AR.id = AL.id_artiste
|
||||||
|
GROUP BY AR.id
|
||||||
|
ORDER BY nb_albums DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**17. Nombre d'écoutes par utilisateur**
|
||||||
|
```sql
|
||||||
|
SELECT U.pseudo, COUNT(*) AS nb_ecoutes
|
||||||
|
FROM UTILISATEURS U
|
||||||
|
INNER JOIN ECOUTES E ON U.id = E.id_utilisateur
|
||||||
|
GROUP BY U.id
|
||||||
|
ORDER BY nb_ecoutes DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**18. Nombre de titres par genre**
|
||||||
|
```sql
|
||||||
|
SELECT AR.genre_principal, COUNT(*) AS nb_titres
|
||||||
|
FROM ARTISTES AR
|
||||||
|
INNER JOIN ALBUMS AL ON AR.id = AL.id_artiste
|
||||||
|
INNER JOIN TITRES T ON AL.id = T.id_album
|
||||||
|
GROUP BY AR.genre_principal
|
||||||
|
ORDER BY nb_titres DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 5 : Requêtes avancées
|
||||||
|
|
||||||
|
**19. Artistes avec plus d'un album**
|
||||||
|
```sql
|
||||||
|
SELECT AR.nom, COUNT(*) AS nb_albums
|
||||||
|
FROM ARTISTES AR
|
||||||
|
INNER JOIN ALBUMS AL ON AR.id = AL.id_artiste
|
||||||
|
GROUP BY AR.id
|
||||||
|
HAVING COUNT(*) > 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
**20. Top 5 des titres les plus écoutés**
|
||||||
|
```sql
|
||||||
|
SELECT T.nom, COUNT(*) AS nb_ecoutes
|
||||||
|
FROM TITRES T
|
||||||
|
INNER JOIN ECOUTES E ON T.id = E.id_titre
|
||||||
|
GROUP BY T.id
|
||||||
|
ORDER BY nb_ecoutes DESC
|
||||||
|
LIMIT 5;
|
||||||
|
```
|
||||||
|
|
||||||
|
**21. Utilisateurs ayant écouté Stromae**
|
||||||
|
```sql
|
||||||
|
SELECT DISTINCT U.pseudo
|
||||||
|
FROM UTILISATEURS U
|
||||||
|
INNER JOIN ECOUTES E ON U.id = E.id_utilisateur
|
||||||
|
INNER JOIN TITRES T ON E.id_titre = T.id
|
||||||
|
INNER JOIN ALBUMS AL ON T.id_album = AL.id
|
||||||
|
INNER JOIN ARTISTES AR ON AL.id_artiste = AR.id
|
||||||
|
WHERE AR.nom = 'Stromae';
|
||||||
|
```
|
||||||
|
|
||||||
|
**22. Durée totale d'écoute par utilisateur (en minutes)**
|
||||||
|
```sql
|
||||||
|
SELECT U.pseudo, SUM(E.duree_ecoute_sec) / 60.0 AS duree_totale_min
|
||||||
|
FROM UTILISATEURS U
|
||||||
|
INNER JOIN ECOUTES E ON U.id = E.id_utilisateur
|
||||||
|
GROUP BY U.id
|
||||||
|
ORDER BY duree_totale_min DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**23. Artistes sans titres explicites**
|
||||||
|
```sql
|
||||||
|
SELECT DISTINCT AR.nom
|
||||||
|
FROM ARTISTES AR
|
||||||
|
WHERE AR.id NOT IN (
|
||||||
|
SELECT DISTINCT AL.id_artiste
|
||||||
|
FROM ALBUMS AL
|
||||||
|
INNER JOIN TITRES T ON AL.id = T.id_album
|
||||||
|
WHERE T.explicite = TRUE
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**24. Écoutes par pays d'utilisateur**
|
||||||
|
```sql
|
||||||
|
SELECT U.pays, COUNT(*) AS nb_ecoutes, SUM(E.duree_ecoute_sec) / 60.0 AS duree_totale_min
|
||||||
|
FROM UTILISATEURS U
|
||||||
|
INNER JOIN ECOUTES E ON U.id = E.id_utilisateur
|
||||||
|
GROUP BY U.pays
|
||||||
|
ORDER BY nb_ecoutes DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 6 : Analyse business
|
||||||
|
|
||||||
|
**25. Taux de conversion**
|
||||||
|
```sql
|
||||||
|
SELECT type_abo, COUNT(*) AS nb_utilisateurs
|
||||||
|
FROM UTILISATEURS
|
||||||
|
GROUP BY type_abo;
|
||||||
|
|
||||||
|
-- Ou en pourcentage
|
||||||
|
SELECT
|
||||||
|
type_abo,
|
||||||
|
COUNT(*) AS nb,
|
||||||
|
ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM UTILISATEURS), 1) AS pourcentage
|
||||||
|
FROM UTILISATEURS
|
||||||
|
GROUP BY type_abo;
|
||||||
|
```
|
||||||
|
|
||||||
|
**26. Artistes populaires auprès des Français**
|
||||||
|
```sql
|
||||||
|
SELECT AR.nom, COUNT(*) AS nb_ecoutes
|
||||||
|
FROM ARTISTES AR
|
||||||
|
INNER JOIN ALBUMS AL ON AR.id = AL.id_artiste
|
||||||
|
INNER JOIN TITRES T ON AL.id = T.id_album
|
||||||
|
INNER JOIN ECOUTES E ON T.id = E.id_titre
|
||||||
|
INNER JOIN UTILISATEURS U ON E.id_utilisateur = U.id
|
||||||
|
WHERE U.pays = 'France'
|
||||||
|
GROUP BY AR.id
|
||||||
|
ORDER BY nb_ecoutes DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
**27. Utilisateurs Daft Punk mais pas Stromae**
|
||||||
|
```sql
|
||||||
|
SELECT DISTINCT U.pseudo
|
||||||
|
FROM UTILISATEURS U
|
||||||
|
INNER JOIN ECOUTES E ON U.id = E.id_utilisateur
|
||||||
|
INNER JOIN TITRES T ON E.id_titre = T.id
|
||||||
|
INNER JOIN ALBUMS AL ON T.id_album = AL.id
|
||||||
|
INNER JOIN ARTISTES AR ON AL.id_artiste = AR.id
|
||||||
|
WHERE AR.nom = 'Daft Punk'
|
||||||
|
AND U.id NOT IN (
|
||||||
|
SELECT DISTINCT U2.id
|
||||||
|
FROM UTILISATEURS U2
|
||||||
|
INNER JOIN ECOUTES E2 ON U2.id = E2.id_utilisateur
|
||||||
|
INNER JOIN TITRES T2 ON E2.id_titre = T2.id
|
||||||
|
INNER JOIN ALBUMS AL2 ON T2.id_album = AL2.id
|
||||||
|
INNER JOIN ARTISTES AR2 ON AL2.id_artiste = AR2.id
|
||||||
|
WHERE AR2.nom = 'Stromae'
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**28. Pourcentage de contenu explicite**
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
ROUND(SUM(CASE WHEN explicite = TRUE THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) AS pct_explicite
|
||||||
|
FROM TITRES;
|
||||||
|
```
|
||||||
|
|
||||||
|
**29. Utilisateurs anciens actifs**
|
||||||
|
```sql
|
||||||
|
SELECT DISTINCT U.pseudo, U.date_inscription
|
||||||
|
FROM UTILISATEURS U
|
||||||
|
INNER JOIN ECOUTES E ON U.id = E.id_utilisateur
|
||||||
|
WHERE U.date_inscription < '2021-01-01'
|
||||||
|
AND E.date_ecoute BETWEEN '2024-01-01' AND '2024-01-31';
|
||||||
|
```
|
||||||
|
|
||||||
|
**30. Vue synthétique par artiste**
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
AR.nom AS artiste,
|
||||||
|
AR.pays,
|
||||||
|
AR.genre_principal AS genre,
|
||||||
|
COUNT(DISTINCT AL.id) AS nb_albums,
|
||||||
|
COUNT(DISTINCT T.id) AS nb_titres,
|
||||||
|
COUNT(E.id) AS nb_ecoutes
|
||||||
|
FROM ARTISTES AR
|
||||||
|
LEFT JOIN ALBUMS AL ON AR.id = AL.id_artiste
|
||||||
|
LEFT JOIN TITRES T ON AL.id = T.id_album
|
||||||
|
LEFT JOIN ECOUTES E ON T.id = E.id_titre
|
||||||
|
GROUP BY AR.id
|
||||||
|
ORDER BY nb_ecoutes DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 7 : Modifications de données
|
||||||
|
|
||||||
|
**31. Ajouter Angèle**
|
||||||
|
```sql
|
||||||
|
INSERT INTO ARTISTES (id, nom, pays, genre_principal, ann_debut)
|
||||||
|
VALUES (11, 'Angèle', 'Belgique', 'Pop', 2014);
|
||||||
|
```
|
||||||
|
|
||||||
|
**32. Ajouter l'album Brol**
|
||||||
|
```sql
|
||||||
|
INSERT INTO ALBUMS (id, titre, id_artiste, ann_sortie, nb_titres)
|
||||||
|
VALUES (19, 'Brol', 11, 2018, 15);
|
||||||
|
```
|
||||||
|
|
||||||
|
**33. Changer l'abonnement d'alex_beats**
|
||||||
|
```sql
|
||||||
|
UPDATE UTILISATEURS
|
||||||
|
SET type_abo = 'etudiant'
|
||||||
|
WHERE pseudo = 'alex_beats';
|
||||||
|
```
|
||||||
|
|
||||||
|
**34. Supprimer les écoutes de jp_kpop**
|
||||||
|
```sql
|
||||||
|
DELETE FROM ECOUTES
|
||||||
|
WHERE id_utilisateur = (SELECT id FROM UTILISATEURS WHERE pseudo = 'jp_kpop');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Questions de réflexion
|
||||||
|
|
||||||
|
**1. Pourquoi séparer artistes et albums ?**
|
||||||
|
- Éviter la redondance : les infos de l'artiste ne sont stockées qu'une fois
|
||||||
|
- Faciliter les mises à jour : changer le pays d'un artiste se fait en un seul endroit
|
||||||
|
- Garantir la cohérence : pas de risque d'avoir "Daft Punk" et "DaftPunk"
|
||||||
|
- C'est le principe de **normalisation** des bases de données
|
||||||
|
|
||||||
|
**2. Problème de suppression d'un artiste**
|
||||||
|
- **Violation de contrainte d'intégrité référentielle** : les albums font référence à cet artiste via la clé étrangère
|
||||||
|
- Solutions possibles :
|
||||||
|
- Empêcher la suppression (comportement par défaut)
|
||||||
|
- Supprimer en cascade tous les albums et titres associés (`ON DELETE CASCADE`)
|
||||||
|
- Mettre la référence à NULL (`ON DELETE SET NULL`)
|
||||||
|
|
||||||
|
**3. Gérer les collaborations**
|
||||||
|
- Créer une **table de liaison** COLLABORATIONS :
|
||||||
|
```sql
|
||||||
|
CREATE TABLE COLLABORATIONS (
|
||||||
|
id_titre INTEGER,
|
||||||
|
id_artiste INTEGER,
|
||||||
|
role TEXT, -- 'principal', 'featuring', 'producteur'
|
||||||
|
PRIMARY KEY (id_titre, id_artiste)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
- Ou modifier la table TITRES pour permettre plusieurs artistes
|
||||||
|
|
||||||
|
**4. Informations supplémentaires pour un vrai service**
|
||||||
|
- **Playlists** : table PLAYLISTS avec les titres associés
|
||||||
|
- **Historique complet** : horodatage précis des écoutes
|
||||||
|
- **Préférences** : genres favoris, artistes suivis
|
||||||
|
- **Social** : amis, partages, playlists collaboratives
|
||||||
|
- **Paiements** : historique des abonnements, factures
|
||||||
|
- **Métadonnées audio** : tempo, tonalité, énergie (pour les recommandations)
|
||||||
|
- **Paroles** : table LYRICS liée aux titres
|
||||||
|
- **Podcasts** : extension du modèle pour d'autres types de contenu
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
554
SQL/Cours_SQL.md
Normal file
554
SQL/Cours_SQL.md
Normal file
@@ -0,0 +1,554 @@
|
|||||||
|
# Cours : Le langage SQL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Introduction
|
||||||
|
|
||||||
|
### 1.1. Historique
|
||||||
|
|
||||||
|
**SQL** (*Structured Query Language*) est un langage de requêtes créé en 1974 par IBM, puis normalisé en 1986. Il permet d'interagir avec les **bases de données relationnelles**.
|
||||||
|
|
||||||
|
SQL est aujourd'hui le standard universel pour :
|
||||||
|
- Créer et modifier la structure des bases de données
|
||||||
|
- Insérer, modifier et supprimer des données
|
||||||
|
- Interroger les données (requêtes)
|
||||||
|
|
||||||
|
### 1.2. Les SGBD
|
||||||
|
|
||||||
|
Un **SGBD** (*Système de Gestion de Base de Données*) est un logiciel qui permet de stocker, organiser et manipuler des données. Exemples :
|
||||||
|
|
||||||
|
| SGBD | Type | Utilisation |
|
||||||
|
|------|------|-------------|
|
||||||
|
| SQLite | Léger, fichier local | Applications mobiles, embarqué |
|
||||||
|
| MySQL / MariaDB | Serveur | Sites web, applications |
|
||||||
|
| PostgreSQL | Serveur avancé | Applications complexes |
|
||||||
|
| Oracle | Entreprise | Grandes entreprises |
|
||||||
|
| Microsoft SQL Server | Entreprise | Environnement Windows |
|
||||||
|
|
||||||
|
En NSI, nous utilisons principalement **SQLite** avec l'outil **DB Browser for SQLite**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Structure d'une base de données
|
||||||
|
|
||||||
|
### 2.1. Tables, attributs et enregistrements
|
||||||
|
|
||||||
|
Une base de données relationnelle est composée de **tables**. Chaque table contient :
|
||||||
|
- Des **colonnes** (ou **attributs**) : les caractéristiques des données
|
||||||
|
- Des **lignes** (ou **enregistrements**) : les données elles-mêmes
|
||||||
|
|
||||||
|
**Exemple : Table LIVRES**
|
||||||
|
|
||||||
|
| id | titre | auteur | ann_publi | note |
|
||||||
|
|----|-------|--------|-----------|------|
|
||||||
|
| 1 | 1984 | Orwell | 1949 | 10 |
|
||||||
|
| 2 | Dune | Herbert | 1965 | 8 |
|
||||||
|
| 3 | Fondation | Asimov | 1951 | 9 |
|
||||||
|
|
||||||
|
### 2.2. Types de données
|
||||||
|
|
||||||
|
| Type SQL | Description | Exemple |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `INT` ou `INTEGER` | Nombre entier | 42, -7, 2024 |
|
||||||
|
| `REAL` ou `FLOAT` | Nombre décimal | 3.14, -0.5 |
|
||||||
|
| `TEXT` ou `VARCHAR` | Chaîne de caractères | 'Bonjour', 'Alice' |
|
||||||
|
| `DATE` | Date | '2024-01-15' |
|
||||||
|
| `BOOLEAN` | Booléen | TRUE, FALSE |
|
||||||
|
|
||||||
|
### 2.3. Clés primaires et étrangères
|
||||||
|
|
||||||
|
- **Clé primaire** (`PRIMARY KEY`) : Identifiant unique de chaque enregistrement
|
||||||
|
- **Clé étrangère** (`FOREIGN KEY`) : Référence vers la clé primaire d'une autre table
|
||||||
|
|
||||||
|
```
|
||||||
|
Table AUTEURS Table LIVRES
|
||||||
|
+----+--------+ +----+--------+-----------+
|
||||||
|
| id | nom | | id | titre | id_auteur |
|
||||||
|
+----+--------+ +----+--------+-----------+
|
||||||
|
| 1 | Orwell |<─────────────────| 1 | 1984 | 1 |
|
||||||
|
| 2 | Asimov |<─────────────────| 2 | Robots | 2 |
|
||||||
|
+----+--------+ +----+--------+-----------+
|
||||||
|
PK PK FK
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Création et modification de tables
|
||||||
|
|
||||||
|
### 3.1. Créer une table : CREATE TABLE
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE LIVRES (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
titre TEXT,
|
||||||
|
auteur TEXT,
|
||||||
|
ann_publi INTEGER,
|
||||||
|
note INTEGER
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2. Créer une table avec clé étrangère
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE LIVRES (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
titre TEXT,
|
||||||
|
id_auteur INTEGER,
|
||||||
|
ann_publi INTEGER,
|
||||||
|
FOREIGN KEY (id_auteur) REFERENCES AUTEURS(id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3. Supprimer une table : DROP TABLE
|
||||||
|
|
||||||
|
```sql
|
||||||
|
DROP TABLE LIVRES;
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ **Attention** : Cette opération est irréversible !
|
||||||
|
|
||||||
|
### 3.4. Modifier une table : ALTER TABLE
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Ajouter une colonne
|
||||||
|
ALTER TABLE LIVRES ADD COLUMN editeur TEXT;
|
||||||
|
|
||||||
|
-- Supprimer une colonne (selon le SGBD)
|
||||||
|
ALTER TABLE LIVRES DROP COLUMN editeur;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Manipulation des données
|
||||||
|
|
||||||
|
### 4.1. Insérer des données : INSERT INTO
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Insérer un enregistrement
|
||||||
|
INSERT INTO LIVRES (id, titre, auteur, ann_publi, note)
|
||||||
|
VALUES (1, '1984', 'Orwell', 1949, 10);
|
||||||
|
|
||||||
|
-- Insérer plusieurs enregistrements
|
||||||
|
INSERT INTO LIVRES (id, titre, auteur, ann_publi, note)
|
||||||
|
VALUES
|
||||||
|
(2, 'Dune', 'Herbert', 1965, 8),
|
||||||
|
(3, 'Fondation', 'Asimov', 1951, 9);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2. Modifier des données : UPDATE
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Modifier tous les enregistrements correspondant à la condition
|
||||||
|
UPDATE LIVRES
|
||||||
|
SET note = 10
|
||||||
|
WHERE auteur = 'Asimov';
|
||||||
|
|
||||||
|
-- Modifier plusieurs colonnes
|
||||||
|
UPDATE LIVRES
|
||||||
|
SET note = 9, ann_publi = 1950
|
||||||
|
WHERE id = 3;
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ **Attention** : Sans clause `WHERE`, tous les enregistrements seront modifiés !
|
||||||
|
|
||||||
|
### 4.3. Supprimer des données : DELETE
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Supprimer les enregistrements correspondant à la condition
|
||||||
|
DELETE FROM LIVRES
|
||||||
|
WHERE ann_publi < 1950;
|
||||||
|
|
||||||
|
-- Supprimer tous les enregistrements (vider la table)
|
||||||
|
DELETE FROM LIVRES;
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ **Attention** : Sans clause `WHERE`, tous les enregistrements seront supprimés !
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Requêtes de sélection : SELECT
|
||||||
|
|
||||||
|
### 5.1. Syntaxe de base
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT colonnes
|
||||||
|
FROM table
|
||||||
|
WHERE conditions
|
||||||
|
ORDER BY colonne [ASC|DESC];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2. Sélectionner des colonnes
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Toutes les colonnes
|
||||||
|
SELECT * FROM LIVRES;
|
||||||
|
|
||||||
|
-- Colonnes spécifiques
|
||||||
|
SELECT titre, auteur FROM LIVRES;
|
||||||
|
|
||||||
|
-- Avec alias
|
||||||
|
SELECT titre AS "Titre du livre", auteur AS "Écrivain"
|
||||||
|
FROM LIVRES;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3. Filtrer avec WHERE
|
||||||
|
|
||||||
|
**Opérateurs de comparaison :**
|
||||||
|
|
||||||
|
| Opérateur | Signification |
|
||||||
|
|-----------|---------------|
|
||||||
|
| `=` | Égal à |
|
||||||
|
| `<>` ou `!=` | Différent de |
|
||||||
|
| `<`, `<=` | Inférieur (ou égal) |
|
||||||
|
| `>`, `>=` | Supérieur (ou égal) |
|
||||||
|
| `BETWEEN a AND b` | Entre a et b (inclus) |
|
||||||
|
| `IN (v1, v2, ...)` | Dans la liste |
|
||||||
|
| `LIKE` | Correspondance de motif |
|
||||||
|
| `IS NULL` | Est nul |
|
||||||
|
|
||||||
|
**Exemples :**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Égalité
|
||||||
|
SELECT * FROM LIVRES WHERE auteur = 'Asimov';
|
||||||
|
|
||||||
|
-- Comparaison
|
||||||
|
SELECT * FROM LIVRES WHERE ann_publi > 1960;
|
||||||
|
|
||||||
|
-- Entre deux valeurs
|
||||||
|
SELECT * FROM LIVRES WHERE ann_publi BETWEEN 1950 AND 1970;
|
||||||
|
|
||||||
|
-- Dans une liste
|
||||||
|
SELECT * FROM LIVRES WHERE auteur IN ('Asimov', 'Bradbury', 'K.Dick');
|
||||||
|
|
||||||
|
-- Correspondance de motif (LIKE)
|
||||||
|
SELECT * FROM LIVRES WHERE titre LIKE '%Robot%';
|
||||||
|
-- % = n'importe quelle suite de caractères
|
||||||
|
-- _ = un seul caractère
|
||||||
|
|
||||||
|
-- Valeur nulle
|
||||||
|
SELECT * FROM LIVRES WHERE note IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4. Combiner les conditions : AND, OR, NOT
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ET logique
|
||||||
|
SELECT * FROM LIVRES
|
||||||
|
WHERE auteur = 'Asimov' AND ann_publi > 1950;
|
||||||
|
|
||||||
|
-- OU logique
|
||||||
|
SELECT * FROM LIVRES
|
||||||
|
WHERE auteur = 'Asimov' OR auteur = 'Bradbury';
|
||||||
|
|
||||||
|
-- Négation
|
||||||
|
SELECT * FROM LIVRES
|
||||||
|
WHERE NOT auteur = 'Asimov';
|
||||||
|
|
||||||
|
-- Combinaison avec parenthèses
|
||||||
|
SELECT * FROM LIVRES
|
||||||
|
WHERE (auteur = 'Asimov' OR auteur = 'Bradbury') AND note >= 8;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.5. Trier les résultats : ORDER BY
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Tri croissant (par défaut)
|
||||||
|
SELECT * FROM LIVRES ORDER BY ann_publi;
|
||||||
|
SELECT * FROM LIVRES ORDER BY ann_publi ASC;
|
||||||
|
|
||||||
|
-- Tri décroissant
|
||||||
|
SELECT * FROM LIVRES ORDER BY note DESC;
|
||||||
|
|
||||||
|
-- Tri multiple
|
||||||
|
SELECT * FROM LIVRES ORDER BY auteur ASC, ann_publi DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.6. Éliminer les doublons : DISTINCT
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Liste des auteurs (sans doublons)
|
||||||
|
SELECT DISTINCT auteur FROM LIVRES;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.7. Limiter les résultats : LIMIT
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Les 5 premiers résultats
|
||||||
|
SELECT * FROM LIVRES LIMIT 5;
|
||||||
|
|
||||||
|
-- Pagination : 5 résultats à partir du 10e
|
||||||
|
SELECT * FROM LIVRES LIMIT 5 OFFSET 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Fonctions d'agrégation
|
||||||
|
|
||||||
|
Les fonctions d'agrégation effectuent des calculs sur un ensemble de valeurs.
|
||||||
|
|
||||||
|
### 6.1. Les fonctions principales
|
||||||
|
|
||||||
|
| Fonction | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `COUNT(*)` | Nombre d'enregistrements |
|
||||||
|
| `COUNT(colonne)` | Nombre de valeurs non nulles |
|
||||||
|
| `SUM(colonne)` | Somme des valeurs |
|
||||||
|
| `AVG(colonne)` | Moyenne des valeurs |
|
||||||
|
| `MIN(colonne)` | Valeur minimale |
|
||||||
|
| `MAX(colonne)` | Valeur maximale |
|
||||||
|
|
||||||
|
### 6.2. Exemples
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Nombre total de livres
|
||||||
|
SELECT COUNT(*) FROM LIVRES;
|
||||||
|
|
||||||
|
-- Nombre de livres par auteur
|
||||||
|
SELECT COUNT(*) FROM LIVRES WHERE auteur = 'Asimov';
|
||||||
|
|
||||||
|
-- Note moyenne
|
||||||
|
SELECT AVG(note) FROM LIVRES;
|
||||||
|
|
||||||
|
-- Note minimale et maximale
|
||||||
|
SELECT MIN(note), MAX(note) FROM LIVRES;
|
||||||
|
|
||||||
|
-- Somme des notes (peu utile ici, mais illustratif)
|
||||||
|
SELECT SUM(note) FROM LIVRES;
|
||||||
|
|
||||||
|
-- Année du livre le plus ancien
|
||||||
|
SELECT MIN(ann_publi) FROM LIVRES;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3. Regrouper les données : GROUP BY
|
||||||
|
|
||||||
|
`GROUP BY` permet de regrouper les enregistrements par valeur d'une colonne.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Nombre de livres par auteur
|
||||||
|
SELECT auteur, COUNT(*) AS nb_livres
|
||||||
|
FROM LIVRES
|
||||||
|
GROUP BY auteur;
|
||||||
|
|
||||||
|
-- Note moyenne par auteur
|
||||||
|
SELECT auteur, AVG(note) AS note_moyenne
|
||||||
|
FROM LIVRES
|
||||||
|
GROUP BY auteur;
|
||||||
|
|
||||||
|
-- Résultat :
|
||||||
|
-- +----------+--------------+
|
||||||
|
-- | auteur | note_moyenne |
|
||||||
|
-- +----------+--------------+
|
||||||
|
-- | Asimov | 8.67 |
|
||||||
|
-- | Bradbury | 7.50 |
|
||||||
|
-- | K.Dick | 8.33 |
|
||||||
|
-- +----------+--------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4. Filtrer les groupes : HAVING
|
||||||
|
|
||||||
|
`HAVING` filtre les résultats **après** le regroupement (contrairement à `WHERE` qui filtre **avant**).
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Auteurs ayant écrit plus de 2 livres
|
||||||
|
SELECT auteur, COUNT(*) AS nb_livres
|
||||||
|
FROM LIVRES
|
||||||
|
GROUP BY auteur
|
||||||
|
HAVING COUNT(*) > 2;
|
||||||
|
|
||||||
|
-- Auteurs avec une note moyenne >= 8
|
||||||
|
SELECT auteur, AVG(note) AS note_moyenne
|
||||||
|
FROM LIVRES
|
||||||
|
GROUP BY auteur
|
||||||
|
HAVING AVG(note) >= 8;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Différence WHERE / HAVING :**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- WHERE filtre les lignes AVANT le regroupement
|
||||||
|
SELECT auteur, AVG(note)
|
||||||
|
FROM LIVRES
|
||||||
|
WHERE ann_publi > 1950 -- Filtre les livres publiés après 1950
|
||||||
|
GROUP BY auteur;
|
||||||
|
|
||||||
|
-- HAVING filtre les groupes APRÈS le regroupement
|
||||||
|
SELECT auteur, AVG(note)
|
||||||
|
FROM LIVRES
|
||||||
|
GROUP BY auteur
|
||||||
|
HAVING AVG(note) > 8; -- Garde les auteurs avec moyenne > 8
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Jointures
|
||||||
|
|
||||||
|
Les jointures permettent de combiner des données provenant de plusieurs tables.
|
||||||
|
|
||||||
|
### 7.1. INNER JOIN (jointure interne)
|
||||||
|
|
||||||
|
Retourne uniquement les enregistrements qui ont une correspondance dans les deux tables.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT LIVRES.titre, AUTEURS.nom, AUTEURS.prenom
|
||||||
|
FROM LIVRES
|
||||||
|
INNER JOIN AUTEURS ON LIVRES.id_auteur = AUTEURS.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Schéma :**
|
||||||
|
```
|
||||||
|
LIVRES AUTEURS Résultat INNER JOIN
|
||||||
|
+----+------+ +----+------+ +------+------+
|
||||||
|
| id | id_a | | id | nom | | titre| nom |
|
||||||
|
+----+------+ +----+------+ +------+------+
|
||||||
|
| 1 | 1 | | 1 | Orwell| | 1984 | Orwell|
|
||||||
|
| 2 | 2 | | 2 | Asimov| | Fond.| Asimov|
|
||||||
|
| 3 | NULL | | 3 | Dick | +------+------+
|
||||||
|
+----+------+ +----+------+ (le livre 3 est exclu car id_a est NULL)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2. LEFT JOIN (jointure externe gauche)
|
||||||
|
|
||||||
|
Retourne tous les enregistrements de la table de gauche, même sans correspondance.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT LIVRES.titre, AUTEURS.nom
|
||||||
|
FROM LIVRES
|
||||||
|
LEFT JOIN AUTEURS ON LIVRES.id_auteur = AUTEURS.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3. Alias de tables
|
||||||
|
|
||||||
|
Pour simplifier l'écriture :
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT L.titre, A.nom, A.prenom
|
||||||
|
FROM LIVRES L
|
||||||
|
INNER JOIN AUTEURS A ON L.id_auteur = A.id
|
||||||
|
WHERE A.langue_ecriture = 'français';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4. Jointures multiples
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT L.titre, A.nom, E.nom_editeur
|
||||||
|
FROM LIVRES L
|
||||||
|
INNER JOIN AUTEURS A ON L.id_auteur = A.id
|
||||||
|
INNER JOIN EDITEURS E ON L.id_editeur = E.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Ordre d'exécution des clauses
|
||||||
|
|
||||||
|
L'ordre d'écriture et l'ordre d'exécution sont différents :
|
||||||
|
|
||||||
|
**Ordre d'écriture :**
|
||||||
|
```sql
|
||||||
|
SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ordre d'exécution :**
|
||||||
|
1. `FROM` et `JOIN` : Sélection des tables
|
||||||
|
2. `WHERE` : Filtrage des lignes
|
||||||
|
3. `GROUP BY` : Regroupement
|
||||||
|
4. `HAVING` : Filtrage des groupes
|
||||||
|
5. `SELECT` : Sélection des colonnes
|
||||||
|
6. `DISTINCT` : Élimination des doublons
|
||||||
|
7. `ORDER BY` : Tri
|
||||||
|
8. `LIMIT` : Limitation du nombre de résultats
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Bonnes pratiques
|
||||||
|
|
||||||
|
### 9.1. Conventions d'écriture
|
||||||
|
|
||||||
|
- Mots-clés SQL en **MAJUSCULES** : `SELECT`, `FROM`, `WHERE`
|
||||||
|
- Noms de tables en **MAJUSCULES** : `LIVRES`, `AUTEURS`
|
||||||
|
- Noms de colonnes en **minuscules** : `titre`, `auteur`
|
||||||
|
- Indentation pour la lisibilité
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT L.titre,
|
||||||
|
A.nom,
|
||||||
|
A.prenom
|
||||||
|
FROM LIVRES L
|
||||||
|
INNER JOIN AUTEURS A ON L.id_auteur = A.id
|
||||||
|
WHERE A.langue_ecriture = 'français'
|
||||||
|
AND L.ann_publi > 1950
|
||||||
|
ORDER BY L.ann_publi DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2. Sécurité : Injections SQL
|
||||||
|
|
||||||
|
⚠️ Ne jamais construire des requêtes SQL par concaténation de chaînes avec des entrées utilisateur !
|
||||||
|
|
||||||
|
```python
|
||||||
|
# DANGEREUX - Injection SQL possible
|
||||||
|
requete = "SELECT * FROM USERS WHERE nom = '" + nom_utilisateur + "'"
|
||||||
|
|
||||||
|
# SÉCURISÉ - Requête paramétrée
|
||||||
|
cursor.execute("SELECT * FROM USERS WHERE nom = ?", (nom_utilisateur,))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Synthèse
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ REQUÊTE SQL │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ SELECT [DISTINCT] colonnes, AGG(colonne) │
|
||||||
|
│ FROM table1 │
|
||||||
|
│ [INNER|LEFT] JOIN table2 ON condition │
|
||||||
|
│ WHERE conditions │
|
||||||
|
│ GROUP BY colonne │
|
||||||
|
│ HAVING condition_agregation │
|
||||||
|
│ ORDER BY colonne [ASC|DESC] │
|
||||||
|
│ LIMIT n [OFFSET m] │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌──────────────┬────────────────────────────────────────────────┐
|
||||||
|
│ Commande │ Action │
|
||||||
|
├──────────────┼────────────────────────────────────────────────┤
|
||||||
|
│ CREATE TABLE │ Créer une table │
|
||||||
|
│ DROP TABLE │ Supprimer une table │
|
||||||
|
│ INSERT INTO │ Insérer des données │
|
||||||
|
│ UPDATE │ Modifier des données │
|
||||||
|
│ DELETE │ Supprimer des données │
|
||||||
|
│ SELECT │ Interroger des données │
|
||||||
|
└──────────────┴────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌──────────────┬────────────────────────────────────────────────┐
|
||||||
|
│ Agrégation │ Description │
|
||||||
|
├──────────────┼────────────────────────────────────────────────┤
|
||||||
|
│ COUNT(*) │ Nombre d'enregistrements │
|
||||||
|
│ SUM(col) │ Somme │
|
||||||
|
│ AVG(col) │ Moyenne │
|
||||||
|
│ MIN(col) │ Minimum │
|
||||||
|
│ MAX(col) │ Maximum │
|
||||||
|
└──────────────┴────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pour aller plus loin
|
||||||
|
|
||||||
|
- **SQL Murder Mystery** : Un jeu pour apprendre SQL en résolvant un meurtre
|
||||||
|
[https://mystery.knightlab.com](https://mystery.knightlab.com)
|
||||||
|
|
||||||
|
- **SQLBolt** : Tutoriel interactif
|
||||||
|
[https://sqlbolt.com](https://sqlbolt.com)
|
||||||
|
|
||||||
|
- **W3Schools SQL** : Référence complète
|
||||||
|
[https://www.w3schools.com/sql/](https://www.w3schools.com/sql/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
279
SQL/EXERCICES.md
Normal file
279
SQL/EXERCICES.md
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
# Exercices — SQL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Pour tous les exercices, on considère la base de données d'une médiathèque contenant les tables suivantes :
|
||||||
|
|
||||||
|
**Table AUTEURS :**
|
||||||
|
|
||||||
|
| id | nom | prenom | nationalite | ann_naissance |
|
||||||
|
|----|-----|--------|-------------|---------------|
|
||||||
|
| 1 | Orwell | George | britannique | 1903 |
|
||||||
|
| 2 | Herbert | Frank | américain | 1920 |
|
||||||
|
| 3 | Asimov | Isaac | américain | 1920 |
|
||||||
|
| 4 | Huxley | Aldous | britannique | 1894 |
|
||||||
|
| 5 | Bradbury | Ray | américain | 1920 |
|
||||||
|
| 6 | Dick | Philip K. | américain | 1928 |
|
||||||
|
| 7 | Barjavel | René | français | 1911 |
|
||||||
|
| 8 | Verne | Jules | français | 1828 |
|
||||||
|
|
||||||
|
**Table LIVRES :**
|
||||||
|
|
||||||
|
| id | titre | id_auteur | ann_publi | genre | nb_pages |
|
||||||
|
|----|-------|-----------|-----------|-------|----------|
|
||||||
|
| 1 | 1984 | 1 | 1949 | dystopie | 328 |
|
||||||
|
| 2 | Dune | 2 | 1965 | space opera | 896 |
|
||||||
|
| 3 | Fondation | 3 | 1951 | space opera | 256 |
|
||||||
|
| 4 | Le meilleur des mondes | 4 | 1931 | dystopie | 288 |
|
||||||
|
| 5 | Fahrenheit 451 | 5 | 1953 | dystopie | 192 |
|
||||||
|
| 6 | Ubik | 6 | 1969 | SF | 224 |
|
||||||
|
| 7 | Chroniques martiennes | 5 | 1950 | SF | 256 |
|
||||||
|
| 8 | La nuit des temps | 7 | 1968 | SF | 318 |
|
||||||
|
| 9 | Les Robots | 3 | 1950 | SF | 253 |
|
||||||
|
| 10 | Ravage | 7 | 1943 | post-apo | 316 |
|
||||||
|
| 11 | De la Terre à la Lune | 8 | 1865 | aventure | 192 |
|
||||||
|
| 12 | Vingt mille lieues sous les mers | 8 | 1870 | aventure | 480 |
|
||||||
|
|
||||||
|
**Table EMPRUNTS :**
|
||||||
|
|
||||||
|
| id | id_livre | id_adherent | date_emprunt | date_retour |
|
||||||
|
|----|----------|-------------|--------------|-------------|
|
||||||
|
| 1 | 1 | 101 | 2024-01-15 | 2024-01-29 |
|
||||||
|
| 2 | 3 | 102 | 2024-01-20 | NULL |
|
||||||
|
| 3 | 5 | 101 | 2024-02-01 | 2024-02-10 |
|
||||||
|
| 4 | 2 | 103 | 2024-02-05 | NULL |
|
||||||
|
| 5 | 1 | 104 | 2024-02-10 | 2024-02-20 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 1 : Requêtes SELECT simples
|
||||||
|
|
||||||
|
Écrire les requêtes SQL permettant d'obtenir :
|
||||||
|
|
||||||
|
1. Tous les livres de la table LIVRES.
|
||||||
|
|
||||||
|
2. Les titres et années de publication de tous les livres.
|
||||||
|
|
||||||
|
3. Les noms et prénoms de tous les auteurs.
|
||||||
|
|
||||||
|
4. Les titres des livres publiés après 1950.
|
||||||
|
|
||||||
|
5. Les titres des livres du genre "dystopie".
|
||||||
|
|
||||||
|
6. Les auteurs de nationalité française.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 2 : Filtres et conditions
|
||||||
|
|
||||||
|
Écrire les requêtes SQL permettant d'obtenir :
|
||||||
|
|
||||||
|
1. Les livres publiés entre 1940 et 1960 (inclus).
|
||||||
|
|
||||||
|
2. Les livres ayant plus de 300 pages.
|
||||||
|
|
||||||
|
3. Les auteurs nés après 1900 et de nationalité américaine.
|
||||||
|
|
||||||
|
4. Les livres dont le titre contient le mot "Terre".
|
||||||
|
|
||||||
|
5. Les livres qui ne sont pas du genre "SF".
|
||||||
|
|
||||||
|
6. Les auteurs dont le prénom commence par la lettre "R".
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 3 : Tri et limitation
|
||||||
|
|
||||||
|
Écrire les requêtes SQL permettant d'obtenir :
|
||||||
|
|
||||||
|
1. Tous les livres triés par année de publication (du plus ancien au plus récent).
|
||||||
|
|
||||||
|
2. Les 5 livres les plus récents.
|
||||||
|
|
||||||
|
3. Les auteurs triés par année de naissance décroissante.
|
||||||
|
|
||||||
|
4. Les 3 livres ayant le plus de pages.
|
||||||
|
|
||||||
|
5. Les livres triés par genre, puis par titre alphabétique.
|
||||||
|
|
||||||
|
6. La liste des genres sans doublons.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 4 : Fonctions d'agrégation
|
||||||
|
|
||||||
|
Écrire les requêtes SQL permettant d'obtenir :
|
||||||
|
|
||||||
|
1. Le nombre total de livres dans la base.
|
||||||
|
|
||||||
|
2. Le nombre de pages du livre le plus long.
|
||||||
|
|
||||||
|
3. L'année de publication du livre le plus ancien.
|
||||||
|
|
||||||
|
4. Le nombre moyen de pages des livres.
|
||||||
|
|
||||||
|
5. Le nombre de livres par genre.
|
||||||
|
|
||||||
|
6. Le nombre total de pages de tous les livres de Jules Verne (id_auteur = 8).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 5 : GROUP BY et HAVING
|
||||||
|
|
||||||
|
Écrire les requêtes SQL permettant d'obtenir :
|
||||||
|
|
||||||
|
1. Le nombre de livres écrits par chaque auteur (afficher id_auteur et le compte).
|
||||||
|
|
||||||
|
2. Le nombre moyen de pages par genre.
|
||||||
|
|
||||||
|
3. Les genres ayant plus de 2 livres.
|
||||||
|
|
||||||
|
4. Les auteurs ayant écrit au moins 2 livres (afficher id_auteur et le nombre).
|
||||||
|
|
||||||
|
5. L'année de publication du premier livre de chaque auteur.
|
||||||
|
|
||||||
|
6. Les genres dont la moyenne de pages dépasse 250.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 6 : Jointures
|
||||||
|
|
||||||
|
Écrire les requêtes SQL permettant d'obtenir :
|
||||||
|
|
||||||
|
1. Les titres des livres avec le nom et prénom de leur auteur.
|
||||||
|
|
||||||
|
2. Les titres des livres écrits par des auteurs français.
|
||||||
|
|
||||||
|
3. Les titres et auteurs des livres du genre "dystopie", triés par année.
|
||||||
|
|
||||||
|
4. Le nom des auteurs ayant écrit des livres de plus de 400 pages.
|
||||||
|
|
||||||
|
5. Les livres (titre) avec le nom de l'auteur, uniquement pour les auteurs américains nés après 1910.
|
||||||
|
|
||||||
|
6. Pour chaque nationalité, le nombre de livres écrits par des auteurs de cette nationalité.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 7 : Requêtes complexes
|
||||||
|
|
||||||
|
Écrire les requêtes SQL permettant d'obtenir :
|
||||||
|
|
||||||
|
1. Le titre du livre le plus long de chaque auteur, avec le nom de l'auteur.
|
||||||
|
|
||||||
|
2. Les auteurs n'ayant écrit qu'un seul livre (afficher nom, prénom, titre du livre).
|
||||||
|
|
||||||
|
3. Le nombre moyen de pages des livres par nationalité d'auteur.
|
||||||
|
|
||||||
|
4. Les genres pour lesquels tous les livres ont été publiés après 1940.
|
||||||
|
|
||||||
|
5. Le nom complet (prénom + nom) des auteurs et le nombre total de pages qu'ils ont écrites.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 8 : INSERT, UPDATE, DELETE
|
||||||
|
|
||||||
|
1. Écrire une requête pour ajouter l'auteur suivant :
|
||||||
|
- id : 9
|
||||||
|
- nom : "Le Guin"
|
||||||
|
- prénom : "Ursula K."
|
||||||
|
- nationalité : "américain"
|
||||||
|
- année de naissance : 1929
|
||||||
|
|
||||||
|
2. Écrire une requête pour ajouter le livre suivant :
|
||||||
|
- id : 13
|
||||||
|
- titre : "La Main gauche de la nuit"
|
||||||
|
- id_auteur : 9
|
||||||
|
- année de publication : 1969
|
||||||
|
- genre : "SF"
|
||||||
|
- nombre de pages : 304
|
||||||
|
|
||||||
|
3. Écrire une requête pour changer le genre de "Dune" en "SF".
|
||||||
|
|
||||||
|
4. Écrire une requête pour augmenter de 10% le nombre de pages de tous les livres de Jules Verne.
|
||||||
|
|
||||||
|
5. Écrire une requête pour supprimer tous les livres publiés avant 1900.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 9 : Création de tables
|
||||||
|
|
||||||
|
1. Écrire la requête SQL pour créer la table EMPRUNTS avec :
|
||||||
|
- id (entier, clé primaire)
|
||||||
|
- id_livre (entier, clé étrangère vers LIVRES)
|
||||||
|
- id_adherent (entier)
|
||||||
|
- date_emprunt (date)
|
||||||
|
- date_retour (date, peut être NULL)
|
||||||
|
|
||||||
|
2. Écrire la requête SQL pour créer une table EDITEURS avec :
|
||||||
|
- id (entier, clé primaire)
|
||||||
|
- nom (texte)
|
||||||
|
- pays (texte)
|
||||||
|
- ann_creation (entier)
|
||||||
|
|
||||||
|
3. Comment modifier la table LIVRES pour ajouter une colonne id_editeur qui référence la table EDITEURS ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 10 : Analyse de requêtes
|
||||||
|
|
||||||
|
Pour chaque requête suivante, décrire en français ce qu'elle retourne :
|
||||||
|
|
||||||
|
1. ```sql
|
||||||
|
SELECT titre FROM LIVRES WHERE ann_publi = (SELECT MIN(ann_publi) FROM LIVRES);
|
||||||
|
```
|
||||||
|
|
||||||
|
2. ```sql
|
||||||
|
SELECT A.nom, COUNT(*)
|
||||||
|
FROM AUTEURS A
|
||||||
|
INNER JOIN LIVRES L ON A.id = L.id_auteur
|
||||||
|
GROUP BY A.id
|
||||||
|
HAVING COUNT(*) = (SELECT MAX(cnt) FROM (SELECT COUNT(*) as cnt FROM LIVRES GROUP BY id_auteur));
|
||||||
|
```
|
||||||
|
|
||||||
|
3. ```sql
|
||||||
|
SELECT DISTINCT genre FROM LIVRES WHERE genre NOT IN (SELECT genre FROM LIVRES WHERE ann_publi < 1950);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 11 : QCM
|
||||||
|
|
||||||
|
1. Quelle clause permet de filtrer les résultats d'un GROUP BY ?
|
||||||
|
- [ ] a) WHERE
|
||||||
|
- [ ] b) FILTER
|
||||||
|
- [ ] c) HAVING
|
||||||
|
- [ ] d) GROUP FILTER
|
||||||
|
|
||||||
|
2. Quelle est la différence entre WHERE et HAVING ?
|
||||||
|
- [ ] a) Aucune, ils sont équivalents
|
||||||
|
- [ ] b) WHERE filtre avant GROUP BY, HAVING filtre après
|
||||||
|
- [ ] c) HAVING filtre avant GROUP BY, WHERE filtre après
|
||||||
|
- [ ] d) WHERE ne fonctionne qu'avec les nombres
|
||||||
|
|
||||||
|
3. Que retourne `SELECT COUNT(*) FROM LIVRES WHERE 1=0;` ?
|
||||||
|
- [ ] a) Une erreur
|
||||||
|
- [ ] b) NULL
|
||||||
|
- [ ] c) 0
|
||||||
|
- [ ] d) Rien
|
||||||
|
|
||||||
|
4. Quel type de jointure retourne tous les enregistrements de la table de gauche ?
|
||||||
|
- [ ] a) INNER JOIN
|
||||||
|
- [ ] b) LEFT JOIN
|
||||||
|
- [ ] c) RIGHT JOIN
|
||||||
|
- [ ] d) CROSS JOIN
|
||||||
|
|
||||||
|
5. Quelle fonction d'agrégation ignore les valeurs NULL ?
|
||||||
|
- [ ] a) COUNT(*)
|
||||||
|
- [ ] b) COUNT(colonne)
|
||||||
|
- [ ] c) Les deux
|
||||||
|
- [ ] d) Aucune
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
Ce TP vous guidera à travers la création et la manipulation d'une base de données en utilisant le logiciel **DB Browser for SQLite**. Vous apprendrez à créer des tables, insérer des données et exécuter des requêtes SQL pour interroger la base de données.
|
Ce TP vous guidera à travers la création et la manipulation d'une base de données en utilisant le logiciel **DB Browser for SQLite**. Vous apprendrez à créer des tables, insérer des données et exécuter des requêtes SQL pour interroger la base de données.
|
||||||
|
|
||||||
|
> **Prérequis** : Avoir lu le cours [Cours_SQL.md](Cours_SQL.md) avant de commencer ce TP.
|
||||||
|
|
||||||
## Prérequis
|
## Prérequis
|
||||||
|
|
||||||
- Télécharger et installer [DB Browser for SQLite](https://sqlitebrowser.org/).
|
- Télécharger et installer [DB Browser for SQLite](https://sqlitebrowser.org/).
|
||||||
@@ -46,7 +48,7 @@ Ce TP vous guidera à travers la création et la manipulation d'une base de donn
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
- Cliquez sur le triangle vert (ou appuyez sur F5) pour exécuter la requête.
|
- Cliquez sur le bouton ▶ (ou appuyez sur F5) pour exécuter la requête.
|
||||||
- Un message "Requête exécutée avec succès" devrait apparaître.
|
- Un message "Requête exécutée avec succès" devrait apparaître.
|
||||||
|
|
||||||

|

|
||||||
@@ -77,7 +79,7 @@ Ce TP vous guidera à travers la création et la manipulation d'une base de donn
|
|||||||
(16, 'De la Terre à la Lune', 'Verne', 1865, 10);
|
(16, 'De la Terre à la Lune', 'Verne', 1865, 10);
|
||||||
```
|
```
|
||||||
|
|
||||||
- Exécutez la requête en cliquant sur le triangle bleu ou en appuyant sur F5.
|
- Exécutez la requête en cliquant sur le bouton ▶ ou en appuyant sur F5.
|
||||||
- Un message de confirmation devrait apparaître.
|
- Un message de confirmation devrait apparaître.
|
||||||
|
|
||||||

|

|
||||||
@@ -140,7 +142,7 @@ Ce TP vous guidera à travers la création et la manipulation d'une base de donn
|
|||||||
WHERE auteur='Asimov' AND ann_publi>1953
|
WHERE auteur='Asimov' AND ann_publi>1953
|
||||||
```
|
```
|
||||||
|
|
||||||
Vérifiez que nous obtenons bien le livre écrit par Asimov publié après 1953.
|
Vérifiez que vous obtenez bien les livres écrits par Asimov publiés après 1953.
|
||||||
|
|
||||||
8. Écrivez une requête permettant d'obtenir les titres des livres publiés après 1945 qui ont une note supérieure ou égale à 9.
|
8. Écrivez une requête permettant d'obtenir les titres des livres publiés après 1945 qui ont une note supérieure ou égale à 9.
|
||||||
|
|
||||||
@@ -253,7 +255,7 @@ VALUES
|
|||||||
14. À l'aide d'une requête SQL, ajoutez à la table LIVRES le livre suivant :
|
14. À l'aide d'une requête SQL, ajoutez à la table LIVRES le livre suivant :
|
||||||
- id : 17
|
- id : 17
|
||||||
- titre : "2001 : L'Odyssée de l'espace"
|
- titre : "2001 : L'Odyssée de l'espace"
|
||||||
- auteur : "Clarcke"
|
- auteur : "Clarke"
|
||||||
- année de publication : 1968
|
- année de publication : 1968
|
||||||
- note : 7
|
- note : 7
|
||||||
|
|
||||||
@@ -268,4 +270,11 @@ Source :
|
|||||||
|
|
||||||
- [Pixees, site de David Roche](https://informatique-lycee.forge.apps.education.fr/terminale_nsi/cours-terminale/c3a/)
|
- [Pixees, site de David Roche](https://informatique-lycee.forge.apps.education.fr/terminale_nsi/cours-terminale/c3a/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
|
|
||||||
|
|||||||
359
SQL/TP_Streaming_Musical.md
Normal file
359
SQL/TP_Streaming_Musical.md
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
# TP : Base de données d'un service de streaming musical
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Vous êtes data analyst chez **Musicfy**, un service de streaming musical concurrent de Spotify et Deezer. Votre mission est d'analyser les données d'écoute pour comprendre les habitudes des utilisateurs et proposer des améliorations.
|
||||||
|
|
||||||
|
Vous disposez d'une base de données contenant les informations sur les artistes, albums, titres, utilisateurs et leurs écoutes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Objectifs
|
||||||
|
|
||||||
|
- Pratiquer les requêtes SQL sur une base de données réaliste
|
||||||
|
- Maîtriser les jointures entre plusieurs tables
|
||||||
|
- Utiliser les fonctions d'agrégation pour l'analyse de données
|
||||||
|
- Comprendre l'organisation d'une base de données relationnelle
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 1 : Découverte de la base de données
|
||||||
|
|
||||||
|
### 1.1. Création de la base
|
||||||
|
|
||||||
|
1. Ouvrez **DB Browser for SQLite** et créez une nouvelle base de données `musicfy.db`.
|
||||||
|
|
||||||
|
2. Exécutez les requêtes suivantes pour créer les tables :
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Table des artistes
|
||||||
|
CREATE TABLE ARTISTES (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
nom TEXT NOT NULL,
|
||||||
|
pays TEXT,
|
||||||
|
genre_principal TEXT,
|
||||||
|
ann_debut INTEGER
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Table des albums
|
||||||
|
CREATE TABLE ALBUMS (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
titre TEXT NOT NULL,
|
||||||
|
id_artiste INTEGER,
|
||||||
|
ann_sortie INTEGER,
|
||||||
|
nb_titres INTEGER,
|
||||||
|
FOREIGN KEY (id_artiste) REFERENCES ARTISTES(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Table des titres (chansons)
|
||||||
|
CREATE TABLE TITRES (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
nom TEXT NOT NULL,
|
||||||
|
id_album INTEGER,
|
||||||
|
duree_sec INTEGER,
|
||||||
|
explicite BOOLEAN DEFAULT FALSE,
|
||||||
|
FOREIGN KEY (id_album) REFERENCES ALBUMS(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Table des utilisateurs
|
||||||
|
CREATE TABLE UTILISATEURS (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
pseudo TEXT NOT NULL,
|
||||||
|
email TEXT UNIQUE,
|
||||||
|
pays TEXT,
|
||||||
|
type_abo TEXT DEFAULT 'gratuit',
|
||||||
|
date_inscription DATE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Table des écoutes
|
||||||
|
CREATE TABLE ECOUTES (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
id_utilisateur INTEGER,
|
||||||
|
id_titre INTEGER,
|
||||||
|
date_ecoute DATE,
|
||||||
|
duree_ecoute_sec INTEGER,
|
||||||
|
FOREIGN KEY (id_utilisateur) REFERENCES UTILISATEURS(id),
|
||||||
|
FOREIGN KEY (id_titre) REFERENCES TITRES(id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2. Insertion des données
|
||||||
|
|
||||||
|
Exécutez les requêtes suivantes pour insérer les données :
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Artistes
|
||||||
|
INSERT INTO ARTISTES (id, nom, pays, genre_principal, ann_debut) VALUES
|
||||||
|
(1, 'Daft Punk', 'France', 'Electro', 1993),
|
||||||
|
(2, 'Kendrick Lamar', 'USA', 'Hip-Hop', 2004),
|
||||||
|
(3, 'Adele', 'Royaume-Uni', 'Pop', 2006),
|
||||||
|
(4, 'BTS', 'Corée du Sud', 'K-Pop', 2013),
|
||||||
|
(5, 'Stromae', 'Belgique', 'Electro', 2009),
|
||||||
|
(6, 'Taylor Swift', 'USA', 'Pop', 2004),
|
||||||
|
(7, 'PNL', 'France', 'Hip-Hop', 2014),
|
||||||
|
(8, 'Beyoncé', 'USA', 'R&B', 1997),
|
||||||
|
(9, 'Ed Sheeran', 'Royaume-Uni', 'Pop', 2004),
|
||||||
|
(10, 'Aya Nakamura', 'France', 'Pop', 2014);
|
||||||
|
|
||||||
|
-- Albums
|
||||||
|
INSERT INTO ALBUMS (id, titre, id_artiste, ann_sortie, nb_titres) VALUES
|
||||||
|
(1, 'Random Access Memories', 1, 2013, 13),
|
||||||
|
(2, 'Discovery', 1, 2001, 14),
|
||||||
|
(3, 'DAMN.', 2, 2017, 14),
|
||||||
|
(4, 'To Pimp a Butterfly', 2, 2015, 16),
|
||||||
|
(5, '21', 3, 2011, 11),
|
||||||
|
(6, '30', 3, 2021, 12),
|
||||||
|
(7, 'Map of the Soul: 7', 4, 2020, 20),
|
||||||
|
(8, 'Racine carrée', 5, 2013, 13),
|
||||||
|
(9, 'Multitude', 5, 2022, 12),
|
||||||
|
(10, '1989', 6, 2014, 13),
|
||||||
|
(11, 'Midnights', 6, 2022, 13),
|
||||||
|
(12, 'Dans la légende', 7, 2016, 17),
|
||||||
|
(13, 'Deux frères', 7, 2019, 18),
|
||||||
|
(14, 'Lemonade', 8, 2016, 12),
|
||||||
|
(15, 'Renaissance', 8, 2022, 16),
|
||||||
|
(16, 'Divide', 9, 2017, 16),
|
||||||
|
(17, 'Nakamura', 10, 2018, 14),
|
||||||
|
(18, 'DNK', 10, 2023, 15);
|
||||||
|
|
||||||
|
-- Titres (sélection)
|
||||||
|
INSERT INTO TITRES (id, nom, id_album, duree_sec, explicite) VALUES
|
||||||
|
(1, 'Get Lucky', 1, 369, FALSE),
|
||||||
|
(2, 'Instant Crush', 1, 337, FALSE),
|
||||||
|
(3, 'Lose Yourself to Dance', 1, 353, FALSE),
|
||||||
|
(4, 'One More Time', 2, 320, FALSE),
|
||||||
|
(5, 'Digital Love', 2, 301, FALSE),
|
||||||
|
(6, 'Harder Better Faster Stronger', 2, 224, FALSE),
|
||||||
|
(7, 'HUMBLE.', 3, 177, TRUE),
|
||||||
|
(8, 'DNA.', 3, 185, TRUE),
|
||||||
|
(9, 'LOYALTY.', 3, 227, TRUE),
|
||||||
|
(10, 'Alright', 4, 219, TRUE),
|
||||||
|
(11, 'King Kunta', 4, 234, TRUE),
|
||||||
|
(12, 'Rolling in the Deep', 5, 228, FALSE),
|
||||||
|
(13, 'Someone Like You', 5, 285, FALSE),
|
||||||
|
(14, 'Set Fire to the Rain', 5, 241, FALSE),
|
||||||
|
(15, 'Easy On Me', 6, 224, FALSE),
|
||||||
|
(16, 'Oh My My', 6, 218, FALSE),
|
||||||
|
(17, 'Dynamite', 7, 199, FALSE),
|
||||||
|
(18, 'ON', 7, 242, FALSE),
|
||||||
|
(19, 'Papaoutai', 8, 234, FALSE),
|
||||||
|
(20, 'Formidable', 8, 192, FALSE),
|
||||||
|
(21, 'Alors on danse', 8, 205, FALSE),
|
||||||
|
(22, 'Santé', 9, 188, FALSE),
|
||||||
|
(23, 'L''enfer', 9, 224, FALSE),
|
||||||
|
(24, 'Shake It Off', 10, 219, FALSE),
|
||||||
|
(25, 'Blank Space', 10, 231, FALSE),
|
||||||
|
(26, 'Style', 10, 231, FALSE),
|
||||||
|
(27, 'Anti-Hero', 11, 201, FALSE),
|
||||||
|
(28, 'Lavender Haze', 11, 202, FALSE),
|
||||||
|
(29, 'Le monde ou rien', 12, 228, TRUE),
|
||||||
|
(30, 'DA', 12, 195, TRUE),
|
||||||
|
(31, 'Au DD', 13, 199, TRUE),
|
||||||
|
(32, 'A l''ammoniaque', 13, 234, TRUE),
|
||||||
|
(33, 'Formation', 14, 226, TRUE),
|
||||||
|
(34, 'Sorry', 14, 232, FALSE),
|
||||||
|
(35, 'BREAK MY SOUL', 15, 279, FALSE),
|
||||||
|
(36, 'CUFF IT', 15, 225, FALSE),
|
||||||
|
(37, 'Shape of You', 16, 233, FALSE),
|
||||||
|
(38, 'Castle on the Hill', 16, 261, FALSE),
|
||||||
|
(39, 'Perfect', 16, 263, FALSE),
|
||||||
|
(40, 'Djadja', 17, 176, FALSE),
|
||||||
|
(41, 'Pookie', 17, 185, TRUE),
|
||||||
|
(42, 'Copines', 18, 179, FALSE),
|
||||||
|
(43, 'SMS', 18, 164, FALSE);
|
||||||
|
|
||||||
|
-- Utilisateurs
|
||||||
|
INSERT INTO UTILISATEURS (id, pseudo, email, pays, type_abo, date_inscription) VALUES
|
||||||
|
(1, 'music_lover_33', 'music33@email.com', 'France', 'premium', '2020-03-15'),
|
||||||
|
(2, 'alex_beats', 'alex.b@email.com', 'France', 'gratuit', '2021-06-22'),
|
||||||
|
(3, 'sophie_melody', 'sophie.m@email.com', 'Belgique', 'premium', '2019-11-08'),
|
||||||
|
(4, 'dj_max', 'maxime.dj@email.com', 'France', 'famille', '2022-01-10'),
|
||||||
|
(5, 'luna_music', 'luna.music@email.com', 'Canada', 'etudiant', '2021-09-01'),
|
||||||
|
(6, 'kevin_hiphop', 'kevin.hh@email.com', 'USA', 'premium', '2020-07-19'),
|
||||||
|
(7, 'emma_pop', 'emma.pop@email.com', 'Royaume-Uni', 'gratuit', '2023-02-28'),
|
||||||
|
(8, 'thomas_electro', 'thomas.e@email.com', 'France', 'premium', '2018-05-12'),
|
||||||
|
(9, 'clara_world', 'clara.w@email.com', 'Espagne', 'etudiant', '2022-10-05'),
|
||||||
|
(10, 'jp_kpop', 'jp.kpop@email.com', 'Japon', 'premium', '2021-03-20');
|
||||||
|
|
||||||
|
-- Écoutes (échantillon)
|
||||||
|
INSERT INTO ECOUTES (id, id_utilisateur, id_titre, date_ecoute, duree_ecoute_sec) VALUES
|
||||||
|
(1, 1, 1, '2024-01-15', 369),
|
||||||
|
(2, 1, 4, '2024-01-15', 320),
|
||||||
|
(3, 1, 19, '2024-01-15', 234),
|
||||||
|
(4, 2, 7, '2024-01-15', 177),
|
||||||
|
(5, 2, 8, '2024-01-15', 150),
|
||||||
|
(6, 2, 29, '2024-01-15', 228),
|
||||||
|
(7, 3, 19, '2024-01-16', 234),
|
||||||
|
(8, 3, 20, '2024-01-16', 192),
|
||||||
|
(9, 3, 22, '2024-01-16', 188),
|
||||||
|
(10, 4, 1, '2024-01-16', 369),
|
||||||
|
(11, 4, 6, '2024-01-16', 224),
|
||||||
|
(12, 5, 17, '2024-01-16', 199),
|
||||||
|
(13, 5, 18, '2024-01-16', 242),
|
||||||
|
(14, 6, 7, '2024-01-17', 177),
|
||||||
|
(15, 6, 10, '2024-01-17', 219),
|
||||||
|
(16, 6, 11, '2024-01-17', 234),
|
||||||
|
(17, 7, 12, '2024-01-17', 228),
|
||||||
|
(18, 7, 24, '2024-01-17', 219),
|
||||||
|
(19, 7, 37, '2024-01-17', 233),
|
||||||
|
(20, 8, 1, '2024-01-17', 369),
|
||||||
|
(21, 8, 4, '2024-01-17', 320),
|
||||||
|
(22, 8, 5, '2024-01-17', 301),
|
||||||
|
(23, 8, 6, '2024-01-17', 224),
|
||||||
|
(24, 9, 40, '2024-01-18', 176),
|
||||||
|
(25, 9, 42, '2024-01-18', 179),
|
||||||
|
(26, 10, 17, '2024-01-18', 199),
|
||||||
|
(27, 10, 18, '2024-01-18', 242),
|
||||||
|
(28, 1, 19, '2024-01-18', 234),
|
||||||
|
(29, 1, 21, '2024-01-18', 205),
|
||||||
|
(30, 2, 31, '2024-01-18', 199),
|
||||||
|
(31, 3, 23, '2024-01-19', 224),
|
||||||
|
(32, 4, 35, '2024-01-19', 279),
|
||||||
|
(33, 5, 27, '2024-01-19', 201),
|
||||||
|
(34, 6, 8, '2024-01-19', 185),
|
||||||
|
(35, 7, 39, '2024-01-19', 263),
|
||||||
|
(36, 8, 2, '2024-01-19', 337),
|
||||||
|
(37, 9, 41, '2024-01-20', 185),
|
||||||
|
(38, 10, 17, '2024-01-20', 199),
|
||||||
|
(39, 1, 1, '2024-01-20', 369),
|
||||||
|
(40, 2, 7, '2024-01-20', 177);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3. Schéma de la base
|
||||||
|
|
||||||
|
Dessinez le schéma relationnel de la base de données en identifiant :
|
||||||
|
- Les clés primaires (PK)
|
||||||
|
- Les clés étrangères (FK)
|
||||||
|
- Les relations entre les tables
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 2 : Requêtes de base
|
||||||
|
|
||||||
|
**Pour chaque question, écrivez la requête SQL et notez le résultat.**
|
||||||
|
|
||||||
|
1. Affichez tous les artistes de la base de données.
|
||||||
|
|
||||||
|
2. Affichez le nom et le pays des artistes français.
|
||||||
|
|
||||||
|
3. Affichez les titres de tous les albums sortis après 2015.
|
||||||
|
|
||||||
|
4. Affichez les pseudos des utilisateurs ayant un abonnement premium.
|
||||||
|
|
||||||
|
5. Affichez les titres (chansons) qui durent plus de 4 minutes (240 secondes).
|
||||||
|
|
||||||
|
6. Affichez les artistes dont le genre principal est "Hip-Hop" ou "Pop".
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 3 : Requêtes avec jointures
|
||||||
|
|
||||||
|
7. Affichez le nom des titres avec le nom de leur album.
|
||||||
|
|
||||||
|
8. Affichez le nom des titres avec le nom de l'artiste qui les a créés.
|
||||||
|
*(Indice : vous devez passer par la table ALBUMS)*
|
||||||
|
|
||||||
|
9. Affichez les albums de Daft Punk avec leur année de sortie.
|
||||||
|
|
||||||
|
10. Affichez la liste des écoutes avec le pseudo de l'utilisateur et le nom du titre écouté.
|
||||||
|
|
||||||
|
11. Affichez tous les titres des artistes français.
|
||||||
|
|
||||||
|
12. Pour chaque écoute, affichez le pseudo de l'utilisateur, le nom du titre, le nom de l'album et le nom de l'artiste.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 4 : Agrégations
|
||||||
|
|
||||||
|
13. Combien y a-t-il de titres dans la base de données ?
|
||||||
|
|
||||||
|
14. Quelle est la durée moyenne des titres (en secondes) ?
|
||||||
|
|
||||||
|
15. Quel est le titre le plus long ? Le plus court ?
|
||||||
|
|
||||||
|
16. Combien d'albums a sorti chaque artiste ? Affichez le nom de l'artiste et le nombre d'albums.
|
||||||
|
|
||||||
|
17. Combien d'écoutes ont été effectuées par chaque utilisateur ? Affichez le pseudo et le nombre d'écoutes.
|
||||||
|
|
||||||
|
18. Quel est le nombre total de titres par genre musical ?
|
||||||
|
*(Indice : il faut passer par ARTISTES et ALBUMS)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 5 : Requêtes avancées
|
||||||
|
|
||||||
|
19. Quels sont les artistes ayant sorti plus d'un album ?
|
||||||
|
|
||||||
|
20. Quels sont les 5 titres les plus écoutés ? Affichez le nom du titre et le nombre d'écoutes.
|
||||||
|
|
||||||
|
21. Quels utilisateurs ont écouté des titres de Stromae ?
|
||||||
|
|
||||||
|
22. Quelle est la durée totale d'écoute (en minutes) par utilisateur ?
|
||||||
|
|
||||||
|
23. Quels artistes n'ont aucun titre explicite dans leurs chansons ?
|
||||||
|
|
||||||
|
24. Pour chaque pays d'utilisateur, calculez le nombre d'écoutes et la durée totale d'écoute.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 6 : Analyse business
|
||||||
|
|
||||||
|
Le département marketing vous demande des analyses pour améliorer le service.
|
||||||
|
|
||||||
|
25. **Taux de conversion** : Combien d'utilisateurs ont un abonnement payant (premium, famille ou étudiant) vs gratuit ?
|
||||||
|
|
||||||
|
26. **Artistes populaires en France** : Quels artistes sont les plus écoutés par les utilisateurs français ?
|
||||||
|
|
||||||
|
27. **Recommandations** : Trouvez les utilisateurs qui ont écouté Daft Punk mais pas Stromae (ces utilisateurs pourraient aimer Stromae).
|
||||||
|
|
||||||
|
28. **Contenu explicite** : Quel pourcentage des titres est marqué comme explicite ?
|
||||||
|
|
||||||
|
29. **Rétention** : Quels utilisateurs inscrits avant 2021 ont écouté de la musique en janvier 2024 ?
|
||||||
|
|
||||||
|
30. **Rapport final** : Créez une vue synthétique montrant pour chaque artiste : son nom, son pays, son genre, le nombre d'albums, le nombre de titres et le nombre total d'écoutes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Partie 7 : Modifications de données (Bonus)
|
||||||
|
|
||||||
|
31. Ajoutez l'artiste "Angèle" (Belgique, Pop, 2014).
|
||||||
|
|
||||||
|
32. Ajoutez son album "Brol" sorti en 2018 avec 15 titres.
|
||||||
|
|
||||||
|
33. Un utilisateur change d'abonnement : mettez à jour `alex_beats` vers l'abonnement "etudiant".
|
||||||
|
|
||||||
|
34. Supprimez toutes les écoutes de l'utilisateur `jp_kpop`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Questions de réflexion
|
||||||
|
|
||||||
|
1. Pourquoi a-t-on séparé les artistes et les albums dans des tables différentes au lieu de tout mettre dans une seule table ?
|
||||||
|
|
||||||
|
2. Quel problème pourrait survenir si on supprimait un artiste qui a des albums dans la base ?
|
||||||
|
|
||||||
|
3. Comment pourrait-on améliorer la base pour gérer les collaborations entre artistes sur un même titre ?
|
||||||
|
|
||||||
|
4. Quelles informations supplémentaires pourrait-on ajouter pour un vrai service de streaming ?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Barème indicatif
|
||||||
|
|
||||||
|
| Partie | Points |
|
||||||
|
|--------|--------|
|
||||||
|
| Partie 1 : Création et schéma | 2 |
|
||||||
|
| Partie 2 : Requêtes de base | 3 |
|
||||||
|
| Partie 3 : Jointures | 4 |
|
||||||
|
| Partie 4 : Agrégations | 4 |
|
||||||
|
| Partie 5 : Requêtes avancées | 4 |
|
||||||
|
| Partie 6 : Analyse business | 3 |
|
||||||
|
| **Total** | **20** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
BIN
SQL/assets/sql_join.JPG
Normal file
BIN
SQL/assets/sql_join.JPG
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
263
Soc/CORRIGE.md
Normal file
263
Soc/CORRIGE.md
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
# Corrigé des exercices — Circuits intégrés et SoC
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 1 : Loi de Moore (QCM)
|
||||||
|
|
||||||
|
1. **Réponse : b)** Le nombre de transistors double environ tous les deux ans (à coût constant)
|
||||||
|
|
||||||
|
2. **Réponse : d)** Toutes les réponses ci-dessus
|
||||||
|
- Les transistors approchent de la taille atomique
|
||||||
|
- L'effet tunnel quantique perturbe le fonctionnement
|
||||||
|
- La chaleur dégagée devient difficile à évacuer
|
||||||
|
|
||||||
|
3. **Réponse : b)** Multiplier le nombre de cœurs
|
||||||
|
- L'augmentation de la fréquence génère trop de chaleur
|
||||||
|
- La multiplication des cœurs permet d'augmenter la puissance totale sans augmenter la fréquence unitaire
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 2 : Composants d'un SoC
|
||||||
|
|
||||||
|
1. **Composants intégrables dans un SoC :**
|
||||||
|
- ✅ CPU
|
||||||
|
- ✅ GPU
|
||||||
|
- ❌ Disque dur mécanique
|
||||||
|
- ✅ Contrôleur Wi-Fi
|
||||||
|
- ✅ Mémoire cache
|
||||||
|
- ✅ NPU (Neural Processing Unit)
|
||||||
|
- ❌ Écran
|
||||||
|
- ✅ Contrôleur USB
|
||||||
|
|
||||||
|
2. **Pourquoi un disque dur mécanique ne peut pas être intégré :**
|
||||||
|
- Un disque dur mécanique contient des pièces mobiles (plateaux rotatifs, têtes de lecture)
|
||||||
|
- Ces composants mécaniques ne peuvent pas être miniaturisés à l'échelle d'une puce
|
||||||
|
- La technologie est fondamentalement différente (mécanique vs électronique)
|
||||||
|
- Les SSD (mémoire flash) peuvent en revanche être intégrés car ils sont purement électroniques
|
||||||
|
|
||||||
|
3. **Avantages et inconvénients :**
|
||||||
|
|
||||||
|
| Avantages | Inconvénients |
|
||||||
|
|-----------|---------------|
|
||||||
|
| Compacité : tout tient sur une puce | Non évolutif : impossible de remplacer un composant |
|
||||||
|
| Consommation réduite : moins de distance entre composants | Obsolescence : tout le système vieillit ensemble |
|
||||||
|
| Performance : communication ultra-rapide | Réparation difficile : panne = remplacement total |
|
||||||
|
| Coût de production réduit en masse | Impact environnemental : recyclage complexe |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 3 : Calculs sur les transistors
|
||||||
|
|
||||||
|
1. **Facteur multiplicatif entre Intel 4004 et Apple M1 :**
|
||||||
|
|
||||||
|
```
|
||||||
|
Facteur = 16 000 000 000 / 2 300
|
||||||
|
Facteur = 6 956 521,7
|
||||||
|
Facteur ≈ 7 millions
|
||||||
|
```
|
||||||
|
|
||||||
|
Le nombre de transistors a été multiplié par environ **7 millions** en 49 ans.
|
||||||
|
|
||||||
|
2. **Calcul théorique selon la loi de Moore :**
|
||||||
|
|
||||||
|
Entre 1971 et 2020, il y a 49 ans, soit 49/2 = 24,5 doublements.
|
||||||
|
|
||||||
|
```
|
||||||
|
Transistors théoriques = 2 300 × 2^24,5
|
||||||
|
Transistors théoriques = 2 300 × 23 726 566
|
||||||
|
Transistors théoriques ≈ 54,6 milliards
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Comparaison :**
|
||||||
|
|
||||||
|
- Valeur théorique : ~54,6 milliards de transistors
|
||||||
|
- Valeur réelle (Apple M1) : 16 milliards de transistors
|
||||||
|
|
||||||
|
L'Apple M1 a **moins** de transistors que prévu par la loi de Moore pure. Cependant, la loi de Moore a été globalement respectée jusqu'aux années 2010, puis le rythme a légèrement ralenti. De plus, l'Apple M3 (2023) atteint 37 milliards, montrant que la progression continue.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 4 : ARM vs x86
|
||||||
|
|
||||||
|
| Critère | Architecture x86 | Architecture ARM |
|
||||||
|
|---------|------------------|------------------|
|
||||||
|
| Type d'instructions | CISC | **RISC** |
|
||||||
|
| Consommation électrique | **Élevée** | Faible |
|
||||||
|
| Utilisation principale | **PC, serveurs** | Smartphones, tablettes |
|
||||||
|
| Exemples de fabricants | Intel, AMD | **Apple, Qualcomm, ARM Ltd** |
|
||||||
|
| Complexité des instructions | Élevée | **Faible** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 5 : Systèmes embarqués
|
||||||
|
|
||||||
|
### 1. Thermostat connecté
|
||||||
|
|
||||||
|
| Élément | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| **Capteurs** | Température (thermistance), humidité, présence (infrarouge) |
|
||||||
|
| **Contraintes** | Faible consommation (fonctionnement sur batterie ou alimentation continue), connectivité Wi-Fi stable |
|
||||||
|
| **SoC possible** | ESP32 (Wi-Fi + Bluetooth intégrés, faible coût) |
|
||||||
|
|
||||||
|
### 2. Système ABS d'une voiture
|
||||||
|
|
||||||
|
| Élément | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| **Capteurs** | Capteurs de vitesse de roue (effet Hall), accéléromètres |
|
||||||
|
| **Contraintes** | Temps réel strict (réaction en millisecondes), fiabilité critique (sécurité), résistance aux vibrations et températures |
|
||||||
|
| **SoC possible** | Microcontrôleur automobile certifié (ex: Infineon AURIX, NXP S32) |
|
||||||
|
|
||||||
|
### 3. Montre connectée (smartwatch)
|
||||||
|
|
||||||
|
| Élément | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| **Capteurs** | Accéléromètre, gyroscope, cardiofréquencemètre optique, GPS |
|
||||||
|
| **Contraintes** | Ultra-faible consommation (autonomie de plusieurs jours), miniaturisation extrême, connectivité Bluetooth |
|
||||||
|
| **SoC possible** | Qualcomm Snapdragon Wear, Apple S9 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 6 : Impact environnemental
|
||||||
|
|
||||||
|
### 1. Trois problèmes environnementaux liés à la fabrication des SoC :
|
||||||
|
|
||||||
|
1. **Extraction des terres rares** : mines polluantes, déforestation, contamination des sols et des eaux
|
||||||
|
2. **Consommation d'eau** : les usines de fabrication (fabs) utilisent des millions de litres d'eau ultra-pure par jour
|
||||||
|
3. **Consommation énergétique** : la fabrication d'une puce de quelques grammes nécessite plusieurs kWh d'énergie
|
||||||
|
|
||||||
|
### 2. Pourquoi les SoC contribuent à l'obsolescence programmée :
|
||||||
|
|
||||||
|
- **Non évolutivité** : impossible de remplacer uniquement le GPU ou d'ajouter de la RAM
|
||||||
|
- **Fin des mises à jour** : les fabricants cessent de supporter les anciens SoC après quelques années
|
||||||
|
- **Incompatibilité logicielle** : les nouvelles applications nécessitent des fonctionnalités absentes des anciens SoC
|
||||||
|
- **Batterie soudée** : souvent associée à un SoC, rendant le remplacement difficile
|
||||||
|
|
||||||
|
### 3. Trois actions concrètes pour réduire l'impact :
|
||||||
|
|
||||||
|
1. **Allonger la durée de vie** : utiliser ses appareils le plus longtemps possible, faire réparer plutôt que remplacer
|
||||||
|
2. **Acheter reconditionné** : donner une seconde vie aux appareils
|
||||||
|
3. **Recycler correctement** : déposer les appareils en fin de vie dans les points de collecte dédiés (pas à la poubelle)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 7 : Analyse d'un SoC réel
|
||||||
|
|
||||||
|
### 1. Pourquoi des cœurs à différentes fréquences ?
|
||||||
|
|
||||||
|
Architecture **big.LITTLE** (ou hétérogène) :
|
||||||
|
- **Cœurs performants** (3.3 GHz) : pour les tâches exigeantes (jeux, montage vidéo)
|
||||||
|
- **Cœurs efficients** (2.3 GHz) : pour les tâches légères (veille, notifications)
|
||||||
|
|
||||||
|
Avantages :
|
||||||
|
- Meilleure autonomie : les tâches simples utilisent les cœurs économes
|
||||||
|
- Puissance disponible : les cœurs performants interviennent quand nécessaire
|
||||||
|
|
||||||
|
### 2. Rôle du NPU :
|
||||||
|
|
||||||
|
Le **NPU** (Neural Processing Unit) est optimisé pour les calculs d'intelligence artificielle :
|
||||||
|
- Reconnaissance faciale et vocale
|
||||||
|
- Amélioration des photos (mode nuit, flou d'arrière-plan)
|
||||||
|
- Traduction en temps réel
|
||||||
|
- Suggestions de texte prédictif
|
||||||
|
|
||||||
|
Il est plus efficace que le CPU/GPU pour ces tâches spécifiques.
|
||||||
|
|
||||||
|
### 3. Avantage du modem 5G intégré :
|
||||||
|
|
||||||
|
- **Consommation réduite** : moins de distance entre le modem et le CPU = moins d'énergie
|
||||||
|
- **Latence réduite** : communication plus rapide entre composants
|
||||||
|
- **Compacité** : gain de place sur la carte mère
|
||||||
|
- **Coût** : moins de composants à assembler
|
||||||
|
|
||||||
|
### 4. Rapport de fréquence :
|
||||||
|
|
||||||
|
```
|
||||||
|
Rapport = 3.3 GHz / 2.3 GHz
|
||||||
|
Rapport = 1,43
|
||||||
|
```
|
||||||
|
|
||||||
|
Le cœur le plus rapide est **1,43 fois** plus rapide que le cœur le plus lent (soit 43% de plus).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 8 : RISC-V et open source
|
||||||
|
|
||||||
|
### 1. Définition "open source" pour une architecture :
|
||||||
|
|
||||||
|
- Les **spécifications** de l'architecture sont publiques et libres d'utilisation
|
||||||
|
- N'importe qui peut concevoir un processeur RISC-V sans payer de licence
|
||||||
|
- Contrairement à ARM (licence payante) ou x86 (propriétaire Intel/AMD)
|
||||||
|
|
||||||
|
### 2. Deux avantages de RISC-V :
|
||||||
|
|
||||||
|
1. **Pas de royalties** : économie sur les coûts de licence
|
||||||
|
2. **Personnalisation** : possibilité d'ajouter des instructions spécifiques à ses besoins
|
||||||
|
|
||||||
|
### 3. Domaine adapté :
|
||||||
|
|
||||||
|
- **IoT et objets connectés** : coût très faible par unité
|
||||||
|
- **Recherche et éducation** : possibilité d'étudier et modifier l'architecture
|
||||||
|
- **Systèmes embarqués spécialisés** : personnalisation pour des tâches précises
|
||||||
|
|
||||||
|
### 4. Intérêt des grandes entreprises :
|
||||||
|
|
||||||
|
- **Indépendance** : ne pas dépendre d'ARM ou Intel
|
||||||
|
- **Réduction des coûts** : pas de licence à payer sur des millions d'unités
|
||||||
|
- **Souveraineté technologique** : certains pays (Chine) veulent des alternatives aux technologies occidentales
|
||||||
|
- **Innovation** : liberté de créer des designs optimisés pour leurs besoins spécifiques
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 9 : Conversion et calculs
|
||||||
|
|
||||||
|
### 1. Conversions de 3 nm :
|
||||||
|
|
||||||
|
```
|
||||||
|
3 nm = 3 × 10⁻⁹ m
|
||||||
|
|
||||||
|
En micromètres :
|
||||||
|
3 nm = 0,003 µm = 3 × 10⁻³ µm
|
||||||
|
|
||||||
|
En mètres :
|
||||||
|
3 nm = 0,000000003 m = 3 × 10⁻⁹ m
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Nombre d'atomes de silicium :
|
||||||
|
|
||||||
|
```
|
||||||
|
Diamètre d'un atome de Si ≈ 0,2 nm
|
||||||
|
|
||||||
|
Nombre d'atomes = 3 nm / 0,2 nm = 15 atomes
|
||||||
|
```
|
||||||
|
|
||||||
|
Environ **15 atomes** de silicium peuvent tenir sur une largeur de 3 nm.
|
||||||
|
|
||||||
|
### 3. Limites de la miniaturisation :
|
||||||
|
|
||||||
|
- Avec seulement 15 atomes, le contrôle des électrons devient très difficile
|
||||||
|
- L'**effet tunnel quantique** permet aux électrons de "traverser" les barrières
|
||||||
|
- Les fluctuations statistiques (un atome de plus ou de moins) ont un impact significatif
|
||||||
|
- La chaleur générée par unité de surface devient extrême
|
||||||
|
- On approche des **limites fondamentales de la physique**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exercice 10 : Synthèse
|
||||||
|
|
||||||
|
**Exemple de rédaction :**
|
||||||
|
|
||||||
|
Les systèmes sur puce (SoC) ont révolutionné l'informatique mobile en permettant d'intégrer tous les composants essentiels d'un ordinateur sur une seule puce de silicium. Cette intégration offre des avantages majeurs : une compacité remarquable permettant de créer des smartphones toujours plus fins, une consommation énergétique réduite grâce à la proximité des composants, et des performances accrues par une communication ultra-rapide entre le CPU, le GPU et la mémoire.
|
||||||
|
|
||||||
|
Les architectures ARM, avec leur philosophie RISC, ont particulièrement contribué à cette révolution en proposant un excellent rapport performance/consommation. Les récents processeurs Apple Silicon démontrent que ces architectures peuvent désormais rivaliser avec les processeurs x86 traditionnels, même sur ordinateur.
|
||||||
|
|
||||||
|
Cependant, l'avenir des SoC fait face à des défis importants. La miniaturisation approche ses limites physiques : avec des transistors de 3 nm (environ 15 atomes), les effets quantiques perturbent le fonctionnement normal des circuits. Les fabricants doivent explorer de nouvelles pistes comme l'empilement 3D, les nouveaux matériaux, ou les architectures hétérogènes.
|
||||||
|
|
||||||
|
Enfin, l'impact environnemental devient une préoccupation majeure. La fabrication des SoC nécessite des ressources rares et beaucoup d'énergie, tandis que leur nature non évolutive contribue à l'obsolescence programmée. L'industrie devra concilier innovation technologique et responsabilité écologique pour un avenir durable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auteur : Florian Mathieu
|
||||||
|
|
||||||
|
Licence CC BY NC
|
||||||
|
|
||||||
|
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||||
537
Soc/Corrige_TP_Station_Meteo_IoT.py
Normal file
537
Soc/Corrige_TP_Station_Meteo_IoT.py
Normal file
@@ -0,0 +1,537 @@
|
|||||||
|
"""
|
||||||
|
Corrigé du TP Station Météo IoT — Simulation d'un système embarqué
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 1 : Simulation des capteurs
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class Capteur:
|
||||||
|
"""Simule un capteur embarqué sur SoC."""
|
||||||
|
|
||||||
|
def __init__(self, nom, unite, val_min, val_max, precision=1):
|
||||||
|
"""
|
||||||
|
Initialise un capteur.
|
||||||
|
|
||||||
|
:param nom: (str) Nom du capteur
|
||||||
|
:param unite: (str) Unité de mesure
|
||||||
|
:param val_min: (float) Valeur minimale possible
|
||||||
|
:param val_max: (float) Valeur maximale possible
|
||||||
|
:param precision: (int) Nombre de décimales
|
||||||
|
"""
|
||||||
|
self.nom = nom
|
||||||
|
self.unite = unite
|
||||||
|
self.val_min = val_min
|
||||||
|
self.val_max = val_max
|
||||||
|
self.precision = precision
|
||||||
|
self._derniere_valeur = None
|
||||||
|
|
||||||
|
def lire(self):
|
||||||
|
"""
|
||||||
|
Simule une lecture du capteur.
|
||||||
|
Retourne une valeur réaliste (proche de la précédente).
|
||||||
|
|
||||||
|
:return: (float) Valeur mesurée
|
||||||
|
"""
|
||||||
|
if self._derniere_valeur is None:
|
||||||
|
# Première lecture : valeur aléatoire dans la plage
|
||||||
|
valeur = random.uniform(self.val_min, self.val_max)
|
||||||
|
else:
|
||||||
|
# Lectures suivantes : variation réaliste (±10% de la plage)
|
||||||
|
amplitude = (self.val_max - self.val_min) * 0.1
|
||||||
|
variation = random.uniform(-amplitude, amplitude)
|
||||||
|
valeur = self._derniere_valeur + variation
|
||||||
|
|
||||||
|
# S'assurer que la valeur reste dans les limites
|
||||||
|
valeur = max(self.val_min, min(self.val_max, valeur))
|
||||||
|
|
||||||
|
# Arrondir selon la précision
|
||||||
|
valeur = round(valeur, self.precision)
|
||||||
|
|
||||||
|
self._derniere_valeur = valeur
|
||||||
|
return valeur
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Capteur({self.nom}, {self.unite})"
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 2 : Buffer circulaire
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class BufferCirculaire:
|
||||||
|
"""
|
||||||
|
Buffer circulaire pour stocker les dernières mesures.
|
||||||
|
Mémoire fixe, adapté aux systèmes embarqués.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, taille):
|
||||||
|
"""
|
||||||
|
Initialise le buffer.
|
||||||
|
|
||||||
|
:param taille: (int) Nombre maximum d'éléments
|
||||||
|
"""
|
||||||
|
self.taille = taille
|
||||||
|
self.donnees = [None] * taille # Pré-allocation
|
||||||
|
self.index = 0
|
||||||
|
self.compte = 0
|
||||||
|
|
||||||
|
def ajouter(self, valeur):
|
||||||
|
"""
|
||||||
|
Ajoute une valeur au buffer.
|
||||||
|
Écrase la plus ancienne si plein.
|
||||||
|
|
||||||
|
:param valeur: La valeur à ajouter
|
||||||
|
"""
|
||||||
|
self.donnees[self.index] = valeur
|
||||||
|
self.index = (self.index + 1) % self.taille
|
||||||
|
|
||||||
|
if self.compte < self.taille:
|
||||||
|
self.compte += 1
|
||||||
|
|
||||||
|
def moyenne(self):
|
||||||
|
"""
|
||||||
|
Calcule la moyenne des valeurs stockées.
|
||||||
|
|
||||||
|
:return: (float) Moyenne ou None si vide
|
||||||
|
"""
|
||||||
|
if self.compte == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
total = sum(self.donnees[i] for i in range(self.compte))
|
||||||
|
return total / self.compte
|
||||||
|
|
||||||
|
def min_max(self):
|
||||||
|
"""
|
||||||
|
Retourne le minimum et le maximum.
|
||||||
|
|
||||||
|
:return: (tuple) (min, max) ou (None, None) si vide
|
||||||
|
"""
|
||||||
|
if self.compte == 0:
|
||||||
|
return (None, None)
|
||||||
|
|
||||||
|
valeurs = [self.donnees[i] for i in range(self.compte)]
|
||||||
|
return (min(valeurs), max(valeurs))
|
||||||
|
|
||||||
|
def ecart_type(self):
|
||||||
|
"""
|
||||||
|
Calcule l'écart-type des valeurs stockées.
|
||||||
|
|
||||||
|
:return: (float) Écart-type ou None si vide
|
||||||
|
"""
|
||||||
|
if self.compte < 2:
|
||||||
|
return None
|
||||||
|
|
||||||
|
moy = self.moyenne()
|
||||||
|
variance = sum((self.donnees[i] - moy) ** 2 for i in range(self.compte)) / self.compte
|
||||||
|
return math.sqrt(variance)
|
||||||
|
|
||||||
|
def valeurs(self):
|
||||||
|
"""Retourne la liste des valeurs stockées."""
|
||||||
|
return [self.donnees[i] for i in range(self.compte)]
|
||||||
|
|
||||||
|
def est_plein(self):
|
||||||
|
"""Vérifie si le buffer est plein."""
|
||||||
|
return self.compte >= self.taille
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.compte
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 3 : Station météo complète
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
class StationMeteo:
|
||||||
|
"""
|
||||||
|
Station météo IoT simulant un système sur SoC.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Constantes de consommation (en mW)
|
||||||
|
CONSO_VEILLE = 0.5
|
||||||
|
CONSO_MESURE = 10
|
||||||
|
CONSO_ENVOI = 50
|
||||||
|
CONSO_CALCUL = 5
|
||||||
|
|
||||||
|
def __init__(self, nom, taille_buffer=10):
|
||||||
|
"""
|
||||||
|
Initialise la station.
|
||||||
|
|
||||||
|
:param nom: (str) Identifiant de la station
|
||||||
|
:param taille_buffer: (int) Taille des buffers de données
|
||||||
|
"""
|
||||||
|
self.nom = nom
|
||||||
|
|
||||||
|
# Capteurs
|
||||||
|
self.capteurs = {
|
||||||
|
'temperature': Capteur("Température", "°C", -20, 50, 1),
|
||||||
|
'humidite': Capteur("Humidité", "%", 0, 100, 0),
|
||||||
|
'pression': Capteur("Pression", "hPa", 950, 1050, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Buffers pour chaque capteur
|
||||||
|
self.buffers = {
|
||||||
|
nom: BufferCirculaire(taille_buffer)
|
||||||
|
for nom in self.capteurs
|
||||||
|
}
|
||||||
|
|
||||||
|
# Compteur de mesures
|
||||||
|
self.nb_mesures = 0
|
||||||
|
|
||||||
|
# Mode économie d'énergie
|
||||||
|
self.mode_eco = False
|
||||||
|
|
||||||
|
# Consommation énergétique
|
||||||
|
self.energie_totale = 0 # mWh consommés
|
||||||
|
|
||||||
|
# Buffer d'envoi pour mode éco
|
||||||
|
self.buffer_envoi = []
|
||||||
|
|
||||||
|
def _consommer(self, activite, duree_ms):
|
||||||
|
"""
|
||||||
|
Enregistre la consommation d'énergie.
|
||||||
|
|
||||||
|
:param activite: (str) Type d'activité
|
||||||
|
:param duree_ms: (int) Durée en millisecondes
|
||||||
|
"""
|
||||||
|
conso = {
|
||||||
|
'veille': self.CONSO_VEILLE,
|
||||||
|
'mesure': self.CONSO_MESURE,
|
||||||
|
'envoi': self.CONSO_ENVOI,
|
||||||
|
'calcul': self.CONSO_CALCUL
|
||||||
|
}
|
||||||
|
# Convertir en mWh : mW * (ms / 3600000)
|
||||||
|
self.energie_totale += conso.get(activite, 1) * (duree_ms / 3600000)
|
||||||
|
|
||||||
|
def mesurer(self):
|
||||||
|
"""
|
||||||
|
Effectue une mesure sur tous les capteurs.
|
||||||
|
|
||||||
|
:return: (dict) Dictionnaire des mesures
|
||||||
|
"""
|
||||||
|
self._consommer('mesure', 50) # 50ms pour une mesure
|
||||||
|
|
||||||
|
mesures = {}
|
||||||
|
for nom, capteur in self.capteurs.items():
|
||||||
|
valeur = capteur.lire()
|
||||||
|
mesures[nom] = valeur
|
||||||
|
self.buffers[nom].ajouter(valeur)
|
||||||
|
|
||||||
|
self.nb_mesures += 1
|
||||||
|
return mesures
|
||||||
|
|
||||||
|
def statistiques(self):
|
||||||
|
"""
|
||||||
|
Calcule les statistiques sur les données collectées.
|
||||||
|
|
||||||
|
:return: (dict) Statistiques par capteur
|
||||||
|
"""
|
||||||
|
self._consommer('calcul', 20) # 20ms pour les calculs
|
||||||
|
|
||||||
|
stats = {}
|
||||||
|
for nom, buffer in self.buffers.items():
|
||||||
|
unite = self.capteurs[nom].unite
|
||||||
|
moy = buffer.moyenne()
|
||||||
|
min_val, max_val = buffer.min_max()
|
||||||
|
|
||||||
|
stats[nom] = {
|
||||||
|
'moyenne': round(moy, 1) if moy else None,
|
||||||
|
'min': min_val,
|
||||||
|
'max': max_val,
|
||||||
|
'nb_mesures': len(buffer),
|
||||||
|
'unite': unite
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats
|
||||||
|
|
||||||
|
def detecter_anomalies(self):
|
||||||
|
"""
|
||||||
|
Détecte les valeurs anormales.
|
||||||
|
Une anomalie est une valeur qui s'écarte de plus de 2 écarts-types
|
||||||
|
de la moyenne.
|
||||||
|
|
||||||
|
:return: (list) Liste des anomalies détectées
|
||||||
|
"""
|
||||||
|
self._consommer('calcul', 30)
|
||||||
|
|
||||||
|
anomalies = []
|
||||||
|
for nom, buffer in self.buffers.items():
|
||||||
|
if buffer.compte < 3:
|
||||||
|
continue
|
||||||
|
|
||||||
|
moy = buffer.moyenne()
|
||||||
|
ecart = buffer.ecart_type()
|
||||||
|
|
||||||
|
if ecart is None or ecart == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for valeur in buffer.valeurs():
|
||||||
|
if abs(valeur - moy) > 2 * ecart:
|
||||||
|
anomalies.append({
|
||||||
|
'capteur': nom,
|
||||||
|
'valeur': valeur,
|
||||||
|
'moyenne': round(moy, 2),
|
||||||
|
'ecart_type': round(ecart, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
return anomalies
|
||||||
|
|
||||||
|
def envoyer_cloud(self, donnees):
|
||||||
|
"""
|
||||||
|
Simule l'envoi de données vers le cloud.
|
||||||
|
|
||||||
|
:param donnees: (dict) Données à envoyer
|
||||||
|
:return: (bool) Succès de l'envoi
|
||||||
|
"""
|
||||||
|
self._consommer('envoi', 200) # 200ms pour un envoi
|
||||||
|
|
||||||
|
# Simuler une latence réseau
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# Simuler un échec occasionnel (5% de chance)
|
||||||
|
if random.random() < 0.05:
|
||||||
|
print(f"[{self.nom}] Échec d'envoi - Données mises en cache")
|
||||||
|
self.buffer_envoi.append(donnees)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Afficher les données "envoyées"
|
||||||
|
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
print(f"\n[{self.nom}] Envoi cloud @ {timestamp}")
|
||||||
|
for cle, valeur in donnees.items():
|
||||||
|
if isinstance(valeur, dict):
|
||||||
|
print(f" {cle}: moy={valeur['moyenne']}{valeur['unite']}, "
|
||||||
|
f"min={valeur['min']}, max={valeur['max']}")
|
||||||
|
else:
|
||||||
|
print(f" {cle}: {valeur}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def activer_mode_eco(self):
|
||||||
|
"""Active le mode économie d'énergie."""
|
||||||
|
self.mode_eco = True
|
||||||
|
print(f"[{self.nom}] Mode économie activé")
|
||||||
|
|
||||||
|
def desactiver_mode_eco(self):
|
||||||
|
"""Désactive le mode économie d'énergie."""
|
||||||
|
self.mode_eco = False
|
||||||
|
print(f"[{self.nom}] Mode économie désactivé")
|
||||||
|
|
||||||
|
def rapport(self):
|
||||||
|
"""
|
||||||
|
Génère un rapport formaté.
|
||||||
|
|
||||||
|
:return: (str) Rapport textuel
|
||||||
|
"""
|
||||||
|
self._consommer('calcul', 10)
|
||||||
|
|
||||||
|
lignes = [
|
||||||
|
f"Station : {self.nom}",
|
||||||
|
f"Nombre total de mesures : {self.nb_mesures}",
|
||||||
|
f"Mode économie : {'Oui' if self.mode_eco else 'Non'}",
|
||||||
|
"",
|
||||||
|
"Statistiques par capteur :"
|
||||||
|
]
|
||||||
|
|
||||||
|
stats = self.statistiques()
|
||||||
|
for nom, data in stats.items():
|
||||||
|
lignes.append(f" {nom.capitalize()} :")
|
||||||
|
lignes.append(f" Moyenne : {data['moyenne']} {data['unite']}")
|
||||||
|
lignes.append(f" Min/Max : {data['min']} / {data['max']} {data['unite']}")
|
||||||
|
lignes.append(f" Échantillons : {data['nb_mesures']}")
|
||||||
|
|
||||||
|
anomalies = self.detecter_anomalies()
|
||||||
|
if anomalies:
|
||||||
|
lignes.append("")
|
||||||
|
lignes.append(f"Anomalies détectées : {len(anomalies)}")
|
||||||
|
for a in anomalies[:5]: # Limiter à 5
|
||||||
|
lignes.append(f" - {a['capteur']}: {a['valeur']} "
|
||||||
|
f"(moy={a['moyenne']}, σ={a['ecart_type']})")
|
||||||
|
|
||||||
|
return "\n".join(lignes)
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# BONUS : Persistance locale
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
def sauvegarder_local(self, fichier="backup.json"):
|
||||||
|
"""Sauvegarde les données en local (simulation de mémoire flash)."""
|
||||||
|
donnees = {
|
||||||
|
'station': self.nom,
|
||||||
|
'timestamp': time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
'nb_mesures': self.nb_mesures,
|
||||||
|
'energie': self.energie_totale,
|
||||||
|
'buffers': {}
|
||||||
|
}
|
||||||
|
|
||||||
|
for nom, buffer in self.buffers.items():
|
||||||
|
donnees['buffers'][nom] = buffer.valeurs()
|
||||||
|
|
||||||
|
with open(fichier, 'w') as f:
|
||||||
|
json.dump(donnees, f, indent=2)
|
||||||
|
|
||||||
|
print(f"[{self.nom}] Données sauvegardées dans {fichier}")
|
||||||
|
|
||||||
|
def charger_local(self, fichier="backup.json"):
|
||||||
|
"""Charge les données depuis la sauvegarde locale."""
|
||||||
|
try:
|
||||||
|
with open(fichier, 'r') as f:
|
||||||
|
donnees = json.load(f)
|
||||||
|
|
||||||
|
for nom, valeurs in donnees.get('buffers', {}).items():
|
||||||
|
if nom in self.buffers:
|
||||||
|
for v in valeurs:
|
||||||
|
self.buffers[nom].ajouter(v)
|
||||||
|
|
||||||
|
self.nb_mesures = donnees.get('nb_mesures', 0)
|
||||||
|
print(f"[{self.nom}] Données chargées depuis {fichier}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"[{self.nom}] Aucune sauvegarde trouvée")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# BONUS : Protocole MQTT simulé
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
def publier(self, topic, message):
|
||||||
|
"""Publie un message sur un topic (simulation MQTT)."""
|
||||||
|
timestamp = time.strftime("%H:%M:%S")
|
||||||
|
print(f"[MQTT {timestamp}] {self.nom}/{topic} -> {message}")
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# PARTIE 5 : Programme principal
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Programme principal de la station météo."""
|
||||||
|
|
||||||
|
# Créer la station
|
||||||
|
station = StationMeteo("STATION_01", taille_buffer=20)
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print(" STATION MÉTÉO IoT - Simulation SoC")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Tenter de charger une sauvegarde précédente
|
||||||
|
station.charger_local()
|
||||||
|
|
||||||
|
# Simuler 30 mesures (accéléré pour le test)
|
||||||
|
nb_mesures = 30
|
||||||
|
intervalle = 0.5 # Secondes entre les mesures
|
||||||
|
|
||||||
|
try:
|
||||||
|
for i in range(nb_mesures):
|
||||||
|
# Effectuer une mesure
|
||||||
|
mesure = station.mesurer()
|
||||||
|
print(f"\rMesure {i+1:3d}/{nb_mesures}: "
|
||||||
|
f"T={mesure['temperature']:5.1f}°C, "
|
||||||
|
f"H={mesure['humidite']:3.0f}%, "
|
||||||
|
f"P={mesure['pression']:6.1f}hPa", end="")
|
||||||
|
|
||||||
|
# Publier sur MQTT (simulation)
|
||||||
|
if (i + 1) % 5 == 0:
|
||||||
|
station.publier("temperature", mesure['temperature'])
|
||||||
|
|
||||||
|
# Envoyer au cloud toutes les 10 mesures
|
||||||
|
if (i + 1) % 10 == 0:
|
||||||
|
stats = station.statistiques()
|
||||||
|
station.envoyer_cloud(stats)
|
||||||
|
|
||||||
|
# Sauvegarder localement toutes les 15 mesures
|
||||||
|
if (i + 1) % 15 == 0:
|
||||||
|
station.sauvegarder_local()
|
||||||
|
|
||||||
|
# Simuler le mode éco après 20 mesures
|
||||||
|
if i == 19:
|
||||||
|
station.activer_mode_eco()
|
||||||
|
|
||||||
|
# Attendre avant la prochaine mesure
|
||||||
|
time.sleep(intervalle)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\nArrêt demandé par l'utilisateur.")
|
||||||
|
|
||||||
|
# Rapport final
|
||||||
|
print("\n\n" + "=" * 60)
|
||||||
|
print("RAPPORT FINAL")
|
||||||
|
print("=" * 60)
|
||||||
|
print(station.rapport())
|
||||||
|
print(f"\nÉnergie totale consommée : {station.energie_totale:.6f} mWh")
|
||||||
|
|
||||||
|
# Sauvegarde finale
|
||||||
|
station.sauvegarder_local()
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# TESTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=" * 60)
|
||||||
|
print("TEST 1 : Capteur")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
capteur_temp = Capteur("Température", "°C", -20, 50, 1)
|
||||||
|
print(f"Capteur créé : {capteur_temp}")
|
||||||
|
print("Lectures :", [capteur_temp.lire() for _ in range(5)])
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("TEST 2 : Buffer circulaire")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
buffer = BufferCirculaire(5)
|
||||||
|
for i in range(1, 8):
|
||||||
|
buffer.ajouter(i)
|
||||||
|
print(f"Ajout {i}: données={buffer.donnees}, compte={buffer.compte}")
|
||||||
|
|
||||||
|
print(f"Moyenne : {buffer.moyenne()}")
|
||||||
|
print(f"Min/Max : {buffer.min_max()}")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("TEST 3 : Station météo complète")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# RÉPONSES AUX QUESTIONS DE SYNTHÈSE
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
"""
|
||||||
|
1. Pourquoi utiliser un buffer circulaire ?
|
||||||
|
- Allocation mémoire fixe et prévisible
|
||||||
|
- Pas de fragmentation mémoire
|
||||||
|
- Temps d'accès constant O(1)
|
||||||
|
- Adapté aux contraintes des microcontrôleurs (RAM limitée)
|
||||||
|
|
||||||
|
2. Compromis fréquence/consommation :
|
||||||
|
- Plus de mesures = meilleure précision mais plus de consommation
|
||||||
|
- Mode éco : réduire la fréquence quand les données varient peu
|
||||||
|
- Adapter dynamiquement selon l'activité détectée
|
||||||
|
|
||||||
|
3. Gestion de la perte de connexion :
|
||||||
|
- Stockage local temporaire (mémoire flash)
|
||||||
|
- File d'attente des messages
|
||||||
|
- Renvoi automatique à la reconnexion
|
||||||
|
- Compression des données pour économiser la bande passante
|
||||||
|
|
||||||
|
4. Différences Python vs microcontrôleur :
|
||||||
|
- Python : haut niveau, garbage collector, pas de contraintes temps réel
|
||||||
|
- Microcontrôleur : C/C++/MicroPython, gestion manuelle mémoire,
|
||||||
|
interruptions matérielles, accès direct aux registres
|
||||||
|
|
||||||
|
5. Optimisation SoC pour l'IoT :
|
||||||
|
- Composants faible consommation intégrés
|
||||||
|
- Modes de veille profonde
|
||||||
|
- Réveil sur interruption (capteur ou timer)
|
||||||
|
- Accélérateurs matériels pour crypto et radio
|
||||||
|
"""
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user