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

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