DigitalKitty has asked for the
wisdom of the Perl Monks concerning the following question:

Hi all.

In an effort to entertain myself (and share a little knowledge), I ported a recursion based java program to perl.
I fear many monks (especially beginning programmers) might not be aware that using recursion in their programs offers numerous benefits. If anyone can suggest how I might enhance the following application, feel free to post.

I wanted to point out that your "guess the number" algorithm is O(log N) in its computational efficiency. What that means is that as the dataset (or in this case, the size of the range in which the secret number may be hidden) grows by "N", the amount of work needed to find the secret number grows by "log N".

This happens to be, roughly, the same algorithm used in a binary search, which is known to be O(log N) too (of course).

The recursion is mostly a convenience here. An iterative approach could be used to implement the same basic algorithm, but recursion is kinda cool. :)

By the way, Perl (with warnings turned on) warns you when your recursion level reaches 100 levels deep. You can turn this off, if I recall, with "no warnings qw/recursion/;".

"This happens to be, roughly, the same algorithm used in a binary search,"

Indeed this is binary search itself. This used to be a trick I played when I was a kid. I first ask other kids to have a number between 1 and 1024, don't tell me, just keep it to themselves. Then ask them whether they believe that I can guess their number by asking no more than 10 questions. Nobody believed me at the beginning, but ... ;-)

This is pretty easy to solve for "worst case" and "average case" mathematically. But DigitalKitty's script makes it pretty easy to also empirically test. This isn't intended to be a replacement for mathematical proof, but rather, just a quick real-world example of how efficient the binary-search algorithm is. For each of the items in the table below, a modified DigitalKitty script was run. The modification causes the script to search for random numbers ten times in each range, and then print the average number of guesses for those ten searches. In each case, the range is assumed to be "0 .. N".

So this unscientific example shows that you can find one number hidden among a range of one trillion numbers, on average, after 39 guesses, using a binary search. Also, it should be noted that the larger the range, the smaller the standard deviation in number of guesses needed, as a percent of total guesses. A binary search is fairly stable; not very sensitive to the dataset.

If you were to bet someone you could guess their number between zero and 100 in less than ten guesses, you will most likely walk home with the money. But you'll really surprise someone if you can guess a one out of a trillion number in well under 50 guesses.

FWIW, with your code &function(args) syntax is not considered best style, you can drop the & - which you actually do in the sub. A consistent style is a good idea. You could declare and set $ans in one call.

It is worth noting that while an iterative solution will almost inevitably run faster than a recursive solution, a good recursive solution is often quite terse. A classic very simple example is the calculation of factorial n! The factorial is defined n! = 1 x 2 x 3 x .... (n-1) x n You can code using either iteration or recursion.....

As you can see the recursive solution is short and sweet. It also has a bug that can cause it to go infinite. One gotcha with recursion is that you need to be *positive* that your exit condition (ie stop recursing when we are done) will be *always* be met. A typical real world use for recursion is in dealing with tree like structures

The bug:

Negative numbers or floats mean that $num -1 will never equal 0, and so always it will always be true.

It is worth noting that while an iterative solution will almost inevitably run faster than a recursive solution, a good recursive solution is often quite terse.

It's further worth noting that DigitalKitty's algorithm uses only simple tail recursion, which can be easily compiled to be as fast or faster than an iterative solution.

Although I'm no expert, I believe the compiler would actually have to be smarter to optimize your iterative solution compared to the tail recursive one. Making the inference that $lower and $higher are candidates for register variables is slightly easier to make in the tail recursive version as there is a tendency to use registers for the subroutine calls variables for low-arity functions.

I could be wrong there, but a good compiler could do about as well with either, I would bet.

Now, implementing full tail call elimination is trickier, but recognizing tail recursion in this case and performing the optimization is a fairly trivial thing to do. Of course, we are talking about perl code here, and perl does not perform this optimization...

The number is 1001
Is your number 512? Go higher ...
Is your number 768? Go higher ...
Is your number 896? Go higher ...
Is your number 960? Go higher ...
Is your number 992? Go higher ...
Is your number 1008? Go lower ...
Is your number 1000? Go higher ...
Is your number 1004? Go lower ...
Is your number 1002? Go lower ...
Is your number 1001? You got it!

This program tells how many guesses you need for a number range, including total number of guesses for all numbers in the range, and the average: (the basic idea behind it is that, you have 1 number that you can hit by exactly 1 guess, you have 2 numbers that you can hit by exactly 2 guesses, you have 4 numbers that you can hit by exactly 3 guesses... you have 2 ** n numbers that you can hit by exactly n + 1 guesses...)

I don't mean to diminish your effort. I just wish to illustrate that power of recursion is sometimes an illusion. Performance should increase by removing recursion where it's not needed, and now you know how!

The only time that recursion is actually useful is when you're branching out. If each instance of your function only calls the function once, you should be using a loop to eliminate the function overhead. Even branching functions can often be done better with stacks, since with a stack you can allocate all memory needed in advance, while a recursive function has to allocate and deallocate every time it runs through an instance of itself.

The only time that recursion is actually useful is when you're branching out.

I think you maybe have the "usefulness" of recursion confused with the "misuse of an un-optimized implementation of recursion in a language compiler".

Just because most C compilers inefficiently implement recursion and many other C based languages do not fix this problem does not mean it is a lost cause. If you look at the compiler technology underlying recent implementations of Standard ML, Haskell or Scheme you will see that recursion can be implemented efficiently.

Would you please give URL references here. I would like to follow your thoughts, but am not familiar with the literature. This is a particularly interesting thread for me, having been severely reprimanded for using recursion (some years ago) and having made an effort to repress the impulse to use it ever since. -ben

You wanted suggestions and you have already recieved a good lecture on removal of tail recursion (as it should be done). I also recommend ditching the overly wordy java naming style, the calling convention, and adding "use strict".

Almost all my solutions to various maze-like perlgolf competitions have been recursive. For example, the rush-hour problem, where given a state of cars on a 'road', move them around until you can get a particular car out of the jam.

I can't think of a way to solve this sort of thing iteratively, unless you're willing/planning to take forever. And I think the solutions for that problem illustrate the terseness available to recursion to solve quite an awkward problem. (my solution was 170-odd characters - the winning solution under 140!). And my 500MHz machine still solved the problems in a reasonable length of time.

When putting a smiley right before a closing parenthesis, do you:

Use two parentheses: (Like this: :) )
Use one parenthesis: (Like this: :)
Reverse direction of the smiley: (Like this: (: )
Use angle/square brackets instead of parentheses
Use C-style commenting to set the smiley off from the closing parenthesis
Make the smiley a dunce: (:>
I disapprove of emoticons
Other