3
votes

SPOILER ALERT: Don't look at this if you are trying to solve Project Euler's problem #2 on your own w/o looking at the answer.

I've already completed problem #2 of Project Euler (computing the sum of all even Fibonacci(n) numbers less than or equal to 4 million) - I'm using these problems to practice my C/Ada skills, to revisit my Common Lisp and to learn Haskell.

When I'm trying to re-implement my solution in Haskell, I'm running into a problem. In classical fashion, it is calculating the wrong answer. However, I think my Haskell implementation resembles my Common Lisp one (which does produce the correct result.)

The algorithm only computes the Fibonacci number for n where n % 3 == 0. This is because

  1. We need to sum only the even-valued Fibonacci numbers F(n) <= 4M, and
  2. (n % 3 == 0) <--> (F(n) % 2 == 0)

Here is the Haskell implementation:

uber_bound      = 40000000  -- Upper bound (exclusive) for fibonacci values
expected        = 4613732   -- the correct answer

-- The implementation amenable for tail-recursion optimization
fibonacci :: Int -> Int
fibonacci n = __fibs (abs n) 0 1
  where
    -- The auxiliary, tail-recursive fibs function
    __fibs    :: Int -> Int -> Int -> Int
    __fibs 0 f1 f2 = f1 -- the stopping case
    __fibs n f1 f2 = __fibs (n - 1) f2 (f1 + f2)

-- NOT working. It computes 19544084 when it should compute 4613732
find_solution :: Int
find_solution = sum_fibs 0
  where
    sum_fibs :: Int -> Int
    sum_fibs n = 
      if fibs > uber_bound
        then
          0 -- stopping condition
        else
          -- remember, (n % 3 == 0) <--> (fib(n) % 2 == 0)
          -- so, seek the next even fibs by looking at the
          -- the next n = n@pre + 3
          fibs + sum_fibs (n + 3)
      where
        fibs = fibonacci n

actual = find_solution

problem_2 = (expected == actual)

The thing is printing 19544084 when the correct answer is 4613732. My Common Lisp solution (which does work) is below.

I thought my Haskell implementation would resemble it, but I'm missing something.

(set `expected 4613732)  ;; the correct answer

;; tail-recursive fibonacci
(defun fibonacci (n)
  (labels 
    ( ;; define an auxiliary fibs for tail recursion optimization
      (__fibs (n f1 f2)
        (if (<= n 0)
          f1 ;; the stopping condition
          (__fibs 
            (- n 1) ;; decrement to ensure a stopping condition 
            f2 
            (+ f1 f2))))
    ) ;; end tail_rec_fibs auxiliary
   (__fibs n 0 1)
  );; end labels
) ;; end fibonacci

(defun sum_fibs(seed)
  (let*
    ((f (fibonacci seed)))
    (if (> f 4000000)
      0
    ;; else
      (+ f (sum_fibs (+ 3 seed)))
    );; end if
  );; end of let
);; end of sum-fibs

(defun solution () (sum_fibs 0))

(defun problem_2 ()
  (let
    (
     (actual (solution))
    )
    (format t "expected:~d actual:~d" expected actual)
    (= expected actual)
  )
) ;; end of problem_2 defun

What on Earth am I doing wrong? Granted that I'm using a Neanderthal approach to learning Haskell (I'm currently just re-implementing my Lisp on Haskell as opposed to learning idiomatic Haskell, the coding/problem solving approach that goes with the language.)

I'm not looking for somebody to just give me the solution (this is not a can i haz the codez?). I'm looking more, but much more for an explanation of what I'm missing in my Haskell program. Where is the bug, or am I missing a very specific Haskell idiosyncratic evaluation/pattern matching thing? Thanks.

2

2 Answers

7
votes

You have a typo

uber_bound = 40000000

when it should be

uber_bound = 4000000

Just for reference, a more idiomatic solution would be to generate a list of all the Fibonacci numbers (lazy evaluation is really useful for this), and then use takeWhile, filter and sum.

This will be more efficient too, since tail recursion is rarely helpful in Haskell (lazy evaluation gets in the way), and since the element of the list are shared (if the list is define appropriately) each Fibonacci number is computed exactly once.

1
votes

deleted, wasn't supposed to give a spoiler. dbaupp's suggestions are good. There's a well known expression using zipWith but I think it's too clever--there are more straightforward ways.