Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 518 minDébutant

Tableaux JavaScript : map, filter, reduce, find et sort

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • créer un tableau, y ajouter et en retirer des éléments ;
  • chercher un élément avec find, some ou includes ;
  • transformer, filtrer et résumer un tableau avec map, filter et reduce ;
  • trier correctement des nombres et des chaînes ;
  • distinguer les méthodes qui modifient le tableau de celles qui en créent un nouveau.

4 exercices corrigés1 schéma

Un tableau JavaScript (Array) est une liste ordonnée de valeurs, numérotées à partir de 0, dont la taille varie librement. Ses méthodes map, filter et reduce transforment, filtrent et résument une liste en une ligne, sans boucle écrite à la main. Ce chapitre présente la création d'un tableau, l'ajout et le retrait d'éléments, la recherche, les trois méthodes clés, le tri, et la différence entre les méthodes qui modifient le tableau et celles qui en créent un nouveau. Les algorithmes de base sur les listes (recherche, maximum, comptage) sont expliqués dans le chapitre Tableaux et listes du cours d'algorithmique.

Tous les exemples utilisent le tableau pieces du chapitre 3.

Créer un tableau, lire et modifier un élément

js
const refs = ['SSD-512', 'RAM-16', 'CHG-65'];

refs[0]               // 'SSD-512' : le premier élément est à l'indice 0
refs.length           // 3
refs[refs.length - 1] // 'CHG-65' : le dernier
refs.at(-1)           // 'CHG-65' : at accepte un indice négatif, compté depuis la fin
refs[10]              // undefined : pas d'erreur, mais pas de valeur
refs[1] = 'RAM-32';   // modification : ['SSD-512', 'RAM-32', 'CHG-65']

Un tableau peut contenir des valeurs de tous types, y compris des objets et d'autres tableaux. Dans une application, tu manipuleras surtout des tableaux d'objets : une liste de pièces, de clients, de commandes, comme les lignes d'une table SQL.

Ajouter et retirer des éléments

MéthodeEffetRenvoieExemple sur ['SSD-512', 'RAM-16']
push(x)Ajoute à la finLa nouvelle longueurpush('CHG-65') renvoie 3
pop()Retire le dernierL'élément retirérenvoie 'RAM-16'
unshift(x)Ajoute au débutLa nouvelle longueurunshift('PAT-01') renvoie 3
shift()Retire le premierL'élément retirérenvoie 'SSD-512'
splice(i, n)Retire n éléments à partir de l'indice iUn tableau des éléments retirésvoir ci-dessous
js
const liste = ['SSD-512', 'RAM-16', 'CHG-65', 'ECR-156'];
liste.splice(1, 2);           // renvoie ['RAM-16', 'CHG-65']
console.log(liste);           // ['SSD-512', 'ECR-156']
liste.splice(1, 0, 'NEW-01'); // insère sans rien retirer
console.log(liste);           // ['SSD-512', 'NEW-01', 'ECR-156']

Ces cinq méthodes modifient le tableau sur lequel tu les appelles. slice(debut, fin), au nom presque identique, ne modifie rien : elle renvoie une copie d'une partie du tableau (['a', 'b', 'c', 'd'].slice(1, 3) donne ['b', 'c']).

Chercher dans un tableau : includes, find, some

MéthodeQuestion poséeRenvoie
includes(valeur)La valeur est-elle présente ?true ou false
indexOf(valeur)À quel indice ?L'indice, ou -1
find(test)Quel est le premier élément qui passe le test ?L'élément, ou undefined
findIndex(test)À quel indice est-il ?L'indice, ou -1
findLast(test)Quel est le dernier élément qui passe le test ?L'élément, ou undefined
some(test)Au moins un élément passe-t-il le test ?true ou false
every(test)Tous les éléments passent-ils le test ?true ou false

includes et indexOf comparent avec === : ils conviennent aux valeurs simples (chaînes, nombres), pas aux objets. Pour un tableau d'objets, passe un test, une fonction qui reçoit un élément et renvoie vrai ou faux :

js
const ecran = pieces.find((p) => p.ref === 'ECR-156');
console.log(ecran.nom);                                  // 'Dalle 15,6 pouces'

pieces.find((p) => p.ref === 'XXX-00');                  // undefined
pieces.find((p) => p.prix > 40).ref;                     // 'SSD-512' (le premier)
pieces.findIndex((p) => p.ref === 'ECR-156');            // 3
pieces.some((p) => p.quantite === 0);                    // true : au moins une rupture
pieces.every((p) => p.quantite > 0);                     // false

map : transformer chaque élément

map applique une fonction à chaque élément et renvoie un nouveau tableau de même longueur, rempli des résultats :

js
pieces.map((p) => p.ref);
// ['SSD-512', 'RAM-16', 'CHG-65', 'ECR-156', 'PAT-01']

pieces.map((p) => p.quantite * p.prix);
// [514.8, 115.5, 0, 178, 162.5]

pieces.map((p) => ({ ref: p.ref, valeur: p.quantite * p.prix }));
// [{ ref: 'SSD-512', valeur: 514.8 }, { ref: 'RAM-16', valeur: 115.5 }, …]

La fonction passée à map doit renvoyer une valeur. Avec des accolades sans return, tu obtiens un tableau de undefined :

js
[12, 8, 15].map((n) => { n + 1; });   // [undefined, undefined, undefined]
[12, 8, 15].map((n) => n + 1);        // [13, 9, 16]

filter : garder certains éléments

filter renvoie un nouveau tableau qui ne contient que les éléments pour lesquels le test renvoie vrai. Il peut être plus court que l'original, voire vide :

js
const enAlerte = pieces.filter((p) => p.quantite <= p.seuil);
enAlerte.length;                       // 2
enAlerte.map((p) => p.ref);            // ['RAM-16', 'CHG-65']

pieces.filter((p) => p.prix < 30).length;   // 2 : CHG-65 et PAT-01

reduce : résumer un tableau en une valeur

reduce parcourt le tableau en faisant évoluer un accumulateur, et renvoie sa valeur finale. Elle prend deux arguments : une fonction (accumulateur, element) => nouvelAccumulateur, et la valeur de départ de l'accumulateur.

js
const unites = pieces.reduce((total, p) => total + p.quantite, 0);
console.log(unites);   // 42

const valeur = pieces.reduce((total, p) => total + p.quantite * p.prix, 0);
console.log(valeur);   // 970.8

Pour la valeur du stock, l'accumulateur total part de 0 et reçoit, à chaque pièce, sa quantité multipliée par son prix :

TourPiècetotal reçuCalcultotal renvoyé
1SSD-51200 + 12 × 42,9514,8
2RAM-16514,8514,8 + 3 × 38,5630,3
3CHG-65630,3630,3 + 0 × 24630,3
4ECR-156630,3630,3 + 2 × 89808,3
5PAT-01808,3808,3 + 25 × 6,5970,8
piecesréférence, quantité / seuilSSD-51212 / 5RAM-163 / 4CHG-650 / 2ECR-1562 / 1PAT-0125 / 10.filter(p =>p.quantite <= p.seuil)RAM-163 / 4CHG-650 / 22 objets sur 5.map(p => p.ref)'RAM-16''CHG-65'2 chaînespieces.reduce((total, p) => total + p.quantite * p.prix, 0)0514,8630,3630,3808,3970,8départ+ SSD-512+ RAM-16+ CHG-65+ ECR-156+ PAT-01l'accumulateur total après chaque pièce
filter garde des éléments, map transforme chacun, reduce cumule tout en une seule valeur

L'accumulateur n'est pas forcément un nombre. Pour trouver la pièce la plus chère, il contient la meilleure pièce vue jusque-là :

js
const plusChere = pieces.reduce((max, p) => (p.prix > max.prix ? p : max));
console.log(plusChere.ref);   // 'ECR-156'

Sans valeur de départ, reduce prend le premier élément comme accumulateur. C'est pratique pour un maximum, mais sur un tableau vide, il plante : TypeError: Reduce of empty array with no initial value. Pour une somme, donne toujours la valeur de départ 0 : la somme d'un tableau vide vaut alors 0.

Enchaîner filter, map et reduce

Chaque méthode renvoie un tableau, sur lequel on peut appeler la suivante. Le code se lit comme une phrase : « parmi les pièces, garde celles en alerte, puis fabrique pour chacune une ligne de commande » :

js
const bonDeCommande = pieces
  .filter((p) => p.quantite <= p.seuil)
  .map((p) => `${p.ref} : commander ${p.seuil * 2 - p.quantite}`);

console.log(bonDeCommande);
// ['RAM-16 : commander 5', 'CHG-65 : commander 4']

La règle de réassort de Réparinfo : remonter le stock au double du seuil. RAM-16 (seuil 4, quantité 3) : 8 − 3 = 5 ; CHG-65 (seuil 2, quantité 0) : 4 − 0 = 4.

Autre exemple : la valeur du stock hors consommables.

js
pieces
  .filter((p) => p.categorie !== 'Consommable')
  .reduce((total, p) => total + p.quantite * p.prix, 0);   // 808.3

Trier un tableau : sort et toSorted

sort trie le tableau sur place (il le modifie) et, sans argument, compare les éléments comme des chaînes :

js
[10, 9, 1, 100].sort();                     // [1, 10, 100, 9] : ordre alphabétique !
[10, 9, 1, 100].sort((a, b) => a - b);      // [1, 9, 10, 100] : ordre croissant
[10, 9, 1, 100].sort((a, b) => b - a);      // [100, 10, 9, 1] : ordre décroissant

La fonction de comparaison reçoit deux éléments a et b et renvoie un nombre négatif si a doit venir avant b, positif si a doit venir après, 0 s'ils sont équivalents. a - b fait exactement cela pour des nombres. Pour des chaînes en français, utilise localeCompare, qui range correctement les accents et les majuscules :

js
['écran', 'Zèbre', 'alim', 'Écran'].sort();
// ['Zèbre', 'alim', 'Écran', 'écran'] : ordre des codes Unicode, inutilisable

['écran', 'Zèbre', 'alim', 'Écran'].toSorted((a, b) => a.localeCompare(b, 'fr'));
// ['alim', 'écran', 'Écran', 'Zèbre']

Depuis ECMAScript 2023, toSorted fait la même chose que sort mais renvoie une copie triée et laisse l'original intact. Utilise-la dès que le tableau d'origine sert ailleurs, par exemple l'état de ton application :

js
const parPrixDecroissant = pieces.toSorted((a, b) => b.prix - a.prix);
parPrixDecroissant.map((p) => p.ref);
// ['ECR-156', 'SSD-512', 'RAM-16', 'CHG-65', 'PAT-01']

const parNom = pieces.toSorted((a, b) => a.nom.localeCompare(b.nom, 'fr'));
parNom.map((p) => p.ref);
// ['RAM-16', 'CHG-65', 'ECR-156', 'PAT-01', 'SSD-512']

De la même façon, toReversed() et with(indice, valeur) sont les versions sans modification de reverse() et de tableau[indice] = valeur.

Modifier ou créer : le tableau récapitulatif

Savoir si une méthode modifie le tableau évite une catégorie entière de bugs, en particulier dans React, qui ne voit pas un tableau modifié sur place (l'état dans React).

Modifient le tableau (mutation)Renvoient un nouveau tableau ou une valeur
push, pop, shift, unshiftconcat, slice, [...a, x]
splicetoSpliced
sort, reversetoSorted, toReversed
tableau[i] = xwith(i, x)
fill, copyWithinmap, filter, reduce, flat, flatMap

forEach ou for…of ?

forEach appelle une fonction pour chaque élément, sans rien renvoyer. Elle remplace une boucle for…of, avec une limite : impossible d'en sortir avec break, et un return dans le callback ne fait que passer à l'élément suivant.

js
pieces.forEach((p, i) => console.log(`${i + 1}. ${p.ref}`));

Règle simple : map si tu fabriques un tableau, filter si tu sélectionnes, reduce si tu résumes, find ou some si tu cherches, et for…of pour tout le reste (afficher, enregistrer), surtout s'il faut pouvoir s'arrêter.

Copier et décomposer : spread et destructuring

L'opérateur de décomposition ... (spread) étale les éléments d'un tableau. Il sert à copier, à fusionner, à ajouter sans modifier :

js
const a = ['SSD-512', 'RAM-16'];
const b = ['CHG-65'];
const tous = [...a, ...b];        // ['SSD-512', 'RAM-16', 'CHG-65']
const copie = [...a];             // une copie, indépendante de a
const avecNouvelle = [...a, 'X']; // ajout sans toucher à a
Math.max(...[3, 9, 2]);           // 9 : les éléments deviennent des arguments

La déstructuration (destructuring) fait l'inverse : elle range les éléments d'un tableau dans des variables, dans l'ordre.

js
const [premiere, deuxieme, ...autres] = pieces.map((p) => p.ref);
// premiere = 'SSD-512', deuxieme = 'RAM-16', autres = ['CHG-65', 'ECR-156', 'PAT-01']

let x = 'a';
let y = 'b';
[x, y] = [y, x];   // échange : x = 'b', y = 'a'

Quelques méthodes utiles en plus

js
['SSD-512', 'RAM-16'].join(', ');                     // 'SSD-512, RAM-16'
Array.from({ length: 3 }, (_, i) => i + 1);           // [1, 2, 3]
Array.from('RAM');                                    // ['R', 'A', 'M']
[[1, 2], [3]].flat();                                 // [1, 2, 3]
pieces.flatMap((p) => (p.quantite === 0 ? [] : [p.ref]));
// ['SSD-512', 'RAM-16', 'ECR-156', 'PAT-01'] : map et filtre en un passage

Object.groupBy (ECMAScript 2024) range les éléments dans un objet, selon une clé calculée :

js
const groupes = Object.groupBy(pieces, (p) => (p.quantite <= p.seuil ? 'alerte' : 'ok'));
groupes.alerte.map((p) => p.ref);   // ['RAM-16', 'CHG-65']
groupes.ok.map((p) => p.ref);       // ['SSD-512', 'ECR-156', 'PAT-01']

Fil rouge : les règles du stock en une ligne

Avec les méthodes de ce chapitre, les règles de Gestock deviennent plus courtes et plus lisibles. Voici celles du futur fichier stock.js :

js
function estEnAlerte(piece) {
  return piece.quantite <= piece.seuil;
}

function valeurStock(pieces) {
  return pieces.reduce((total, p) => total + p.quantite * p.prix, 0);
}

function filtrerPieces(pieces, texte = '', alertesSeules = false) {
  const recherche = texte.trim().toLowerCase();
  return pieces.filter((p) => {
    const correspond =
      p.nom.toLowerCase().includes(recherche) || p.ref.toLowerCase().includes(recherche);
    return correspond && (!alertesSeules || estEnAlerte(p));
  });
}

filtrerPieces(pieces, 'ssd').map((p) => p.ref);         // ['SSD-512']
filtrerPieces(pieces, '', true).map((p) => p.ref);      // ['RAM-16', 'CHG-65']
filtrerPieces(pieces, 'ram', true).map((p) => p.ref);   // ['RAM-16']

filtrerPieces garde une pièce si sa référence ou son nom contient le texte cherché (sans tenir compte des majuscules), et, quand alertesSeules vaut true, seulement si elle est en alerte. Une recherche vide ('') garde tout : toute chaîne contient la chaîne vide. Cette fonction sera branchée sur le champ de recherche au chapitre 8.

Exercices

Exercice 1 sur 4 Prédire les résultats Réussi À revoir

Donne le résultat de chaque expression avec le tableau pieces.

  1. pieces.map((p) => p.quantite)
  2. pieces.filter((p) => p.quantite > 10).map((p) => p.ref)
  3. pieces.find((p) => p.categorie === 'Écran').prix
  4. pieces.some((p) => p.prix > 100)
  5. pieces.reduce((t, p) => t + p.seuil, 0)
  6. [5, 25, 100, 3].sort()
Voir la réponseMasquer la réponse de l'exercice 1
  1. [12, 3, 0, 2, 25]
  2. ['SSD-512', 'PAT-01']
  3. 89
  4. false : la pièce la plus chère coûte 89 €.
  5. 22 : 5 + 4 + 2 + 1 + 10.
  6. [100, 25, 3, 5] : sans fonction de comparaison, sort compare des chaînes, et '100' vient avant '25', qui vient avant '3', qui vient avant '5'. Écris .sort((a, b) => a - b) pour obtenir [3, 5, 25, 100].

Tu avais trouvé ?

Exercice 2 sur 4 Statistiques du stock Réussi À revoir

Écris, chacune en une instruction, les expressions qui donnent :

  1. les noms des pièces en majuscules, sauf celles en rupture ;
  2. le prix moyen des pièces (sans tenir compte des quantités), arrondi à deux décimales ;
  3. le nombre de pièces des catégories Stockage ou Mémoire.
Voir la réponseMasquer la réponse de l'exercice 2
js
// 1.
pieces.filter((p) => p.quantite > 0).map((p) => p.nom.toUpperCase());
// ['SSD NVME 512 GO', 'BARRETTE DDR4 16 GO', 'DALLE 15,6 POUCES', 'PÂTE THERMIQUE']

// 2.
(pieces.reduce((s, p) => s + p.prix, 0) / pieces.length).toFixed(2);
// '40.18' : (42,9 + 38,5 + 24 + 89 + 6,5) / 5 = 200,9 / 5

// 3.
pieces.filter((p) => p.categorie === 'Stockage' || p.categorie === 'Mémoire').length;
// 2

Tu avais trouvé ?

Exercice 3 sur 4 Totaliser les ventes par référence Réussi À revoir

Les sorties de stock de la journée sont enregistrées ainsi :

js
const sorties = [
  { ref: 'SSD-512', qte: 2 },
  { ref: 'RAM-16', qte: 1 },
  { ref: 'SSD-512', qte: 1 },
  { ref: 'PAT-01', qte: 3 },
];

Avec reduce, construis un objet qui donne la quantité totale sortie pour chaque référence.

Voir la réponseMasquer la réponse de l'exercice 3
js
const parRef = sorties.reduce((acc, s) => {
  acc[s.ref] = (acc[s.ref] ?? 0) + s.qte;
  return acc;
}, {});
// { 'SSD-512': 3, 'RAM-16': 1, 'PAT-01': 3 }

L'accumulateur est un objet vide au départ. Pour chaque sortie, on ajoute sa quantité au total de sa référence (?? 0 donne 0 la première fois). La fonction doit renvoyer l'accumulateur à chaque tour.

Tu avais trouvé ?

Exercice 4 sur 4 Sans modifier l'original Réussi À revoir

Ce code doit afficher les pièces de la moins chère à la plus chère, sans changer l'ordre du stock affiché ailleurs dans l'application. Pourquoi est-il faux ? Corrige-le.

js
const parPrix = pieces.sort((a, b) => a.prix - b.prix);
Voir la réponseMasquer la réponse de l'exercice 4

sort trie sur place : pieces lui-même est réordonné, et parPrix désigne le même tableau. Tout le reste de l'application voit le stock trié par prix.

js
const parPrix = pieces.toSorted((a, b) => a.prix - b.prix);

toSorted renvoie une copie triée. Sur un navigateur très ancien qui ne la connaîtrait pas, [...pieces].sort(…) fait la même chose.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Objets, JSON, Map et Set en JavaScriptRevoir le sommaire du cours