ajout de tous les cours et TP préparés cet été

This commit is contained in:
2026-01-17 23:10:49 +01:00
parent ed9415bc81
commit 301cf5a98f
125 changed files with 21614 additions and 542 deletions

419
Arbres/ABR.md Normal file
View 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>.

View 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",
"![fig15.png](assets/fig15.png)"
]
},
{
"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",
"![fig16.png](assets/fig16.png)\n",
"\n",
"![fig17.png](assets/fig17.png)\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",
"![fig18.png](assets/fig18.png)\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",
"![fig19.png](assets/fig19.png)\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",
"![fig20.png](assets/fig20.png)\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",
"![fig21.png](assets/fig21.png)"
]
},
{
"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",
"![fig22.png](assets/fig22.png)"
]
},
{
"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",
"![fig23.png](assets/fig23.png)\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",
"![fig25.png](assets/fig25.png)"
]
},
{
"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",
"![fig24.png](assets/fig24.png)\n",
"\n",
"Voici un algorithme permettant de réaliser ce parcours:\n",
"\n",
"![fig26.png](assets/fig26.png)"
]
},
{
"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",
"![fig27.png](assets/fig27.png)\n",
"![fig28.png](assets/fig28.png)\n",
"![fig29.png](assets/fig29.png)\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
}

View File

@@ -8,7 +8,7 @@
### 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**.
@@ -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:
```python
['*','-',5,2,6,None,None,None,None,None,None,None,None,None,None]
```
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
@@ -152,53 +138,13 @@ print(arbre)
**<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...
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
![fig20.png](assets/fig20.png)
@@ -251,52 +197,14 @@ print(arbre1)
### Exercice :
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:
* 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:**
L'idée ets la suivante:
L'idée est la suivante:
* 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.
* Ces sous-arbres sont eux même des arbres dont il faut calculer la hauteur.
@@ -318,7 +226,7 @@ Voici l'algorithme:
## Les parcours
**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:
![fig23.png](assets/fig23.png)
@@ -344,14 +252,6 @@ Voici l'algorithme:
2. Implémenter alors cette fonction et l'essayer sur l'arbre précédent.
```python
```
```python
```
**Les parcours en profondeur**
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 la droite
### Definition:
Dans un parcours **prefixe**, on liste le noeud la première fois qu'on le rencontre.
### Définition:
Dans un parcours **préfixe**, on liste le noeud la première fois qu'on le rencontre.
### 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
```python
```
-------

View File

@@ -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 doptimiser 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 doptimiser certaines opérations comme la recherche ou le tri. En NSI, nous allons explorer une version particulière de ces structures : les arbres binaires.
---------------------------------

View File

@@ -3,13 +3,13 @@
Soit les 4 arbres binaires suivant :
<div style="display: flex; justify-content: space-around;">
<img src="assets/img2.png" alt="Méthodes_natives" style="zoom: 80%;" />
<img src="assets/img3.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%;" />
</div>
<div style="display: flex; justify-content: space-around;">
<img src="assets/img4.png" alt="Méthodes_natives" style="zoom: 80%;" />
<img src="assets/img5.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="Arbre 4" style="zoom: 80%;" />
</div>
@@ -23,7 +23,7 @@ Question 1 - Compléter le tableau :
|arbre 3| | | | | |
|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 une seule affectation (une ligne) de l'arbre ayant pour racine le noeud **1**.

281
Arbres/TP_Recommandation.md Normal file
View 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>.

View File

@@ -91,7 +91,7 @@ class BinaryTree():
def size(self):
"""
:return: (int) numbre of Nodes in Tree
:return: (int) number of Nodes in Tree
"""
pass

View File

@@ -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.
[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é **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 ?
@@ -109,10 +154,113 @@ Chaque attribut doit contenir des valeurs valides en fonction de son type (domai
#### 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) :
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.
La normalisation est un processus pour structurer une base et réduire la redondance. Voici les trois premières **formes normales** (FN).
---
#### 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 |
---

View 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>.

View File

@@ -1,4 +1,4 @@
### Entraînement sur les Schémas Relationnels
# Entraînement sur les Schémas Relationnels
## Introduction
@@ -91,3 +91,11 @@ Chaque utilisateur peut créer un ou plusieurs **albums** contenant des messages
### **Amélioration** :
- 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>.

View File

@@ -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.
#### **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.
#### **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.
---

View File

@@ -115,7 +115,7 @@ Dans les années 1990, une banque a oublié dappliquer la contrainte dint
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) !"

380
BDD_SGBD/TP_StreamFlix.md Normal file
View 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
View 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>.

View File

@@ -1,4 +1,4 @@
## Calculabilité et décidabilité en informatique ##
# Calculabilité et décidabilité en informatique
> À chaque problème sa non solution !
@@ -12,7 +12,7 @@
#### 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 :
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.
### 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
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
### Définition : Décidabilité
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é
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
@@ -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.
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 ?
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 !
@@ -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?
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.
@@ -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.
### 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 !
@@ -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 ??
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.
@@ -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.
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
@@ -253,16 +252,23 @@ Maintenant, définissons notre fonction absurde
```python
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:
continue
else:
return True
pass
else:
# 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 ?
@@ -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 !
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 ?

View 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>.

View File

@@ -1,4 +1,4 @@
## Machine de Turing
# Activité : Machine de Turing
Cette machine est constituée :
@@ -34,24 +34,40 @@ Réalisez pas à pas l'algorithme ci-dessous :
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| | | | | | | | | | | | 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 | | | | | | |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| | | | | | | | | | | | 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 | | | | | | | |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| | | | | | | | | | | | 1 | 1 | 0 | | | | | | |
*Etape 5* : ...
*Étape 4* : On retourne à la position initiale. Le calcul est terminé.
| | | | | | | | | | | V | | | | | | | | | |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| | | | | | | | | | | | 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). In­ver­sement, 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>.

View 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>.

View File

@@ -6,14 +6,14 @@ Construire un graphe non orienté du réseau social à partir des informations s
- **Benoit** est ami avec **Arthur** et **Coralie** ;
- **Coralie** est amie avec **Benoit**, **Franck** et **David** ;
- **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**.
---
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 :
@@ -40,3 +40,10 @@ Voici ses données :
![graphe](assets/img3.PNG)
---
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>.

View File

@@ -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.
@@ -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.
---
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>.

View File

@@ -1,9 +1,7 @@
## Les Graphes
# Les Graphes
>
------
### Le programme
@@ -12,7 +10,7 @@
---------
### Quest-quun graphe?
### Qu'est-ce qu'un graphe ?
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
* 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ù :
* 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 :
@@ -34,13 +32,13 @@ Voici ce que cela donne avec le réseau social décrit ci-dessus :
Ce genre de figure sappelle 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
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 dun graphe :**
Lordre dun 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
**Définition:**
On appelle **chaîne** toute succession darêtes dont lextrémité de lune (sauf la dernière) estlorigine 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 darêtes qui composent une chaîne est appelé **longueur de la chaîne**.
* On appelle **chaîne fermée** toute chaîne dont lorigine et lextrémité coïncident.
@@ -201,7 +199,7 @@ On appelle **chaîne** toute succession darêtes dont lextrémité de l
**Exemple:**
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 nest pas un cycle car larê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%;" />
---
## 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
View 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
View 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>.

View File

@@ -1,7 +1,7 @@
## Débug
# Débug
> 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
@@ -16,38 +16,37 @@ Les erreurs, également appelées bugs, peuvent se produire à différents nivea
### 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.
Exemple :
```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.
Exemple :
```python
result = 10 / 0
print (result)
```
```python
result = 10 / 0
print(result)
```
### Erreurs logiques
#### Erreurs logiques
Ces erreurs ne provoquent pas l'arrêt du programme, mais produisent des résultats incorrects.
Exemple :
```python
def somme(a, b):
return a - b # L'erreur est ici, il faut utiliser + au lieu de -
```
```python
def somme(a, b):
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 :
```python
def somme(a, b):
print(f"a = {a}, b = {b}")
return a + b
```
```python
def somme(a, b):
print(f"a = {a}, b = {b}")
return a + b
```
@@ -83,15 +80,12 @@ Utilisez `assert` pour vérifier que certaines conditions sont vraies à différ
Exemple :
```python
def somme(a, b):
assert isinstance(a, int), "a doit être un entier"
assert isinstance(b, int), "b doit être un entier"
return a + b
```
```python
def somme(a, b):
assert isinstance(a, int), "a doit être un entier"
assert isinstance(b, int), "b doit être un entier"
return a + b
```
@@ -111,29 +105,25 @@ Programme avec une erreur :
```python
def moyenne(liste):
somme = 0
for valeur in liste:
somme += valeur
return somme / len(liste)
somme = 0
for valeur in liste:
somme += valeur
return somme / len(liste) - 1 # Erreur : pourquoi -1 ?
notes = [15, 18, 12, 9]
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
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.
------

View File

@@ -1,4 +1,4 @@
## Modularité
# Modularité
### Le programme
@@ -22,7 +22,7 @@ Il faudra donc prendre de bonnes habitudes :
- grouper les fonctions par fichier
- 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
## importations
@@ -35,10 +35,10 @@ VALEUR = 25
## fonctions
def racine(val):
"""
Renvoie la racine carré d'un nombre.
Renvoie la racine carrée d'un nombre.
:param val: (int) un entier
:return: (float) la racine carré
:return: (float) la racine carrée
:CU: type(val) == int && val >= 0
example:
@@ -81,7 +81,7 @@ from math import *
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.
@@ -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.
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
import random
@@ -114,7 +114,7 @@ import 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 :
@@ -179,7 +179,7 @@ if __name__=="__main__":
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.

View File

@@ -4,7 +4,7 @@
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
>>> de() in [1, 2, 3, 4, 5, 6]

View 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
View 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)

View File

@@ -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 quon a vu jusquici.
@@ -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.
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
@@ -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*)
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
class Etudiant :
@@ -104,13 +104,13 @@ False
### 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).
```python
def change_spe(self,nouvelle_spe) :
self.spe = nouvelle_spe
self.spe1 = nouvelle_spe
```
<u>Pour appeler la méthode :</u>
@@ -144,12 +144,12 @@ def nouvelle_spe(self,new_spe):
>>> etudiant2 = Etudiant('Timo','Alice','SES',17)
>>> etudiant2.spe1
'SES'
>>> e.nouvelle_spe(self,etudiant2.spe1)
>>> e.nouvelle_spe(etudiant2.spe1)
>>> e.spe2
'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.
### Méthodes particulières
@@ -195,7 +195,7 @@ Actuellement appeler un objet renvoie ceci :
```python
def __repr__(self):
return self.nom + ' ' + self.prenon
return self.nom + ' ' + self.prenom
```
```python
@@ -210,7 +210,7 @@ Dupond Bob
### 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
>>> e = Etudiant('Dupond','Bob','NSI',17)
@@ -218,7 +218,7 @@ Il est possible de rendre les attributs privés. Actuellement nous pouvons modif
'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
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

251
POO/TP/Corrige_TP_POO.md Normal file
View 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>.

View 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)

View File

@@ -4,7 +4,7 @@
## 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
@@ -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.
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
View 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>.

View File

@@ -70,7 +70,7 @@ class Bibliotheque :
#Initialisation des valeurs :
a1 = Auteur('Bob',1990)
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)
l3 = Livre('Les animaux de compagnies','Animaux',a1)
l4 = Livre('Les animaux sauvages','Animaux',a2)

View 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>.

View 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>.

View File

@@ -1,4 +1,4 @@
## Paradigmes de programmation
# Paradigmes de programmation
> Un paradigme est à la programmation ce quun 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 **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)
- Procédurale (FORTRAN, C)
- Objet (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)
- 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.
Ceci est du au fait quun 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 ;
- modifie les données, ou dautres : affectation ;
@@ -109,7 +109,7 @@ Une fonction à effet de bord est une fonction qui modifie ou utilise une variab
**__À nutiliser 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
# 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):
return n + k
print("Avec k=2 : ", ajoute_k_a_n(2))
print("Avec k=3 : ", ajoute_k_a_n(3))
n = 2
print("Avec k=2 : ", ajoute_k_a_n(n, 2))
print("Avec k=3 : ", ajoute_k_a_n(n, 3))
```
#### Fonctions lambdas
@@ -144,7 +145,7 @@ Tout comme la machine de Turing, le λ-calcul peut représenter nimporte quel
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
add_a_b = lambda a,b: a + b
@@ -191,7 +192,7 @@ maximum = functools.reduce(f_maximum, liste)
__Le filtrage__
Sélection les éléments dune liste vérifiant un certain critère.
Sélectionne les éléments d'une liste vérifiant un certain critère.
```python
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

View 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
View 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.
![image-20220729234146926](Images/Pile.png)
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.
![File](Images/File.png)
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>.

View 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>.

View File

@@ -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 :
- Depile(), Depile(), Empile(7), Empile(8),Depile()
- 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 ?
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)
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 cet ordre : 19982018. (1 est en bas de pile)
### 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 :
- Defile(), Defile(), Enfile(7), Enfile(8),Defile()
- 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 ?
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)
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 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>.

View 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()

View File

@@ -53,8 +53,8 @@ class File1 :
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
"""
if file.taille() < 5 :
file.enfile(fichier)
if self.taille() < 7 :
self.enfile(element)
return True
else :
return False
@@ -63,8 +63,8 @@ class File1 :
"""
Fonction qui vide une file et affiche ces éléments
"""
while file.est_vide() == False :
e = file.defile()
while self.est_vide() == False :
e = self.defile()
print(e)
# PARTIE 3 :

View File

@@ -27,7 +27,7 @@ Supposons que notre file à une taille fixe, disons 7 éléments maximum.
## 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
* **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 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 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 :
@@ -69,4 +69,12 @@ Un fichier carte.py contient la classe carte et les fonctions suivantes :
## 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>.

View 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>.

View File

@@ -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.
@@ -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.
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.
@@ -98,3 +98,10 @@ Le but ici est de trier une pile. Pour cela nous utiliserons une autre pile temp
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>.

View File

@@ -8,8 +8,8 @@ class Pile :
def empile(self,x) :
"""
Méthode qui enpile un élément
param x : () Elément x à enpiler
Méthode qui empile un élément
param x : () Elément x à empiler
"""
self.pile.append(x)
@@ -52,8 +52,8 @@ class Pile :
while pile_tmp.taille() != 0 and pile_tmp.top() < val_tmp:
self.empile(pile_tmp.depile())
pile_tmp.empile(val_tmp)
p.pile = pile_tmp.pile
return p
self.pile = pile_tmp.pile
return self
p = Pile()

View File

@@ -1,16 +1,95 @@
# TD Gestion des processus et des ressources corrigé
# Corrigé des exercices — Gestion des processus
------
---
## 1. QCM
1. a.
2. a.
3. c.
4. c.
5. c.
| Question | Réponse | Explication |
|----------|---------|-------------|
| 1 | **a.** | Le terminal (interpréteur de commandes) permet d'exécuter des commandes système. |
| 2 | **a.** | La commande `ps` (process status) affiche les processus sous Linux. |
| 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
1. Grâce à l'ordonnancement et le découpage des programmes en processus.
2. Un processus élu est en cours d'exécution, un prêt est en file d'attente dans l'ordonnanceur.
### Question 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.**
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>.

View 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()

View File

@@ -1,3 +1,5 @@
# Exercices — Gestion des processus
## 1. QCM
1. Le terminal, interpréteur de commande :
@@ -30,7 +32,7 @@
b. Changer l'état des processus
c. Choisi le processus à exécuter
c. Choisit le processus à exécuter
5. Un interblocage est :
@@ -42,5 +44,13 @@
## 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.
2. Quels sont les différences entre les états prêt et élu.
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. 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>.

View File

@@ -1,58 +1,56 @@
## Gestion des processus et des ressources
# Gestion des processus et des ressources
### Le programme
## Le programme
![bo.png](assets/bo.png)
------
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.
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
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 :*
<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 |
| ---------- | ----------------------------------------------------- |
| PID | Process ID, identifiant du processus |
| Etat | Etat du processus |
| 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. |
## 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 :
- Prêt : Le processus attend d'être exécuté. Il est dans la file 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.
- 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 :
- 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.
<u>Voici un schéma des différents états :</u>
@@ -61,8 +59,8 @@ Il existe aussi deux autres états :
## 3. Interblocage :
L'interblocage intervient lorsque plusieurs processus sont bloqués les un aux autres.
Imaginons deux programme.
L'interblocage intervient lorsque plusieurs processus sont bloqués les uns par les autres.
Imaginons deux programmes.
```
# Programme 1 :
@@ -90,7 +88,7 @@ Imaginons deux programme.
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 :
@@ -127,14 +125,14 @@ Imaginons deux programme.
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 :
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 :
@@ -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é.
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. 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.
<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**,
![ps_aux](assets/ps_aux.webp)
### 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).
<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

View 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>.

View 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>.

View 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}%")

View 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>.

View File

@@ -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.
@@ -27,16 +27,16 @@ def recherche(texte, motif):
for car in texte:
if car == motif:
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***).
```python
def recherche (texte, motif):
for i in range (len(texte)):
def recherche(texte, motif):
for i in range(len(texte)):
if texte[i] == motif:
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
> 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
- 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):
n = len(texte)
m = len(motif)
for j in range(n - m +1):
for j in range(n - m + 1):
i = 0
while i < m and texte [j + i] == motif [i]:
while i < m and texte[j + i] == motif[i]:
i = i + 1
if i == m :
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.
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)
i = 0
j = 0
while i < m and j < n :
if motif [i] != texte[j]:
while i < m and j < n:
if motif[i] != texte[j]:
j = j - i + 1
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 | | | | |
| 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 |
| -------- | ---- | ---- | ----- | ---- | ---- | ----- | ---- | ---- | ---- | ---- | ---- |
@@ -153,7 +153,7 @@ Idem ici, on redécale.
| Motif | | | | a | b | **c** | | | | | |
| 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 |
| -------- | ---- | ---- | ---- | ---- | ----- | ----- | ---- | ---- | ---- | ---- | ---- |
@@ -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...
>
> 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***"
@@ -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.
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
def morris_pratt(texte, motif):
m = len (motif)
m = len(motif)
n = len(texte)
i = 0
j = 0
sol = [] #tableau pour stocker les solutions
s = traitement (motif) #modification du programme naïf recherche
while i < m and j < n :
if i >= 0 and motif[i] != texte[j] :
sol = [] # Tableau pour stocker les solutions
s = traitement(motif) # Pré-traitement du motif
while i < m and j < n:
if i >= 0 and motif[i] != texte[j]:
i = s[i]
else:
i += 1
j += 1
if i >= m : #Si le motif est présent
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é
if i >= m: # Si le motif est présent
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é
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.
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.
#### 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>.

View 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>.

View 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 dune 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 lalgorithme**
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 quil y aura environ log_2(a) itérations.

View File

@@ -1,4 +1,3 @@
# Diviser pour régner
### 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 :
- `[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
def tri_fusion(gauche:list, droite:list):
tab_fusion = []
l1, l2 = len(gauche), len(droite)
i1, i2 = 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 fusionner(gauche: list, droite: list) -> list:
"""Fusionne deux listes triées en une seule liste triée."""
resultat = []
i, j = 0, 0
def fusion(tab:list):
if len (tab) <=1:
return tab
m = len(tab) // 2
return tri_fusion(fusion(tab[:m]), fusion (tab[m:]))
while i < len(gauche) and j < len(droite):
if gauche[i] < droite[j]:
resultat.append(gauche[i])
i += 1
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)
```
```
• 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 lon na pas parcouru complètement lune des deux listes (gauche ou droite), on compare les éléments actuels de chaque liste (cest-à-dire gauche[i1] et droite[i2]).
• Si lélément de gauche est plus petit, on lajoute à 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 lune des deux listes est entièrement parcourue, on ajoute les éléments restants de lautre liste à tab_fusion. Cest ce que fait gauche[i1:] pour la première liste, et droite[i2:] pour la deuxième.
```
#### Explication de la fonction `fusionner`
```
Condition de base :
• Si le tableau contient un seul élément (ou aucun), il est déjà trié, donc on le retourne tel quel.
2. Diviser :
• On divise le tableau en deux sous-tableaux de taille approximativement égale à laide de m = len(tab) // 2.
• tab[:m] correspond à la première moitié du tableau et tab[m:] à la deuxième.
3. Récursion et fusion :
• On applique récursivement fusion() aux deux moitiés du tableau.
• Une fois que les deux sous-tableaux sont triés, on les combine en utilisant la fonction tri_fusion.
```
- `resultat` est une liste vide où seront ajoutés les éléments fusionnés
- `i` et `j` sont des indices qui parcourent respectivement `gauche` et `droite`
- **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]`)
- Si l'élément de gauche est plus petit, on l'ajoute à `resultat` et on incrémente `i`
- Sinon, on ajoute l'élément de droite et on incrémente `j`
- **Éléments restants** : une fois qu'une liste est entièrement parcourue, on ajoute les éléments restants de l'autre liste
#### Explication de 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
<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 dUtilisation 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>.

View 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* ?

View File

@@ -1,4 +1,4 @@
## Recursivité
# Récursivité
@@ -17,11 +17,11 @@
### Un peu de culture
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%;" />
**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()
```
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 :
@@ -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.
@@ -67,17 +67,22 @@ Par exemple :
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
def somme(n) :
if n == 0 :
return 0
return 0 # Cas de base
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)* ?
@@ -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.
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
@@ -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.
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
>>> 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
```
### 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é :
- La récursivité multiple
- La récursivité double
- La récursivité imbriquée
- etc ...
- La **récursivité simple** : un seul appel récursif (comme `somme`)
- La **récursivité multiple** : plusieurs appels récursifs dans la fonction
- La **récursivité mutuelle** : deux fonctions qui s'appellent mutuellement
- 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.
$$
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
def fibonacci(n):
if n == 0 :
if n == 0:
return 0
elif n == 1 :
elif n == 1:
return 1
else :
return fibonnaci(n-1) + fibonnaci(n-2)
else:
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
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 dUtilisation 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>.

View File

@@ -42,7 +42,7 @@ def somme(n) :
def mystere(i,k):
if i<=k :
print(i)
mystère(i+1,k)
mystere(i+1,k)
```
### 2. 2. Nombre de chiffre d'un nombre :

View 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
View 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
```

View 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 sintéresse à lindice 12, on voit les valeurs 2 et 4 qui sont\n",
"différentes : lintrus est donc à gauche de lindice 12 (indice 12 compris)\n",
"\n",
"\n",
"En revanche, si on sintéresse à lindice 3, on voit les valeurs 9 et 9 qui sont\n",
"identiques : lintrus est donc à droite des indices 3-4-5, donc à partir de lindice 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
}

View 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`.

View 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)

View 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()

View File

@@ -3,34 +3,7 @@
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 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."
]
"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."
},
{
"cell_type": "code",

View File

@@ -3,34 +3,7 @@
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 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."
]
"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."
},
{
"cell_type": "code",

View 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))

View 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))

View 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)"]}
}
}
}

View 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))

View 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))

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View 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)&#45;&gt;fib4 -->
<g id="edge1" class="edge">
<title>fib(6)&#45;&gt;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)&#45;&gt;fib5 -->
<g id="edge2" class="edge">
<title>fib(6)&#45;&gt;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&#45;&gt;fib2 -->
<g id="edge3" class="edge">
<title>fib4&#45;&gt;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&#45;&gt;fib3 -->
<g id="edge4" class="edge">
<title>fib4&#45;&gt;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&#45;&gt;fib33 -->
<g id="edge7" class="edge">
<title>fib5&#45;&gt;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&#45;&gt;fib44 -->
<g id="edge8" class="edge">
<title>fib5&#45;&gt;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&#45;&gt;fib1 -->
<g id="edge5" class="edge">
<title>fib3&#45;&gt;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&#45;&gt;fib22 -->
<g id="edge6" class="edge">
<title>fib3&#45;&gt;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&#45;&gt;fib11 -->
<g id="edge9" class="edge">
<title>fib33&#45;&gt;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&#45;&gt;fib222 -->
<g id="edge10" class="edge">
<title>fib33&#45;&gt;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&#45;&gt;fib2222 -->
<g id="edge11" class="edge">
<title>fib44&#45;&gt;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&#45;&gt;fib333 -->
<g id="edge12" class="edge">
<title>fib44&#45;&gt;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&#45;&gt;fib111 -->
<g id="edge13" class="edge">
<title>fib333&#45;&gt;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&#45;&gt;fib22222 -->
<g id="edge14" class="edge">
<title>fib333&#45;&gt;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
View 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>.

View 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'])}")

View File

@@ -28,7 +28,7 @@ Lacheminement 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 @@ Lacheminement 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.
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.
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
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 :
- **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.
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** !
@@ -123,11 +123,11 @@ Si un routeur (et donc un réseau) n'est plus accessible passé 3 minutes, il se
À retenir :
- 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.
- Au delà de 15 sauts, on considère un routeur inaccessible.
- 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.
- Utilise lalgorithme à vecteur de distance de Bellman-Ford et est simple à configurer, mais limité en performance.
- 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.
- 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.
@@ -177,7 +177,7 @@ Enfin, il reste un algorithme :
Que nous ne verrons pas cette année.
Merci jamy !
Merci Jamy !
![merci jamy](assets/jamy.png)

391
Routage/TP_GPS_Navigator.md Normal file
View 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>.

View 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()

View File

@@ -24,13 +24,13 @@ def dijkstra(g, s):
res[IND[s]] = 0
a_faire = [e for e in g[0]]
while a_faire != []:
ind_courrant = select_min(res, a_faire)
sommet_courrant = g[0][ind_courrant]
a_faire.remove(sommet_courrant)
cout_courrant = res[ind_courrant]
ind_courant = select_min(res, a_faire)
sommet_courant = g[0][ind_courant]
a_faire.remove(sommet_courant)
cout_courant = res[ind_courant]
for l in g[1]:
if l[0]==sommet_courrant:
cout_tmp=l[2]+cout_courrant
if l[0]==sommet_courant:
cout_tmp=l[2]+cout_courant
if res[IND[l[1]]]>=cout_tmp:
res[IND[l[1]]] = cout_tmp
return res

View File

@@ -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é.
3. **Effectuer une opération OU (OR) bit à bit** :
- 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** :
- 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.
@@ -131,3 +131,10 @@ Déterminez ladresse réseau et ladresse de broadcast des adresses suivant
2. `172.16.100.200/22`
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
View 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>.

View 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}")

View File

@@ -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 |
| 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 |
| 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**.
### 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`).
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
1. Trouvez votre adresse IP locale avec la commande :
```sh
ipconfig (Windows) ou ifconfig/ip a (Linux/Mac)
ipconfig (Windows) ou ifconfig / ip a (Linux/Mac)
```
2. Testez une requête DNS avec :
```sh

380
Réseau/TP_MiniChat.md Normal file
View 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>.

View File

@@ -7,7 +7,7 @@
> 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.
@@ -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.
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éé.
![dns.gif](assets/dns.gif)
@@ -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.
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 ?
@@ -70,7 +70,7 @@ Répondez aux questions suivantes :
- De combien de parties est composée une url ?
- 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
@@ -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__* ?
[![Resolution de nom](https://img.youtube.com/vi/av0zX-dr8o8/0.jpg)](https://www.youtube.com/watch?v=av0zX-dr8o8)

View File

@@ -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.
*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__* :
@@ -23,17 +23,17 @@ Arobase souhaite passer une commande auprès d'une célèbre chaine de magasin d
__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 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 :
- 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é
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 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 ?__*
@@ -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 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.
@@ -78,7 +78,7 @@ Hélas on voit ici que le message était trop long, et que le canal s'est referm
![blague_1.png](assets/blague_3.png)
Et que se passe t-il dans ce cas ...?
Et que se passe-t-il dans ce cas... ?
![blague_4.png](assets/blague_4.png)

464
SQL/CORRIGE.md Normal file
View 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>.

View 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
View 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
View 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>.

View File

@@ -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.
> **Prérequis** : Avoir lu le cours [Cours_SQL.md](Cours_SQL.md) avant de commencer ce TP.
## Prérequis
- 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.
![Table créée avec succès](assets/5.png)
@@ -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);
```
- 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.
![Données insérées avec succès](assets/6.png)
@@ -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
```
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.
@@ -253,7 +255,7 @@ VALUES
14. À l'aide d'une requête SQL, ajoutez à la table LIVRES le livre suivant :
- id : 17
- titre : "2001 : L'Odyssée de l'espace"
- auteur : "Clarcke"
- auteur : "Clarke"
- année de publication : 1968
- note : 7
@@ -268,4 +270,11 @@ Source :
- [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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

263
Soc/CORRIGE.md Normal file
View 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>.

View 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