L60 - Memoización
🧠 Concepto
Section titled “🧠 Concepto”La memoización es una técnica de optimización que consiste en cachear los resultados de funciones costosas para evitar recalcularlos cuando se invocan con los mismos argumentos. Es una aplicación práctica de closures y programación funcional.
💻 Implementación manual básica
Section titled “💻 Implementación manual básica”function memoizar(fn) { const cache = {}; // Closure: el cache persiste entre llamadas
return function(arg) { if (cache[arg] !== undefined) { console.log(`Cache hit: ${arg} → ${cache[arg]}`); return cache[arg]; }
console.log(`Calculando: ${arg}`); const resultado = fn(arg); cache[arg] = resultado; return resultado; };}
// Función costosa (simulada)function lenta(numero) { // Simular operación pesada for (let i = 0; i < 1e7; i++) {} return numero * 2;}
const rapida = memoizar(lenta);
console.log(rapida(5)); // Calcula y cacheaconsole.log(rapida(5)); // Cache hit, instantáneoconsole.log(rapida(10)); // Calcula y cacheaconsole.log(rapida(5)); // Cache hit🎯 Fibonacci con y sin memoización
Section titled “🎯 Fibonacci con y sin memoización”Sin memoización: O(2ⁿ)
Section titled “Sin memoización: O(2ⁿ)”function fib(n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2);}
console.time('fib(40)');console.log(fib(40)); // ~1 segundoconsole.timeEnd('fib(40)');El problema es que fib(5) llama a fib(4) y fib(3), que a su vez llaman a los mismos subproblemas múltiples veces. El árbol de llamadas crece exponencialmente.
Con memoización: O(n)
Section titled “Con memoización: O(n)”function fibMemoizada(n, cache = {}) { if (cache[n] !== undefined) return cache[n]; if (n <= 1) return n;
cache[n] = fibMemoizada(n - 1, cache) + fibMemoizada(n - 2, cache); return cache[n];}
console.time('fibMemo(1000)');console.log(fibMemoizada(1000)); // Instantáneo (4.3e208)console.timeEnd('fibMemo(1000)');De O(2ⁿ) a O(n). La diferencia es astronómica: fib(100) con memoización es trivial, sin ella requeriría más cálculos que átomos en el universo.
💻 Versión genérica con Map
Section titled “💻 Versión genérica con Map”function memoizar(fn) { const cache = new Map();
return function(...args) { const clave = JSON.stringify(args); // Serializar argumentos múltiples
if (cache.has(clave)) { console.log(`Cache hit: ${clave}`); return cache.get(clave); }
console.log(`Calculando: ${clave}`); const resultado = fn.apply(this, args); cache.set(clave, resultado); return resultado; };}
const fibMemo = memoizar(function fib(n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2);});
console.log(fibMemo(50)); // 12586269025📝 Memoización con closures privadas
Section titled “📝 Memoización con closures privadas”function crearCalculadora() { const cache = new Map();
return { factorial(n) { if (cache.has(n)) return cache.get(n); if (n <= 1) return 1; const resultado = n * this.factorial(n - 1); cache.set(n, resultado); return resultado; }, limpiarCache() { cache.clear(); }, tamanoCache() { return cache.size; } };}
const calc = crearCalculadora();console.log(calc.factorial(10)); // 3628800console.log(calc.factorial(10)); // Desde cacheconsole.log(calc.tamanoCache()); // 1 (solo guardó el resultado de 10)🧠 Memoización con WeakMap (para objetos)
Section titled “🧠 Memoización con WeakMap (para objetos)”Cuando los argumentos son objetos, usar Map puede causar memory leaks porque las referencias nunca se liberan. WeakMap permite que los objetos sean recolectados:
function memoizarConWeakMap(fn) { const cache = new WeakMap();
return function(obj) { if (cache.has(obj)) return cache.get(obj); const resultado = fn(obj); cache.set(obj, resultado); return resultado; };}
function procesoPesado(data) { // Simular procesamiento costoso return { ...data, procesado: true, timestamp: Date.now() };}
const memoPesado = memoizarConWeakMap(procesoPesado);
const obj = { id: 1, nombre: 'test' };console.log(memoPesado(obj)); // Calculaconsole.log(memoPesado(obj)); // Cache// Si obj se vuelve null, WeakMap permite que se libere la memoria🎯 Casos de uso reales
Section titled “🎯 Casos de uso reales”- Componentes React:
React.memo,useMemo,useCallback. - Peticiones API: cachear respuestas para evitar llamadas repetidas.
- Procesamiento de datos: transformaciones pesadas sobre los mismos datos.
- Algoritmos recursivos: Fibonacci, torres de Hanoi, rutas en grafos.
- Renderizado: evitar volver a calcular estilos o layouts.
⚠️ Cuándo NO memoizar
Section titled “⚠️ Cuándo NO memoizar”- Funciones impuras: si el resultado depende de algo externo (fecha, random, input del usuario).
- Funciones que se llaman una sola vez: el overhead de cachear no compensa.
- Argumentos muy variados: si casi nunca se repiten argumentos, el cache será inútil.
- Funciones muy rápidas: el overhead de
Map.set/getpuede ser mayor que recalcular.
// ❌ No memoizar: función impurafunction obtenerFecha() { return new Date().toISOString();}
// ❌ No memoizar: función con efectos secundariosfunction enviarPeticion(url) { return fetch(url); // Cada llamada debe ser real}🎯 Regla de oro: memoiza solo funciones puras, costosas y con argumentos repetidos. Si no cumple las tres, probablemente no vale la pena.