|
|
|
Your skill will accomplish what the force of many cannot |
|
| PerlMonks |
Loop Abstraction via Recursionby liverpole (Monsignor) |
| on Jan 12, 2006 at 04:19 UTC ( [id://522614]=perlmeditation: print w/replies, xml ) | Need Help?? |
|
Several weeks ago, a coworker and I were discussing the "Eight Queens" problem in chess, where 8 queens are placed on a chessboard in such a way that no queen attacks another. I had written this program which generated all 92 solutions, but we wondered if there was a way to generalize the program to extend to other pieces, like 8 rooks? Or even combinations of pieces, like 4 queens and 7 kings?
I realized that my approach in the original program, that of 8 nested loops (1 per queen), wouldn't work, since the number of loops would either have to be an unmanageable 64 (for the number of squares on the board), or vary depending on the number of pieces, if each loop tracked the placement of a single piece. That got me to wondering ... was there some way I could "abstract" a loop construct so that a program could effectively execute a loop some variable number of times? In order to experiment with this concept, I created the following program: It's important to note the match() subroutine could serve just about any purpose. I didn't put any time into picking something particularly relevant; all that it does is calculate whether the sum of a list of number divides the product of the same set. At this point, there is a single loop "foreach (@list)", and therefore only a one-element list being generated each time through the loop. So it's no surprise when all "sums" divide all "products", and the result is: Here's what happens when a second loop is nested into the first (and added after the single-loop example at the end of the program): Now we're creating 2-element lists each time, and calling it a "match" when (A*B)/(A+B) is an integer value. Here are the more interesting results for 1 and 2 loops together: Likewise, here's the code for 3 nested loops, which starts to get quite unwieldy: ... and 4 loops (which is as far as I got doing it by hand), where I actually did have some "cut-and-paste" errors in the original code because of the growing complexity: And the results for 1 through 4 levels of loops are: In order to now abstract the concept of a loop so that an arbitrary number of "loops" can be called, one of the more simple (and thus elegant) approaches is to use recursion: This above pair of subroutines have a mutually symbiotic relationship. One calls the other, which in turn calls the first, and so on until the final level of recursion is reached, and the match subroutine is finally called. Here is a description of how they work together ... First, arbitrary_N_loops is called, with the desired number of loops. If the second variable $plevel is undefined, this is the top-level call to the subroutine, so some initialization is done, including setting the reference $pvals to an empty list (which will be passed through all levels of recursion), and setting the globals $nmatch and $total to zero. Then, if the maximum depth (level) has not yet been reached, the subroutine abstract_loop is called, to effectively invoke another loop. Its function is to push each value from the list onto the current list, descend one level further down by calling its partner arbitray_N_loops yet again, and later (maybe much later), popping the value off of the stack. Each time arbitrary_N_loops is called, the working level is increased by 1, and compared to N. The bottom level of the set of "loops" is only reached when the value reference by $plevel is greater than N, at which time the criteria matching subroutine match is called, and $nmatch incremented if the match occurred. At the end of the arbitrary_N_loops subroutine, if the decremented level is back down to zero, the subroutine is finished recursing, and the final call to show_results tells us the answer we've been waiting for. Here is the final run, showing the original "by-hand" loops (for comparison), followed by the recursively generated loops: [A final note] I had originally intended to publish the chess "attack" problem here as well. However, it ultimately grew fairly involved, and contains several other interesting intricacies (besides "loop abstraction") which would take too long to explain here. I will therefore publish the attack program in the near future, along with several related files, and a description of some of the elements which make it unique in its own right. Update: I finished working on the writeup of the attack program, which was my original impetus for this meditation. @ARGV=split//,"/:L"; map{print substr crypt($_,ord pop),2,3}qw"PerlyouC READPIPE provides"
Back to
Meditations
|
|
||||||||||||||||||||||||||||||||||||||||||