Jedna technika, która jest całkiem przydatna w programowaniu dynamicznym, nazywa się z pamięcią nr. Aby uzyskać więcej informacji, zobacz na przykład blog post by Don Syme lub introduction by Matthew Podwysocki.
Chodzi o to, że piszesz (naiwną) funkcję rekursywną, a następnie dodajesz pamięć podręczną przechowującą poprzednie wyniki. Dzięki temu można napisać funkcję w zwykłym stylu funkcjonalnym, ale uzyskać wydajność algorytmu zaimplementowanego za pomocą programowania dynamicznego.
Na przykład, naiwny (nieskuteczny) funkcja obliczania liczby Fibonacciego wygląda następująco:
let rec fibs n =
if n < 1 then 1 else
(fibs (n - 1)) + (fibs (n - 2))
To jest nieefektywne, ponieważ podczas rozmowy fibs 3
, to zadzwoń fibs 1
trzy razy (i wiele więcej razy, jeśli zadzwonisz, na przykład, fibs 6
). Pomysł zapamiętywania polega na tym, że zapisujemy pamięć podręczną, która przechowuje wynik fib 1
i fib 2
itd., Więc powtarzające się połączenia będą po prostu wybierać wstępnie obliczoną wartość z pamięci podręcznej.
rodzajowy funkcja, która robi memoization można zapisać tak:
open System.Collections.Generic
let memoize(f) =
// Create (mutable) cache that is used for storing results of
// for function arguments that were already calculated.
let cache = new Dictionary<_, _>()
(fun x ->
// The returned function first performs a cache lookup
let succ, v = cache.TryGetValue(x)
if succ then v else
// If value was not found, calculate & cache it
let v = f(x)
cache.Add(x, v)
v)
Aby napisać bardziej wydajną funkcję Fibonacciego, możemy teraz nazwać memoize
i nadać mu funkcję, która wykonuje obliczenia jako argument:
let rec fibs = memoize (fun n ->
if n < 1 then 1 else
(fibs (n - 1)) + (fibs (n - 2)))
Należy zauważyć, że jest to wartość rekursywna - treść funkcji wywołuje funkcję pamiętaną fibs
.
Jest to często o wiele łatwiej czytać memoized (rekurencyjne) dynamiczny programowanie w przeciwieństwie do zaprogramowanego, ale może być łatwiejsze do zrozumienia czasu działania wersji proceduralnej. – gradbot