di Josh Pitzalis

Il metodo reduce di JavaScript è uno dei capisaldi della programmazione funzionale. Esploriamo come funziona, quando dovresti usarlo e alcune delle cose interessanti che può fare.

Una riduzione di base

Usalo quando: hai una array di importi e vuoi sommarli tutti.

const euros = [29.76, 41.85, 46.5];

const sum = euros.reduce((total, amount) => total + amount); 

sum // 118.11

Come usarlo:

  • In questo esempio, Reduce accetta due parametri, il totale e l'importo corrente.
  • Il metodo Reduce scorre ogni numero nell'array proprio come farebbe in un ciclo for.
  • Quando il ciclo inizia, il valore totale è il numero all'estrema sinistra (29.76) e l'importo corrente è quello accanto (41.85).
  • In questo particolare esempio, vogliamo aggiungere l'importo corrente al totale.
  • Il calcolo viene ripetuto per ogni importo nell'array, e ogni volta che il valore corrente cambia ci si sposta verso destra nell'array.
  • Quando non sono rimasti più numeri nell'array, il metodo restituisce il valore totale.

La versione ES5 del metodo Reduce in JavaScript

Se non hai mai usato la sintassi ES6 prima, non lasciare che l'esempio sopra ti intimidisca. È esattamente come scrivere:

var euros = [29.76, 41.85, 46.5]; 

var sum = euros.reduce( function(total, amount){
  return total + amount
});

sum // 118.11

Usiamo const al posto di var e sostituiamo la parola function con una “fat arrow” ( =>) dopo i parametri, e omettiamo la parola 'return'.

Userò la sintassi ES6 per il resto degli esempi, poiché è più concisa e lascia meno spazio agli errori.

Trovare una media con il metodo Reduce in JavaScript​

Invece di effettuare la somma, puoi dividere la somma per la lunghezza dell'array prima di restituire un valore finale.

Il modo per farlo è sfruttare gli altri argomenti del metodo reduce. Il primo di questi argomenti è l'index . Proprio come un ciclo for, l'indice si riferisce al numero di volte in cui il reducer ha eseguito un loop sull'array. L'ultimo argomento è l'array stesso.

const euros = [29.76, 41.85, 46.5];

const average = euros.reduce((total, amount, index, array) => {
  total += amount;
  if( index === array.length-1) { 
    return total/array.length;
  }else { 
    return total;
  }
});

average // 39.37

Map e Filter come riduzioni

Se puoi usare la funzione di riduzione per calcolare una media, puoi usarla come preferisci.

Ad esempio, potresti raddoppiare il totale o dimezzare ogni numero prima di sommarli, oppure utilizzare un'istruzione if all'interno del reducer per aggiungere solo numeri maggiori di 10. Personalmente ritengo che il metodo Reduce in JavaScript ti dà una sorta di editor dove puoi scrivere qualsiasi logica tu voglia. Ripeterà la logica per ogni importo nell'array e quindi restituirà un singolo valore.

Il fatto è che non devi sempre restituire un singolo valore. È possibile ridurre un array in un nuovo array.

Ad esempio, riduciamo un array di importi in un altro array in cui ogni importo viene raddoppiato. Per fare ciò dobbiamo impostare il valore iniziale per il nostro accumulatore su un array vuoto.

Il valore iniziale è il valore del parametro totale quando inizia la riduzione. Puoi impostare il valore iniziale aggiungendo una virgola seguita dal tuo valore iniziale tra parentesi ma dopo le parentesi graffe (indicato con un commento nell'esempio seguente).

const average = euros.reduce((total, amount, index, array) => {
  total += amount
  return total/array.length
}, 0); // questo 0 (zero) qua è il valore iniziale - può avere qualsiasi valore

Negli esempi precedenti, il valore iniziale era zero, quindi l'ho omesso. Omettendo il valore iniziale, il totale verrà impostato automaticamente sul primo importo nell'array.

Impostando il valore iniziale su un array vuoto possiamo quindi inserire ogni amount in total. Se vogliamo ridurre un array di valori in un altro array in cui ogni valore viene raddoppiato, dobbiamo inserire l'importo * 2. Quindi restituiremo il totale quando non ci sono più importi da inserire.

const euros = [29.76, 41.85, 46.5];

const doubled = euros.reduce((total, amount) => {
  total.push(amount * 2);
  return total;
}, []);

doubled // [59.52, 83.7, 93]

Abbiamo creato un nuovo array in cui ogni importo viene raddoppiato. Potremmo anche filtrare i numeri che non vogliamo raddoppiare aggiungendo un'istruzione if all'interno del nostro reducer.

const euro = [29.76, 41.85, 46.5];

const above30 = euro.reduce((total, amount) => {
  if (amount > 30) {
    total.push(amount);
  }
  return total;
}, []);

above30 // [ 41.85, 46.5 ]

Queste operazioni sono i metodi di mappatura e filtratura riscritti come metodo di riduzione.

Per questi esempi, avrebbe più senso usare map o  filter perché sono più semplici da usare. Il vantaggio dell'utilizzo di reduce entra in gioco quando si desidera mappare e filtrare contemporaneamente e si hanno molti dati da esaminare.

Se concateni map e filter insieme, fai il lavoro due volte. Filtri ogni singolo valore e poi mappi i valori rimanenti. Con reduce puoi filtrare e quindi mappare in un unico passaggio.

Usa map e filter ma quando inizi a concatenare molti metodi insieme,  sai che è invece più veloce ridurre i dati.

Creazione di un conteggio con il metodo Reduce in JavaScript​

Usalo quando: hai una collezione di elementi e vuoi sapere quanti di essi ci sono in questa collezione.

const fruitBasket = ['banana', 'cherry', 'orange', 'apple', 'cherry', 'orange', 'apple', 'banana', 'cherry', 'orange', 'fig' ];

const count = fruitBasket.reduce( (tally, fruit) => {
  tally[fruit] = (tally[fruit] || 0) + 1 ;
  return tally;
} , {})

count // { banana: 2, cherry: 3, orange: 3, apple: 2, fig: 1 }

Per contare gli elementi in un array, il nostro valore iniziale deve essere un oggetto vuoto, non un array vuoto come nell'ultimo esempio.

Poiché restituiamo un oggetto, ora possiamo memorizzare le coppie chiave-valore nel totale.

fruitBasket.reduce( (tally, fruit) => {
  tally[fruit] = 1;
  return tally;
}, {})

Al nostro primo passaggio, vogliamo che il nome della prima chiave sia il nostro valore corrente e vogliamo assegnargli un valore di 1.

Questo ci dà un oggetto con tutti i frutti come chiavi, ognuno con un valore di 1. Vogliamo che la quantità di ogni frutto aumenti se si ripetono.

Per fare ciò, nel nostro secondo ciclo controlliamo se il nostro totale contiene una chiave con il frutto corrente del reducer. Se così non è, lo creiamo. Se invece è così, incrementiamo l'importo di uno.

fruitBasket.reduce((tally, fruit) => {
  if (!tally[fruit]) {
    tally[fruit] = 1;
  } else {
    tally[fruit] = tally[fruit] + 1;
  }
  return tally;
}, {});

Ho riscritto la stessa identica logica in modo più conciso.

Appiattimento di un array di array con il metodo Reduce in JavaScript​​

Possiamo usare reduce per appiattire gli importi nidificati in un singolo array.

Impostiamo il valore iniziale su un array vuoto e quindi concateniamo il valore corrente al totale.

const data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];

const flat = data.reduce((total, amount) => {
  return total.concat(amount);
}, []);

flat // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

Il più delle volte, le informazioni sono nidificate in modi più complicati. Ad esempio, diciamo che vogliamo solo tutti i colori presenti nella seguente variabile.

const data = [
  {a: 'happy', b: 'robin', c: ['blue','green']}, 
  {a: 'tired', b: 'panther', c: ['green','black','orange','blue']}, 
  {a: 'sad', b: 'goldfish', c: ['green','red']}
];

Analizzeremo ogni oggetto ed estrarremo i colori. Lo facciamo puntando amount.c per ogni oggetto nell'array. Usiamo quindi un ciclo forEach per inserire ogni valore dell'array nidificato nel totale.

const colors = data.reduce((total, amount) => {
  amount.c.forEach( color => {
      total.push(color);
  })
  return total;
}, [])

colors //['blue','green','green','black','orange','blue','green','red']

Se abbiamo solo bisogno di un numero univoco, possiamo verificare se il numero esiste già in totale prima di inserirlo.

const uniqueColors = data.reduce((total, amount) => {
  amount.c.forEach( color => {
    if (total.indexOf(color) === -1){
     total.push(color);
    }
  });
  return total;
}, []);

uniqueColors // [ 'blue', 'red', 'green', 'black', 'orange']

Piping con Reduce

Un aspetto interessante del metodo Reduce in JavaScript è che puoi ridurre le funzioni oltre che i numeri e le stringhe.

Diciamo di avere una raccolta di semplici funzioni matematiche. Queste funzioni ci consentono di incrementare, decrementare, raddoppiare e dimezzare un importo.

function increment(input) { return input + 1;}

function decrement(input) { return input — 1; }

function double(input) { return input * 2; }

function halve(input) { return input / 2; }

Per qualsiasi motivo, dobbiamo aumentare, poi raddoppiare, quindi decrementare un importo.

Potresti scrivere una funzione che accetta un input e restituisce (input + 1) * 2 -1. Il problema è che sappiamo che dovremo aumentare l'importo tre volte, quindi raddoppiarlo, poi diminuirlo e quindi dimezzarlo in futuro. Non vogliamo dover riscrivere la nostra funzione ogni volta, quindi useremo Reduce per creare una pipeline.

Una pipeline è un termine utilizzato per indicare un elenco di funzioni che trasformano un valore iniziale in un valore finale. La nostra pipeline sarà composta dalle nostre tre funzioni nell'ordine in cui vogliamo usarle.

let pipeline = [increment, double, decrement];

Invece di ridurre una serie di valori, riduciamo la nostra pipeline di funzioni. Funziona perché impostiamo il valore iniziale come l'importo che vogliamo trasformare.

const result = pipeline.reduce(function(total, func) {
  return func(total);
}, 1);

result // 3

Poiché la pipeline è un array, può essere facilmente modificata. Se vogliamo decrementare qualcosa tre volte, quindi raddoppiarlo, decrementarlo e dimezzarlo, modifichiamo semplicemente la pipeline.

var pipeline = [

  increment,
  
  increment,
  
  increment,
  
  double,
  
  decrement,
  
  halve
  
];

La funzione di riduzione rimane esattamente la stessa.

Errori stupidi da evitare

Se non si passa un valore iniziale, reduce assumerà che il primo elemento nell'array sia il suo valore iniziale. Questo ha funzionato bene nei primi esempi perché stavamo sommando un elenco di numeri.

Se stai cercando di contare la frutta e ometti il ​​valore iniziale, le cose si fanno strane. Non inserire un valore iniziale è un errore facile da fare ed e una delle prime cose che dovresti controllare durante il debug.

Un altro errore comune è dimenticare di restituire il totale. È necessario restituire qualcosa affinché la funzione di riduzione funzioni. Ricontrolla sempre e assicurati di restituire effettivamente il valore che desideri.

Strumenti, suggerimenti e riferimenti

  • Tutto in questo post proviene da una fantastica serie di video su Egghead chiamata Introducing Reduce . Riconosco a Mykola Bilokonsky il pieno merito e gli sono grato per tutto ciò che ora so sull'utilizzo del metodo Reduce in JavaScript. Ho cercato di riscrivere con parole mie molto di ciò che lui spiega, come esercizio per comprendere meglio ogni concetto. Inoltre, è più facile per me fare riferimento a un articolo, al contrario di un video, quando ho bisogno di ricordare come fare qualcosa.
  • La documentazione MDN Reduce etichetta  come accumulator ciò che io ho chiamato total . È importante sapere questo perché la maggior parte delle persone lo chiamerà  accumulatore se leggi la documentazione online. Alcune persone lo chiamano prev inteso come valore precedente . Si riferisce tutto alla stessa cosa. Ho trovato più facile pensare a un totale quando stavo imparando a usare Reduce.
  • Se desideri esercitarti con reduce, ti consiglio di iscriverti a freeCodeCamp e di completare quanti più algoritmi intermedi che puoi usando reduce.
  • Se le variabili 'const' negli esempi di su sono nuove per te, ho scritto un altro articolo sulle variabili ES6 e sul perché potresti volerle usare .
  • Ho anche scritto un articolo chiamato The Trouble With Loops che spiega come usare map() e filter() se sono nuovi per te.

Grazie per aver letto! Se desideri essere avvisato quando scrivo un nuovo articolo, inserisci qui la tua email.

E se l'articolo ti è piaciuto, condividilo sui social media in modo che altri possano trovarlo.