5

I hope it is ok that I am posting this question here even though I have also posted it on other sites. If I have failed to follow proper protocols, I apologize and please let me know right away so I may remove the post and learn my lessons.

I've been a front end developer for over a year now. I went to school to learn web development, and I consider myself a somewhat capable coder when it comes to simple JavaScript stuff. But when it comes to writing any type of Fibonacci function I cannot do it. It is as if I have a piece missing from my brain that would understand how to deal with this simple sequence of numbers. Here is a piece of a working code that I'm pretty sure I got from a John Resig book or somewhere online:

fibonacci = (function () {
    var cache = {};
    return function (n) {

        var cached = cache[n];
        if (cached) return cached;

        if (n <= 1) return n;
        console.log(n);

        return (cache[n] = fibonacci(n - 2) + fibonacci(n - 1));
    };
}());

When I call this function with 10 as the argument, I get this sequence: 10,8,6,4,2,3,5,7,9

Here's what I understand:

fibonnaci is assigned an immediately invoked function expression (or self executing blah blah blah), to which a cache is initiated with whatever argument was passed. If the argument was already in the cache, we just return it and live our lives in everlasting peace. If the argument is 1 or less, that is also the end of the function, everlasting peace ensues once more. But if neither of these conditions exist, then the function returns this statement that makes me feel as if I am just a monkey in a human suit.

What I'd like to do is generate the first 10 fibonnaci numbers in the correct sequence, because if I can do that, then I'll feel like I at least understand it.

So when the first two conditions fail, the code creates a new cache variable and sets it equal to the result of the fibonacci function with whatever argument was passed minus 2, and then it adds the result minus 1.... now for my questions

  • Question 1: How does the function know what fibonacci(n-2) is if fibonacci(n) has never been computed?
  • Question 2: Are recursive functions linear, or what order do they follow?
  • Question 3: If I can't understand this, do I have any hope of becoming a programmer?

Thank you for your time.

After getting past this block, I changed the function a bit to see if I could hold on to the result in a variable and output it, just to see what happens, and I got some really unexpected results.

Here's the change:

fibonacci = (function () {
        var cache = {};
        return function (n) {

            var cached = cache[n];
            if (cached) {
                console.log(cached);
                return cached;
            }

            if (n <= 1) {
                console.log(n);
                return n;
            }
            console.log(n);

            var result = (cache[n] = fibonacci(n - 2) + fibonacci(n - 1));
            console.log(result);
            return result;
        };
    }());

Here's the resulting pattern: 10,8,6,4,2,0,1,1,3,1,1,2,3,5,2,3,5,8,7,5,8,13,21,9,13,21,34,55 Any help with why this happens?

laserface
  • 449
  • 1
  • 4
  • 12
  • Have you tried to debug it? you will be able to see the whole process. – Roy Miloh Sep 24 '13 at 11:40
  • I have... and after getting someone else's response, I now have one more question: My brain thinks linearly. This means that one step happens first, then something else happens next, and so on until there is an end result. But when I see the results of recursive functions, it appears as if they are computing until they meet their final condition, then they explode with all of the other answers that they didn't know the initial questions to. Is this a correct understanding of how they work? – laserface Sep 24 '13 at 11:47
  • @ClasslessAndFree: well, I didn't go into too much details, but JavaScript (well, ECMAScript, [up until ES5 strict mode](http://programmers.stackexchange.com/questions/179863/performance-recursion-vs-iteration-in-javascript/181001#181001)) didn't actually do recursion all that well (no TCO). Effectively, recursive calls were handled as `goto` statements... that's what makes them harder to step through and debug. They're also hard to grasp at first, but once you've got the hang of them, you'll love them – Elias Van Ootegem Sep 24 '13 at 12:19
  • @ClasslessAndFree: where else did you post this question, btw? I'm curious to see other responses... – Elias Van Ootegem Sep 24 '13 at 12:49
  • Why do people keep doing this? Functions like fibonacci are horrible examples of recursion; the recursive solution has absolutely no benefit over the iterative one, unless the language has built-in memoization. – cHao Sep 24 '13 at 13:14
  • Would the Hanoi problem be a better one to focus on, @cHao? – laserface Sep 24 '13 at 13:16
  • @ClasslessAndFree: Yes. That's a case where recursion shines. As is basically any case involving trees rather than lists. Languages that don't by definition have TCO should not be using recursion on lists. – cHao Sep 24 '13 at 13:17
  • @cHao: better yet, the _Collatz conjecture_, which is rather addictive, (`function r(n){ return +(n <= 1) || r(n%2 ? n*3+1 : n/2);}`) – Elias Van Ootegem Sep 24 '13 at 13:20
  • @cHao: noticed that, edited, and made it even less readable :-P – Elias Van Ootegem Sep 24 '13 at 13:22
  • Awesome, Collatz conjecture. This is actually what I'm looking for... other types of number patterns that can be achieved best via recursion.... – laserface Sep 24 '13 at 13:30
  • @EliasVanOotegem, sorry I missed a few of your questions. I only got around to posting it on Reddit. I was going to post more, but this became so active that I didn't need to. Here's the link: [http://redd.it/1n0u1n](http://redd.it/1n0u1n) – laserface Sep 24 '13 at 13:39
  • @EliasVanOotegem, this also is all a result of my discovery of [Project Euler](http://projecteuler.net/). – laserface Sep 24 '13 at 13:42

3 Answers3

3

Well, let's start with what you understand (or say you understand):

fibonnaci is assigned an immediately invoked function expression (or self executing blah blah blah), to which a cache is initiated with whatever argument was passed.

Not quite: fibonnaci is assigned the return value of an IIFE. There's a difference. Inside the IIFE, we se a return function(n) statement. The IIFE is, as it's name suggests, invoked immediatly. the function is created, executed and, once returned, is not being referenced (explicitly) anywhere. The function is returned, is assigned to the variable fibonacci.
This IIFE does create an object literal, called cache. This object resides in the scope of the IIFE, but because of JS's scope scanning(this answer links to others... all of them together explain how JS resolves names to their values), this object is still accessible to the returned function, now assigned to fibonaci.

If the argument was already in the cache, we just return it and live our lives in everlasting peace. If the argument is 1 or less, that is also the end of the function, everlasting peace ensues once more. But [...]

Well, now cache is not created over and over again on each function call (the IIFE is only called once, and that's where cache is created). If the returned function (fibonnaci) changes it, that change to the object will persist in memory. Closure vars, for that is what cache is can be used to hold state between function calls. All the other things you say (n <= 1) is standard recursive function stuff... it's the condition that prevents infinite recursion.

But if neither of these conditions exist, then the function returns this statement that makes me feel as if I am just a monkey in a human suit.

Well, this is actually the fun part. This is where the actual magic happens.
As I've explained, fibonnaci does not reference the IIFE, but rather, it references teh returned function:

function(n)
{
    var cached = cache[n];
    if (cached)
    {
        return cached;
    }
    if (n <= 1)
    {
        return n;
    }
    return (cache[n] = (fibonacci(n-2) + fibonnaci(n-1)));
}

This means, that every occurance of fibonacci can be replaced with the function body. When calling fibonnaci(10), the last (return) statement should be read as:

return (cahce[n] = fibonacci(8) + fibonnaci(9));

Now, as yousee, fibonacci(8) and fibonnaci(9) are called, in the return value. These expression can be written in full, too:

return (cache[10] = (fibonnaci(6) + fibonacci(7)) + (fibonacci(7) + fibonacci(8)));
//which is, actually:
return (cache[10 = ( retrun (cache[6] = fibonacci(4) + fibonacci(5))
                   //since fibonacci(6) cached both fibonacci(5) & fibonacci(6)
                     + return (cache[7] = cache[5] + cache[6])
           + return cache[7] + return (cache[8] = cache[6] + cache[7]

That's how this cache function actually ties in...

We can repeat this until we call fibonnacci(1) or fibonacci(0), because in that case n<=1, and will be returned without any more recursive calls.
Also note that, in writing fibonnaci(9) in full, this actually breaks down into fibonacci(7) + fibonacci(8), both these calls have been made before, and that's why there results were cached. If you don't cache the results of each call, you'll waste time calculating the same thing twice...

BTW: this code is very much "condesed", and works because the specs say that an assignment expression (cache[n] = ...) evaluates to the assigned value, you're returning cache[n], essentially.

Community
  • 1
  • 1
Elias Van Ootegem
  • 74,482
  • 9
  • 111
  • 149
  • Ok, your answer is extremely helpful, because you filled in some serious gaps of my knowledge. I got one more though... when I call fibonacci(10), am I returning the result of 10 invocations of the same function? – laserface Sep 24 '13 at 12:08
  • @ClasslessAndFree: I posted my answer too quickly, I've expanded a bit on who you should interpret the `fibonnaci(n-2) + fibonnaci(n-1)` bit. Basically, n is 10 to begin with, then you repeat the same function with `n-2 ==> 10 -2 === 8`, which then is broken up into 2 calls (8-2 and 8-1), and so on, until you get to `fibonnaci(1)`, which doesn't call itself again, but just returns 1, this stops the recursion... Also added a link please do check the links at the bottom of the linked answer (think of it as a recursive link :-P) – Elias Van Ootegem Sep 24 '13 at 12:11
  • Ok thanks! I also edited my inital question because I made a few changes. Do you have any idea why I got that result after I made the change? It's not a huge deal, it just seemed really strange. – laserface Sep 24 '13 at 12:24
  • @ClasslessAndFree: The logs you're getting are perfectly reasonable: `result` will be assigned _the sum_ of the row, as computed thusfar (that's an incrementing sequence, following [this pattern](http://meta.wikimedia.org/wiki/Template:Fibonacci_row)). Since you're calling `fib(10)`, the last value logged is, indeed 55. Because you also added a `console.log(cached)` statement, you're also logging everything (after the first call) double: 10 is broken into recusive calls with 8 and 9, 9 is broken into 8 and 7, while 8 is broken into 7 and 6. so the function is called with each number _twice_ – Elias Van Ootegem Sep 24 '13 at 12:31
  • let's try to write the calls in full: `fibonacci(10)`: = `f(8) + f(9) = f(6) + f(7) + f(7) + f(8) = f(4) + f(4) + f(5) + f(5) + f(6) + f(5) + f(6) + f(6) + f(7) = ...` ok, I can't be bothered... but basically, you get the point... some of these calls you see several times: because each call can be broken up into _the sum of the return values of the same function for the 2 preceding integers_ f(10) = (f(8) + (f(9)), and `f(8) = (f(6) + f(7))`, but because recursive functions for `f(8)` and both `f(9)` will both process `0,1,2,4,5,6,7`, you'll log this twice... – Elias Van Ootegem Sep 24 '13 at 12:41
  • Ok... this is helping. I'm making a google drawing just to help myself with it and if it it's any good I'll try posting it. It's such a simple concept that if I don't completely know it in and out, I won't be happy. – laserface Sep 24 '13 at 12:58
1

Great questions. Thinking in recursive terms is tricky. If you try to grasp all the process you probably will fail miserably. I remembered to be frustrated as you are for not understanding the recursive solution to the Hanoi towers problem. I tried to trace on paper the sequence of steps but it was not of any help to understand the magic of what was going on.

What it worked for me was to think that the recursive function is a kind of "oracle" that knows the return value of the function fib(i) for any value of i < n. If the oracle knows fib(n-1) and fib(n-2), then it just has to give the instructions to calculate fib(n) from these known values. As a consequence we can say that the oracle, this function, also knows the value of fib(n).

A warning: there is a tricky part in all recursive functions, we need at least one not recursive value known at start the process, otherwise we will have an infinite recursion. In fibonacci example, these values are: fib(0) = 0 and fib(1) = 1

Your example is a bit more complex than that, as is using memoization, a technique to store the values of fib(n) in a cache to avoid recalculating them. In this case, this cache is an sparse array (array with "holes") that stores in its position i the value of fib(i) the first time it is calculated. This is a way to avoid repeating a costly computation the next time the same value of fib(i) is requested.

Aswering your questions:

  1. fib(n-2) doesn't need to know the value of fib(n) to be calculated, what it needs are the values of fib(n-3) and fib(n-4). The only thing to do is to invoke them in ordert to ask the "oracle" their values.
  2. It depends, there are linear recursive functions and tree shaped recursive functions, depending of how many other values they use. In this case you have a tree shaped invocation order. (Actually memoization makes it more complex and I would represent it as as a directed acyclical graph instead of a tree, but it is not relevant for the discussion).
  3. Keep thinking on it, one day you will have an "aha" moment and then recursive functions will become evident to you.

If you still want to trace the execution of this function maybe it would help to refactor your calculation to an equivalent way as it makes more evident the order of execution:

// var result = (cache[n] = fibonacci(n - 2) + fibonacci(n - 1));

var fib2 = fibonacci(n - 2); 
var fib1 = fibonacci(n - 1);

var result = fib2 + fib1;
cache[n] = result;
Manolo Santos
  • 1,915
  • 1
  • 14
  • 25
  • I have to say, your answer to Question #2 just opened up a new world for me.... This is what I'm really driving at and is what interests (as well as frustrates) me the most. I googled Directed acyclic graph just to see a picture, and this is what my output seems to correspond to. – laserface Sep 24 '13 at 13:11
  • Good answer, but I must disagree with the _"acyclical graph"_ bit. regardless of wether the return value was computed or came from the `cache` object, the first recursive call to any of the `n-x` values will result in another sequence of calls, or a return of 1 || 0, and is therefore a regular tree. It's because the function is called twice on each recursion, you get a heap of trees hence => [Fibonacci heap](http://en.wikipedia.org/wiki/Fibonacci_heap) is listed as a tree data-structure – Elias Van Ootegem Sep 24 '13 at 13:29
  • Ok... thanks for setting me straight on that one. I had just never heard of that before and started getting off my main topic – laserface Sep 24 '13 at 13:33
0

I know the question is a bit old, and the answers are helpful. I was doing this exercise in GoLang, and thought how I would write in Javascript, and use this answer to refresh my mind as well. I see your code has a cache variable to store the value of the fib(n-2) + fib(n-1) iteration. If you're doing recursive, you don't need a variable to store the result, because each time the function is called it returns a number, and these numbers are added up to the first function call.

function fib(n) {
    if (n<=1) return n;
    return fib(n - 1) + fib(n - 2);
}

To see why you don't need the cache variable is go through each function call, and start calculate the values once n equal 1 or 0.

for example:

iteration 1)

fib(3) {
   return fib(3-1) + f(3-2)   
}
---------------------------    
iteration 2)

fib(3-1) {
   return fib(2 - 1) + fib(2-2)
}

iteration 3)

fib(3-2) {
   return 1
}    
---------------------------

iteration 4)

fib(2-1) {
   return 1
}


iteration 5)
fib(2-2) {
   return 0
}
----------------------

if you calculate it the return value backward from iteration 5)

5) 0
4) 1
3) 1
2) 1 <== 4) + 5)  = 1 + 0  
1) 2  <== 2) + 3)  = 1 + 1

so fib(3) is 2

Lance
  • 865
  • 8
  • 19