# 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 Licence Creative Commons
Ce cours est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International.