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
Simple, efficient way of creating a list of fibonacci numbers:
(defun fibs (n &optional (a 1) (b 1))
(loop repeat n
collect (shiftf a b (+ a b))))
(shiftf) takes any number of places and finally a value. Each place is set to the value of the next variable, with the last variable taking the value that comes after it. It returns the value of the first place. In other words, it shifts all the values left by one.
However, you don't need the full list (you only need the evens) and you don't need the list at all (you only need the sum), so this can be worked into the function directly. Every third fibonacci number is even, so...
(defun euler-2 (limit &optional (a 1) (b 1))
(loop for x upfrom 1
until (> a limit)
if (zerop (mod x 3))
sum a
do (shiftf a b (+ a b))))
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.)