If you absolutely have to write this method recursively (see below), then the clearest, most readable, and most efficient way of doing so is the following:
public int count8_worker(int n, boolean lastWasEight)
{
if (n == 0)
{
return 0;
}
boolean isEight = ((n % 10) == 8);
n /= 10;
return (isEight ? (lastWasEight ? 2 : 1) : 0) + count8_worker(n, isEight);
}
public int count8(int n)
{
return count8_worker(n, false);
}
Giving credit where credit is due, this is a bit of a riff on Taemyr's solution in that it uses a Boolean variable to track whether or not the last digit processed was an 8, which I believe is more elegant than your original if(count==1)
approach.
While I certainly wouldn't make an issue of it in a code review, I don't think that defining a getRightMostDigit
(as suggested by Heslacher and others) is terribly important in terms of readability. Unless you need it for reuse (and remember, YAGNI), the logic is sufficiently short and obvious that it can be written on the other side of an assignment operator to an appropriately-named variable.
The repeated use of the conditional operator here is the only thing that detracts from the code's readability, and that's mainly because Java does not allow a Boolean value to be implicitly converted into an integer. The equivalent C code would be slightly easier to read:
return (isEight ? (lastWasEight + 1) : 0) + count8_worker(n, isEight);
What makes this maximally efficient is that we're only doing each operation once—each time through the loop, there is only one modulo, one division, and one comparison. Because they are back-to-back, a smart JIT compiler will fold the n % 10
and n /= 10
into a single divide instruction, which is by far the slowest instruction of the bunch. In fact, a really smart JIT compiler won't emit a divide instruction at all—at least, not when targeting x86. Rather, it will use a series of bit-twiddling and multiply-by-constant instructions to achieve the same effect but much more quickly.
Also, the use of these ternary conditional operators allows the JIT compiler to produce branchless code, which will be faster on balance, since the inputs are unpredictable, and mispredicted branches are slow. The alternative way of writing the code, used by some of the other answers, introduces branches that are extremely unlikely to be optimized out by the JIT compiler, essentially serving as performance landmines.
That said, if you actually care about performance, you would not write this recursively. I know, I know—the problem asked you to write a recursive routine, so that's what you did. That's great; computer science people love their recursion because it's theoretically elegant and theoretically as fast as iterative routines. In practice, though, it is neither. You had a difficult time proving to yourself that your recursive implementation was correct, and the way I read the question, you're still not completely confident. You relied on the few test cases that were provided, without being certain that there are no edge cases for which your code will return the incorrect result.
There's no valid reason why something like this would need to be implemented recursively, so I believe that a good programmer would question that part of the assignment and offer this iterative routine as an alternative:
int count8_iteratively(int n)
{
int count = 0;
boolean lastWasEight = false;
while (n != 0)
{
boolean isEight = ((n % 10) == 8);
n /= 10;
count += isEight ? (lastWasEight ? 2 : 1) : 0;
lastWasEight = isEight;
}
return count;
}
Not only is this easier to read, understand, and prove correct (in my opinion), it is objectively faster because it results in the generation of vastly more efficient code—and less of it, which is a win both in terms of caching and time-to-JIT.
Although an extremely smart ahead-of-time C++ compiler might (Clang does, for example, but not GCC, ICC, or MSVC), a Java JIT compiler will not inline the recursive method and turn it into iterative code. Java doesn't even do tail-recursion, so it certainly won't transform this into a loop. Frankly, the language is an iterative one, and so this is the style that programmers should follow. There is a good reason why you haven't had to use recursion in a long time. :-)