return
only exits that specific method call. It doesn't abort all of the times sequenceCreator()
was called.
In this case, that should be okay, because it returns to this line:
return sequenceCreator(nextVal, numOfSteps);
which will in turn return again to it's caller, and so on, until you finally resolve all of the recursive calls.
But I might write the method like this, instead:
public int sequenceCreator(int currentVal)
{
if (currentVal == 1) return 1;
if (currentVal % 2 == 0)
{
return 1 + sequenceCreator(currentVal / 2);
}
else // (currentVal % 2 > 0)
{
return 1 + sequenceCreator(currentVal * 3 + 1);
}
}
That code produces the same result for the same input, but with less code that's easier to understand, and without needing to pass extra state between method calls.
For fun, we can reduce the code further with a ternary operator (but I don't recommend using this version, as imo readability suffers):
public int sequenceCreator(int currentVal)
{
if (currentVal == 1) return 1;
return 1 + sequenceCreatetor(currentValue % 2 == 0? currentVal / 2 : currentVal * 3 + 1);
}
I show this version to illustrate why recursion is used. Recursive problems are, at their hearts, stack problems. For every recursive solution, there exists a matching solution that relies only on traditional methods, using a stack instead (the recursive solution merely "hides" the stack, by relying on the program's call stack). However, for certain types of problem, recursion allows for a drastic reduction in the amount of code needed to solve it. And so we have also the inverse; if you find yourself using a stack, there might be a way to use recursion to greatly simplify the problem. In this case, the method body is only two lines, and if I really wanted to I could get it down to a single line of code.
You also need to understand this does not create a sequence. It creates exactly one value, in cases where it converges at all. If you actually want to create a sequence, you need to return an IEnumerable
, ideally using the yield
keyword:
public IEnumerable<int> sequenceCreator(int currentVal)
{
if (currentVal == 1) yield return 1;
if (currentVal % 2 == 0)
{
yield return 1 + sequenceCreator(currentVal / 2);
}
else // (currentVal % 2 > 0)
{
yield return 1 + sequenceCreator(currentVal * 3 + 1);
}
}
Logically, I think you're safe here, and it will converge. The "final" or base case of this method is an input of 1
. Calls where the input is even reduce towards that base case. Calls where the input is odd increase away from the base case, but in such a way where next input is always even, and always a different even value than we've tried previously. Eventually we hope to end up with a power of two, or 3 time a power of two, that will remain even as it reduces all the way down to 3
or 2
, and then 1
, and then exit.
However, I'm concerned there might exist some values that never reach this state, or overflow integer before they can, or after increasing a few times reduce back to an even value we've already tried, thus creating a never-ending cycle.