Existem vários artigos excelentes que falam sobre a técnica de otimização chamada memoization. Você pode acessá-los aqui e aqui. O que vamos fazer é dar-lhe uma breve visão geral sobre o que é a memorização. Vamos um pouco mais longe, fazendo uma análise passo a passo do que o código “memoizing” está realmente a fazer, linha a linha. Ao final do artigo, você entenderá completamente a memorização.
Memoization é a prática programática de fazer funções recursivas/iterativas longas correrem muito mais rápido.
Como, você pergunta? Ao fazer o cache dos valores que a função retorna após sua execução inicial.
Quando inserimos o mesmo valor em nossa função memoized, ela retorna o valor armazenado no cache ao invés de executar a função novamente, aumentando assim a performance. O seu programa não precisa mais recalcular cada número para obter um resultado.
Sons incríveis, certo? Bem, o que é ainda melhor é que não é difícil entender o que uma função memoize está fazendo uma vez que você quebra o código.
Aqui está um exemplo de um básico memo
function:
Vamos começar do início…
Vamos retornar o que é chamado de fechamento anônimo. Nosso fechamento anônimo é capaz de herdar qualquer variável ou, neste caso, função passada para memo
. Podemos então jogar com o objeto argumentos que nossa função passada fornece.
Nota lateral: Então, se cada função tem um objeto argumentos, por que não estamos herdando o objeto argumentos de memo
? Isso é porque, como regra, os fechamentos não herdam o objeto argumentos de uma função externa. Usamos esta regra em nosso benefício para jogar com a função que queremos memorizar.
Vamos decompor exatamente o que a memorização está fazendo, olhando para um exemplo muito simples e impraticável. A propósito, você deve copiar/colar este código, ou qualquer código para isso, para realmente ter uma idéia de como ele funciona.
Acho que já entendemos a essência deste exemplo de código. Ele praticamente fala por si mesmo. Vamos dar um pouco mais de carne, olhando para um instantâneo da função memo real:
Agora quebramos o que uma função memo está fazendo, vamos criar uma expressão de função e passar em uma função Fibonacci recursiva para memo
e ver o que acontece.
//Now we'll test our code!
// =======TEST=======
fib(10)//On the first try we need to load//=> loading…//=> loading…//=> loading…//=> loading…//=> loading…//=> 4 loadings later we get...//=> 89// And on the second try…fib(10)//=> 89//No loading! Yaay!//The cool thing about memoizing the recursive Fibonacci algorithm is that once we make a call for the value of the nth number in the series, we are able to store all the previous numbers in the series.//So if we try...
fib(7) //=> 21
//We don't have to recalculate anything.//And if we want to try fib(11)...
fib(11)
//loading...
//=> 144
//Our memoized recursive function already cached fib(1-10),so all it needed to do was to calculate the cached values. // This is what the cache now looks like:
/*
{ '{"0":0}': 1,
'{"0":1}': 1,
'{"0":2}': 2,
'{"0":3}': 3,
'{"0":4}': 5,
'{"0":5}': 8,
'{"0":6}': 13,
'{"0":7}': 21,
'{"0":8}': 34,
'{"0":9}': 55,
'{"0":10}': 89,
'{"0":11}': 144}
*/
Conclusão
Memoização torna-se desmistificada quando se reduz aos pares de valores chave. Tudo o que estamos a fazer é criar um objecto, verificar os valores existentes que correspondem à entrada do utilizador, e armazenar novos pares de valores chave se não existirem no nosso objecto.
Obviamente, armazenar todos estes dados significa que vamos estar a utilizar a memória. É melhor implementar memoization em funções que são puras e envolvem cálculos pesados e repetitivos.
Bónus:
Apenas por diversão, aqui está uma versão funcional do nosso memoizer: