JavaScript: map, filter e reduce per creare report e statistiche da array di dati

by Anastasia P.
SHARE
JavaScript: map, filter e reduce per creare report e statistiche da array di dati
© Guida-HTML5.it

Introduzione

Le funzioni di ordine superiore map, filter e reduce sono tra gli strumenti più utili di JavaScript quando devi lavorare con collezioni di dati. In molti tutorial si vede il loro uso generico su liste di numeri o nomi, ma un caso molto pratico e realistico è la generazione di report e statistiche a partire da un array di oggetti.

Immagina di avere una lista di ordini e di dover ottenere:

  • solo gli ordini completati;
  • il totale incassato;
  • la spesa media per cliente;
  • un riepilogo per categoria di prodotto.

Con un approccio imperativo potresti scrivere molti cicli for e variabili temporanee. Con map, filter e reduce invece puoi esprimere il flusso dei dati in modo più leggibile, componibile e facile da mantenere.

Codice completo

// Dataset di esempio: ordini di un piccolo e-commerce
const orders = [
  { id: 1, customer: "Luca", category: "books", amount: 24.90, status: "completed" },
  { id: 2, customer: "Anna", category: "tech", amount: 199.99, status: "pending" },
  { id: 3, customer: "Luca", category: "books", amount: 15.00, status: "completed" },
  { id: 4, customer: "Marta", category: "home", amount: 48.50, status: "completed" },
  { id: 5, customer: "Anna", category: "tech", amount: 89.90, status: "completed" },
  { id: 6, customer: "Sara", category: "books", amount: 12.00, status: "cancelled" },
  { id: 7, customer: "Marta", category: "home", amount: 22.00, status: "completed" }
];

// 1) filter: teniamo solo gli ordini completati
const completedOrders = orders.filter(order => order.status === "completed");

// 2) map: estraiamo solo gli importi degli ordini completati
const completedAmounts = completedOrders.map(order => order.amount);

// 3) reduce: calcoliamo il totale incassato
const totalRevenue = completedAmounts.reduce((sum, amount) => sum + amount, 0);

// 4) reduce: calcoliamo la spesa media per cliente
const spendingByCustomer = completedOrders.reduce((acc, order) => {
  if (!acc[order.customer]) {
    acc[order.customer] = {
      total: 0,
      count: 0
    };
  }

  acc[order.customer].total += order.amount;
  acc[order.customer].count += 1;

  return acc;
}, {});

const averageSpendingByCustomer = Object.fromEntries(
  Object.entries(spendingByCustomer).map(([customer, data]) => [
    customer,
    Number((data.total / data.count).toFixed(2))
  ])
);

// 5) reduce: riepilogo per categoria
const revenueByCategory = completedOrders.reduce((acc, order) => {
  acc[order.category] = (acc[order.category] || 0) + order.amount;
  return acc;
}, {});

// 6) map: creiamo un array leggibile per stampare il report
const categoryReport = Object.entries(revenueByCategory).map(([category, total]) => ({
  category,
  total: Number(total.toFixed(2))
}));

// Output finale
console.log("Ordini completati:", completedOrders);
console.log("Totale incassato:", totalRevenue.toFixed(2));
console.log("Spesa media per cliente:", averageSpendingByCustomer);
console.log("Ricavi per categoria:", categoryReport);

// Esempio di report testuale
const reportLines = [
  `Totale incassato: €${totalRevenue.toFixed(2)}`,
  "Spesa media per cliente:",
  ...Object.entries(averageSpendingByCustomer).map(([customer, average]) => `- ${customer}: €${average.toFixed(2)}`),
  "Ricavi per categoria:",
  ...categoryReport.map(item => `- ${item.category}: €${item.total.toFixed(2)}`)
];

console.log(reportLines.join("n"));

Spiegazione

1. filter: selezionare solo i dati utili

filter restituisce un nuovo array contenente solo gli elementi che rispettano una condizione. Nel nostro caso, abbiamo tenuto solo gli ordini con stato completed.

Questo passaggio è importante perché evita di includere nel calcolo ordini pending o cancelled. In un report reale, filtrare correttamente i dati è spesso più importante del calcolo stesso.

2. map: trasformare i dati in una forma più semplice

map serve a trasformare ogni elemento dell’array in un nuovo valore. Dopo aver filtrato gli ordini, abbiamo usato map per ottenere solo gli importi.

Questo è utile quando vuoi passare da una struttura complessa, come un oggetto ordine, a un dato più specifico, come un numero. In alternativa, potresti anche usare map per creare un array di etichette, oggetti semplificati o valori pronti per un grafico.

3. reduce: aggregare informazioni

reduce è la funzione più potente delle tre quando devi accumulare risultati. Riceve un accumulatore e aggiorna il valore passo dopo passo. Nel tutorial abbiamo usato reduce in due modi:

  • per sommare gli importi e ottenere il fatturato totale;
  • per costruire un oggetto con il totale per cliente e per categoria.

Questo pattern è molto comune nei report: parti da una lista e costruisci una struttura aggregata, come un oggetto o una mappa di statistiche.

4. Combinare map, filter e reduce

La vera forza di queste funzioni emerge quando le combini. Nel nostro esempio:

  • filter elimina i dati non validi per il report;
  • map estrae o riformatta i campi necessari;
  • reduce calcola metriche e aggregazioni.

Questa sequenza rende il codice più leggibile rispetto a un ciclo unico pieno di condizioni annidate. Inoltre, ogni passaggio ha un solo scopo, quindi è più facile da testare e modificare.

5. Perché usare Object.entries e Object.fromEntries

Nel codice abbiamo usato Object.entries e Object.fromEntries per convertire un oggetto in array e viceversa. Questo è molto comodo quando vuoi applicare map ai dati aggregati.

Per esempio, dopo aver costruito un oggetto con la spesa media per cliente, lo abbiamo trasformato in una struttura più facile da formattare per un report. È un passaggio utile quando i dati devono essere stampati, inviati a un’API o mostrati in tabella.

Best practice

  • Usa nomi descrittivi: variabili come completedOrders, totalRevenue o revenueByCategory rendono il codice immediato da capire.
  • Evita effetti collaterali dentro map: map dovrebbe trasformare dati, non modificare variabili esterne. Se devi accumulare, usa reduce.
  • Inizializza sempre l’accumulatore in reduce: passare il valore iniziale evita bug e rende il comportamento prevedibile.
  • Separa i passaggi complessi: invece di scrivere una catena troppo lunga, spezza il lavoro in variabili intermedie. La leggibilità migliora molto.
  • Controlla i dati in ingresso: se gli oggetti possono avere valori mancanti o nulli, valida prima di calcolare statistiche.
  • Usa toFixed con attenzione: è ottimo per il display, ma restituisce una stringa. Se ti serve un numero, riconverti con Number.
  • Preferisci reduce per aggregazioni complesse: se devi costruire conteggi, somme per gruppo o strutture annidate, reduce è spesso la scelta più pulita.

Riepilogo

In questo tutorial abbiamo visto un caso d’uso molto pratico di map, filter e reduce: generare report e statistiche da un array di ordini. Questo approccio è utile in dashboard, pannelli amministrativi, analisi dati e applicazioni che devono trasformare informazioni grezze in riepiloghi leggibili.

Il flusso mentale da ricordare è semplice:

  • filter = seleziona i dati rilevanti;
  • map = trasforma i dati;
  • reduce = aggrega o sintetizza i dati.

Se impari a combinare bene questi tre strumenti, il tuo codice JavaScript diventerà più espressivo, modulare e facile da mantenere.

Approfondisci con risorse ufficiali

  • MDN - Array.prototype.map(): documentazione completa con esempi e comportamento dettagliato.
  • MDN - Array.prototype.filter(): guida ufficiale sull’uso del filtro sugli array.
  • MDN - Array.prototype.reduce(): riferimento essenziale per capire accumulatori e valori iniziali.
  • MDN - Object.entries() e Object.fromEntries(): utili per convertire oggetti in array e viceversa.
  • ECMAScript Language Specification: per chi vuole approfondire il comportamento standard del linguaggio.

SHARE