Una técnica que es bastante útil para la programación dinámica se llama memoria. Para obtener más información, consulte, por ejemplo, blog post by Don Syme o introduction by Matthew Podwysocki.
La idea es que escriba (ingenua) la función recursiva y luego agregue el caché que almacena los resultados anteriores. Esto le permite escribir la función en un estilo funcional habitual, pero obtener el rendimiento del algoritmo implementado mediante programación dinámica.
Por ejemplo, una función ingenua (ineficaz) para el cálculo del número de Fibonacci es así:
let rec fibs n =
if n < 1 then 1 else
(fibs (n - 1)) + (fibs (n - 2))
Esto es ineficiente, porque cuando se llama a fibs 3
, se llamará fibs 1
tres veces (y muchas veces más si llame, por ejemplo, fibs 6
). La idea detrás de la memorización es que escribimos un caché que almacena el resultado de fib 1
y fib 2
, y así sucesivamente, por lo que las llamadas repetidas simplemente seleccionarán el valor precalculado de la caché.
una función genérica que hace el memoization se puede escribir así:
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)
Para escribir la función de Fibonacci más eficiente, ahora podemos llamar memoize
y darle la función que realiza el cálculo como un argumento:
let rec fibs = memoize (fun n ->
if n < 1 then 1 else
(fibs (n - 1)) + (fibs (n - 2)))
Tenga en cuenta que este es un valor recursivo: el cuerpo de la función llama a la función fibs
memorizada.
A menudo es mucho más fácil de leer un memoized (recursivo) dinámico problema de programación vs. uno procesal programado, sin embargo, puede ser más fácil razonar el tiempo de ejecución de la versión de procedimiento. – gradbot