JS protip : Aplatir des tableaux imbriqués
Par Christophe Porteneuve • Publié le 7 décembre 2022 • 3 min

This page is also available in English.

Parfois, on se retrouve avec des tableaux… dans des tableaux. Un .map() qui a mal tourné, un traitement récursif, des données sources imbriquées : les causes sont nombreuses, et souvent cette représentation en profondeur nous va très bien.

Mais lorsqu’on veut traiter toutes ces données d’un seul tenant, sans parcours profond, on fait comment ?

Historiquement, on se tournait vers des bibliothèques, par exemple Lodash et ses fonctions .flattenDeep() ou .flatMapDepth(). Mais depuis ES2019, c’est dans la bibliothèque standard de JavaScript !

Aplanir, plus ou moins

Les tableaux (Array) proposent la méthode .flat(), qui prend une profondeur optionnelle. Par défaut, celle-ci vaut 1 (un), on n’aplanit donc que d’un niveau :

items = ['hello', ['world', 'this'], 'is', [['nice'], '!']]
items.flat()
// => ['hello', 'world', 'this', 'is', ['nice'], '!']

Évidemment, pour aplanir un grand coup, il suffit de passer une profondeur maximum suffisante ; un aplanissement « garanti » peut parfaitement limiter à… l’infini avec +Infinity ou Number.POSITIVE_INFINITY, d’ailleurs.

items = ['hello', ['world', 'this'], 'is', [['nice'], '!']]
items.flat(2)
// => ['hello', 'world', 'this', 'is', 'nice', '!']

Mais… c’est pas un verbe, ça !

En effet. Le verbe correct serait .flatten(). Seulement voilà, comme trop souvent, on a buté sur tous ces sites existants qui utilisent encore MooTools, le coupable habituel. Ce dernier définissait déjà .flatten() sur Array, avec naturellement une sémantique différente (aplanissement intégral systématique), et a donc privé le TC39 de ce joli nom. (Si tu te demandais aussi pourquoi on a .includes() au lieu de .contains() sur les chaînes et les tableaux, maintenant tu sais.)

À propos des tableaux clairsemés (sparse arrays)

Petit point de détail qui mérite d’être mentionné : les cellules inexistantes au sein du tableau sont ignorées par l’aplanissement, qui ne produira donc jamais de tableau clairsemé. C’est un bon moyen de « retirer les trous ».

items = ['hello', 'world', , , 'this', 'is', , 'nice']

items.length
// => 8

Object.keys(items)
// => ['0', '1', '4', '5', '7']

items.flat()
// => ['hello', 'world', 'this', 'is', 'nice']

Aplanir tout en transformant

Il arrive fréquemment qu’on souhaite appliquer une transformation aux éléments du tableau avant de l’aplanir ; on pourrait donc faire d’abord un .map(), puis un .flat(). Ce qui est dommage, c’est que ça parcourt deux fois les données. C’est notamment le cas lorsque notre mapper renvoie un tableau, mais qu’on souhaite avoir le résultat à plat.

Il est possible d’optimiser ça avec .flatMap(). Celle-ci a la même signature que .map() (fonction de rappel acceptant jusqu’à 3 arguments et, s’il ne s’agit pas d’une fonction fléchée, this optionnel à définir au sein du rappel).

borked = [1, 2, 3].map((x) => [x, x * 2, x * 3])
// => [[1, 2, 3], [2, 4, 6], [3, 6, 9]]

multiples = [1, 2, 3].flatMap((x) => [x, x * 2, x * 3])
// => [1, 2, 3, 2, 4, 6, 3, 6, 9]

Attention ! Il ne s’agit pas de l’équivalent d’un .flat() suivi d’un .map(), mais de l’inverse ! Notre mapper se prend donc les éléments d’origine en direct ; notamment, les tableaux imbriqués lui sont passés en tant que tableaux, pas en tant qu’appels individuels fournissant un seul élément. Ça correspond bien au cas d’usage dominant, où les mappers renvoient des tableaux qu’il faut accumuler à plat.

C’est aussi pour ça qu’on n’a pas d’argument optionnel de profondeur max : puisque notre fonction de rappel reçoit les tableaux imbriqués tels quels, libre à elle de décider de faire un .flatMap() récursif dessus si elle le souhaite.

Tableaux ou itérables ?

Ce serait super d’avoir ça sur tous les itérables (et même tous les itérateurs), mais il faudra pour ça attendre que la proposition Iterator helpers, qui vient d’atteindre le stade 3 du processus de standardisation, passe au stade 4. On récupèrera alors bien d’autres méthodes utiles sur tous les itérateurs.

Pour le moment, ce n’est que sur les tableaux (le type Array).

C’est dispo où ?

Bin écoute, partout. Dans tous les navigateurs modernes, ainsi que depuis Node 11 et Deno 1.0. Donc vas-y !

Des astuces en veux-tu en voilà !

On a tout plein d’articles et de vidéos existants et encore beaucoup à venir. Pour ne rien manquer, tu devrais penser à t’abonner à notre newsletter, à notre chaîne YouTube, nous suivre sur Twitter ou encore mieux, à suivre une de nos formations du feu de dieu 🔥 !