Hi myty,
Thanks for the explanation!
The link to the section on memoization was also very interesting.
So, it seems then that memoization is a form of caching that is used to reduce duplication of effort by your program, as previously calculated results don’t need to be recalculated.
Just to help this sink into my brain, I wrote a small script to compare the performance of a memoized function against that of the same non-memoized version.
The results were quite interesting:
var f = [],
timesCalledMemoizedFactorial = 0,
timesCalledrecursiveFactorial = 0;
function memoizedFactorial(n){
timesCalledMemoizedFactorial++;
if(n==1){
return 1;
} else {
if(f[n]>0){
return f[n];
} else{
return f[n] = memoizedFactorial(n-1) * n;
}
}
};
function recursiveFactorial(n){
timesCalledrecursiveFactorial++;
if (n <= 1){
return 1;
} else {
return recursiveFactorial(n-1) * n;
}
}
//Calculate the factorials 1 - 10)
for(var i=1; i<=100; i ++){
memoizedFactorial(i);
recursiveFactorial(i);
}
console.log("memoizedFactorial was called " + timesCalledMemoizedFactorial + " times.");
console.log("recursiveFactorial was called " + timesCalledrecursiveFactorial + " times.");
Output:
memoizedFactorial was called 199 times.
recursiveFactorial was called 5050 times.
Cool!
Anyway, memoized or not, you can still blow the call stack by passing it too large a number:
var f=[];
function factorial (n) {
return n == 1 ? 1 : f[n] > 0 ? f[n] : f[n] = factorial(n-1) * n;
}
console.log(factorial(30000));
Uncaught RangeError: Maximum call stack size exceeded
Whereas in Ruby, as of version 1.9.2 you can handle this with tail recursion:
RubyVM::InstructionSequence.compile_option = {
:tailcall_optimization => true,
:trace_instruction => false
}
eval <<EOF
def factorial(n, result = 1)
if n == 1
result
else
factorial(n - 1, n * result)
end
end
EOF
p factorial(30_000)
Output (in about 1.5 seconds):
27595372462193845993799421664254627839807620445293309855296350368000758688503605 ... 000
Although Ruby 1.9.2 actually ships with native support for tail recursion, it’s disabled by default and you have to either enable it through a compile-flag or by specifying the compile options at runtime (as above).
JavaScript does indeed kick a**e, but saying that, you gotta love Ruby!