Reputation: 6908
I'm working on Project Euler number 5. I've not Googled, as that will typically lead to a SO with the answer. So, this is what I've got:
private int Euler5(int dividend, int divisor)
{
if (divisor < 21)
{
// if it equals zero, move to the next divisor
if (dividend % divisor == 0)
{
divisor++;
return Euler5(dividend, divisor);
}
else
{
dividend++;
return Euler5(dividend, 1); // move to the dividend
}
}
// oh hey, the divisor is above 20, so what's the dividend
return dividend;
}
In my mind, this makes sense. Yet VS2012 is giving me a StackOverFlowException suggesting I make sure I'm not in an infinite loop or using recursion. My question is, why is this an infinite loop? I've a feeling I'm missing something completely silly.
EDIT
Since people seem to keep posting them, I'll reiterate the fact that I didn't use Google for fear of stumbling on the answer. I don't want the answer to the problem. I only wanted to know why I was getting the exception.
Upvotes: 5
Views: 180
Reputation: 4591
+1 for Jason's answer, which clearly explains the problem.
Now for some solution! I know at least of three ways to remove recursion from an algorithm:
An algorithm is tail-recursive if all its recursive calls are tail-calls, which means they are the last thing done before returning. If it's unclear to you, lookup better examples with Google.
Such algorithms can easily be transformed by adjusting the parameters and using a goto rather than a real call. Look at your example again:
private int Euler5(int dividend, int divisor)
{
tail_call:
if (divisor < 21)
{
// if it equals zero, move to the next divisor
if (dividend % divisor == 0)
{
divisor++;
goto tail_call; // return Eular5(dividend, divisor);
}
else
{
dividend++;
// return Eular5(dividend, 1); // move to the dividend
divisor = 1;
goto tail_call;
}
}
// oh hey, the divisor is above 20, so what's the dividend
return dividend;
}
Oh hey! It's exactly the same function, but with a fixed stack size (there's no call, only jumps). Now some would say: "Ugh... gotos! They are evil! Die goto, die!". I'd say this is one of the few legitimate uses. After all if your compiler was smart enough, it would do the tail-call optimization itself (F# actually does, C# does not, the JIT might do it on x64, not on x86).
But for those people I'd say: look a little better. Because there is a goto at the end of each if/else branch, I can move it outside of the "if" completely. Now I have something like "start: if (X) { Y(); goto start; }" Think about it, it's just a "while(X) Y()" loop. So you just found the iterative version of your function:
private int Euler5(int dividend, int divisor)
{
while (divisor < 21)
{
// if it equals zero, move to the next divisor
if (dividend % divisor == 0)
{
divisor++;
}
else
{
dividend++;
divisor = 1;
}
}
// oh hey, the divisor is above 20, so what's the dividend
return dividend;
}
Nice!
Upvotes: 1
Reputation: 241591
Of course this kind of logic is going to blow the stack. Think about this, if you were to implement this logic to solve the problem of finding the smallest number evenly divisible by 1--10, you'd be at least 2520 calls deep in the stack, per the problem statement:
2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder.
For 1--20, the answer is obviously much larger and it's not surprising at all that you're blowing the stack. You should find a non-recursive solution.
My question is, why is this an infinite loop?
It's not. The stack is limited in size. You're making too many recursive calls and eventually breaching the maximum stack size.
I've a feeling I'm missing something completely silly.
You came to the right place.
Upvotes: 10