Aller au contenu principal

Programmation fonctionnelle

En JS, il peut être intéressant de penser avec le paradigme de la programmation fonctionnelle. Dans ce tuto, nous allons étudier les plus couramment utilisés : map, filter, find et reduce. Ces méthodes sont des fonctions de haut niveau qui permettent de traiter des tableaux de manière déclarative, en appliquant des fonctions sur les éléments du tableau plutôt qu'en utilisant des boucles impératives. Ce paradigme encourage un style de programmation plus déclaratif et immuable.

map

La fonction map est une fonction de programmation fonctionnelle fréquemment utilisée en JavaScript. Elle est principalement associée aux opérations sur des tableaux. La fonction map permet de créer un nouveau tableau en appliquant une fonction donnée à chaque élément du tableau initial. Elle ne modifie pas le tableau d'origine, mais retourne un nouveau tableau contenant les résultats de l'application de la fonction à chaque élément.

La fonction map crée un nouveau tableau avec les résultats de l'appel de la fonction de rappel pour chaque élément du tableau d'origine. Voici un exemple simple :

const numbers = [1, 2, 3, 4, 5];

// Utilisation de map pour doubler chaque élément du tableau
const doubledNumbers = numbers.map((num) => num * 2);

console.log(doubledNumbers); // Output: [2, 4, 6, 8, 10]

Dans cet exemple, la fonction fléchée (num) => num * 2 est la fonction callback. Elle est appliquée à chaque élément du tableau numbers, créant ainsi un nouveau tableau doubledNumbers contenant les éléments originaux multipliés par 2.

filter

La fonction filter est une autre fonction de programmation fonctionnelle fréquemment utilisée en JavaScript. Tout comme map, filter est associée à la manipulation de tableaux. Elle permet de créer un nouveau tableau contenant uniquement les éléments qui satisfont une condition spécifiée.

La fonction filter crée un nouveau tableau contenant uniquement les éléments pour lesquels la fonction callback renvoie true.

Voici un exemple simple d'utilisation de filter :

const numbers = [1, 2, 3, 4, 5];

// Utilisation de filter pour ne conserver que les nombres pairs
const evenNumbers = numbers.filter((num) => num % 2 === 0);

console.log(evenNumbers); // Output: [2, 4]

Dans cet exemple, la fonction fléchée (num) => num % 2 === 0 est la fonction de filtrage. Elle est appliquée à chaque élément du tableau numbers, créant ainsi un nouveau tableau evenNumbers contenant uniquement les nombres pairs.

find

La fonction find est une autre fonction de manipulation de tableaux en JavaScript, souvent utilisée dans le contexte de la programmation fonctionnelle. La fonction find est utilisée pour récupérer la première valeur d'un tableau qui satisfait une condition spécifiée. Elle retourne la première valeur trouvée ou undefined si aucune valeur ne satisfait la condition.

La fonction find s'arrête dès qu'elle trouve la première valeur qui satisfait la condition spécifiée par la fonction de rappel.

Voici un exemple d'utilisation de find :

const fruits = ["apple", "banana", "orange", "kiwi"];

// Utilisation de find pour récupérer le premier fruit contenant la lettre "r"
const result = fruits.find((fruit) => fruit.includes("r"));

console.log(result); // Output: "orange"

Dans cet exemple, la fonction de rappel (fruit) => fruit.includes("a") est utilisée pour trouver le premier fruit dans le tableau fruits qui contient la lettre "a". La valeur "apple" est la première qui satisfait cette condition, donc elle est retournée par la fonction find.

reduce

La fonction reduce est une fonction de manipulation de tableaux en JavaScript qui permet de réduire un tableau à une seule valeur. Elle prend une fonction de rappel et l'applique de manière cumulative à chaque élément du tableau, en produisant finalement une seule valeur résultante. La valeur résultante peut être de n'importe quel type (nombre, chaîne, objet, etc.), et elle est souvent utilisée pour effectuer des opérations telles que la somme des éléments, la concaténation de chaînes, la création d'objets, etc.

Voici un exemple simple d'utilisation de reduce pour calculer la somme des éléments d'un tableau :

const numbers = [1, 2, 3, 4, 5];

// Utilisation de reduce pour calculer la somme des nombres
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);

console.log(sum); // Output: 15


const words = ["Hello", " ", "World", "!"];

// Utilisation de reduce pour concaténer les éléments du tableau en une seule chaîne
const concatenatedString = words.reduce((accumulator, currentValue) => accumulator + currentValue, "");

console.log(concatenatedString); // Output: "Hello World!"

Dans cet exemple, la fonction de rappel (accumulator, currentValue) => accumulator + currentValue est utilisée pour accumuler la somme des éléments du tableau. La valeur initiale de l'accumulateur est spécifiée comme 0, mais si elle n'est pas fournie, le premier élément du tableau (1 dans ce cas) serait utilisé comme valeur initiale.

La fonction reduce est puissante et polyvalente, et elle peut être utilisée pour effectuer une variété d'opérations de transformation sur les tableaux.

Cas pratique

Étudions un cas pratique mélangeant programmation fonctionnelle et fonctions fléchées (inspiré d'un exemple de delicious-insights).

Partons d'un code sans fonctions fléchées qui permet de retourner tous les noms de personnes qui ont plus de 18 ans.

const people = [
{name: 'Alice', age: 21},
{name: 'Bob', age: 16},
{name: 'Claire', age: 18}
];

const tableauFiltre = people.
filter(function (person){
return person.age >= 18;
}).map(function (person){
return person.name;
});
console.log(tableauFiltre); //(2) ['Alice', 'Claire']

Ce code fonctionne, mais il est maintenant standard d'écrire ce code avec des fonctions fléchées. Dans ce code, pas besoin de faire de return, car après la => il n'y a pas d'accolades. On rappelle que s'il y a des accolades, c'est un bloc de fonction (il faudrait faire un return si on veut retourner quelque chose). Dans l'exemple ci-dessous, il n'y en a pas. C'est donc une expression. Elle va être automatiquement retournée.

const people = [
{name: 'Alice', age: 21},
{name: 'Bob', age: 16},
{name: 'Claire', age: 18}
];

const tableauFiltre = people
.filter((person) => person.age >= 18)
.map((person) => person.name);
console.log(tableauFiltre);//(2) ['Alice', 'Claire']

Comme on a vu l'année dernière, on pourrait utiliser la déstructuration pour adapter notre code.

const people = [
{name: 'Alice', age: 21},
{name: 'Bob', age: 16},
{name: 'Claire', age: 18}
];

const tableauFiltre = people
.filter(({age}) => age >= 18)
.map(({name}) => name);
console.log(tableauFiltre);//(2) ['Alice', 'Claire']

Attention, certains cas peuvent devenir complexes. Partons de ce cas.

const people = ['Alice', 'Bob', 'Claire'];

const tableauTailles = people.map((name) => name.length);
console.log(tableauTailles);//(3) [5, 3, 6]

Plutôt que de retourner un tableau d'entiers, on souhaite retourner un objet littéral. Ce code ci-dessous ne fonctionne pas. Il retourne un tableau de 3 cases valant toutes undefined

const people = ['Alice', 'Bob', 'Claire'];

const tableauTailles = people.map((name) => {size : name.length, name});
console.log(tableauTailles);//(3) [undefined, undefined, undefined]

Ceci est dû au fait que l'accolade ici est prise pour le début d'un bloc de fonction (et pas la création d'un objet littéral comme on pourrait s'y attendre si on n'a pas ouvert un livre de JS). De plus, il faut faire un return, car c'est seulement quand il n'y a pas d'accolades que le return est automatique.

const people = ['Alice', 'Bob', 'Claire'];

const tableauTailles = people.map((name) => { return {size : name.length, name}});
console.log(tableauTailles);//(3) [{…}, {…}, {…}]
//il faut cliquer sur {...} pour voir apparaitre 3 objets :
//{size: 5, name: 'Alice'}
//{size: 3, name: 'Bob'}
//{size: 6, name: 'Claire'}

Une version plus concise (sans le return) s'écrit en mettant des parenthèses. Mettre des parenthèses force les accolades à désigner un littéral objet. On les place dans un contexte qui n'autorise que des expressions. Les accolades sont ainsi considérées comme un objet littéral (et plus un bloc de fonction).

const people = ['Alice', 'Bob', 'Claire'];

const tableauTailles = people.map((name) => ({size : name.length, name}));
console.log(tableauTailles);

Exercices : Manipulation des tableaux avec de la PF

Exercice 1

Soit un tableau d'objets représentant une collection de livres dans une bibliothèque. Chaque livre a un titre, un auteur, un nombre de pages, et un booléen indiquant s'il a été lu ou non.

const books = [
{ title: "Le Petit Prince", author: "Antoine de Saint-Exupéry", pages: 96, read: true },
{ title: "L'Étranger", author: "Albert Camus", pages: 123, read: false },
{ title: "1984", author: "George Orwell", pages: 328, read: true },
{ title: "La Peste", author: "Albert Camus", pages: 247, read: true },
{ title: "Le Comte de Monte-Cristo", author: "Alexandre Dumas", pages: 1312, read: false },
{ title: "Les Misérables", author: "Victor Hugo", pages: 1488, read: true }
];
  1. Filtrer les livres non lus : Utilisez la méthode filter pour créer un nouveau tableau contenant uniquement les livres qui n'ont pas encore été lus.

  2. Récupérer uniquement les titres des livres non lus : Ensuite, utilisez la méthode map sur le tableau précédemment filtré pour obtenir un tableau avec seulement les titres des livres non lus.

  1. Calculer le nombre total de pages lues : Utilisez la méthode reduce pour calculer le nombre total de pages des livres qui ont déjà été lus (read: true).

  1. Trouver un livre spécifique par son titre : Utilisez la méthode find pour rechercher et retourner le livre dont le titre est "La Peste". Si le livre existe, renvoyez l'objet livre, sinon renvoyez undefined (comportement par défaut du find).

  2. Trouver l'auteur avec le plus grand nombre de pages lues : Utilisez une combinaison des méthodes pour trouver l'auteur qui a le plus de pages lues dans la collection.

Exercice 2 (complexe)

Vous avez une base de données contenant des informations sur plusieurs étudiants, y compris leurs notes dans différents sujets. Chaque étudiant a un nom, un âge, et un tableau de matières, chaque matière ayant un nom et une note.

const students = [
{
name: "Alice",
age: 22,
subjects: [
{ subjectName: "Math", score: 85 },
{ subjectName: "Literature", score: 74 },
{ subjectName: "Sport", score: 91 }
]
},
{
name: "Bob",
age: 24,
subjects: [
{ subjectName: "Math", score: 89 },
{ subjectName: "Literature", score: 81 },
{ subjectName: "Sport", score: 78 }
]
},
{
name: "Charlie",
age: 23,
subjects: [
{ subjectName: "Math", score: 92 },
{ subjectName: "Literature", score: 69 },
{ subjectName: "Sport", score: 85 }
]
},
{
name: "David",
age: 21,
subjects: [
{ subjectName: "Math", score: 70 },
{ subjectName: "Literature", score: 95 },
{ subjectName: "Sport", score: 72 }
]
}
];
  1. Trouver les étudiants ayant une moyenne générale supérieure ou égale à 80 :

    • Calculez la moyenne de chaque étudiant à partir de ses notes en utilisant map et reduce.
    • Filtrez les étudiants dont la moyenne est supérieure ou égale à 80.
  2. Lister les noms des étudiants ayant une moyenne de plus de 80 en utilisant map :

    Utilisez la méthode map pour extraire uniquement les noms des étudiants que vous avez filtrés à l'étape précédente.

  1. Trouver l'étudiant ayant obtenu la meilleure note en Mathématiques :

    • Utilisez reduce et find pour trouver l'étudiant avec la meilleure note en Mathématiques.
  2. Trouver la moyenne des notes en "Literature" de tous les étudiants :

    • Utilisez map et find pour extraire les notes en "Literature" pour chaque étudiant.
    • Puis, utilisez reduce pour calculer la moyenne de ces notes.
  1. Trouver l'étudiant ayant la moyenne générale la plus élevée :

    • Utilisez reduce pour parcourir tous les étudiants et trouver celui avec la meilleure moyenne générale.
  1. Classer les étudiants par leur moyenne générale, du meilleur au moins bon : Utilisez map et reduce, puis sort pour classer les étudiants selon leur moyenne générale. La documentation de sort.