It is possible to improve \"raw\" Fibonacci recursive procedure
Fib[n_] := If[n < 2, n, Fib[n - 1] + Fib[n - 2]]
with
The solution provided by rgrinberg can be generalized so that we can memoize any function. I am going to use associative lists instead of hashtables. But it does not really matter, you can easily convert all my examples to use hashtables.
First, here is a function memo
which takes another function and returns its memoized version. It is what nlucaroni suggested in one of the comments:
let memo f =
let m = ref [] in
fun x ->
try
List.assoc x !m
with
Not_found ->
let y = f x in
m := (x, y) :: !m ;
y
The function memo f
keeps a list m
of results computed so far. When asked to compute f x
it first checks m
to see if f x
has been computed already. If yes, it returns the result, otherwise it actually computes f x
, stores the result in m
, and returns it.
There is a problem with the above memo
in case f
is recursive. Once memo
calls f
to compute f x
, any recursive calls made by f
will not be intercepted by memo
. To solve this problem we need to do two things:
In the definition of such a recursive f
we need to substitute recursive calls with calls to a function "to be provided later" (this will be the memoized version of f
).
In memo f
we need to provide f
with the promised "function which you should call when you want to make a recursive call".
This leads to the following solution:
let memo_rec f =
let m = ref [] in
let rec g x =
try
List.assoc x !m
with
Not_found ->
let y = f g x in
m := (x, y) :: !m ;
y
in
g
To demonstrate how this works, let us memoize the naive Fibonacci function. We need to write it so that it accepts an extra argument, which I will call self
. This argument is what the function should use instead of recursively calling itself:
let fib self = function
0 -> 1
| 1 -> 1
| n -> self (n - 1) + self (n - 2)
Now to get the memoized fib
, we compute
let fib_memoized = memo_rec fib
You are welcome to try it out to see that fib_memoized 50
returns instantly. (This is not so for memo f
where f
is the usual naive recursive definition.)
You pretty much do what the mathematica version does but manually:
let rec fib =
let cache = Hashtbl.create 10 in
begin fun n ->
try Hashtbl.find cache n
with Not_found -> begin
if n < 2 then n
else
let f = fib (n-1) + fib (n-2) in
Hashtbl.add cache n f; f
end
end
Here I choose a hashtable to store already computed results instead of recomputing them. Note that you should still beware of integer overflow since we are using a normal and not a big int.