Retirer facilement les « valeurs vides » d’un tableau
Par Christophe Porteneuve • Publié le 11 mai 2020
• 4 min
This page is also available in English.
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 :
- Court-circuiter plusieurs niveaux de boucles
- Inverser deux valeurs avec la déstructuration
- Retirer facilement les « valeurs vides » d’un tableau (cet article)
- Vive les séparateurs numériques !
- Trier proprement des textes
- …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 afor…of
) ;map
pour produire un tableau dérivé via une fonction de transformation ;every
etsome
pour déterminer si un prédicat (fonction vrai/faux) est satisfait par tout ou partie du tableau ;reduce
etreduceRight
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 :
- La fonction de rappel (pour
filter
, le prédicat, donc) ; - Le contexte : si la fonction de rappel s’attend à un
this
particulier, mais que la passer par référence « perdrait » cethis
(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 !