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
My understanding of "the spirit of lisp" is to detach yourself from any fixed, dogmatic, stuckup idea of the spirit of lisp, and use the lisp construct that most closely reflects the structure of computation required to solve your problem. For example,
(defun euler2 (&optional (n 4000000))
(do ((i 1 j)
(j 2 (+ i j))
(k 0))
((<= n i) k)
(when (evenp i) (incf k i))))
If you insist on recursion, here is another way:
(defun euler2 (&optional (n 4000000))
(labels ((fn (i j k)
(if (<= n i) k (fn j (+ i j) (if (oddp i) k (+ k i))))))
(fn 1 2 0)))
The way to solve this is to work bottom-up, generating each Fibonnaci term one-by-one, and adding it to the sum if it's even, and stopping once we reach the 4 million threshold. My LISP is rusty, so here it is in psuedocode:
one_prior = 1
two_prior = 1
curr = 2
sum = 0
while curr < 4000000000
if curr % 2 == 0
sum = sum + curr
two_prior = one_prior
one_prior = curr
curr = one_prior + two_prior
danio's answer will help greatly with the performance questions.
Here's a short critic of your style:
(defun fib(i)
(if (= i 1) ;//Could rewrite this as a case statement
1
(if (= i 2)
1
(+ (fib (- i 1)) (fib (- i 2)))
)
)
)
Refactor nested IFs into a COND.
Don't put parentheses on a line by themselves.
(defun solve(i) (let ((f (fib i))) ;//Store result in local variable (print f) ;//For debugging (if (Using ZEROP is clearer.
(+ f (solve (+ i 1))) ;//add number (solve (+ i 1)) ;//Don'tWhy do you put those // in? A semicolon followed by a space is enough.
) ) ) ) (print (solve 1))
You last PRINT statement makes me a bit suspicious. Are you running this program from a file or from the REPL? If you do the former then you should consider doing the latter. If you do the latter you can just say (solve 1) to get the result.
Here is a memoised version. In this case, since you have to compute each fibonacci number until you find one more than four million, using closed form solutions would no do much good.
This approach creates a lexical environment via let
; create a dictionary fib-table
and a function fib-memoed
in that environment. The end product of this is fib-table
is accesible from fib-memoed
but no where else.
Now the kicker: since we have to compute fib
for sequential values until some condition is met, we start the solver at fib 1
. From here on, computing the next fib
number entails at most 2 dictionary lookups and one addition: O(1)
BOOM!
;; memoised fib function: make a hash table, then create
;; the fib function in the lexical scope of the hash table
(let ((fib-table (make-hash-table)))
(setf (gethash 0 fib-table) 1)
(setf (gethash 1 fib-table) 1)
(defun fib-memoed (n)
(let ((x (gethash n fib-table)))
(cond ((not (null x))
x)
(t
(let ((y (+ (fib-memoed (- n 1))
(fib-memoed (- n 2)))))
(setf (gethash n fib-table) y)
y))))))
(defun solve ()
(let ((maxval 4000000))
(labels ((aux (i acc)
(let ((x (fib-memoed i)))
(cond ((> x maxval) acc)
((evenp x)
(aux (1+ i) (+ x acc)))
(t (aux (1+ i) acc))))))
(aux 1 0))))
http://fare.tunes.org/files/fun/fibonacci.lisp has a walk through of solving fibonacci, gradually improving the time and memory performance of the implementation.
See both the videos and text located at: http://groups.csail.mit.edu/mac/classes/6.001/abelson-sussman-lectures/