Reputation: 457
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
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?
Upvotes: 5
Views: 2460
Reputation: 885
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
Upvotes: 0
Reputation: 76395
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.
Upvotes: 3
Reputation: 1913
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:
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.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;
Upvotes: 1