JavaScript

JavaScript

Tutorial JavaScript: Reducción con reduce()

JavaScript reduce: uso en arrays y ejemplos. Aprende a usar reduce en arrays de JavaScript con ejemplos prácticos y detallados.

Aprende JavaScript y certifícate

Fundamentos de reduce(): Agregación y transformación de colecciones hacia un único valor

El método reduce() es una de las herramientas más versátiles y potentes del paradigma de programación funcional en JavaScript. A diferencia de otros métodos de iteración como map() o filter(), reduce() nos permite condensar o "reducir" una colección de valores a un único resultado, lo que lo convierte en una herramienta fundamental para operaciones de agregación y transformación.

Sintaxis básica

La sintaxis básica de reduce() es la siguiente:

array.reduce(callback(accumulator, currentValue[, index[, array]]), initialValue)

Donde:

  • callback: Función que se ejecuta en cada elemento del array
  • accumulator: Acumula el valor retornado por la función callback
  • currentValue: El elemento actual que está siendo procesado
  • index (opcional): Índice del elemento actual
  • array (opcional): El array sobre el cual se llamó a reduce()
  • initialValue (opcional): Valor inicial del acumulador

Funcionamiento interno

El proceso de reduce() puede visualizarse como una cadena de transformaciones donde cada paso toma el resultado anterior y lo combina con el siguiente elemento:

  1. Si se proporciona un initialValue, el acumulador comienza con ese valor
  2. Si no se proporciona, el primer elemento del array se usa como acumulador inicial
  3. La función callback se ejecuta para cada elemento, actualizando el acumulador
  4. El valor final del acumulador se devuelve como resultado

Ejemplos básicos

Veamos algunos ejemplos fundamentales para entender cómo funciona reduce():

  • 1. Suma de valores en un array:
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(sum); // 15
  • 2. Calculando el producto de todos los elementos:
const numbers = [1, 2, 3, 4];
const product = numbers.reduce((accumulator, currentValue) => accumulator * currentValue, 1);
console.log(product); // 24
  • 3. Encontrando el valor máximo:
const numbers = [5, 20, 100, 60, 1];
const max = numbers.reduce((max, current) => current > max ? current : max, numbers[0]);
console.log(max); // 100

La importancia del valor inicial

El parámetro initialValue es crucial en muchas operaciones con reduce(). Aunque es opcional, omitirlo puede llevar a comportamientos inesperados:

// Con valor inicial
[].reduce((acc, curr) => acc + curr, 0); // Devuelve 0

// Sin valor inicial
[].reduce((acc, curr) => acc + curr); // Error: Reduce of empty array with no initial value

Proporcionar un valor inicial adecuado tiene varias ventajas:

  • Evita errores con arrays vacíos
  • Define claramente el tipo de dato del resultado
  • Mejora la legibilidad del código
  • Permite transformaciones entre diferentes tipos de datos

Transformación de tipos

Una característica poderosa de reduce() es que el acumulador puede ser de un tipo diferente al de los elementos del array:

// Transformando un array de strings a un objeto
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const count = fruits.reduce((countObj, fruit) => {
  countObj[fruit] = (countObj[fruit] || 0) + 1;
  return countObj;
}, {});

console.log(count); // { apple: 3, banana: 2, orange: 1 }

En este ejemplo, transformamos un array de strings en un objeto que cuenta las ocurrencias de cada fruta.

Encadenamiento con otros métodos de array

reduce() se puede combinar con otros métodos de array para crear pipelines de procesamiento más complejos:

const orders = [
  { id: 1, items: 3, total: 150 },
  { id: 2, items: 1, total: 50 },
  { id: 3, items: 5, total: 200 }
];

// Calcular el total de ventas para pedidos con más de 2 items
const totalForLargeOrders = orders
  .filter(order => order.items > 2)
  .reduce((sum, order) => sum + order.total, 0);

console.log(totalForLargeOrders); // 350

Casos de uso comunes

  • 1. Aplanar arrays anidados:
const nestedArrays = [[1, 2], [3, 4], [5, 6]];
const flattened = nestedArrays.reduce((acc, curr) => acc.concat(curr), []);
console.log(flattened); // [1, 2, 3, 4, 5, 6]
  • 2. Agrupar objetos por una propiedad:
const people = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 25 },
  { name: 'Dave', age: 30 }
];

const groupedByAge = people.reduce((groups, person) => {
  const age = person.age;
  groups[age] = groups[age] || [];
  groups[age].push(person);
  return groups;
}, {});

console.log(groupedByAge);
// {
//   25: [{ name: 'Alice', age: 25 }, { name: 'Charlie', age: 25 }],
//   30: [{ name: 'Bob', age: 30 }, { name: 'Dave', age: 30 }]
// }
  • 3. Eliminar duplicados de un array:
const numbers = [1, 2, 2, 3, 4, 4, 5];
const unique = numbers.reduce((acc, curr) => {
  if (!acc.includes(curr)) {
    acc.push(curr);
  }
  return acc;
}, []);

console.log(unique); // [1, 2, 3, 4, 5]

Consideraciones de rendimiento

Aunque reduce() es muy versátil, hay algunas consideraciones importantes:

  • Para operaciones simples en arrays grandes, los bucles tradicionales pueden ser más rápidos
  • Evita modificar el acumulador directamente (mutación) para mantener los principios de programación funcional
  • Considera usar reduceRight() cuando necesites procesar un array de derecha a izquierda
// Ejemplo de reduceRight() para invertir un string
const string = "Hello";
const reversed = Array.from(string).reduceRight((acc, char) => acc + char, "");
console.log(reversed); // "olleH"

Depuración de reducciones

Depurar operaciones complejas con reduce() puede ser desafiante. Una técnica útil es agregar un console.log dentro de la función callback:

const result = [1, 2, 3, 4].reduce((acc, curr, idx) => {
  console.log(`Iteración ${idx}: acc = ${acc}, curr = ${curr}`);
  return acc + curr;
}, 0);

// Muestra:
// Iteración 0: acc = 0, curr = 1
// Iteración 1: acc = 1, curr = 2
// Iteración 2: acc = 3, curr = 3
// Iteración 3: acc = 6, curr = 4
// Resultado final: 10

Este enfoque de visualización paso a paso es invaluable para entender y depurar reducciones complejas.

El método reduce() es una herramienta fundamental que todo desarrollador JavaScript debería dominar. Su capacidad para transformar colecciones en valores únicos lo convierte en un componente esencial para implementar algoritmos de agregación y transformación de datos de manera elegante y funcional.

Patrones avanzados de reducción: Más allá de las agregaciones numéricas simples

Aunque reduce() es comúnmente asociado con operaciones numéricas como sumas o promedios, su verdadero potencial se revela cuando lo aplicamos a transformaciones de datos más complejas. En esta sección exploraremos patrones avanzados que demuestran la versatilidad de este método en escenarios reales de desarrollo.

Composición de funciones con reduce()

Uno de los patrones más elegantes es utilizar reduce() para componer funciones, similar a cómo funcionan las tuberías (pipes) en programación funcional:

const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);

// Funciones individuales
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;

// Componiendo funciones
const transform = compose(square, double, addOne);

console.log(transform(3)); // square(double(addOne(3))) = square(double(4)) = square(8) = 64

Este patrón permite crear transformaciones complejas a partir de funciones simples, mejorando la modularidad y reutilización del código.

Implementación de máquinas de estado

reduce() puede implementar máquinas de estado finitas, procesando secuencias de eventos y manteniendo un estado interno:

const events = [
  { type: 'LOGIN', user: 'alice' },
  { type: 'ADD_TO_CART', item: 'book', quantity: 1 },
  { type: 'ADD_TO_CART', item: 'laptop', quantity: 1 },
  { type: 'CHECKOUT' },
  { type: 'LOGOUT' }
];

const initialState = { user: null, cart: [], status: 'idle' };

const finalState = events.reduce((state, event) => {
  switch (event.type) {
    case 'LOGIN':
      return { ...state, user: event.user, status: 'active' };
    case 'ADD_TO_CART':
      return { 
        ...state, 
        cart: [...state.cart, { item: event.item, quantity: event.quantity }] 
      };
    case 'CHECKOUT':
      return { ...state, status: 'completed', cart: [] };
    case 'LOGOUT':
      return { ...state, user: null, status: 'idle' };
    default:
      return state;
  }
}, initialState);

console.log(finalState);
// { user: null, cart: [], status: 'idle' }

Este patrón es la base conceptual de bibliotecas como Redux, donde el estado de la aplicación evoluciona a través de reducciones sucesivas.

Procesamiento de estructuras de datos anidadas

reduce() es especialmente útil para procesar estructuras de datos jerárquicas o anidadas:

const fileSystem = {
  name: 'root',
  files: [
    { name: 'notes.txt', size: 200 },
    { name: 'data.csv', size: 300 }
  ],
  folders: [
    {
      name: 'projects',
      files: [
        { name: 'project1.js', size: 400 },
        { name: 'project2.js', size: 500 }
      ],
      folders: [
        {
          name: 'legacy',
          files: [{ name: 'old.js', size: 100 }],
          folders: []
        }
      ]
    }
  ]
};

// Función recursiva para calcular el tamaño total
function calculateSize(item) {
  // Tamaño de archivos directos
  const filesSize = item.files.reduce((sum, file) => sum + file.size, 0);
  
  // Tamaño de carpetas anidadas (recursión)
  const foldersSize = item.folders.reduce((sum, folder) => sum + calculateSize(folder), 0);
  
  return filesSize + foldersSize;
}

console.log(calculateSize(fileSystem)); // 1500

Este patrón permite recorrer y transformar estructuras de datos complejas de manera declarativa y elegante.

Construcción de pipelines de procesamiento

Podemos crear pipelines de procesamiento de datos que apliquen múltiples transformaciones en secuencia:

const pipeline = functions => data => {
  return functions.reduce((acc, fn) => fn(acc), data);
};

// Funciones de transformación
const removeEmptyLines = text => text.split('\n').filter(line => line.trim()).join('\n');
const capitalizeFirstLetter = text => text.replace(/^(.)|\n(.)/g, match => match.toUpperCase());
const addTimestamp = text => `[${new Date().toISOString()}]\n${text}`;

// Crear pipeline
const processText = pipeline([
  removeEmptyLines,
  capitalizeFirstLetter,
  addTimestamp
]);

const text = `
hello world

this is a test
`;

console.log(processText(text));
// [2023-11-10T12:34:56.789Z]
// Hello World
// This Is A Test

Este patrón permite descomponer problemas complejos en transformaciones más pequeñas y manejables.

Implementación de promesas encadenadas

reduce() puede simular el comportamiento de Promise.then() para operaciones asíncronas secuenciales:

const asyncSequence = (initialValue, asyncFunctions) => {
  return asyncFunctions.reduce(
    (promise, fn) => promise.then(fn),
    Promise.resolve(initialValue)
  );
};

// Funciones asíncronas
const fetchUser = async (id) => {
  // Simulación de API
  return { id, name: `User ${id}` };
};

const fetchPosts = async (user) => {
  // Simulación de API
  return { ...user, posts: [`Post 1 by ${user.name}`, `Post 2 by ${user.name}`] };
};

const formatData = async (userData) => {
  return {
    ...userData,
    postCount: userData.posts.length,
    formattedName: userData.name.toUpperCase()
  };
};

// Ejecutar secuencia
asyncSequence(1, [fetchUser, fetchPosts, formatData])
  .then(result => console.log(result))
  .catch(error => console.error(error));

Este patrón permite orquestar operaciones asíncronas de manera limpia y mantenible.

Acumuladores multidimensionales

A veces necesitamos acumular múltiples valores simultáneamente:

const transactions = [
  { type: 'sale', amount: 100, category: 'electronics' },
  { type: 'refund', amount: 20, category: 'electronics' },
  { type: 'sale', amount: 50, category: 'books' },
  { type: 'sale', amount: 30, category: 'books' },
  { type: 'refund', amount: 10, category: 'books' }
];

const summary = transactions.reduce((acc, transaction) => {
  const { type, amount, category } = transaction;
  
  // Actualizar totales por tipo
  acc.byType[type] = (acc.byType[type] || 0) + amount;
  
  // Actualizar totales por categoría
  acc.byCategory[category] = (acc.byCategory[category] || 0) + (type === 'sale' ? amount : -amount);
  
  // Actualizar total general
  acc.total += type === 'sale' ? amount : -amount;
  
  return acc;
}, { byType: {}, byCategory: {}, total: 0 });

console.log(summary);
// {
//   byType: { sale: 180, refund: 30 },
//   byCategory: { electronics: 80, books: 70 },
//   total: 150
// }

Este patrón permite calcular múltiples métricas en una sola pasada por los datos, mejorando la eficiencia.

Implementación de algoritmos de agrupación

reduce() es ideal para implementar algoritmos de agrupación y clasificación:

const students = [
  { name: 'Ana', grade: 85, subject: 'Math' },
  { name: 'John', grade: 70, subject: 'Science' },
  { name: 'Ana', grade: 90, subject: 'Science' },
  { name: 'John', grade: 80, subject: 'Math' }
];

// Agrupar por estudiante y calcular promedio por materia
const studentPerformance = students.reduce((result, { name, grade, subject }) => {
  // Inicializar entrada si no existe
  if (!result[name]) {
    result[name] = { subjects: {}, averageGrade: 0, totalGrades: 0 };
  }
  
  // Agregar calificación a la materia
  if (!result[name].subjects[subject]) {
    result[name].subjects[subject] = { total: 0, count: 0, average: 0 };
  }
  
  result[name].subjects[subject].total += grade;
  result[name].subjects[subject].count += 1;
  result[name].subjects[subject].average = 
    result[name].subjects[subject].total / result[name].subjects[subject].count;
  
  // Actualizar promedio general
  result[name].totalGrades += grade;
  result[name].averageGrade = result[name].totalGrades / 
    Object.values(result[name].subjects).reduce((sum, { count }) => sum + count, 0);
  
  return result;
}, {});

console.log(studentPerformance);

Este patrón permite transformar datos planos en estructuras jerárquicas más útiles para análisis y visualización.

Implementación de funciones curry

reduce() puede implementar el currying, una técnica de programación funcional que transforma una función con múltiples argumentos en una secuencia de funciones:

const curry = (fn) => {
  const arity = fn.length;
  
  return function curried(...args) {
    if (args.length >= arity) {
      return fn(...args);
    }
    
    return (...moreArgs) => {
      return curried(...args, ...moreArgs);
    };
  };
};

// Función normal
const add = (a, b, c) => a + b + c;

// Versión currificada
const curriedAdd = curry(add);

console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6

Este patrón permite crear APIs más flexibles y componibles, facilitando la reutilización de código.

Los patrones avanzados de reduce() demuestran que este método va mucho más allá de simples sumas o promedios. Su capacidad para transformar datos de manera declarativa lo convierte en una herramienta fundamental para implementar algoritmos complejos de manera elegante y mantenible en JavaScript moderno.

Optimización y composición: Estrategias para reducciones eficientes en contextos funcionales

La optimización de operaciones reduce() va más allá de simplemente escribir código que funcione. En entornos de producción con grandes volúmenes de datos o en sistemas con restricciones de rendimiento, implementar estrategias eficientes puede marcar una diferencia significativa. Veamos cómo podemos mejorar nuestras reducciones y componerlas de manera óptima.

Selección del valor inicial adecuado

La elección del valor inicial no solo afecta la corrección del resultado, sino también el rendimiento:

// Menos eficiente: conversión innecesaria en cada iteración
const sum = [1, 2, 3, 4, 5].reduce((acc, num) => Number(acc) + Number(num));

// Más eficiente: tipo de dato consistente desde el inicio
const optimizedSum = [1, 2, 3, 4, 5].reduce((acc, num) => acc + num, 0);

Cuando el valor inicial coincide con el tipo de resultado esperado, evitamos conversiones de tipo innecesarias en cada iteración.

Minimización de creaciones de objetos

En reducciones que manipulan objetos, la creación frecuente de nuevos objetos puede afectar el rendimiento:

// Menos eficiente: crea un nuevo objeto en cada iteración
const inefficient = data.reduce((acc, item) => {
  return { ...acc, [item.id]: item.value };
}, {});

// Más eficiente: modifica el acumulador directamente cuando la inmutabilidad no es crítica
const efficient = data.reduce((acc, item) => {
  acc[item.id] = item.value;
  return acc;
}, {});

Aunque la inmutabilidad es un principio importante en programación funcional, en casos de rendimiento crítico, puede ser necesario balancear pureza y eficiencia.

Reducción temprana de conjuntos de datos

Cuando sea posible, reduce el tamaño del conjunto de datos antes de aplicar operaciones complejas:

// Menos eficiente: procesa todos los elementos
const result = bigArray
  .reduce((acc, item) => {
    if (item.value > 1000) {
      // Cálculos complejos...
      return acc + complexCalculation(item);
    }
    return acc;
  }, 0);

// Más eficiente: filtra primero, reduce después
const optimizedResult = bigArray
  .filter(item => item.value > 1000)
  .reduce((acc, item) => acc + complexCalculation(item), 0);

Este patrón de "filtrar primero, reducir después" puede mejorar significativamente el rendimiento cuando muchos elementos serían ignorados en la reducción.

Uso de reducciones paralelas

Para conjuntos de datos muy grandes, podemos implementar estrategias de reducción paralela:

// Función para dividir un array en chunks
const chunk = (array, size) => {
  const chunks = [];
  for (let i = 0; i < array.length; i += size) {
    chunks.push(array.slice(i, i + size));
  }
  return chunks;
};

// Reducción paralela usando Promise.all
const parallelReduce = async (array, reducer, initialValue, chunkSize = 1000) => {
  // Dividir en chunks para procesamiento paralelo
  const chunks = chunk(array, chunkSize);
  
  // Reducir cada chunk en paralelo
  const results = await Promise.all(
    chunks.map(chunk => {
      return new Promise(resolve => {
        const result = chunk.reduce(reducer, structuredClone(initialValue));
        resolve(result);
      });
    })
  );
  
  // Combinar los resultados parciales
  return results.reduce((acc, result) => {
    // Lógica para combinar resultados parciales
    return combineResults(acc, result);
  }, initialValue);
};

Esta técnica es especialmente útil cuando:

  • El conjunto de datos es muy grande
  • La operación de reducción es computacionalmente intensiva
  • Las reducciones parciales pueden combinarse fácilmente

Memoización de resultados intermedios

Para cálculos repetitivos, la memoización puede evitar recálculos innecesarios:

// Función para crear un reductor memoizado
const memoizedReducer = (reducer) => {
  const cache = new Map();
  
  return (acc, current, index, array) => {
    const cacheKey = JSON.stringify(current);
    
    if (cache.has(cacheKey)) {
      return reducer(acc, cache.get(cacheKey), index, array);
    }
    
    const result = expensiveComputation(current);
    cache.set(cacheKey, result);
    
    return reducer(acc, result, index, array);
  };
};

// Uso
const efficientSum = data.reduce(memoizedReducer((acc, value) => acc + value), 0);

Esta técnica es particularmente valiosa cuando:

  • Los mismos valores aparecen repetidamente en el conjunto de datos
  • El cálculo por elemento es costoso
  • El conjunto de valores únicos es significativamente menor que el total

Composición de reductores

Podemos crear reductores más complejos componiendo reductores más simples:

// Reductores simples y reutilizables
const sumReducer = (acc, val) => acc + val;
const countReducer = (acc, _) => acc + 1;
const maxReducer = (acc, val) => Math.max(acc, val);
const minReducer = (acc, val) => Math.min(acc, val);

// Combinador de reductores
const combineReducers = (reducers) => {
  return (acc, current, index, array) => {
    return Object.keys(reducers).reduce((newAcc, key) => {
      newAcc[key] = reducers[key](acc[key], current, index, array);
      return newAcc;
    }, {});
  };
};

// Uso combinado
const statsReducer = combineReducers({
  sum: sumReducer,
  count: countReducer,
  max: maxReducer,
  min: minReducer
});

const stats = [5, 10, 15, 20, 25].reduce(statsReducer, { sum: 0, count: 0, max: -Infinity, min: Infinity });
console.log(stats); // { sum: 75, count: 5, max: 25, min: 5 }

Este patrón de composición de reductores permite:

  • Mayor reutilización de código
  • Mejor organización y mantenibilidad
  • Cálculo de múltiples estadísticas en una sola pasada

Transductores: composición eficiente de transformaciones

Los transductores representan un concepto avanzado que permite componer transformaciones de manera eficiente:

// Implementación básica de transductores
const map = (f) => (reducer) => (acc, value) => reducer(acc, f(value));
const filter = (predicate) => (reducer) => (acc, value) => 
  predicate(value) ? reducer(acc, value) : acc;

// Función para componer transductores
const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));

// Transductor compuesto
const transducer = compose(
  filter(x => x % 2 === 0),  // Solo pares
  map(x => x * x)            // Elevar al cuadrado
);

// Reductor base
const arrayReducer = (acc, value) => {
  acc.push(value);
  return acc;
};

// Aplicar transductor
const efficientTransform = [1, 2, 3, 4, 5].reduce(
  transducer(arrayReducer),
  []
);

console.log(efficientTransform); // [4, 16]

Los transductores ofrecen varias ventajas:

  • Eliminan la creación de colecciones intermedias
  • Realizan una sola pasada por los datos
  • Combinan múltiples operaciones de manera eficiente

Estrategias para conjuntos de datos infinitos o streams

Para trabajar con flujos potencialmente infinitos, podemos implementar reducciones incrementales:

// Reductor incremental que procesa datos por lotes
class IncrementalReducer {
  constructor(reducer, initialValue) {
    this.reducer = reducer;
    this.accumulator = initialValue;
  }
  
  // Procesa un nuevo lote de datos
  add(batch) {
    this.accumulator = batch.reduce(this.reducer, this.accumulator);
    return this;
  }
  
  // Obtiene el resultado actual
  getResult() {
    return this.accumulator;
  }
}

// Ejemplo de uso con datos que llegan en lotes
const averageCalculator = new IncrementalReducer(
  (acc, value) => ({
    sum: acc.sum + value,
    count: acc.count + 1,
    average: (acc.sum + value) / (acc.count + 1)
  }),
  { sum: 0, count: 0, average: 0 }
);

// Procesar lotes de datos a medida que llegan
averageCalculator.add([1, 2, 3]);
console.log(averageCalculator.getResult()); // { sum: 6, count: 3, average: 2 }

averageCalculator.add([4, 5]);
console.log(averageCalculator.getResult()); // { sum: 15, count: 5, average: 3 }

Esta técnica es especialmente útil para:

  • Procesamiento de streams de datos
  • Análisis en tiempo real
  • Situaciones donde los datos llegan en lotes o incrementalmente

Optimización de casos específicos

A veces, los casos específicos permiten optimizaciones que no serían posibles en el caso general:

// Caso general
const sum = array.reduce((acc, val) => acc + val, 0);

// Optimización para arrays numéricos
const fastSum = Float64Array.from(array).reduce((acc, val) => acc + val, 0);

Algunas optimizaciones específicas incluyen:

  • Uso de arrays tipados para datos numéricos
  • Algoritmos especializados para operaciones comunes (suma, promedio, etc.)
  • Aprovechamiento de propiedades matemáticas (asociatividad, conmutatividad)

Monitoreo y perfilado de rendimiento

Para optimizaciones basadas en datos reales, implementa mediciones de rendimiento:

const measureReducePerformance = (reducer, array, initialValue, label = 'Reduce operation') => {
  console.time(label);
  const result = array.reduce(reducer, initialValue);
  console.timeEnd(label);
  return result;
};

// Comparar diferentes implementaciones
const result1 = measureReducePerformance(
  (acc, val) => ({ ...acc, [val.id]: val }),
  largeArray,
  {},
  'Object spread implementation'
);

const result2 = measureReducePerformance(
  (acc, val) => {
    acc[val.id] = val;
    return acc;
  },
  largeArray,
  {},
  'Direct mutation implementation'
);

El perfilado sistemático permite:

  • Identificar cuellos de botella reales
  • Validar optimizaciones con datos concretos
  • Tomar decisiones informadas sobre compensaciones

Equilibrio entre legibilidad y rendimiento

La optimización no debe sacrificar excesivamente la claridad del código:

// Altamente optimizado pero difícil de entender
const optimizedButObscure = data.reduce((a, c) => (a[0] += c.v, a[1] = Math.max(a[1], c.v), a), [0, -Infinity]);

// Buen equilibrio entre rendimiento y legibilidad
const balancedApproach = data.reduce((acc, item) => {
  acc.sum += item.value;
  acc.max = Math.max(acc.max, item.value);
  return acc;
}, { sum: 0, max: -Infinity });

Las mejores optimizaciones mantienen un equilibrio razonable entre rendimiento y mantenibilidad.

La optimización de reducciones es tanto un arte como una ciencia. Requiere comprender profundamente el problema, las características de los datos y el contexto de ejecución. Al aplicar estas estrategias de manera juiciosa, podemos crear código que no solo sea elegante y funcional, sino también eficiente y escalable.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende JavaScript online

Ejercicios de esta lección Reducción con reduce()

Evalúa tus conocimientos de esta lección Reducción con reduce() con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Funciones flecha

JavaScript
Puzzle

Polimorfismo

JavaScript
Test

Array

JavaScript
Código

Transformación con map()

JavaScript
Test

Gestor de tareas con JavaScript

JavaScript
Proyecto

Manipulación DOM

JavaScript
Test

Funciones

JavaScript
Test

Funciones flecha

JavaScript
Código

Async / Await

JavaScript
Código

Creación y uso de variables

JavaScript
Test

Excepciones

JavaScript
Puzzle

Promises

JavaScript
Código

Funciones cierre (closure)

JavaScript
Test

Herencia

JavaScript
Puzzle

Herencia

JavaScript
Test

Estructuras de control

JavaScript
Código

Selección de elementos DOM

JavaScript
Test

Modificación de elementos DOM

JavaScript
Test

Filtrado con filter() y find()

JavaScript
Test

Funciones cierre (closure)

JavaScript
Puzzle

Funciones

JavaScript
Puzzle

Mapas con Map

JavaScript
Test

Reducción con reduce()

JavaScript
Test

Callbacks

JavaScript
Puzzle

Manipulación DOM

JavaScript
Puzzle

Promises

JavaScript
Test

Async / Await

JavaScript
Test

Eventos del DOM

JavaScript
Puzzle

Async / Await

JavaScript
Puzzle

Promises

JavaScript
Puzzle

Filtrado con filter() y find()

JavaScript
Código

Callbacks

JavaScript
Test

Creación de clases y objetos Restaurante

JavaScript
Código

Reducción con reduce()

JavaScript
Código

Filtrado con filter() y find()

JavaScript
Puzzle

Reducción con reduce()

JavaScript
Puzzle

Conjuntos con Set

JavaScript
Puzzle

Herencia de clases

JavaScript
Código

Eventos del DOM

JavaScript
Test

Clases y objetos

JavaScript
Puzzle

Modificación de elementos DOM

JavaScript
Puzzle

Mapas con Map

JavaScript
Puzzle

Introducción a JavaScript

JavaScript
Test

Funciones

JavaScript
Código

Tipos de datos

JavaScript
Test

Clases y objetos

JavaScript
Test

Array

JavaScript
Test

Conjuntos con Set

JavaScript
Test

Array

JavaScript
Puzzle

Encapsulación

JavaScript
Puzzle

Clases y objetos

JavaScript
Código

Uso de operadores

JavaScript
Puzzle

Uso de operadores

JavaScript
Test

Estructuras de control

JavaScript
Test

Excepciones

JavaScript
Test

Transformación con map()

JavaScript
Puzzle

Funciones flecha

JavaScript
Test

Selección de elementos DOM

JavaScript
Puzzle

Encapsulación

JavaScript
Test

Mapas con Map

JavaScript
Código

Creación y uso de variables

JavaScript
Puzzle

Polimorfismo

JavaScript
Puzzle

Tipos de datos

JavaScript
Puzzle

Estructuras de control

JavaScript
Puzzle

Todas las lecciones de JavaScript

Accede a todas las lecciones de JavaScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a JavaScript y certifícate

Certificados de superación de JavaScript

Supera todos los ejercicios de programación del curso de JavaScript y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  1. Entender el propósito y uso del método reduce() en JavaScript.
  2. Conocer la sintaxis del método reduce() y cómo se aplica a un array.
  3. Aprender a definir funciones reductoras que operen sobre los elementos del array y acumulen resultados.
  4. Comprender el concepto de acumulador y cómo se utiliza en la función reductora.
  5. Saber cómo proporcionar un valor inicial para el acumulador y cómo afecta el proceso de reducción.
  6. Conocer ejemplos prácticos de uso del método reduce() para calcular sumas, productos, promedios y conteos de elementos en un array.
  7. Comprender la flexibilidad y utilidad del método reduce() para adaptarse a diferentes casos de uso en el procesamiento de arrays.