Reduce() : Le couteau suisse du développeur Javascript

Une fonction à tout faire.

Pour aller droit au but, nous allons voir comment reduce() peut remplacer toutes (ou presque) les fonctions que nous utilisons pour traiter les données que nos applications javascript gèrent.

Nous allons, dans cet article, implémenter une dizaine de fonctions très souvent utilisées quand on traite des collections de données.

Les fonctions que nous allons implémenter sont :

01. min()       // renvoie la plus petite valeur d'une collection
02. max() // renvoie la plus grande valeur d'une collection
03. length() // renvoie la taille d'une collection
04. map() // ou fmap() transforme une collection
05. filter() // renvoie un sous-ensemble répondant à un critère.
06. reverse() // renvoie la collection dans le sens inverse
07. unique() // renvoie les valeurs uniques d'une collection
08. take() // renvoie un sous-ensemble d'une taille donnée
09. any() // vérifie qu'au moins un élément de la collection répond à des critères donnés
10. every() // vérifie que tous les éléments de la collection répondent à des critères donnés.

La fonction min():

Cette fonction prend en entrée une collection de données de même type et dont les valeurs sont comparables. Exemple: [1, 2, 4, 6, 89, 0, -1, 43]
Une implémentation naïve de cette fonction ressemblera à :

// es5
function min(collection){
var valMin = collection[0];
var i;
for(i=0; i < collection.length; i++){
if( valMin > collection[i] ){
valMin = collection[i];
}
}
return valMin;
}
// es6
const min = (collection) => {
let valMin = collection[0];
let i;
for(i=0; i < collection.length; i++){
if(valMin > collection[i]){
valMin = collection[i];
}
}
return valMin;
}

Nous avons là, la même implémentation de la fonction min() dans deux différentes syntaxes. Nous allons continuer avec la syntaxe es6.

Maintenant que nous avons une implémentation (qui marche) de la fonction min(), nous allons voir comment re-créer la même fonction min() en utilisant reduce().

reduce() est une fonction de rang supérieur. Pour plus de détails à ce sujet, vous pouvez jeter un coup d’oeil sur cet article.

Notre objectif maintenant est de ré-implémenter les 10 fonctions listées plus haut en utilisant reduce(). Tout d’abord parlons de reduce().

C’est une fonction qui s’applique sur une collection de données et qui prend en entrée une fonction de transformation et un accumulateur. Ce dernier donne souvent une idée du type de données retourné par cette fonction. La fonction de transformation sera appliquée à chaque élément de la collection. Le résultat de cette transformation sera utilisé comme valeur de départ pour la prochaine itération.

La signature de reduce() est la suivante :

collection.reduce(fonctionDeTransformation, accumulateur); 
// accumulateur = valeurDeDépart aussi

Dans le cas de notre fonction min(), nous devons fournir une fonction de transformation qui retourne la plus petite valeur entre deux éléments de la collection. La première valeur sera la valeur de départ (le fameux accumulateur) que nous passerons à reduce(), la deuxième est le premier élément de la collection lors de la première itération.

const MinAetB = (x, y) => x < y ? x : y

La fonction MinAetB prend deux valeurs et retourne la plus petite des deux. Nous allons utiliser MinAetB comme fonction de transformation dans notre implémentation basée sur reduce(). Au boulot.

const MinAetB = (x, y) => x < y ? x : y
const min = 
(collection) => collection.reduce(MinAetB, collection[0]);
// nous avons utilisé collection[0] comme valeur de départ. 
const notes = [17, 15, 8, 14, 3, 0, 9];
min(notes); // 0

Et voilà…


La fonction max():

Maintenant que nous savons comment utiliser reduce(), nous allons directement écrire notre fonction de transformation pour avoir la nouvelle fonction max().

const MaxAetB = (a, b) =>  a > b ? a : b;

Puis utiliser cette fonction de transformation pour générer la fonction max().

const max = 
(collection) => collection.reduce(MaxAetB, collection[0]);
const notes = [17, 15, 8, 14, 3, 0, 9, 18];
max(notes); // 18

Ca devient de plus en plus simple :)


La fonction length():

Cette fonction va introduire une première différence avec les deux premières déjà définies plus haut. Pour déterminer la taille d’une collection, on ne fait pas de traitement spécifique aux éléments de cette collection. Ce qui fait que notre fonction de transformation n’aura pas besoin des deux arguments qu’elle prend en entrée.

const length = 
(collection) => collection.reduce((count, _) => count + 1, 0)

Dans cet exemple, nous avons choisi de ne pas extraire notre fonction de transformation. Pour les fonctions de transformation simples, il est souvent plus simple de ne pas les extraire.


La fonction map():

const map = 
(fn, collection) => collection.reduce(
(acc, item) => acc.concat( fn(item) ),
[]);
const addOne = (x) => x + 1;
const data = [0, 1, 2, 3, 4, 5];
map(addOne, data); // => [1, 2, 3, 4, 5, 6]

La fonction filter():

const filter = 
(fn, collection) => collection.reduce(
(acc, item) => fn(item) ? acc.concat(item) : acc,
[]);
const greaterThan2 = (x) => x > 2;
const data = [0, 1, 2, 3, 4, 5];
filter(greaterThan2, data); // [3, 4, 5]

La fonction reverse():

const reverse = 
(collection) => collection.reduce(
(acc, item) => [item].concat(acc),
[]);
const data = [0, 1, 2, 3, 4, 5];
reverse(data); // [5, 4, 3, 2, 1, 0]

La fonction unique():

const unique = 
(collection) => collection.reduce(
(acc, item) => {
if( collection.indexOf(item) === -1 ){
return acc.concat(item);
} else {
return acc;
}
},
[]);

La fonction take():

const take = 
(nb, collection) => collection.reduce(
(acc, item) => {
if(nb > 0){
nb--;
return acc.concat(item);
} else {
return acc;
}
},
nb);
const data   = [0, 1, 2, 3, 4, 5];
take(2, data); // [0, 1];

La fonction any():

const any = 
(predicat, collection) => collection.reduce(
(acc, item) => predicat(item) || acc,
false);
const isFive = (x) => x === 5;
any(isFive, data);

La fonction every():

const every = 
(predicat, collection) => collection.reduce(
(acc, item) => predicat(item) && acc,
true);
const data = [0, 1, 2, 3, 4, 5];
const isGreaterThan5 = (x) => x > 5;
const data = [2, 3, 4, 5];
const isGreaterThan1 = (x) => x > 1;
every(isGreaterThan1, data); // true

Un petit défi : choisissez 3 fonctions ( différentes de celles que nous avons plus haut) et utilisez reduce() pour les ré-implémenter.

Merci.