Retirer facilement les « valeurs vides » d’un tableau
Par Delicious Insights • Publié le 11 mai 2020

Cet article est également disponible en anglais.

Voici le huitième article de notre série quotidienne « 19 pépites de JS pur ». Besoin d’apurer un tableau ? On a tout un tas de solutions, et certaines sont… particulièrement concises !

Dans la série…

Extrait de la liste des articles :

  1. Court-circuiter plusieurs niveaux de boucles
  2. Inverser deux valeurs avec la déstructuration
  3. Retirer facilement les « valeurs vides » d’un tableau (cet article)
  4. Vive les séparateurs numériques !
  5. Trier proprement des textes
  6. …au-delà, c’est la surprise ! (mais la liste est déjà calée)…

Rappels sur filter

Depuis ES5 (2009), le type Array est doté de tout un tas de méthodes itératives directement inspirées du module Enumerable de Prototype.js :

  • forEach pour appeler une fonction de rappel sur chaque élément (globalement déconseillé maintenant qu’on a for…of) ;
  • map pour produire un tableau dérivé via une fonction de transformation ;
  • every et some pour déterminer si un prédicat (fonction vrai/faux) est satisfait par tout ou partie du tableau ;
  • reduce et reduceRight pour produire une valeur consolidée sur la base de toutes les valeurs du tableau (une somme, une concaténation, etc.) ;
  • Enfin, filter pour produire un tableau dérivé ne retenant que les valeurs ayant satisfait un prédicat.

À l'exception de reduce et reduceRight qui en prennent 3, toutes les autres acceptent deux arguments :

  1. La fonction de rappel (pour filter, le prédicat, donc) ;
  2. Le contexte : si la fonction de rappel s’attend à un this particulier, mais que la passer par référence « perdrait » ce this (en gros, elle est déclarée avec le mot-clé function ou comme méthode concise), ce deuxième argument optionnel permet de recaler le contexte correctement (ce qui est fort utile et performant).

(Et, soit dit en passant, les fonctions de rappel pour celles-ci acceptent non pas un mais 3 arguments : la valeur, son index, et le tableau complet. Ça peut être super pratique…)

Petits exemples :

function isEven(n) {
  return n % 2 === 0
}

const values = [1, 1, 2, 3, 5, 8, 13, 21, 34]
values.filter(isEven) // => [2, 8, 34]

const numbers = [1, Math.PI, 42, Infinity]
numbers.filter(Number.isInteger) // => [1, 42]

« Vide » ?

« Retirer les valeurs vides », mais encore ? Qu’est-ce qu’une valeur « vide » ? En fait ça va largement dépendre de ton besoin (même si « ça dépend », ça dépasse).

Le plus souvent, tu auras sous la main un tableau de valeurs numériques (où zéro ne serait pas acceptable) ou de chaînes (où les chaînes vides ne seraient pas valides). Dans les deux cas, false, null, NaN, undefined ne seraient pas valides non plus.

C’est là le cas bien pratique que nous verrons tout à l’heure. Mais quid si ton cas est différent ? Si tu veux bien zéro ? Ou false ? Ou si même les chaînes blanches (uniquement constituées de whitespace) sont à virer ?

Pas de souci, j’ai une série de petits prédicats tout mimi pour toi.

Quelques prédicats utiles

Valeur à tester Prédicat
undefined x === undefined ou typeof x === 'undefined'
null ou undefined x == null (attention : égalité laxiste !)
chaîne vide ('') Considérer booléen, ou x === ''
chaîne blanche x.trim() comme booléen, ou x.trim() === ''
Texte convertissable en nombre (hors NaN) Number
Nombre « utilisable » (ni NaN ni infini) Number.isFinite (ES2015)
Nombre entier Number.isInteger (ES2015)
Nombre entier fiable1 Number.isSafeInteger (ES2015)

_1 Tous les Number en JS utilisent le standard IEEE 754 : ce sont des nombres à virgule flottante 64-bit, qui n’ont que 15 chiffres de précision globale. Au-delà d’une certaine limite, les entiers sont ramenés à leur valeur représentable la plus proche. Essaie d’évaluer 9999999999999999 dans ta console ou REPL Node, et même ensuite 1234567890123456789…_

Si tu traites des textes, mais veux considérer les falsy values (du genre null, undefined, false ou NaN) comme des chaînes vides, tu peux faire un String(x || '') (car String(undefined) renverrait 'undefined', notamment).

Si tu considères aussi les chaînes blanches comme vides en prime : String(x || '').trim() est ton ami. Exemples :

const data = [
  0,
  1,
  42,
  1e100,
  Infinity,
  NaN,
  false,
  '',
  '   ',
  '3.14',
  '42',
  'Infinity',
]

// Que les _truthy_, mais on vire en prime les chaînes blanches :
data.filter((x) => String(x || '').trim())
// => [1, 42, 1e+100, Infinity, '3.14', '42', 'Infinity']

// Que les entiers fiables :
data.filter(Number.isSafeInteger)
// => [0, 1, 42]

// Que les valeurs convertissables en entiers « utilisables » :
data.filter((x) => Number.isSafeInteger(Number(x)))
// => [0, 1, 42, false, '', '   ', '42'] (false donnera zéro)

Besoin de rejeter plutôt que de garder ?

Tu as parfois sous la main un prédicat tout prêt, peut-être très compliqué à écrire, et pas de bol, il teste l’inverse de ce que tu veux : il dit « oui » pour ce que tu veux virer, et « non » pour ce que tu veux garder. Seulement voilà, tu n’as que filter sous la main, pas reject.

Ne vas surtout pas réécrire le prédicat toi-même en inversant sa logique (si le prédicat est complexe, ça peut vite être casse-gueule). Le plus simple est d’utiliser un inverseur :

function isEven(n) {
  // Imaginons qu’en fait ce truc est super chaud…
  return n % 2 === 0
}

const values = [1, 1, 2, 3, 5, 8, 13, 21, 34]
const odds = values.filter((n) => !isEven(n))
// => [1, 1, 3, 5, 13, 21]

Et si ça t'arrive souvent, tu peux génériser l'inversion :

function not(fn) {
  return (x) => !fn(x)
}

// Ou pour des signatures quelconques :
function not(fn) {
  return (...args) => !fn(...args)
}

const odds = values.filter(not(isEven))

Évidemment, Lodash a ça : _.negate

Le cas pratique : Boolean !

On évoquait tout à l’heure le cas finalement très répandu d’un tableau de valeurs numériques (où zéro ne serait pas acceptable) ou de chaînes (où les chaînes vides ne seraient pas valides). Dans les deux cas, false, null, NaN, undefined ne seraient pas valides non plus.

Lorsque tu veux filtrer toutes les falsy values, y’a un moyen hyper simple :

data.filter(Boolean) // 😎

C’est un peu l’équivalent du data.compact qu’on trouverait dans d’autres langages (ex. Ruby) : ça virera évidemment null et undefined, ce qui est en général ce qu’on veut, mais aussi au passage false, NaN et '' notamment, ce qui est souvent un bonus appréciable. Bon, ça virera aussi 0, donc gaffe si ça te gêne. Mais je m’en sers hyper souvent pour nettoyer, par exemple, un tableau où chaque donnée résultat doit être un texte non vide ou un objet quelconque.

Back to the future

Il y a des vérités intemporelles… Je t’expliquais déjà les falsy values en 2012 !

Découvrez notre cours vidéo : JavaScript : this is it ! 🖥

Tout savoir sur le fonctionnement de this en JavaScript, des règles fondamentales aux ajustements des API, en passant par les fonctions fléchées, le binding et bien plus encore…