ajout de tous les cours et TP préparés cet été
This commit is contained in:
320
Paradigmes/Corrige_Exercice.md
Normal file
320
Paradigmes/Corrige_Exercice.md
Normal file
@@ -0,0 +1,320 @@
|
||||
# Corrigé de l'exercice — Cuisson des pâtes
|
||||
|
||||
---
|
||||
|
||||
## Énoncé
|
||||
|
||||
Nous voulons faire cuire des pâtes. Écrire la recette sous forme de code Python selon trois paradigmes différents.
|
||||
|
||||
---
|
||||
|
||||
## Version impérative
|
||||
|
||||
Liste d'instructions étape par étape, avec modification d'état.
|
||||
|
||||
```python
|
||||
# Version impérative : liste d'instructions étape par étape
|
||||
|
||||
def cuire_pates_imperatif():
|
||||
"""Fait cuire des pâtes de manière impérative."""
|
||||
|
||||
# État initial
|
||||
eau = "froide"
|
||||
pates = "crues"
|
||||
sel = False
|
||||
|
||||
# Étape 1 : Remplir la casserole
|
||||
print("Remplissage de la casserole...")
|
||||
quantite_eau = 1.5 # litres
|
||||
|
||||
# Étape 2 : Faire chauffer l'eau
|
||||
print("Chauffage de l'eau...")
|
||||
eau = "chaude"
|
||||
|
||||
# Étape 3 : Attendre l'ébullition
|
||||
print("Attente de l'ébullition...")
|
||||
eau = "bouillante"
|
||||
|
||||
# Étape 4 : Saler l'eau
|
||||
print("Ajout du sel...")
|
||||
sel = True
|
||||
|
||||
# Étape 5 : Ajouter les pâtes
|
||||
print("Ajout des pâtes...")
|
||||
pates = "en cuisson"
|
||||
|
||||
# Étape 6 : Attendre le temps de cuisson
|
||||
temps_cuisson = 10 # minutes
|
||||
print(f"Cuisson pendant {temps_cuisson} minutes...")
|
||||
|
||||
# Étape 7 : Égoutter
|
||||
print("Égouttage des pâtes...")
|
||||
pates = "cuites"
|
||||
|
||||
print(f"Résultat : pâtes {pates}")
|
||||
return pates
|
||||
|
||||
|
||||
# Exécution
|
||||
resultat = cuire_pates_imperatif()
|
||||
```
|
||||
|
||||
**Caractéristiques :**
|
||||
- Modification d'état (variables `eau`, `pates`, `sel`)
|
||||
- Instructions séquentielles
|
||||
- Effets de bord (print)
|
||||
|
||||
---
|
||||
|
||||
## Version fonctionnelle
|
||||
|
||||
Une fonction pure qui prend un état et retourne un nouvel état, sans modification.
|
||||
|
||||
```python
|
||||
# Version fonctionnelle : fonctions pures et composition
|
||||
|
||||
def remplir_casserole(etat):
|
||||
"""Retourne un nouvel état avec la casserole remplie."""
|
||||
return {**etat, "eau": "froide", "quantite_eau": 1.5}
|
||||
|
||||
|
||||
def chauffer_eau(etat):
|
||||
"""Retourne un nouvel état avec l'eau chauffée."""
|
||||
return {**etat, "eau": "chaude"}
|
||||
|
||||
|
||||
def faire_bouillir(etat):
|
||||
"""Retourne un nouvel état avec l'eau bouillante."""
|
||||
return {**etat, "eau": "bouillante"}
|
||||
|
||||
|
||||
def saler(etat):
|
||||
"""Retourne un nouvel état avec le sel ajouté."""
|
||||
return {**etat, "sel": True}
|
||||
|
||||
|
||||
def ajouter_pates(etat):
|
||||
"""Retourne un nouvel état avec les pâtes en cuisson."""
|
||||
return {**etat, "pates": "en cuisson"}
|
||||
|
||||
|
||||
def cuire(etat, duree):
|
||||
"""Retourne un nouvel état après cuisson."""
|
||||
return {**etat, "temps_cuisson": duree, "pates": "cuites"}
|
||||
|
||||
|
||||
def egoutter(etat):
|
||||
"""Retourne un nouvel état avec les pâtes égouttées."""
|
||||
return {**etat, "egouttees": True}
|
||||
|
||||
|
||||
# Composition de fonctions
|
||||
def cuire_pates_fonctionnel(etat_initial):
|
||||
"""
|
||||
Fait cuire des pâtes de manière fonctionnelle.
|
||||
Chaque étape retourne un nouvel état sans modifier l'ancien.
|
||||
"""
|
||||
etat = remplir_casserole(etat_initial)
|
||||
etat = chauffer_eau(etat)
|
||||
etat = faire_bouillir(etat)
|
||||
etat = saler(etat)
|
||||
etat = ajouter_pates(etat)
|
||||
etat = cuire(etat, 10)
|
||||
etat = egoutter(etat)
|
||||
return etat
|
||||
|
||||
|
||||
# Version encore plus fonctionnelle avec reduce
|
||||
import functools
|
||||
|
||||
def cuire_pates_reduce(etat_initial):
|
||||
"""Version avec reduce pour composer les fonctions."""
|
||||
etapes = [
|
||||
remplir_casserole,
|
||||
chauffer_eau,
|
||||
faire_bouillir,
|
||||
saler,
|
||||
ajouter_pates,
|
||||
lambda e: cuire(e, 10),
|
||||
egoutter
|
||||
]
|
||||
return functools.reduce(lambda etat, f: f(etat), etapes, etat_initial)
|
||||
|
||||
|
||||
# Exécution
|
||||
etat_initial = {"pates": "crues", "sel": False}
|
||||
resultat = cuire_pates_fonctionnel(etat_initial)
|
||||
print(f"État final : {resultat}")
|
||||
|
||||
# Vérification : l'état initial n'a pas été modifié
|
||||
print(f"État initial préservé : {etat_initial}")
|
||||
```
|
||||
|
||||
**Caractéristiques :**
|
||||
- Pas de modification d'état (immutabilité)
|
||||
- Fonctions pures (même entrée → même sortie)
|
||||
- Composition de fonctions
|
||||
- L'état initial n'est jamais modifié
|
||||
|
||||
---
|
||||
|
||||
## Version orientée objet
|
||||
|
||||
Une classe `Pates` avec des attributs et une méthode `cuire()`.
|
||||
|
||||
```python
|
||||
# Version orientée objet : classe Pates avec méthode cuire()
|
||||
|
||||
class Casserole:
|
||||
"""Représente une casserole pour la cuisson."""
|
||||
|
||||
def __init__(self):
|
||||
self.eau = None
|
||||
self.temperature = "froide"
|
||||
self.sel = False
|
||||
|
||||
def remplir(self, quantite=1.5):
|
||||
"""Remplit la casserole d'eau."""
|
||||
self.eau = quantite
|
||||
print(f"Casserole remplie avec {quantite}L d'eau")
|
||||
|
||||
def chauffer(self):
|
||||
"""Chauffe l'eau jusqu'à ébullition."""
|
||||
self.temperature = "chaude"
|
||||
print("Chauffage en cours...")
|
||||
self.temperature = "bouillante"
|
||||
print("L'eau bout !")
|
||||
|
||||
def saler(self):
|
||||
"""Ajoute du sel dans l'eau."""
|
||||
self.sel = True
|
||||
print("Sel ajouté")
|
||||
|
||||
|
||||
class Pates:
|
||||
"""Représente des pâtes à cuire."""
|
||||
|
||||
def __init__(self, type_pates="spaghetti", quantite=500):
|
||||
self.type = type_pates
|
||||
self.quantite = quantite # en grammes
|
||||
self.etat = "crues"
|
||||
self.temps_cuisson = 0
|
||||
|
||||
def cuire(self, casserole, duree=10):
|
||||
"""
|
||||
Fait cuire les pâtes dans une casserole.
|
||||
|
||||
:param casserole: (Casserole) la casserole à utiliser
|
||||
:param duree: (int) temps de cuisson en minutes
|
||||
"""
|
||||
# Vérifications
|
||||
if casserole.eau is None:
|
||||
raise ValueError("La casserole doit contenir de l'eau !")
|
||||
if casserole.temperature != "bouillante":
|
||||
raise ValueError("L'eau doit être bouillante !")
|
||||
|
||||
print(f"Ajout de {self.quantite}g de {self.type} dans la casserole")
|
||||
self.etat = "en cuisson"
|
||||
|
||||
print(f"Cuisson pendant {duree} minutes...")
|
||||
self.temps_cuisson = duree
|
||||
|
||||
self.etat = "cuites"
|
||||
print(f"Les {self.type} sont cuites !")
|
||||
|
||||
def egoutter(self):
|
||||
"""Égoutte les pâtes."""
|
||||
if self.etat != "cuites":
|
||||
raise ValueError("Les pâtes doivent être cuites avant d'être égouttées !")
|
||||
print("Égouttage des pâtes...")
|
||||
self.etat = "prêtes"
|
||||
|
||||
def __str__(self):
|
||||
return f"Pates({self.type}, {self.quantite}g, état={self.etat})"
|
||||
|
||||
|
||||
# Exécution
|
||||
def preparer_pates_oo():
|
||||
"""Prépare des pâtes en utilisant la POO."""
|
||||
|
||||
# Création des objets
|
||||
casserole = Casserole()
|
||||
pates = Pates("fusilli", 400)
|
||||
|
||||
print(f"Début : {pates}")
|
||||
|
||||
# Préparation de la casserole
|
||||
casserole.remplir(2.0)
|
||||
casserole.chauffer()
|
||||
casserole.saler()
|
||||
|
||||
# Cuisson des pâtes
|
||||
pates.cuire(casserole, duree=12)
|
||||
pates.egoutter()
|
||||
|
||||
print(f"Fin : {pates}")
|
||||
return pates
|
||||
|
||||
|
||||
resultat = preparer_pates_oo()
|
||||
```
|
||||
|
||||
**Caractéristiques :**
|
||||
- Encapsulation (attributs et méthodes regroupés)
|
||||
- Objets avec état interne
|
||||
- Interactions entre objets (Pates et Casserole)
|
||||
- Méthodes qui modifient l'état de l'objet
|
||||
|
||||
---
|
||||
|
||||
## Tableau comparatif
|
||||
|
||||
| Aspect | Impératif | Fonctionnel | Orienté Objet |
|
||||
|--------|-----------|-------------|---------------|
|
||||
| **État** | Variables modifiées | États immuables | Attributs d'objets |
|
||||
| **Données** | Variables simples | Dictionnaires/tuples | Objets |
|
||||
| **Actions** | Instructions séquentielles | Fonctions composées | Méthodes |
|
||||
| **Modification** | Sur place | Nouvel état retourné | Via méthodes |
|
||||
| **Réutilisabilité** | Faible | Moyenne | Forte |
|
||||
| **Testabilité** | Difficile | Facile | Moyenne |
|
||||
|
||||
---
|
||||
|
||||
## Bonus : Version lambda
|
||||
|
||||
```python
|
||||
# Version ultra-fonctionnelle avec lambdas et reduce
|
||||
|
||||
import functools
|
||||
|
||||
# Chaque étape est une lambda qui transforme l'état
|
||||
etapes = [
|
||||
lambda e: {**e, "eau": 1.5},
|
||||
lambda e: {**e, "temperature": "bouillante"},
|
||||
lambda e: {**e, "sel": True},
|
||||
lambda e: {**e, "pates": "en cuisson"},
|
||||
lambda e: {**e, "temps": 10, "pates": "cuites"},
|
||||
lambda e: {**e, "egouttees": True}
|
||||
]
|
||||
|
||||
# Composition avec reduce
|
||||
cuire_pates_lambda = lambda etat: functools.reduce(
|
||||
lambda e, f: f(e),
|
||||
etapes,
|
||||
etat
|
||||
)
|
||||
|
||||
# Exécution
|
||||
resultat = cuire_pates_lambda({"pates": "crues"})
|
||||
print(resultat)
|
||||
# {'pates': 'cuites', 'eau': 1.5, 'temperature': 'bouillante',
|
||||
# 'sel': True, 'temps': 10, 'egouttees': True}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||
|
||||
Licence CC BY NC
|
||||
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||
433
Paradigmes/Corrige_TP_SpotifyWrapped.md
Normal file
433
Paradigmes/Corrige_TP_SpotifyWrapped.md
Normal file
@@ -0,0 +1,433 @@
|
||||
# Corrigé du TP Spotify Wrapped
|
||||
|
||||
---
|
||||
|
||||
## Partie 2 : Fonctions de base
|
||||
|
||||
### Exercice 1 : Extraction de données avec `map`
|
||||
|
||||
**Question 1.1** :
|
||||
```python
|
||||
get_titre = lambda ecoute: ecoute["titre"]
|
||||
|
||||
titres = list(map(get_titre, ecoutes))
|
||||
print(titres[:3]) # ['Blinding Lights', 'Bohemian Rhapsody', 'Bad Guy']
|
||||
```
|
||||
|
||||
**Question 1.2** :
|
||||
```python
|
||||
get_duree_minutes = lambda ecoute: round(ecoute["duree"] / 60, 2)
|
||||
|
||||
durees = list(map(get_duree_minutes, ecoutes))
|
||||
print(durees[:3]) # [3.38, 5.9, 3.23]
|
||||
```
|
||||
|
||||
**Question 1.3** :
|
||||
```python
|
||||
formater_ecoute = lambda e: f"{e['titre']} - {e['artiste']} ({round(e['duree']/60, 2)}min)"
|
||||
|
||||
formatees = list(map(formater_ecoute, ecoutes))
|
||||
print(formatees[0]) # "Blinding Lights - The Weeknd (3.38min)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Exercice 2 : Filtrage avec `filter`
|
||||
|
||||
**Question 2.1** :
|
||||
```python
|
||||
est_pop = lambda ecoute: ecoute["genre"] == "pop"
|
||||
|
||||
ecoutes_pop = list(filter(est_pop, ecoutes))
|
||||
print(f"Nombre de morceaux pop : {len(ecoutes_pop)}") # 12
|
||||
```
|
||||
|
||||
**Question 2.2** :
|
||||
```python
|
||||
est_long = lambda ecoute: ecoute["duree"] > 300
|
||||
|
||||
ecoutes_longues = list(filter(est_long, ecoutes))
|
||||
print(f"Morceaux longs : {len(ecoutes_longues)}") # 5
|
||||
```
|
||||
|
||||
**Question 2.3** :
|
||||
```python
|
||||
def filtre_genre(genre):
|
||||
"""
|
||||
Retourne une fonction lambda qui filtre par genre.
|
||||
C'est une fonction d'ordre supérieur !
|
||||
"""
|
||||
return lambda ecoute: ecoute["genre"] == genre
|
||||
|
||||
ecoutes_rock = list(filter(filtre_genre("rock"), ecoutes))
|
||||
ecoutes_rap = list(filter(filtre_genre("rap"), ecoutes))
|
||||
print(f"Rock: {len(ecoutes_rock)}, Rap: {len(ecoutes_rap)}") # Rock: 4, Rap: 2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Exercice 3 : Agrégation avec `reduce`
|
||||
|
||||
```python
|
||||
import functools
|
||||
```
|
||||
|
||||
**Question 3.1** :
|
||||
```python
|
||||
temps_total = functools.reduce(
|
||||
lambda acc, ecoute: acc + ecoute["duree"],
|
||||
ecoutes,
|
||||
0
|
||||
)
|
||||
print(f"Temps total : {temps_total} secondes") # 5301 secondes
|
||||
```
|
||||
|
||||
**Question 3.2** :
|
||||
```python
|
||||
heures = temps_total // 3600
|
||||
minutes = (temps_total % 3600) // 60
|
||||
print(f"Temps d'écoute : {heures}h {minutes}min") # 1h 28min
|
||||
```
|
||||
|
||||
**Question 3.3** :
|
||||
```python
|
||||
plus_long = functools.reduce(
|
||||
lambda acc, ecoute: ecoute if ecoute["duree"] > acc["duree"] else acc,
|
||||
ecoutes
|
||||
)
|
||||
print(f"Plus long : {plus_long['titre']} ({plus_long['duree']}s)")
|
||||
# Plus long : Stairway to Heaven (482s)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Partie 3 : Statistiques avancées
|
||||
|
||||
### Exercice 4 : Comptage par catégorie
|
||||
|
||||
**Question 4.1** : (fournie dans l'énoncé)
|
||||
```python
|
||||
def compter_par_genre(ecoutes):
|
||||
return functools.reduce(
|
||||
lambda acc, e: {**acc, e["genre"]: acc.get(e["genre"], 0) + 1},
|
||||
ecoutes,
|
||||
{}
|
||||
)
|
||||
|
||||
stats_genres = compter_par_genre(ecoutes)
|
||||
print(stats_genres) # {'pop': 12, 'rock': 4, 'rap': 2, 'metal': 2}
|
||||
```
|
||||
|
||||
**Question 4.2** :
|
||||
```python
|
||||
def compter_par_artiste(ecoutes):
|
||||
"""Retourne un dictionnaire {artiste: nombre_ecoutes}."""
|
||||
return functools.reduce(
|
||||
lambda acc, e: {**acc, e["artiste"]: acc.get(e["artiste"], 0) + 1},
|
||||
ecoutes,
|
||||
{}
|
||||
)
|
||||
|
||||
stats_artistes = compter_par_artiste(ecoutes)
|
||||
print(stats_artistes)
|
||||
# {'The Weeknd': 3, 'Queen': 1, 'Billie Eilish': 1, 'Led Zeppelin': 1, ...}
|
||||
```
|
||||
|
||||
**Question 4.3** :
|
||||
```python
|
||||
def artiste_prefere(stats_artistes):
|
||||
"""Retourne le nom de l'artiste avec le plus d'écoutes."""
|
||||
return functools.reduce(
|
||||
lambda acc, item: item if item[1] > acc[1] else acc,
|
||||
stats_artistes.items()
|
||||
)[0]
|
||||
|
||||
top_artiste = artiste_prefere(stats_artistes)
|
||||
print(f"Artiste préféré : {top_artiste}") # The Weeknd
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Exercice 5 : Chaînage fonctionnel (Pipeline)
|
||||
|
||||
**Question 5.1** : (fournie dans l'énoncé)
|
||||
```python
|
||||
temps_pop = functools.reduce(
|
||||
lambda acc, e: acc + e["duree"],
|
||||
filter(lambda e: e["genre"] == "pop", ecoutes),
|
||||
0
|
||||
)
|
||||
print(f"Temps pop : {temps_pop // 60} minutes") # 42 minutes
|
||||
```
|
||||
|
||||
**Question 5.2** : (fournie dans l'énoncé)
|
||||
```python
|
||||
titres_rock_longs = list(map(
|
||||
lambda e: e["titre"],
|
||||
sorted(
|
||||
filter(
|
||||
lambda e: e["duree"] > 240,
|
||||
filter(
|
||||
lambda e: e["genre"] == "rock",
|
||||
ecoutes
|
||||
)
|
||||
),
|
||||
key=lambda e: e["duree"],
|
||||
reverse=True
|
||||
)
|
||||
))
|
||||
print(titres_rock_longs)
|
||||
# ['Stairway to Heaven', 'Bohemian Rhapsody', 'Smells Like Teen Spirit']
|
||||
```
|
||||
|
||||
**Question 5.3** : (fournie dans l'énoncé)
|
||||
```python
|
||||
def pipeline(*fonctions):
|
||||
"""Retourne une fonction qui applique les fonctions en séquence."""
|
||||
return lambda x: functools.reduce(
|
||||
lambda acc, f: f(acc),
|
||||
fonctions,
|
||||
x
|
||||
)
|
||||
|
||||
traitement = pipeline(
|
||||
lambda data: filter(lambda e: e["genre"] == "pop", data),
|
||||
lambda data: map(lambda e: e["titre"], data),
|
||||
list
|
||||
)
|
||||
|
||||
print(traitement(ecoutes)[:3]) # ['Blinding Lights', 'Bad Guy', 'Shape of You']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Partie 4 : Génération du Wrapped
|
||||
|
||||
### Exercice 6 : Créer le résumé final
|
||||
|
||||
```python
|
||||
import functools
|
||||
|
||||
def compter_par_artiste(ecoutes):
|
||||
return functools.reduce(
|
||||
lambda acc, e: {**acc, e["artiste"]: acc.get(e["artiste"], 0) + 1},
|
||||
ecoutes,
|
||||
{}
|
||||
)
|
||||
|
||||
def compter_par_genre(ecoutes):
|
||||
return functools.reduce(
|
||||
lambda acc, e: {**acc, e["genre"]: acc.get(e["genre"], 0) + 1},
|
||||
ecoutes,
|
||||
{}
|
||||
)
|
||||
|
||||
def generer_wrapped(ecoutes):
|
||||
"""
|
||||
Génère un résumé Wrapped complet de manière fonctionnelle.
|
||||
"""
|
||||
# Temps total
|
||||
temps_total = functools.reduce(lambda acc, e: acc + e["duree"], ecoutes, 0)
|
||||
|
||||
# Comptages
|
||||
stats_artistes = compter_par_artiste(ecoutes)
|
||||
stats_genres = compter_par_genre(ecoutes)
|
||||
|
||||
# Top artiste (celui avec le plus d'écoutes)
|
||||
top_artiste = functools.reduce(
|
||||
lambda acc, item: item if item[1] > acc[1] else acc,
|
||||
stats_artistes.items()
|
||||
)[0]
|
||||
|
||||
# Top genre
|
||||
top_genre = functools.reduce(
|
||||
lambda acc, item: item if item[1] > acc[1] else acc,
|
||||
stats_genres.items()
|
||||
)[0]
|
||||
|
||||
# Morceau le plus écouté (celui qui apparaît le plus)
|
||||
stats_titres = functools.reduce(
|
||||
lambda acc, e: {**acc, e["titre"]: acc.get(e["titre"], 0) + 1},
|
||||
ecoutes,
|
||||
{}
|
||||
)
|
||||
top_titre = functools.reduce(
|
||||
lambda acc, item: item if item[1] > acc[1] else acc,
|
||||
stats_titres.items()
|
||||
)[0]
|
||||
|
||||
# Morceau le plus long
|
||||
plus_long = functools.reduce(
|
||||
lambda acc, e: e if e["duree"] > acc["duree"] else acc,
|
||||
ecoutes
|
||||
)
|
||||
|
||||
return {
|
||||
"nombre_ecoutes": len(ecoutes),
|
||||
"temps_total_minutes": round(temps_total / 60, 1),
|
||||
"artiste_prefere": top_artiste,
|
||||
"genre_prefere": top_genre,
|
||||
"titre_prefere": top_titre,
|
||||
"morceau_plus_long": plus_long["titre"],
|
||||
"stats_genres": stats_genres,
|
||||
"top_5_artistes": dict(sorted(
|
||||
stats_artistes.items(),
|
||||
key=lambda x: x[1],
|
||||
reverse=True
|
||||
)[:5])
|
||||
}
|
||||
|
||||
|
||||
# Exécution
|
||||
wrapped = generer_wrapped(ecoutes)
|
||||
|
||||
print("=" * 40)
|
||||
print(" VOTRE WRAPPED 2026")
|
||||
print("=" * 40)
|
||||
print(f"Écoutes totales : {wrapped['nombre_ecoutes']}")
|
||||
print(f"Temps d'écoute : {wrapped['temps_total_minutes']} minutes")
|
||||
print(f"Artiste préféré : {wrapped['artiste_prefere']}")
|
||||
print(f"Genre préféré : {wrapped['genre_prefere']}")
|
||||
print(f"Titre préféré : {wrapped['titre_prefere']}")
|
||||
print(f"Top 5 artistes : {list(wrapped['top_5_artistes'].keys())}")
|
||||
print("=" * 40)
|
||||
```
|
||||
|
||||
**Sortie attendue :**
|
||||
```
|
||||
========================================
|
||||
VOTRE WRAPPED 2026
|
||||
========================================
|
||||
Écoutes totales : 20
|
||||
Temps d'écoute : 88.4 minutes
|
||||
Artiste préféré : The Weeknd
|
||||
Genre préféré : pop
|
||||
Titre préféré : Blinding Lights
|
||||
Top 5 artistes : ['The Weeknd', 'Michael Jackson', 'Metallica', 'Queen', 'Billie Eilish']
|
||||
========================================
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Partie 5 : Bonus — Récursivité
|
||||
|
||||
### Exercice 7 : Implémenter reduce sans boucle
|
||||
|
||||
```python
|
||||
def mon_reduce(fonction, liste, initial):
|
||||
"""
|
||||
Implémentation récursive de reduce.
|
||||
|
||||
:param fonction: (callable) fonction à 2 arguments (acc, elem)
|
||||
:param liste: (list) liste à réduire
|
||||
:param initial: valeur initiale de l'accumulateur
|
||||
:return: résultat de la réduction
|
||||
"""
|
||||
if len(liste) == 0:
|
||||
# Cas de base : liste vide, on retourne l'accumulateur
|
||||
return initial
|
||||
else:
|
||||
# Appel récursif : on applique la fonction au premier élément
|
||||
# puis on continue avec le reste de la liste
|
||||
nouvel_acc = fonction(initial, liste[0])
|
||||
return mon_reduce(fonction, liste[1:], nouvel_acc)
|
||||
|
||||
|
||||
# Tests
|
||||
total = mon_reduce(lambda acc, x: acc + x, [1, 2, 3, 4, 5], 0)
|
||||
print(f"Somme : {total}") # 15
|
||||
|
||||
produit = mon_reduce(lambda acc, x: acc * x, [1, 2, 3, 4, 5], 1)
|
||||
print(f"Produit : {produit}") # 120
|
||||
|
||||
concatenation = mon_reduce(lambda acc, x: acc + x, ["a", "b", "c"], "")
|
||||
print(f"Concaténation : {concatenation}") # "abc"
|
||||
```
|
||||
|
||||
**Explication de la récursivité :**
|
||||
|
||||
Pour `mon_reduce(lambda acc, x: acc + x, [1, 2, 3], 0)` :
|
||||
|
||||
```
|
||||
mon_reduce(f, [1, 2, 3], 0)
|
||||
→ nouvel_acc = f(0, 1) = 1
|
||||
→ mon_reduce(f, [2, 3], 1)
|
||||
→ nouvel_acc = f(1, 2) = 3
|
||||
→ mon_reduce(f, [3], 3)
|
||||
→ nouvel_acc = f(3, 3) = 6
|
||||
→ mon_reduce(f, [], 6)
|
||||
→ return 6 (cas de base)
|
||||
→ return 6
|
||||
→ return 6
|
||||
→ return 6
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Code complet fonctionnel
|
||||
|
||||
```python
|
||||
"""
|
||||
Spotify Wrapped - Version fonctionnelle complète
|
||||
"""
|
||||
|
||||
import functools
|
||||
|
||||
# Données
|
||||
ecoutes = [
|
||||
{"titre": "Blinding Lights", "artiste": "The Weeknd", "duree": 203, "genre": "pop", "date": "2026-01-15"},
|
||||
{"titre": "Bohemian Rhapsody", "artiste": "Queen", "duree": 354, "genre": "rock", "date": "2026-01-15"},
|
||||
{"titre": "Bad Guy", "artiste": "Billie Eilish", "duree": 194, "genre": "pop", "date": "2026-01-16"},
|
||||
{"titre": "Stairway to Heaven", "artiste": "Led Zeppelin", "duree": 482, "genre": "rock", "date": "2026-01-16"},
|
||||
{"titre": "Shape of You", "artiste": "Ed Sheeran", "duree": 234, "genre": "pop", "date": "2026-01-17"},
|
||||
{"titre": "Smells Like Teen Spirit", "artiste": "Nirvana", "duree": 279, "genre": "rock", "date": "2026-01-17"},
|
||||
{"titre": "Levitating", "artiste": "Dua Lipa", "duree": 203, "genre": "pop", "date": "2026-01-18"},
|
||||
{"titre": "Back in Black", "artiste": "AC/DC", "duree": 255, "genre": "rock", "date": "2026-01-18"},
|
||||
{"titre": "Blinding Lights", "artiste": "The Weeknd", "duree": 203, "genre": "pop", "date": "2026-01-19"},
|
||||
{"titre": "drivers license", "artiste": "Olivia Rodrigo", "duree": 242, "genre": "pop", "date": "2026-01-19"},
|
||||
{"titre": "Lose Yourself", "artiste": "Eminem", "duree": 326, "genre": "rap", "date": "2026-01-20"},
|
||||
{"titre": "HUMBLE.", "artiste": "Kendrick Lamar", "duree": 177, "genre": "rap", "date": "2026-01-20"},
|
||||
{"titre": "Blinding Lights", "artiste": "The Weeknd", "duree": 203, "genre": "pop", "date": "2026-01-21"},
|
||||
{"titre": "Watermelon Sugar", "artiste": "Harry Styles", "duree": 174, "genre": "pop", "date": "2026-01-21"},
|
||||
{"titre": "Enter Sandman", "artiste": "Metallica", "duree": 332, "genre": "metal", "date": "2026-01-22"},
|
||||
{"titre": "Nothing Else Matters", "artiste": "Metallica", "duree": 388, "genre": "metal", "date": "2026-01-22"},
|
||||
{"titre": "Thriller", "artiste": "Michael Jackson", "duree": 358, "genre": "pop", "date": "2026-01-23"},
|
||||
{"titre": "Billie Jean", "artiste": "Michael Jackson", "duree": 294, "genre": "pop", "date": "2026-01-23"},
|
||||
{"titre": "Anti-Hero", "artiste": "Taylor Swift", "duree": 200, "genre": "pop", "date": "2026-01-24"},
|
||||
{"titre": "Flowers", "artiste": "Miley Cyrus", "duree": 200, "genre": "pop", "date": "2026-01-24"},
|
||||
]
|
||||
|
||||
# Fonctions utilitaires
|
||||
compter = lambda cle: lambda data: functools.reduce(
|
||||
lambda acc, e: {**acc, e[cle]: acc.get(e[cle], 0) + 1},
|
||||
data,
|
||||
{}
|
||||
)
|
||||
|
||||
top = lambda stats: functools.reduce(
|
||||
lambda acc, item: item if item[1] > acc[1] else acc,
|
||||
stats.items()
|
||||
)[0]
|
||||
|
||||
# Pipeline de génération du Wrapped
|
||||
generer_wrapped = lambda data: {
|
||||
"nombre_ecoutes": len(data),
|
||||
"temps_total_minutes": round(functools.reduce(lambda a, e: a + e["duree"], data, 0) / 60, 1),
|
||||
"artiste_prefere": top(compter("artiste")(data)),
|
||||
"genre_prefere": top(compter("genre")(data)),
|
||||
"titre_prefere": top(compter("titre")(data)),
|
||||
"morceau_plus_long": functools.reduce(lambda a, e: e if e["duree"] > a["duree"] else a, data)["titre"],
|
||||
}
|
||||
|
||||
# Exécution
|
||||
wrapped = generer_wrapped(ecoutes)
|
||||
print(wrapped)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||
|
||||
Licence CC BY NC
|
||||
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||
@@ -1,4 +1,4 @@
|
||||
## Paradigmes de programmation
|
||||
# Paradigmes de programmation
|
||||
|
||||
> Un paradigme est à la programmation ce qu’un style culinaire est à la cuisine : il impose des règles, des outils, et une manière de faire.
|
||||
|
||||
@@ -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 qu’un traitement de données :
|
||||
Ceci est dû au fait qu'un traitement de données :
|
||||
|
||||
- dépend de paramètres extérieurs non maîtrisés : variables ;
|
||||
- modifie les données, ou d’autres : affectation ;
|
||||
@@ -109,7 +109,7 @@ Une fonction à effet de bord est une fonction qui modifie ou utilise une variab
|
||||
|
||||
**__À n’utiliser que lorsque cela est nécessaire !__**
|
||||
|
||||
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 n’importe quel
|
||||
|
||||
La programmation **fonctionnelle** puise son origine du __λ-calcul__ alors que la programmation **impérative** puise son origine de la __machine de Turing__.
|
||||
|
||||
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 d’une 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
|
||||
|
||||
|
||||
413
Paradigmes/TP_SpotifyWrapped.md
Normal file
413
Paradigmes/TP_SpotifyWrapped.md
Normal file
@@ -0,0 +1,413 @@
|
||||
# TP : Créer son Spotify Wrapped — Programmation Fonctionnelle
|
||||
|
||||
> **Thème** : Paradigmes de programmation, fonctions lambda, map, filter, reduce
|
||||
|
||||
---
|
||||
|
||||
## Contexte
|
||||
|
||||
Chaque fin d'année, **Spotify Wrapped** révèle aux utilisateurs leurs statistiques d'écoute : artistes préférés, genres favoris, minutes écoutées... Ce résumé personnalisé est devenu un phénomène viral sur les réseaux sociaux.
|
||||
|
||||
Dans ce TP, vous allez créer votre propre **mini Wrapped** en utilisant exclusivement la **programmation fonctionnelle**. Vous découvrirez comment les géants du streaming traitent des milliards de données grâce à ce paradigme.
|
||||
|
||||
**Règle d'or du TP** : Aucune boucle `for` ou `while` n'est autorisée ! Uniquement `map`, `filter`, `reduce` et la récursivité.
|
||||
|
||||
---
|
||||
|
||||
## Partie 1 : Les données
|
||||
|
||||
### Les écoutes
|
||||
|
||||
Voici un extrait de l'historique d'écoute d'un utilisateur. Chaque écoute est représentée par un dictionnaire.
|
||||
|
||||
```python
|
||||
# Données d'écoute (à copier dans votre fichier)
|
||||
ecoutes = [
|
||||
{"titre": "Blinding Lights", "artiste": "The Weeknd", "duree": 203, "genre": "pop", "date": "2026-01-15"},
|
||||
{"titre": "Bohemian Rhapsody", "artiste": "Queen", "duree": 354, "genre": "rock", "date": "2026-01-15"},
|
||||
{"titre": "Bad Guy", "artiste": "Billie Eilish", "duree": 194, "genre": "pop", "date": "2026-01-16"},
|
||||
{"titre": "Stairway to Heaven", "artiste": "Led Zeppelin", "duree": 482, "genre": "rock", "date": "2026-01-16"},
|
||||
{"titre": "Shape of You", "artiste": "Ed Sheeran", "duree": 234, "genre": "pop", "date": "2026-01-17"},
|
||||
{"titre": "Smells Like Teen Spirit", "artiste": "Nirvana", "duree": 279, "genre": "rock", "date": "2026-01-17"},
|
||||
{"titre": "Levitating", "artiste": "Dua Lipa", "duree": 203, "genre": "pop", "date": "2026-01-18"},
|
||||
{"titre": "Back in Black", "artiste": "AC/DC", "duree": 255, "genre": "rock", "date": "2026-01-18"},
|
||||
{"titre": "Blinding Lights", "artiste": "The Weeknd", "duree": 203, "genre": "pop", "date": "2026-01-19"},
|
||||
{"titre": "drivers license", "artiste": "Olivia Rodrigo", "duree": 242, "genre": "pop", "date": "2026-01-19"},
|
||||
{"titre": "Lose Yourself", "artiste": "Eminem", "duree": 326, "genre": "rap", "date": "2026-01-20"},
|
||||
{"titre": "HUMBLE.", "artiste": "Kendrick Lamar", "duree": 177, "genre": "rap", "date": "2026-01-20"},
|
||||
{"titre": "Blinding Lights", "artiste": "The Weeknd", "duree": 203, "genre": "pop", "date": "2026-01-21"},
|
||||
{"titre": "Watermelon Sugar", "artiste": "Harry Styles", "duree": 174, "genre": "pop", "date": "2026-01-21"},
|
||||
{"titre": "Enter Sandman", "artiste": "Metallica", "duree": 332, "genre": "metal", "date": "2026-01-22"},
|
||||
{"titre": "Nothing Else Matters", "artiste": "Metallica", "duree": 388, "genre": "metal", "date": "2026-01-22"},
|
||||
{"titre": "Thriller", "artiste": "Michael Jackson", "duree": 358, "genre": "pop", "date": "2026-01-23"},
|
||||
{"titre": "Billie Jean", "artiste": "Michael Jackson", "duree": 294, "genre": "pop", "date": "2026-01-23"},
|
||||
{"titre": "Anti-Hero", "artiste": "Taylor Swift", "duree": 200, "genre": "pop", "date": "2026-01-24"},
|
||||
{"titre": "Flowers", "artiste": "Miley Cyrus", "duree": 200, "genre": "pop", "date": "2026-01-24"},
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Partie 2 : Fonctions de base
|
||||
|
||||
### Exercice 1 : Extraction de données avec `map`
|
||||
|
||||
La fonction `map(fonction, liste)` applique une fonction à chaque élément d'une liste.
|
||||
|
||||
**Question 1.1** : Écrivez une fonction lambda `get_titre` qui extrait le titre d'une écoute.
|
||||
|
||||
```python
|
||||
get_titre = lambda ecoute: # À compléter
|
||||
|
||||
# Test
|
||||
titres = list(map(get_titre, ecoutes))
|
||||
print(titres[:3]) # ['Blinding Lights', 'Bohemian Rhapsody', 'Bad Guy']
|
||||
```
|
||||
|
||||
**Question 1.2** : Écrivez une fonction lambda `get_duree_minutes` qui convertit la durée (en secondes) en minutes (arrondi à 2 décimales).
|
||||
|
||||
```python
|
||||
get_duree_minutes = lambda ecoute: # À compléter
|
||||
|
||||
# Test
|
||||
durees = list(map(get_duree_minutes, ecoutes))
|
||||
print(durees[:3]) # [3.38, 5.9, 3.23]
|
||||
```
|
||||
|
||||
**Question 1.3** : Créez une fonction `formater_ecoute` qui retourne une chaîne au format `"Titre - Artiste (Xmin)"`.
|
||||
|
||||
```python
|
||||
formater_ecoute = lambda e: # À compléter
|
||||
|
||||
# Test
|
||||
formatees = list(map(formater_ecoute, ecoutes))
|
||||
print(formatees[0]) # "Blinding Lights - The Weeknd (3.38min)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Exercice 2 : Filtrage avec `filter`
|
||||
|
||||
La fonction `filter(fonction, liste)` conserve uniquement les éléments pour lesquels la fonction retourne `True`.
|
||||
|
||||
**Question 2.1** : Filtrez les écoutes pour ne garder que les morceaux de genre "pop".
|
||||
|
||||
```python
|
||||
est_pop = lambda ecoute: # À compléter
|
||||
|
||||
ecoutes_pop = list(filter(est_pop, ecoutes))
|
||||
print(f"Nombre de morceaux pop : {len(ecoutes_pop)}") # 12
|
||||
```
|
||||
|
||||
**Question 2.2** : Filtrez les écoutes de plus de 5 minutes (300 secondes).
|
||||
|
||||
```python
|
||||
est_long = lambda ecoute: # À compléter
|
||||
|
||||
ecoutes_longues = list(filter(est_long, ecoutes))
|
||||
print(f"Morceaux longs : {len(ecoutes_longues)}") # 5
|
||||
```
|
||||
|
||||
**Question 2.3** : Créez un filtre paramétrable `filtre_genre(genre)` qui retourne une fonction lambda.
|
||||
|
||||
```python
|
||||
def filtre_genre(genre):
|
||||
"""
|
||||
Retourne une fonction lambda qui filtre par genre.
|
||||
C'est une fonction d'ordre supérieur !
|
||||
"""
|
||||
return lambda ecoute: # À compléter
|
||||
|
||||
# Test
|
||||
ecoutes_rock = list(filter(filtre_genre("rock"), ecoutes))
|
||||
ecoutes_rap = list(filter(filtre_genre("rap"), ecoutes))
|
||||
print(f"Rock: {len(ecoutes_rock)}, Rap: {len(ecoutes_rap)}") # Rock: 4, Rap: 2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Exercice 3 : Agrégation avec `reduce`
|
||||
|
||||
La fonction `reduce(fonction, liste, valeur_initiale)` combine tous les éléments en une seule valeur.
|
||||
|
||||
```python
|
||||
import functools
|
||||
```
|
||||
|
||||
**Question 3.1** : Calculez le temps total d'écoute en secondes.
|
||||
|
||||
```python
|
||||
temps_total = functools.reduce(
|
||||
lambda acc, ecoute: # À compléter,
|
||||
ecoutes,
|
||||
0
|
||||
)
|
||||
print(f"Temps total : {temps_total} secondes") # 5301 secondes
|
||||
```
|
||||
|
||||
**Question 3.2** : Convertissez ce temps en heures et minutes.
|
||||
|
||||
```python
|
||||
heures = temps_total // 3600
|
||||
minutes = (temps_total % 3600) // 60
|
||||
print(f"Temps d'écoute : {heures}h {minutes}min") # 1h 28min
|
||||
```
|
||||
|
||||
**Question 3.3** : Trouvez le morceau le plus long en utilisant `reduce`.
|
||||
|
||||
```python
|
||||
plus_long = functools.reduce(
|
||||
lambda acc, ecoute: # À compléter,
|
||||
ecoutes
|
||||
)
|
||||
print(f"Plus long : {plus_long['titre']} ({plus_long['duree']}s)")
|
||||
# Plus long : Stairway to Heaven (482s)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Partie 3 : Statistiques avancées
|
||||
|
||||
### Exercice 4 : Comptage par catégorie
|
||||
|
||||
**Question 4.1** : Comptez le nombre d'écoutes par genre en utilisant `reduce`.
|
||||
|
||||
```python
|
||||
def compter_par_genre(ecoutes):
|
||||
"""
|
||||
Retourne un dictionnaire {genre: nombre_ecoutes}.
|
||||
Utilise reduce pour accumuler les comptages.
|
||||
"""
|
||||
return functools.reduce(
|
||||
lambda acc, e: {**acc, e["genre"]: acc.get(e["genre"], 0) + 1},
|
||||
ecoutes,
|
||||
{}
|
||||
)
|
||||
|
||||
stats_genres = compter_par_genre(ecoutes)
|
||||
print(stats_genres) # {'pop': 12, 'rock': 4, 'rap': 2, 'metal': 2}
|
||||
```
|
||||
|
||||
**Question 4.2** : Adaptez cette fonction pour compter les écoutes par artiste.
|
||||
|
||||
```python
|
||||
def compter_par_artiste(ecoutes):
|
||||
"""Retourne un dictionnaire {artiste: nombre_ecoutes}."""
|
||||
# À compléter (inspirez-vous de compter_par_genre)
|
||||
pass
|
||||
|
||||
stats_artistes = compter_par_artiste(ecoutes)
|
||||
print(stats_artistes)
|
||||
# {'The Weeknd': 3, 'Queen': 1, 'Billie Eilish': 1, ...}
|
||||
```
|
||||
|
||||
**Question 4.3** : Trouvez l'artiste le plus écouté.
|
||||
|
||||
```python
|
||||
def artiste_prefere(stats_artistes):
|
||||
"""
|
||||
Retourne le nom de l'artiste avec le plus d'écoutes.
|
||||
Utilise reduce sur les items du dictionnaire.
|
||||
"""
|
||||
return functools.reduce(
|
||||
lambda acc, item: # À compléter,
|
||||
stats_artistes.items()
|
||||
)[0]
|
||||
|
||||
top_artiste = artiste_prefere(stats_artistes)
|
||||
print(f"Artiste préféré : {top_artiste}") # The Weeknd
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Exercice 5 : Chaînage fonctionnel (Pipeline)
|
||||
|
||||
L'intérêt de la programmation fonctionnelle est de **chaîner** les opérations.
|
||||
|
||||
**Question 5.1** : Calculez le temps total d'écoute des morceaux pop uniquement.
|
||||
|
||||
```python
|
||||
# En une seule expression chaînée
|
||||
temps_pop = functools.reduce(
|
||||
lambda acc, e: acc + e["duree"],
|
||||
filter(lambda e: e["genre"] == "pop", ecoutes),
|
||||
0
|
||||
)
|
||||
print(f"Temps pop : {temps_pop // 60} minutes")
|
||||
```
|
||||
|
||||
**Question 5.2** : Listez les titres des morceaux rock de plus de 4 minutes, triés par durée décroissante.
|
||||
|
||||
```python
|
||||
# Pipeline : filter -> filter -> map -> sorted
|
||||
titres_rock_longs = list(map(
|
||||
lambda e: e["titre"],
|
||||
sorted(
|
||||
filter(
|
||||
lambda e: e["duree"] > 240,
|
||||
filter(
|
||||
lambda e: e["genre"] == "rock",
|
||||
ecoutes
|
||||
)
|
||||
),
|
||||
key=lambda e: e["duree"],
|
||||
reverse=True
|
||||
)
|
||||
))
|
||||
print(titres_rock_longs)
|
||||
# ['Stairway to Heaven', 'Bohemian Rhapsody', 'Smells Like Teen Spirit']
|
||||
```
|
||||
|
||||
**Question 5.3** : Créez une fonction `pipeline` qui compose plusieurs fonctions.
|
||||
|
||||
```python
|
||||
def pipeline(*fonctions):
|
||||
"""
|
||||
Retourne une fonction qui applique les fonctions en séquence.
|
||||
pipeline(f, g, h)(x) équivaut à h(g(f(x)))
|
||||
"""
|
||||
return lambda x: functools.reduce(
|
||||
lambda acc, f: f(acc),
|
||||
fonctions,
|
||||
x
|
||||
)
|
||||
|
||||
# Exemple d'utilisation
|
||||
traitement = pipeline(
|
||||
lambda data: filter(lambda e: e["genre"] == "pop", data),
|
||||
lambda data: map(lambda e: e["titre"], data),
|
||||
list
|
||||
)
|
||||
|
||||
print(traitement(ecoutes)[:3]) # ['Blinding Lights', 'Bad Guy', 'Shape of You']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Partie 4 : Génération du Wrapped
|
||||
|
||||
### Exercice 6 : Créer le résumé final
|
||||
|
||||
**Question 6** : Créez une fonction `generer_wrapped` qui produit un dictionnaire avec toutes les statistiques.
|
||||
|
||||
```python
|
||||
def generer_wrapped(ecoutes):
|
||||
"""
|
||||
Génère un résumé Wrapped complet de manière fonctionnelle.
|
||||
|
||||
:param ecoutes: (list) liste des écoutes
|
||||
:return: (dict) statistiques complètes
|
||||
"""
|
||||
# Temps total
|
||||
temps_total = functools.reduce(lambda acc, e: acc + e["duree"], ecoutes, 0)
|
||||
|
||||
# Comptages
|
||||
stats_artistes = compter_par_artiste(ecoutes)
|
||||
stats_genres = compter_par_genre(ecoutes)
|
||||
|
||||
# Top artiste
|
||||
top_artiste = # À compléter
|
||||
|
||||
# Top genre
|
||||
top_genre = # À compléter
|
||||
|
||||
# Morceau le plus écouté (celui qui apparaît le plus)
|
||||
stats_titres = functools.reduce(
|
||||
lambda acc, e: {**acc, e["titre"]: acc.get(e["titre"], 0) + 1},
|
||||
ecoutes,
|
||||
{}
|
||||
)
|
||||
top_titre = # À compléter
|
||||
|
||||
# Morceau le plus long
|
||||
plus_long = # À compléter
|
||||
|
||||
return {
|
||||
"nombre_ecoutes": len(ecoutes),
|
||||
"temps_total_minutes": round(temps_total / 60, 1),
|
||||
"artiste_prefere": top_artiste,
|
||||
"genre_prefere": top_genre,
|
||||
"titre_prefere": top_titre,
|
||||
"morceau_plus_long": plus_long["titre"],
|
||||
"stats_genres": stats_genres,
|
||||
"top_5_artistes": dict(sorted(
|
||||
stats_artistes.items(),
|
||||
key=lambda x: x[1],
|
||||
reverse=True
|
||||
)[:5])
|
||||
}
|
||||
|
||||
|
||||
# Test
|
||||
wrapped = generer_wrapped(ecoutes)
|
||||
|
||||
print("=" * 40)
|
||||
print(" VOTRE WRAPPED 2026")
|
||||
print("=" * 40)
|
||||
print(f"Écoutes totales : {wrapped['nombre_ecoutes']}")
|
||||
print(f"Temps d'écoute : {wrapped['temps_total_minutes']} minutes")
|
||||
print(f"Artiste préféré : {wrapped['artiste_prefere']}")
|
||||
print(f"Genre préféré : {wrapped['genre_prefere']}")
|
||||
print(f"Titre préféré : {wrapped['titre_prefere']}")
|
||||
print(f"Top 5 artistes : {list(wrapped['top_5_artistes'].keys())}")
|
||||
print("=" * 40)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Partie 5 : Bonus — Récursivité
|
||||
|
||||
### Exercice 7 : Implémenter reduce sans boucle
|
||||
|
||||
**Question 7** : Réécrivez la fonction `reduce` en utilisant la récursivité.
|
||||
|
||||
```python
|
||||
def mon_reduce(fonction, liste, initial):
|
||||
"""
|
||||
Implémentation récursive de reduce.
|
||||
|
||||
:param fonction: (callable) fonction à 2 arguments (acc, elem)
|
||||
:param liste: (list) liste à réduire
|
||||
:param initial: valeur initiale de l'accumulateur
|
||||
:return: résultat de la réduction
|
||||
"""
|
||||
if len(liste) == 0:
|
||||
# À compléter : cas de base
|
||||
pass
|
||||
else:
|
||||
# À compléter : appel récursif
|
||||
pass
|
||||
|
||||
# Test
|
||||
total = mon_reduce(lambda acc, x: acc + x, [1, 2, 3, 4, 5], 0)
|
||||
print(f"Somme : {total}") # 15
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Résumé des notions
|
||||
|
||||
| Fonction | Description | Exemple |
|
||||
|----------|-------------|---------|
|
||||
| `map(f, lst)` | Applique f à chaque élément | `map(lambda x: x*2, [1,2,3])` → [2,4,6] |
|
||||
| `filter(f, lst)` | Garde les éléments où f est True | `filter(lambda x: x>2, [1,2,3])` → [3] |
|
||||
| `reduce(f, lst, init)` | Combine tous les éléments | `reduce(lambda a,x: a+x, [1,2,3], 0)` → 6 |
|
||||
| `lambda` | Fonction anonyme | `lambda x: x + 1` |
|
||||
| Pipeline | Composition de fonctions | `f(g(h(x)))` |
|
||||
|
||||
---
|
||||
|
||||
## Pour aller plus loin
|
||||
|
||||
- **Compréhensions de liste** : Alternative pythonique à map/filter
|
||||
- **Générateurs** : `map` et `filter` retournent des itérateurs (lazy evaluation)
|
||||
- **Bibliothèques** : `itertools`, `toolz`, `fn.py` pour la programmation fonctionnelle avancée
|
||||
- **Langages fonctionnels purs** : Haskell, Elm, Clojure
|
||||
|
||||
---
|
||||
|
||||
Auteurs : Florian Mathieu, Enzo Frémeaux, Thimothée Decooster
|
||||
|
||||
Licence CC BY NC
|
||||
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.
|
||||
Reference in New Issue
Block a user