I wanted to try and learn Lisp, but I very quickly gave up. I figured I\'d try again. I\'m looking at Problem 2 on Project Euler - finding the sum of all even Fibonacci numbers
Memoization is a way to cache results to a function, to avoid re-calculating the intermediary results over and over. Memoization basically means the first time you call a function with some args, calculate the answer and return it, and cache that answer; for subsequent calls to a function with those same args, just return the cached value.
In Lisp you can easily use higher-order functions and a macro to transparently memoize a function. Clojure has memoize
as an included standard function. Also look on page 65 of On Lisp for a Common Lisp implementation of memoize
. Here it is in Clojure:
(defn fib-naive [i]
(if (or (= i 1) (= i 2))
1
(+ (fib-naive (- i 1)) (fib-naive (- i 2)))))
(def fib-memo
(memoize (fn [i]
(if (or (= i 1) (= i 2))
1
(+ (fib-memo (- i 1)) (fib-memo (- i 2)))))))
user> (time (fib-naive 30))
"Elapsed time: 455.857987 msecs"
832040
user> (time (fib-memo 30))
"Elapsed time: 0.415264 msecs"
832040
user>
This can still cause a stack overflow if you call it on a large integer. e.g. immediately doing (fib 10000)
will blow the stack because it still has to recurse very deeply (once). But if you prime the cache first, it no longer has to recurse deeply and this can be avoided. Simply doing this first (in Clojure):
(dorun (map fib-memo (range 1 10000)))
will be enough to then let you do (fib 10000)
without problems.
(The specific subject of calculating Fibonacci numbers came up recently on the Clojure mailing list. There's a solution there based on the Lucas numbers which I don't understand in the slightest, but which is supposedly 40 times faster than a naive algorithm.)