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 }
];
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.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.
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
).
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 renvoyezundefined
(comportement par défaut dufind
).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 }
]
}
];
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
etreduce
. - Filtrez les étudiants dont la moyenne est supérieure ou égale à 80.
- Calculez la moyenne de chaque étudiant à partir de ses notes en utilisant
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.
Trouver l'étudiant ayant obtenu la meilleure note en Mathématiques :
- Utilisez
reduce
etfind
pour trouver l'étudiant avec la meilleure note en Mathématiques.
- Utilisez
Trouver la moyenne des notes en "Literature" de tous les étudiants :
- Utilisez
map
etfind
pour extraire les notes en "Literature" pour chaque étudiant. - Puis, utilisez
reduce
pour calculer la moyenne de ces notes.
- Utilisez
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.
- Utilisez
Classer les étudiants par leur moyenne générale, du meilleur au moins bon : Utilisez
map
etreduce
, puissort
pour classer les étudiants selon leur moyenne générale. La documentation de sort.