Kolekcje w Praktyce: Memoizacja.

Javascript
Priorytet: Normalny Szkic

Zadanie 5.4: Praktyczne Zastosowanie Map (Cache)

Wstęp

Wyobraź sobie funkcję, która wykonuje bardzo skomplikowane obliczenia (np. liczy silnię z ogromnej liczby albo pobiera dane z wolnego serwera). Jeśli wywołamy ją 10 razy z tym samym argumentem, marnujemy zasoby procesora na liczenie tego samego. Rozwiązaniem jest Memoizacja (Caching) – zapamiętanie wyniku dla danego argumentu, aby następnym razem zwrócić go natychmiast z pamięci.

Cel zadania

Stworzenie funkcji wyższego rzędu (dekoratora), która dodaje mechanizm cache do dowolnej innej funkcji, wykorzystując Map.

Wymagania techniczne

  1. Stworzenie "wolnej" funkcji do testów.
  2. Zaimplementowanie funkcji memoize(fn).
  3. Użycie Map do przechowywania pary Argument -> Wynik.

Kroki do wykonania

1. Problem: Wolna funkcja

Stwórz plik cache.js i symuluj wolną funkcję.

function slowDouble(num) {
    console.log(`Liczenie dłuuugo dla ${num}...`);
    // Symulacja obciążenia (pętla)
    for(let i=0; i<1e8; i++) {} 
    return num * 2;
}

console.log(slowDouble(5));
console.log(slowDouble(5)); // Znowu liczy długo...

2. Rozwiązanie: Funkcja memoize

Tworzymy funkcję, która przyjmuje funkcję fn i zwraca nową, "ulepszoną" funkcję.

function memoize(fn) {
    const cache = new Map(); // Tutaj trzymamy wyniki

    return function(arg) {
        // 1. Sprawdź czy wynik już jest w cache
        if (cache.has(arg)) {
            console.log("Pobieranie z cache dla:", arg);
            return cache.get(arg);
        }

        // 2. Jeśli nie ma, wykonaj funkcję
        const result = fn(arg);

        // 3. Zapisz wynik w cache na przyszłość
        cache.set(arg, result);

        return result;
    };
}

3. Testowanie

Teraz "opakujmy" naszą wolną funkcję.

const fastDouble = memoize(slowDouble);

console.log("Pierwsze wywołanie:");
console.log(fastDouble(5)); // Liczy długo...

console.log("Drugie wywołanie:");
console.log(fastDouble(5)); // Błyskawicznie z cache!

console.log("Inny argument:");
console.log(fastDouble(10)); // Liczy długo...
console.log(fastDouble(10)); // Błyskawicznie z cache!

4. Zadanie praktyczne

Stwórz kalkulator silni (factorial), który używa rekurencji.

  1. Napisz zwykłą funkcję factorial(n).
  2. Użyj funkcji memoize, aby stworzyć cachedFactorial.
  3. Oblicz cachedFactorial(5), a potem cachedFactorial(6).
    • Zauważ, że cachedFactorial(6) to 6 * cachedFactorial(5). Czy Twój cache potrafi to wykorzystać? (W podstawowej wersji memoize powyżej - nie, ponieważ cache jest zewnętrzny. Aby to działało idealnie rekurencyjnie, funkcja musiałaby wywoływać samą siebie już w wersji zcache'owanej, ale to zadanie na poziom expert!).
  4. Dla celów tego zadania wystarczy, że po obliczeniu cachedFactorial(5) ponowne wywołanie cachedFactorial(5) będzie szybkie.

[!TIP] Map jest idealna do cache'owania, bo klucze są unikalne i dostęp do nich jest bardzo szybki (złożoność O(1)).

[!IMPORTANT] Commit: Zadanie 5.4 - Cache System.