=chapter From Recursion to Iterators
R
We've already seen that iterators are useful when a source of data is
prepared to deliver more data than we want, or when it takes a long
time to come up with each data item and we don't want to waste time by
computing more of them than we need to.
Both conditions occur frequently in conjunction with recursive
functions. Recursive functions are often used for searching large,
hierarchical spaces for solutions to some specification. If solutions
are common, the space will contain more of them than we want to use;
if solutions are rare, they will take a long time to find. In either
case, we don't want our program to have to populate an array with all
the possible solutions before it can continue, and it is natural to
use an iterator.
We saw another reason to get rid of recursion in the web robot example
of the previous chapter: Recursive functions naturally perform
depthfirst searches. When this is inappropriate, as for a web robot,
recursion offers no escape. With an iterator solution, we can order
the queue any way we like or even reorder it dynamically when new
information arrives.
But recursive functions are often easy to write, whereas iterators
seemed to require ingenuity. In this chapter, we'll look at
techniques for transforming general recursive functions into iterators.
=section The Partition Problem Revisited
X As our prototypical example of such a problem,
we're going to look at the X, which we saw back
in R. This is a simple but common problem that
arises in many contexts, most commonly in optimization and operations
research problems.
Recall that in the partition problem, we are given a list of
treasures, each with a known value, and a target value, which
represents the share of the treasures that we are trying to allocate
to someone that we will call the wizard. The question is whether
there is any collection of treasures which will add up to the wizard's
share exactly, and if so, which treasures?
One runs into this problem and closely related problems everywhere.
For example, I was recently talking to X, owner of
the X. Hoefler needed to produce type samples
for his catalog. For each X, he needed to find an English word
or phrase that would fit in a column I 3.25 inches wide. He
had a dictionary, and could compute a table of the length of each
word. For large font sizes, this was enough, because a single word
such as "Hieroglyph" or "Cherrypickers" at 48 or 42point size
(respectively) would exactly fill the column; solving the problem for
large sizes is a simple matter of scanning the table for the single
word closest in size to 3.25 inches. But the same column must
accommodate fonts of all sizes, from large to small, and there is no
word that is 3.25 inches wide when set in 20point type. Several
words have to be put together to add up to the required length. For
20point type, the example is "The Defenestration of Prague"N.
=startpicture HoeflerMuseno1
ILLUSTRATION TO COME
=endpicture HoeflerMuseno1
=note Can we get permission to reproduce the relevant page from
Hoefler's _Muse_ #1?
In regular text, the typesetter will expand the spaces between words
slightly to take up extra space when needed, or will press the words
more closely together. In ordinary typesetting, this is acceptable.
But in a Xfont specimen catalog, the font
designer wants everything to look perfect, and the spacing has to be
just so. The designer wants to pick text, which, when spaced in the
most natural way, happens to fill the column as exactly as possible.
The problem of finding words to fit as perfectly as possible into the
space in a font specimen catalog is very similar to the partition
problem we saw in R. The differences are that some
allowance has to be made in the programming to handle appropriate
interword space which follows every word but the last, that words may
be reused, and that it is permissible to miss the target value by a
small amount. We will start by ignoring these differences, and then
adjust the code later on to take them into account.
X Another related problem is that of how to back up
your files from your hard disk onto X, using as few
diskettes as possible. It is not permitted to split any file across
two or more diskettes. This problem was intensely interesting to me
in 1986, because the file backup program for my X did have
just those restrictions, and as a Xpenurious college student, I couldn't afford to
buy lots of diskettes.
We've seen the code for a recursive version of this problem already.
It looks like this:
=inline find_share.pl
This function returns an array of treasures that add up to the target
sum, if there is such a solution, and C if there is no solution.
=subsection Finding All Possible Partitions
We could easily modify it to return I possible solutions, instead
of only one:
=note this function reverse the order of the parameters from find_share.pl
=note this function reverse the order of the parameters from find_share.pl
=startlisting partitionall
sub partition {
my ($target, $treasures) = @_;
return [] if $target == 0;
* return () if $target < 0  @$treasures == 0;
my ($first, @rest) = @$treasures;
* my @solutions = partition($target$first, \@rest);
* return ((map {[$first, @$_]} @solutions),
* partition($target, \@rest));
}
=endlisting partitionall
=test partitionall 93
do "partitionall";
%used;
my ($N, $target) = (10, 20);
for my $res (partition($target, [1..$N])) {
my @parts = @$res;
my $sum = 0;
$sum += $_ for @parts;
is($sum, $target, "partition [@parts]");
ok(!$used{"@parts"}++, "new partition");
my $ok = 1;
for (@parts) { $ok &&= $_ >= 1 && $_ <= $N }
ok($ok, "parts in [1..$N]");
}
=endtest partitionall
=test partitionall2 5
eval { require "partitionall" };
my @results; my @known;
@results = partition(5,[1,2,3,4]);
@known = ([1,4],[2,3]);
for my $result (@results) {
$s = 0;
$s += $_ for @$result;
is($s, 5, "partition sum");
}
ok(@results==2,"partition result count");
is_deeply(\@results,\@known,"Results match expected");
alarm(30); # SLOW TEST
@results = partition(50, [1..20]);
ok(@results==1969,"second partition result count");
=endtest partitionall2
Why might we want to do such a thing? Suppose we're trying to
allocate shares to several people, say a wizard, a barbarian, and a
plumber, out of the same pool of treasure. First we allocate the
wizard's share. There might be several ways to do this, so we choose
one. Next we want to allocate the barbarian's share, but we find that
there's no way to do this. It might be that if we had allocated the
wizard's share differently, we wouldn't have gotten into trouble over
the barbarian's share later. When we find out that we can't allocate
the barbarian's share correctly, we want to
IX and try the wizard's share in a
different way.
Here's a particularly simple example: Suppose that there are four
treasures worth 1, 2, 3, and 4. The wizard is owed treasures worth 5
gold pieces, and the barbarian is owed 3. If we give treasures 2 and
3 to the wizard, we foreclose the only possible solutions for the
barbarian. We need to backtrack and try a different distribution of
treasures; in this case we should give treasures 1 and 4 to the
wizard, and treasure 3 to the barbarian. (The plumber works for union
scale and is paid by the hour.)
The partition function above delivers all possible shares for the
wizard; so if we try C<[2,3]> and discover that that causes problems
later for the barbarian, we can backtrack and try the other solution,
C<[1,4]>, instead.
But this function has a serious problem that we might have foreseen:
Even simple instances of the partition problem often have many
different solutions. For example, the call C
generates 15,272 solutions. Since we probably won't need to find all
these solutions, we would like to convert this function to an
iterator.
In the previous chapter, we saw a technique for doing this. It
involved replacing the implicit recursion stack with an explicit
queue, and appeared to require ingenuity. But it turns out that this
technique always works, and doesn't require much ingenuity at all.
This tactic for turning a recursive function into an iterator is to
have the iterator retain an XN is the Latin word for
"todo list".> or X of partiallycomplete partition
attempts that it has not yet investigated. Each time we invoke the
iterator, it will remove an item from the todo list and investigate
it. If the item represents a solution to the problem, the iterator
will return it immediately. If the item requires further
investigation, the iterator will investigate it a little further,
possibly producing some new partiallyinvestigated items, which it
will put onto the todo list be investigate later, and will continue
to look through the agenda for solutions. If the agenda is exhausted
before a solution is found, the iterator will report failure. Since
the agenda is part of the iterator's state, the iterator can return a
solution to its caller, and the agenda state will remain intact until
the next time the iterator is called.
We saw several examples of this approach, including the web spider, in
the previous chapter.
For this problem, each item in the queue must contain the following
information:
=bulletedlist
=item A current target sum
=item The I of treasures still available for use
=item The I containing the treasures already allocated toward the target
=endbulletedlist
In general, with this technique, each agenda item must contain all the
information that would have been passed as arguments to the recursive
version of the function.
=startlisting partitionit
sub make_partitioner {
my ($n, $treasures) = @_;
my @todo = [$n, $treasures, []];
Initially, the queue contains only one item that the iterator must
investigate: The target sum is C<$n>, the number originally supplied
by the user; the pool contains all the treasures; the share is empty.
The iterator will move treasures from the pool to the share, deducting
their values from the target, until the target is zero.
sub {
while (@todo) {
my $cur = pop @todo;
my ($target, $pool, $share) = @$cur;
Here the iterator extracts the tail item from the agenda. This is the
'current' item that it must investigate. The iterator extracts the
target sum, the available pool of treasures, and the list of treasures
already allocated to the share. The presence of this item in the
todo list indicates that if some subset of the treasures in C<$pool> can
be made to add up to C<$target>, then those treasures, plus the ones
in C<$share>, constitute a solution to the original problem.
The iterator can return under two circumstances. If it finds that the
current item represents a solution, it will return the solution
immediately. But if the agenda is exhausted before this occurs, then
there is nothing left to investigate, there are no more solutions, and
the iterator will immediately return failure.
if ($target == 0) { return $share }
If the target sum is zero, the current share is already a winner. The
iterator returns it immediately. Any items that are still
uninvestigated remain on the todo list, awaiting the next call to the
iterator.
next if $target < 0  @$pool == 0;
On the other hand, if the target is negative, the current item is
hopeless, and the iterator should immediately discard it and
investigate another item; similarly if the pool of treasures in the
current item has been exhausted. The C restarts the C
loop from the top, which begins by extracting a new current item from
the agenda.
With these simple cases out of the way, the bulk of the code follows:
my ($first, @rest) = @$pool;
push @todo, [$target$first, \@rest, [@$share, $first]],
[$target , \@rest, $share ];
}
In the typical case, the current item has two subitems that must be
investigated separately: Either the first treasure in the pool is
included in the share, and the target is smaller, or it isn't
included, and the target is the same. For example, to satisfy C<(28,
[10,18,27], [1])> we can either investigate C<(18, [18,27], [1,10])>
or we can investigate C<(28, [18,27], [1])>.
The iterator appends the two new items to the end of the queue and
returns to the top of the C loop to investigate another item.
return undef;
} # end of anonymous iterator function
} # end of make_partitioner
If the todo list is exhausted, the C loop exits, and the
iterator returns C to indicate failure.
=endlisting partitionit
=test partitionit
sub is_an_iterator { ref shift }
use Iterator_Utils ':all';
do 'Programs/Iterator_trivial';
do 'Programs/imapfinal';
do 'Programs/partitionit';
$pi = make_partitioner(7, [1,2,3,4,5]);
# Result order is different than for previous partioner
is_deeply($pi>(),[3,4], "Third expected result");
is_deeply($pi>(),[2,5], "Second expected result");
is_deeply($pi>(),[1,2,4],"First expected result");
is_deeply($pi>(), undef, "No more results");
$pi = make_partitioner(136, [9,12,14,17,23,32,34,40,42,49]);
# Result order is different than for previous partioner
is_deeply($pi>(), [14, 17, 23, 40, 42], "Second expected result");
is_deeply($pi>(), [9, 12, 32, 34, 49], "First expected result");
is_deeply($pi>(), undef, "No more results");
=endtest partitionit
=test partitionitr
sub is_an_iterator { ref shift }
do 'Programs/list_iterator';
do 'Programs/NEXTVAL';
do 'Programs/Iterator_trivial';
do 'Programs/flatten';
do 'Programs/imapfinal';
do 'Programs/partitionit';
$pi = make_partitioner(7, [1,2,3,4,5]);
# Result order is different than for previous partioner
is_deeply($pi>(),[3,4], "Third expected result");
is_deeply($pi>(),[2,5], "Second expected result");
is_deeply($pi>(),[1,2,4],"First expected result");
is_deeply($pi>(), undef, "No more results");
$pi = make_partitioner(136, [9,12,14,17,23,32,34,40,42,49]);
# Result order is different than for previous partioner
is_deeply($pi>(), [14, 17, 23, 40, 42], "Second expected result");
is_deeply($pi>(), [9, 12, 32, 34, 49], "First expected result");
is_deeply($pi>(), undef, "No more results");
=endtest partitionitr
=subsection Optimizations
There are a few obvious ways to improve the code shown above. Suppose
the current item is C<[12, [12, ...], [...]>. The function then
constructs two new items, C<0, [...], [..., 12]> and C<12, [...],
[...]>, and pushes them on the end of the todo list. But the first
item is obviously a solution (because its target sum is 0), so there's
no point in putting it on the end of the queue and working through
every other item on the queue looking for a different solution;
clearly we should return it right away.
Similarly, if the function constructs an item that is obviously
useless, it could throw it away immediately rather than putting it on
the queue to be thrown away later.
=listing partitionitoptimized
sub make_partitioner {
my ($n, $treasures) = @_;
my @todo = [$n, $treasures, []];
sub {
while (@todo) {
my $cur = pop @todo;
my ($target, $pool, $share) = @$cur;
if ($target == 0) { return $share }
next if $target < 0  @$pool == 0;
my ($first, @rest) = @$pool;
* push @todo, [$target, \@rest, $share ] if @rest;
* if ($target == $first) {
* return [@$share, $first];
* } elsif ($target > $first && @rest) {
* push @todo, [$target$first, \@rest, [@$share, $first]],
* }
}
return undef;
} # end of anonymous iterator function
} # end of make_partitioner
=endlisting partitionitoptimized
=test partitionitoptimized
sub is_an_iterator { ref shift }
use Iterator_Utils ':all';
do 'Programs/Iterator_trivial';
do 'Programs/imapfinal';
do 'Programs/partitionitoptimized';
$pi = make_partitioner(7, [1,2,3,4,5]);
is_deeply($pi>(),[1,2,4],"First expected result");
is_deeply($pi>(),[2,5], "Second expected result");
is_deeply($pi>(),[3,4], "Third expected result");
is_deeply($pi>(), undef, "No more results");
$pi = make_partitioner(136, [9,12,14,17,23,32,34,40,42,49]);
is_deeply($pi>(), [9, 12, 32, 34, 49], "First expected result");
is_deeply($pi>(), [14, 17, 23, 40, 42], "Second expected result");
is_deeply($pi>(), undef, "No more results");
=endtest partitionitoptimized
The first new line here appends to the queue what was previous the
second new item. But here it's conditionalized: The item is only
placed on the queue if its treasure pool will still contain an unused
item. If its pool is empty, then it can't possibly result in a
solution, so we discard it immediately.
The following C block handles what was previously the first
new item. We're about to put the first treasure into the share and to
subtract its size from the target sum. But unlike the previous
version of the code, here we only put the new item on the queue if the
size of the first treasure is smaller than the target sum. If the
first treasure is equal to the target sum, then the item we're about
to put on the queue is actually a solution to the problem, so we
return it immediately instead of queuing it. Conversely, if the
first treasure is larger than the target sum, then the item we were
about to queue would have had a negative target sum, and would have
been discarded the next time we encountered it; instead, we never put
it in the queue at all. The C<&& @rest> condition makes sure we don't
queue an item with a positive target sum and an empty pool, which is
guaranteed to fail.
It's tempting to remove the
if ($target == 0) { return $share }
next if $target < 0  @$pool == 0;
lines now. They're much less useful, since the cases they check
for are all detected at the bottom of the loop, and items that have
C<$target \<= 0> or C<@$pool == 0> aren't put into the queue to begin
with. The only cases they do catch are when such items are placed
directly into the queue by the caller of C.
There are at least three ways we can deal with this. We can leave the
checks in place. We can remove the checks and document the resulting
deficiency in the function: If the initial value of C<$n> is 0, the
iterator fails to report the empty solution. (Even with the extra
checks, the function has a few boundary condition errors of this type.
For example, it only reports three of the eight possible solutions to
C.) Or we can remove the checks and add
preprocessing code that works around the bug. For example:
sub make_partitioner {
my ($n, $treasures) = @_;
* my @todo = $n ? [$n, $treasures, []] : [$n, [], []];
sub {
...
}
}
If C sees that we're about to exercise the bug,
which occurs only for M and a nonempty treasure pool, it silently
adjusts the pool behind the scenes to a case that I produce the
correct answer.
These three tactics are presented in increasing order of 'cleverness'.
Such cleverness should be used only when necessary, since it requires
a corresponding application of cleverness on the part of the
maintenance programmer eight weeks later, and such cleverness may not
be available.
=subsection Variations
The space searched by this function is organized like a tree:
=startpicture partitiontree
6
[2346]
[]
,' `.
4 6
[346] [346]
[2] []
,' \ ,' `.
1 4 3 6
[46] [46] [46] [46]
[23] [2] [3] []
/ \  \ / \ ,' `.
3 1 0 4 1 3 2 6
[6] [6] [6] [6] [6] [6] [6] [6]
[234] [23] *24* [2] [34] [3] [4] []
,'  / \ /  / \ / \
5 1 2 4 3 3 4 2 0 6
[] [] [] [] [] [] [] [] [] []
[236] [23] [26] [2] [36] [3] [46] [4] *6* []
=endpicture partitiontree
Each node of this tree represents one of the items that the
partitioner investigates, showing the target sum, the pool, and the
share so far. For example, the root node represents an item with a
target sum of 6, a pool containing 2, 3, 4, and 6, and an empty share.
The root node is the item that the user of C first
inserted into the todo list. Each node has two child nodes, which
are the two derived items, one of which moves the first treasure from
pool to share and subtracts it from the target sum, and the other of
which removes the first treasure from the pool and discards it without
changing the share or the target sum. The leaf nodes are those from
which no further searching is done, because the pool is empty (bottom
row) or the target sum is too small.
The partitioner always searches a node before searching its children,
so it searches the tree in a generally toptobottom order. In fact,
the version shown above searches the nodes in depthfirst order,
visiting the root node, then the nodes down the leftmost branch, then
the three nodes just to the right of the leftmost branch, and so on.
The second version of the partitioner saves time by refusing to
investigate items that it sees will be leaves, effectively searching
this smaller tree instead:
=startpicture partitiontreetrimmed
6
[2346]
[]
,' `.
4 6
[346] [346]
[2] []
,' \ ,' `.
1 4 3 6
[46] [46] [46] [46]
[23] [2] [3] []
\  \ \ ,' `.
1 0 4 3 2 6
[6] [6] [6] [6] [6] [6]
[23] *24* [2] [3] [4] []
/
0
[]
*6*
=endpicture partitiontreetrimmed
Whether to choose breadth or depthfirst search depends on the nature
of the problem. Each has major contraindications. Depthfirst search
(X) tends to yield shorter todo lists. In any depthfirst
search of a tree, if each node in the tree has no more than V
children, and the depth of the tree is V nodes, then the todo list
will contain at most M<(n1)(d1)+1> items at any time. For the
partition problem, V is 2, and V is no more than the number of
items in the original pool. So in depthfirst search, the todo list
will never exceed the size of the original pool.
In contrast, breadthfirst search (X) can sometimes lead to
enormous todo lists. The tree is searched topdown, and if all the
solutions are in the leaves, every interior tree node must be put on
the todo list and taken off again before the search reaches the
leaves where the solutions are. In the unpruned partition search
example, above, breadthfirst search starts with the root node on the
agenda, then removes it and replaces it with the two secondlevel
nodes, then removes these and replaces them with the four thirdlevel
nodes, then replaces these with the eight fourthlevel nodes. These
are eventually replaced with the ten fifthlevel nodes; if the problem
had been bigger, there would have been sixteen fifthlevel nodes
instead of only ten. Breadthfirst search may be contraindicated when
the tree branches rapidly or when the solutions are all to be found
among the leaves. Depthfirst search, which dives straight down to
where the solutions are, may be a better choice.
For some applications, however, depthfirst search is a loser. Web
spidering is one of these. I was once teaching a class in which one
of the students decided to write a web spider. The central control of
his program was a recursive function, something like this:
sub handle_page {
my $url = shift;
get the document from the network;
if (the document is HTML) {
parse it;
extract the links;
for (links) {
handle_page($_);
}
}
}
Because the function was recursive, it naturally did a depthfirst
search on the web space. The result was completely useless. The
spider started by reading the initial page and making a list of all
the links from that first page. Then it followed the first link on
the first page and made a list of all the links on the second page.
Then it followed the first link on the second to a third page and made
a list of all the links on that page, and so on. The spider went
dashing off toward the horizon, never to return, except perhaps by
accident. Clearly this wasn't particularly useful. This is the major
contraindication for depthfirst search: a very large, or infinite
search space.
To see a particularly simple example of this, consider a search for
strings of the letters C, C, and C that read the same
forwards as backwards. We might imagine a search of the space of all
strings:
X
=startpicture stringtree
""
,'`..
,' `. `.
,' `. `.
,' `. `.
"A" "B" "C"
,`.. ,`. ,`.
,' `. `. ,'  `. ,'  `.
,' `. ` "BA" "BB" "BC" "CA" "CB" "CC"
"AA" "AB" "AC" . . . . . .
,'  `. ,'  `. . . . . . . .
,'  `. ,'  `. . . . . . . .
"AAA" "AAB" "AAC" "ABA" "ABB" "ABC" .
=endpicture stringtree
Breadthfirst search eventually finds all the desired strings, in
order by length: C<"">, C<"A">, C<"B">, C<"C">, C<"AA">, C<"BB">,
C<"CC">, C<"AAA">, C<"ABA">, C<"ACA">, C<"BAB">, ...
Depthfirst search, however, goes diving down the leftmost branch,
finding C<"A">, C<"AA">, C<"AAA">, C<"AAAA">... and never even looking
at any branches that contain C's or C's.
=note maybe also get in a mention of the fact that BFS tends to make
the agenda go walking downward through memory, causing frequent
reallocations and copies; DFS tends to leave the agenda in the same
place.
=section How to Convert a Recursive Function to an Iterator
We've seen several such techniques, including the odometer method and
the agenda method. It appears that these took some ingenuity to
find. What if they don't happen to work for a particular function,
and you don't have enough ingenuity that day to find something that
does work?
It turns out that that won't happen, because the agenda method
I works. This is because we can consider every recursive
function to be doing a tree search!
Ordinary function call semantics create a notional tree of function
calls. Imagine that we have a node for each time a function is
called, and node V is the parent node of V when the function
invocation represented by V is responsible for invoking the
function represented by V. The root node is the main program,
which is started by some agency outside of the program itself. A
simple program like this:
#!/usr/bin/perl
$data = read_the_input();
$result = process_the_data($data);
print_the_output($result);
evolves this simple tree:
=startpicture executiontreesimple
++
(main program)
++
,  .
,' ) `.
,' / `.
++ ++ ++
read input process data print output
++ ++ ++
=endpicture executiontreesimple
Such a tree is called a X.
It's important to realize that the call tree has one tree node
not for each subroutine, but for each I of each
subroutine:
sub read_input {
for (1..8){
read_block($_);
}
...
}
sub read_block {
my $n = shift;
if ($n % 2 == 0) { read_addendum() }
...
}
sub read_addendum { ... }
=startpicture executiontreeloop
++
read_input
++
,. .
,' ,' \ `.
,' ,' `. `.
,' ,' \ `.
++ ++ ++ ++
read_block(1) read_block(2) read_block(3) . . . read_block(8)
++ ++ ++ ++
 
 
++ ++
read_addendum read_addendum
++ ++
=endpicture executiontreeloop
In the call tree for a recursive function, the node for a
subroutine may have children which represent calls to the same
subroutine. For a recursive directory tree walker like
C, the call tree is exactly the same as the
directory tree itself. Here's a more arbitrary example:
sub rec {
my ($n, $k) = @_;
print $k x $n, "\n";
for (1 .. $n1) {
rec($n$_, $_);
}
}
=startpicture executiontreerecursive
++
 rec(4,1) => 1111 
++
, . .
,' `. `.
,' `. `.
,' `. `.
' `` `
++ ++ ++
 rec(3,1) => 111   rec(2,2) => 22   rec(1,3) => 3 
++ ++ ++
,' `. 
,' ` 
++ ++ ++
 rec(2,1) => 11   rec(1,2) => 2   rec(1,1) => 1 
++ ++ ++


++
 rec(1,1) => 1 
++
=endpicture executiontreerecursive
When a recursive function runs, we can imagine that it is performing a
depthfirst tree search on its own call tree. It starts at the
root, which represents the initial invocation of the function. Each
time the function calls itself, it is moving down the tree to a child
node; when the call returns, it moves back up the the parent. When
run, the example immediately above does indeed produce the data from
the tree nodes in depthfirst order:
1111
111
11
1
2
22
1
3
As a result, every recursive function is really doing a depthfirst
tree search. Whenever we want to convert a recursive function to an
iterator, we can use the agenda method. Each agenda item will
represent one call to the recursive function and will contain all the
state information that the recursive function needed to do its work:
in general, all its private variables, and often, just the arguments.
When the iterator removes an item from the agenda, it starts
pretending that it's the recursive function, with the arguments
described by the item it removed. If the recursive function would
have called itself recursively, the iterator puts an item onto the
agenda to represent the new arguments.
Let's look at a new example to see how this works. Some time ago, a
friend, X, was working on a game and asked how to write a
function that would take a positive integer V and produce a list of
all the different ways it could be split into smaller integers. For
example, if M, the desired list is
6
5 1
4 2
4 1 1
3 3
3 2 1
3 1 1 1
2 2 2
2 2 1 1
2 1 1 1 1
1 1 1 1 1 1
Rather confusingly, this is called the X,
X
X
X
and each of the rows in the table is a I of the number 6.
=note can you explain the connection with the other partitioning
problem? Or maybe find a better name for it?
First we have to suppose we have a recursive function that solves this
problem. The function will take a number and split a chunk off of
it. For example, it might split 5 into M<4+1> or 6 into M<3+3>. It
will do this in every possible way. Then it will recurse, and split
another chunk off of the remainder, and so on.
=startlisting partitionrepeats
sub partition {
print "@_\n";
my ($n, @parts) = @_;
for (1 .. $n1) {
partition($n$_, $_, @parts);
}
}
=endlisting partitionrepeats
=test partitionrepeats 65
do 'partitionrepeats';
use STDOUT;
my $N = 6;
partition($N);
my @lines = split /\n/, $OUTPUT;
is(scalar(@lines), 1<<($N1), "count partitions");
my %seen;
for (@lines) {
ok(!$seen{$_}++, "'$_' is new?");
my $sum = 0;
$sum += $_ for split;
is($sum, $N, "sum of '$_' = 6?");
}
=endtest partitionrepeats
=auxtest partitionauxtest
use IO::Scalar;
my $data = "";
my $buf = new IO::Scalar \$data;
for my $N (1..10) {
$data = "";
my $oldout = select($buf);
partition($N);
select($oldout);
my @results = split /\n/,$data;
for my $result (@results) {
my @nums = split / /,$result;
my $sum = 0; $sum += $_ for @nums;
is($sum, $N,"everything adds up to $N");
}
}
=endtest partitionauxtest
=test partitionrepeatsbig 1023
alarm(30); # SLOW TEST
do 'partitionrepeats';
do 'partitionauxtest';
=endtest partitionrepeatsbig
This isn't quite what we want, because it generates some of the
partitions more than once. For example, if we start with 6, and split
off 2 and then 3, we get M<1+3+2>; if we split off 3 first and then 2,
we get M<1+2+3>, which is the same. The function above generates 32
partitions of 6, including M<3+1+1+1>, M<1+3+1+1>, M<1+1+3+1>, and
M<1+1+1+3>, but there are only 11 different partitions.
The trick to eliminating extra items in a listing like this is to
adopt a X for the output. Where there are several
items that are essentially the same, a canonical form is just a
convention about which item you'll choose to represent all of them.
This idea should be familiar. Suppose we wanted to read a list of
words, and report on the ones that appeared more than once. Easy;
just use a hash:
for (@words) { $seen{$_}++ }
@repeats = grep $seen{$_} > 1, keys %seen;
But what if the words are in mixedcase, and the case doesn't matter,
so that we want to consider "perl", "Perl", and "PERL" as
being the same? There's only one easy way to do it: Use a hash, and
store the alllowercase version of the codes:
for (@words) { $seen{lc $_}++ }
@repeats = grep $seen{$_} > 1, keys %seen;
The alllowercase version is the canonical form for the words. Words
are divided into groups of equivalent words, sometimes called
I, and a representative is chosen from each
group. For the group of equivalent words containing
perl Perl pErl peRl
perL PErl PeRl PerL
pERl pErL peRL PERl
PErL PeRL pERL PERL
we choose "perl" as the X. Choosing the
alluppercase member of each group would work as well, of course, as
would any other method that chooses exactly one representative from
every equivalence class. Another familiar example is numerals: We
might consider the numerals "0032.50", "32.5", and "325e01" to be
equivalent; when perl converts these strings to an internal
floatingpoint format, it is converting them to a canonical
representation so that equivalent numerals have the same
representation.
Returning to our problem of duplicate partitions, it appears that one
solution will be to find a canonical form for partitions, and then
discard any partitions that aren't already in canonical form.
Sometimes it can be difficult to find an appropriate canonical form.
But not in the case of the partition problem. The partitions are
lists of numbers, and since every list has one and only one sorted
version, we'll just say that the sorted version of the list is its
canonical form.
We will produce partitions whose elements are in decreasing order, and
no others. (We'll say 'decreasing' when what we really mean is
'nonincreasing', so that we say that 5, 5, 4, 3, 3 is a 'decreasing'
sequence of numbers. This is more convenient than using the clumsy
word 'nonincreasing' everywhere.N, EWD1063.>)
We could refit the subroutine above to suppress the printing for the
elements that aren't in decreasing order:
sub partition {
* print "@_\n" unless decreasing_order(@_);
my ($n, @parts) = @_;
for (1 .. $n1) {
partition($n$_, $_, @parts);
}
}
However, it's more efficient to avoid generating noncanonical
partitions in the first place. To generate only those partitions
whose members are in decreasing order, we just have to take care not
to split off any parts that are smaller than a part we have already
split off.
=startlisting partition
sub partition {
print "@_\n";
my ($largest, @rest) = @_;
my $min = $rest[0]  1;
my $max = int($largest/2);
for ($min .. $max) {
partition($largest$_, $_, @rest);
}
}
=endlisting partition
=test partitioncanonical
do 'partition';
do 'partitionauxtest';
=endtest partitioncanonical
Here instead of splitting off parts with any size at all between 1 and
C<$n1>, we make conditions on the size of the parts we can split off.
We know that the arguments to the function are in decreasing order,
so that the first argument is the largest part, the next is the next
largest (if it exists), and the rest (if there are any) are no bigger
than these two. We don't want to split off a part that is smaller
than one we split off before, so it is sufficient to make sure the
splitoff part is at least as large as C<$rest[0]>, if it exists; if
not, we haven't split anything off yet, so it's okay to split off
any amount down to and including 1.
The splitoff value must not be larger than half the largest element,
or else the part left over after it is subtracted will be smaller than
the part that was split off: we would go from C to
C, and then the arguments wouldn't be in decreasing
order.
Here's the call tree for the invocation C:
=startpicture partition7
(7)
,'`..
(6,1) (5,2) (4,3)
,' `.. 
(5,1,1) (4,2,1) (3,3,1) (3,2,2)
,' `. 
(4,1,1,1) (3,2,1,1) (2,2,2,1)
,' `.
(3,1,1,1,1) (2,2,1,1,1)

(2,1,1,1,1,1)

(1,1,1,1,1,1,1)
=endpicture partition7
The large left branch contains all the partitions that include a part
of size 1. The much smaller second branch contains just the
partitions whose parts are all at least 2. The third branch contains
the single partition, C<(4, 3)>, whose parts are all at least 3.
(Incidentally, it's quite easy to change the function to solve the
slightly different problem of producing the partitions where the parts
are all different: Just change C<$rest[0]> to C<$rest[0]+1> and
C<$largest> to C<($largest1)>.)
The function works just fine, producing each partition exactly once,
and every partition in decreasing order, so now we'll try to turn it
into an iterator.
To do this, we need to identify the state that the function tracks
during each invocation. We'll then package up each state into an
agenda item. In general, the state might include all of the
function's lexical variables, and it has four: C<@rest>, C<$largest>,
C<$min>, and C<$max>.
=startlisting partitioniteratorclumsy
sub make_partition {
my $n = shift;
my @agenda = ([$n, # $largest
[], # \@rest
1, # $min
int($n/2), # $max
]);
return Iterator {
while (@agenda) {
my $item = pop @agenda;
my ($largest, $rest, $min, $max) = @$item;
for ($min .. $max) {
push @agenda, [$largest  $_, # $largest
[$_, @$rest], # \@rest
$_, # $min
int(($largest  $_)/2), # $max
];
}
return [$largest, @$rest];
}
return;
};
}
=endlisting partitioniteratorclumsy
=auxtest partitioniteratorauxtest
for my $N (1..6) {
my $partition = make_partition($N);
while ( my $p = $partition>() ) {
my @nums = @$p;
my $sum = 0; $sum += $_ for @nums;
is($sum, $N,"everything adds up to $N");
}
}
=endtest partitioniteratorauxtest
=test partitioniteratorclumsy
use Iterator_Utils 'Iterator';
do 'partitioniteratorclumsy';
do 'partitioniteratorauxtest';
=endtest partitioniteratorclumsy
The code here has a strong resemblance to the original recursive
function. We can see the C and the C loops lurking inside. But it's rather clumsy. The iterator
we've just constructed is more closely analogous to a different
version of the recursive function, one which passes all four
quantities as arguments:
sub partition {
my ($largest, $rest, $min, $max) = @_;
for ($min .. $max) {
partition($largest$_, [$_, @$rest], $_, int(($largest  $_)/2));
}
return [$largest, @$rest];
}
This does work, but it's not how we did it originally. Instead, we
derived C<$min> and C<$max> from C<$largest> and C<$rest>, and these
in turn were derived from C<@_>, which is the true state of the
recursive function. Realizing this leads to a simpler iterator:
=startlisting make_partition
sub make_partition {
my $n = shift;
my @agenda = [$n];
return Iterator {
while (@agenda) {
my $item = pop @agenda;
my ($largest, @rest) = @$item;
my $min = $rest[0]  1;
my $max = int($largest/2);
for ($min .. $max) {
push @agenda, [$largest$_, $_, @rest];
}
return $item;
}
return;
};
}
=endlisting make_partition
=test partitioniteratormake
use Iterator_Utils 'Iterator';
do 'make_partition';
do 'partitioniteratorauxtest';
=endtest partitioniteratormake
The code here is quite similar to that of the original function.
Now that we have an iterator, we can play around with it. There's no
point to the C loop, because it executes at most once, and a
C loop that executes at most once is just an C in disguise:
=listing make_partition_cleaner
sub make_partition {
my $n = shift;
my @agenda = [$n];
return Iterator {
* return unless @agenda;
my $item = pop @agenda;
my ($largest, @rest) = @$item;
my $min = $rest[0]  1;
my $max = int($largest/2);
for ($min .. $max) {
push @agenda, [$largest$_, $_, @rest];
}
return $item;
};
}
=endlisting make_partition_cleaner
=test partitioniteratormakecleaner
use Iterator_Utils 'Iterator';
do 'make_partition_cleaner';
do 'partitioniteratorauxtest';
=endtest partitioniteratormakecleaner
Because we return each partition immediately, after putting its
children onto the agenda, old nodes are never preempted by new ones,
regardless of whether we use C or C. Consequently this
iterator always produces partitions in breadthfirst order. The
output lists the partitions in increasing order of number of elements:
6
5 1
4 2
3 3
4 1 1
3 2 1
2 2 2
3 1 1 1
2 2 1 1
2 1 1 1 1
1 1 1 1 1 1
We might prefer it to return the partitions in a different order, say
one listing all the partitions with large parts before those with
small parts:
6
5 1
4 2
4 1 1
3 3
3 2 1
3 1 1 1
2 2 2
2 2 1 1
2 1 1 1 1
1 1 1 1 1 1
This is equivalent to sorting the partitions. And we can get this
order by sorting the agenda before we process it. To do that, we'll
need a comparison function for partitions:
=listing partitions
# Compare two partitions for preferred order
sub partitions {
for my $i (0 .. $#$a) {
my $cmp = $b>[$i] <=> $a>[$i];
return $cmp if $cmp;
}
}
=endlisting partitions
To compare two partitions, we just scan through them both one element
at a time until we find a difference; when we do, that's the answer.
Since two partitions must have a difference somewhere before the end
of either, we don't have to worry what happens if we fall off the end.
N and C<"fandango">. In such
a case, we I fall off the end. But that can't happen with
partitions, because two such sequences of positive numbers can't
possibly add up to the same thing.> Now we make a small change to the
iterator:
=listing make_partition_partitions
sub make_partition {
my $n = shift;
my @agenda = [$n];
return Iterator {
return unless @agenda;
my $item = pop @agenda;
my ($largest, @rest) = @$item;
my $min = $rest[0]  1;
my $max = int($largest/2);
for ($min .. $max) {
push @agenda, [$largest$_, $_, @rest];
}
* @agenda = sort partitions @agenda;
return $item;
};
}
=endlisting make_partition_partitions
=test partitioniteratormakepartitions
use Iterator_Utils 'Iterator';
do 'partitions';
do 'make_partition_partitions';
do 'partitioniteratorauxtest';
=endtest partitioniteratormakepartitions
We sort the agenda into the order we want before extracting items from
it. Rather than sorting the entire array so that the item we want is
at the end, a computationally cheaper approach is to scan the agenda
looking for the maximal element and then to C it out once we
find it. If we plan to do a lot of heuristically guided searches, we
should invest in building a priority queue structure for the agenda.
A priority queue contains a collection of items, each with an
associated value; it efficiently supports the operations of adding a
new item to the collection, and of extracting and removing the item
with the largest value.
=section A Generic Search Iterator
You've probably noticed by now that all these agendatype iterators
look more or less the same. We can abstract out the sameness and make
a generic treesearch iterator. To do that, we need to describe the
tree. The constructor function will receive two arguments: The root
node, and a callback function, which, given a node, generates its
children in the tree. It will then carry out tree search, returning
the tree nodes one at a time:
=startlisting makedfssearchsimple
use Iterator_Utils 'Iterator';
sub make_dfs_search {
my ($root, $children) = @_;
my @agenda = $root;
return Iterator {
return unless @agenda;
my $node = pop @agenda;
push @agenda, $children>($node);
return $node;
};
}
=endlisting makedfssearchsimple
With this formulation, C becomes:
=startlisting make_partition_dfs
sub make_partition {
my $n = shift;
my $root = [$n];
my $children = sub {
my ($largest, @rest) = @{shift()};
my $min = $rest[0]  1;
my $max = int($largest/2);
map [$largest$_, $_, @rest], ($min .. $max);
};
make_dfs_search($root, $children);
}
=endlisting make_partition_dfs
=test partitioniteratormakedfs
use Iterator_Utils 'Iterator';
do 'makedfssearchsimple';
do 'make_partition_dfs';
do 'partitioniteratorauxtest';
=endtest partitioniteratormakedfs
Factoring C into two parts in this way allows us to
reuse the C part.
We might outfit C with a filter that rejects
uninteresting items, since this is sure to be a common usage:
=startlisting makedfssearch
use Iterator_Utils 'Iterator';
sub make_dfs_search {
* my ($root, $children, $is_interesting) = @_;
my @agenda = $root;
return Iterator {
while (@agenda) {
my $node = pop @agenda;
push @agenda, $children>($node);
* return $node if !$is_interesting  $is_interesting>($node);
}
* return;
};
}
1;
=endlisting makedfssearch
We don't need this for C, since every node represents
a correct partition. But we might have needed it if we had used a
slightly clumsier implementation of the search:
=note or perhaps rewrite the code so that it generates and then
discards solutions where the parts are out of order?
=startlisting make_partition_dfs_search
require 'makedfssearch';
sub make_partition {
my $n = shift;
my $root = [$n, 1, []];
Here the nodes will have three parts: C<$n>, the part of the original
number that we haven't yet split off to any of the parts of the
partition; a minimum part size, initially 1; and a list of the parts
we've split off so far, initially empty.
my $children = sub {
my ($n, $min, $parts) = @{shift()};
map [$n$_, $_, [@$parts, $_]], ($min .. $n);
};
For each possible part size C<$_>, from the minimum C<$min> up to the
maximum C<$n>, we split off a new part of size C<$_>. To do this, we
subtract the size from C<$n>, indicating that we now have to apportion
a smaller value among the remaining parts; we adjust the minimum value
up to the new part size, so that any future parts are at least that
big and therefore the parts will be generated in order of increasing
size; and we append the new part to the list of parts.
Note that if C<$n \< $min>, there's no possible solution. An example
of such a node will occur when we try to partition the number 6 and we
first split off parts of sizes 2 and then 3. Then we're stuck: Only 1
remains, but C<2, 3, 1> is forbidden because the parts aren't in
increasing order.
my $is_complete = sub {
my ($n) = @{shift()};
$n == 0;
};
The partition is complete once we've reduced C<$n> to exactly 0.
By default, F interesting nodes from the agenda.
Here the nodes have extraneous information in them in addition to the
partitions themselves. So we'll wrap F in a call to
F that strips out the extra data, returning only the partition itself.
imap { $_>[2] }
make_dfs_search($root, $children, $is_complete);
}
=endlisting make_partition_dfs_search
=test partitioniteratormakedfssearch
use Iterator_Utils 'Iterator', 'imap';
# THIS TEST FAILS UGLILY
do 'makedfssearch';
do 'make_partition_dfs_search';
do 'partitioniteratorauxtest';
=endtest partitioniteratormakedfssearch
We could similarly outfit F with a callback to
evaluate nodes and allow the most valuable ones to be processed first.
If we did, we would want to rename it, because it would no longer be
doing DFS. To do this properly requires a good priority queue
implementation, which is outside the scope of the chapter. Here's an
inefficient implementation:
=startlisting makevaluesearch
sub make_dfs_value_search {
* my ($root, $children, $is_interesting, $evaluate) = @_;
* $evaluate = memoize($evaluate);
my @agenda = $root;
return Iterator {
while (@agenda) {
* my $best_node_so_far = 0;
* my $best_node_value = $evaluate>($agenda[0]);
* for (0 .. $#agenda) {
* my $val = $evaluate>($agenda[$_]);
* next unless $val > $best_node_value;
* $best_node_value = $val;
* $best_node_so_far = $_;
* }
* my $node = splice @agenda, $best_node_so_far, 1;
push @agenda, $children>($node);
return $node if !$is_interesting  $is_interesting>($node);
}
return;
};
}
=endlisting makevaluesearch
The inefficient part is the scan over the entire agenda and the
C. There are a number of ways to speed this up, but if it
matters, the priority queue is probably the best approach.
If we did do this, it would include DFS and BFS as easy special cases,
since we could use the following two valuations:
{
my ($d, $b) = (0, 0);
sub dfs_value { return $d++ }
sub bfs_value { return $b }
}
C, like a cantankerous grandfather, always reports the
value of an old node as being greater than that of the newer nodes;
C, like the staff at I magazine, does just the
opposite.
=note now you need an example where it's insufficient to simply
package up the arguments, and there's more state that must be encapsulated.
Perhaps something where there's a loop inside and you do for () { push
@result, recurse() }; then you need to track @result also.
Something that builds a tree will work okay. How about that clpm post
from way back that reads an text file and infers a tree from the indentation?
Can you find it?
=note Fibonacci is a fine example that should work here. Or perhaps Hanoi.
No, Fib didn't work. See 20020608 node in IDEAS file.
=note Here's an example you found on usenet:
# number of partitions into exactly p parts of size at least k each
sub D {
my ($n, $k, $p) = @_;
return $n == 0 if $p == 0;
return 0 if $n < $k * $p;
return 1 if $n == $k * $p;
return $D{$n,$k,$p} if exists $D{$n,$k,$p};
my $sum = 0;
for my $i ($k..$n) {
$sum += D($n$i, $i, $p1);
}
$D{$n,$k,$p} = $sum;
return $sum;
}
One possible trap to be aware of when using F is that
'depth first' doesn't necessarily define the search order uniquely.
Consider the following tree:
=startpicture treefordfs
++
 
++
,' `.
/ `.
++ ++
   
++ ++
 
 
++ ++
   
++ ++
=endpicture treefordfs
DFS says that once we visit a node, we must visit its children before
its siblings. But it doesn't say what order the siblings must be
visited in. Both of the following orders are depthfirst for this
tree:
=startpicture treedfsambiguous
++ ++
 1   1 
++ ++
,' `. ,' `.
/ `. / `.
++ ++ ++ ++
 2   4   4   2 
++ ++ ++ ++
   
   
++ ++ ++ ++
 3   5   5   3 
++ ++ ++ ++
=endpicture treedfsambiguous
Since the nodes generated by the call to C<$children> are pushed onto
the end of the agenda and then popped off from the end, the items will
be processed in the reverse of the order that C<$children> returned
them, with the last item in C<$children>'s return list processed
immediately. To prevent surprises, we'll make one final change to
C:
=startlisting makedfssearchfinal
sub make_dfs_search {
my ($root, $children, $is_interesting) = @_;
my @agenda = $root;
return Iterator {
while (@agenda) {
my $node = pop @agenda;
* push @agenda, reverse $children>($node);
return $node if !$is_interesting  $is_interesting>($node);
}
return;
};
}
=endlisting makedfssearchfinal
=test partitioniteratormakedfssearchfinal
use Iterator_Utils 'Iterator', 'imap';
# THIS TEST FAILS UGLILY
do 'makedfssearchfinal';
do 'make_partition_dfs_search';
do 'partitioniteratorauxtest';
=endtest partitioniteratormakedfssearchfinal
Now branches will be traversed in the order they were generated.
=note blah blah blah finish this section
=section Other general techniques for eliminating recursion
=subsection Tail Call Elimination
In addition the the agenda technique we looked at in detail in the
previous section, there are a few other techniques that are generally
useful for turning recursive functions into iterative ones.
One of the most useful is X.
First, let's consider the implementation of function calls generally.
Usually there is a stack. When function C wants to call C, it
pushes C's arguments onto this stack and transfers control to C.
C then removes the arguments from the stack, does its computations
(possibly including other function calls), pushes its intended return
value onto the stack, and transfers control back to C. C then
pops the return value off the stack and continues. If there are three
functions as follows:
sub A { A1; $B = B(...); A2; }
sub B { B1; $C = C(...); B2; return $Bval; }
sub C { C1; return $Cval; }
Then the sequence of events is:
A: A1;
Push B's arguments
B: Pop B's arguments
B1;
Push C's arguments
C: Pop C's arguments
C1;
Push C's return value
B: Pop C's return value
B2;
Push B's return value
A: Pop B's return value
A2;
Now let's suppose that function C is a little simpler, and doesn't
do anything except return after it calls C:
sub A { A1; $B = B(...); A2; }
* sub B { B1; return C(...); }
sub C { C1; return $Cval; }
The sequence of events is as before, up to C, which was
eliminated; and then goes like this:
...
C: Push C's return value
B: (There is no B2 any more)
Pop C's return value
Push B's return value (the same as C's)
A: Pop B's return value
A2;
All of C's work here is useless. Because C's return value is
the same as C's, all C is doing is removing C's return value
from the stack and then putting it back again immediately. A common
optimization in programming language implementations is to eliminate
the return to C entirely. The final call to C is known as a
X, and the optimization is called X. When function C is compiled, the compiler will
notice that the call from C to C is a tail call, and will
arrange for it to be done in a special way. Normally, C would
record its own address so that C would know where to transfer
control back to when it was finished. Instead, C erases its own
frame from the stack and lets C borrow the return information that
C originally got from C. When C returns, it will return
directly to C, bypassing C entirely:
...
C: Push C's return value
A: Pop C's return value (thinking it is B's)
A2;
This is the X. Perl could in principle
perform this optimization, but as of 5.8.0, it doesn't.
Now let's consider the X or
X function. This function takes two numbers, V and V,
and yields the greatest number V such that V divides evenly into
both V and V. There is always such a number, since 1 divides
evenly into both V and V, although the GCD is often larger than
1. For example, the GCD of 42 and 360 is 6, and the GCD of 48 and 20
is 4. Probably the most wellknown application of the GCD is in
putting fractions into lowest terms. Given a fraction, say 42/360,
one finds the GCD of the numerator and denominator, in this case 6,
and then cancels that factor from the top and bottom of the fraction,
giving 42/360 = 7*6 / 60 * 6 = 7/60. Similarly 48/20 = 12*4 / 5*4 = 12/5.
=note See Knuth vol 2 p335
There is a simple algorithm for calculating the GCD of two numbers,
called X, which is in fact the oldest surviving
nontrivial algorithm. Here it is translated into Perl:
=startlisting gcd
sub gcd {
my ($m, $n) = @_;
if ($n == 0) {
return $m;
}
return gcd($n, $m % $n);
}
=endlisting gcd
=auxtest gcdauxtest
# should this test be in another chapter?
is(gcd(48,20),4);
is(gcd(25,10),5);
is(gcd(90,99),9);
=endtest gcdauxtest
=test gcd
use Iterator_Utils 'Iterator';
do 'gcd';
do 'gcdauxtest';
=endtest gcd
The execution of C goes like this:
call gcd(48, 20) # Call A
call gcd(20, 8) # Call B
call gcd(8, 4) # Call C
call gcd(4, 0) # Call D
return 4
return 4
return 4
return 4
The stack manipulations are as follows:
original
caller: push 48, 20 onto stack
transfer control to 'gcd'
A: pop 48, 80 from stack
...
C: push 4,0 onto stack
transfer control to 'gcd'
D: pop 4, 0 from stack
push 4 onto stack
transfer control to 'gcd'
C: pop 4 from stack
push 4 onto stack
transfer control to 'gcd'
B: pop 4 from stack
push 4 onto stack
transfer control to 'gcd'
A: pop 4 from stack
push 4 onto stack
transfer control back to original caller
The tailcall optimization allows call C to return the 4 directly
back to the original caller, skipping all the steps at the end.
Since Perl doesn't perform the tailcall optimization automatically,
we can help it out. The tailcall optimization would normally replace
the current call frame with the one for the function being called.
Perl won't do that internally, but since the call frame has nothing in
it except a bunch of variable bindings, we can accomplish the same
thing but just rebinding the variables to the appropriate variables.
'Transfer control to C', which normally means 'create a new call
frame and active it' just becomes 'transfer control back the top of
the current function'in other words, a local C. Since
C itself is considered naughty, we'll use a loop, which is the
same thing:
=listing gcd2
sub gcd {
my ($m, $n) = @_;
* until ($n == 0) {
* ($m, $n) = ($n, $m % $n);
* }
return $m;
}
=endlisting gcd2
=test gcd2
use Iterator_Utils 'Iterator';
do 'gcd2';
do 'gcdauxtest';
=endtest gcd2
The condition for performing the C loop is the same as the one
guarding the recursive call in the old code. In the original
function, we made a recursive call unless C<$n> was zero; here we
perform the loop body. The body of the loop transforms the arguments
C<$m> and C<$n> in the same way that the recursive code in the
original function did, replacing C<$m> with C<$n> and C<$n> with C<$m
% $n>. Thus the C loop sets up the new values of C<$m> and
C<$n> that would have been seen by the recursivelycalled instance of
C, and then effectively restarts the function. In the case C<$n
== 0>, there is no recursivelycalled instance, so we skip that step
and just return immediately.
Here's another example: printing the elements of a sorted binary tree
in order. The recursive code looks like this:
sub print_tree {
my $t = shift;
return unless $t; # Null tree
print_tree($t>left);
print $t>root, "\n";
print_tree($t>right);
}
Replacing the tail call with a loop yields this version:
sub print_tree {
my $t = shift;
* while ($t) {
print_tree($t>left);
print $t>root, "\n";
* $t = $t>right;
}
}
Here we've replaced the tail call, Cright)>, with
code that modifies C<$t> appropriately, replacing it with
C<$t\>right>, and then jumps back up to the top of the function.
Since Cleft)> isn't a tail call, we can't eliminate
it in this way. We'll eliminate it in a different way later on.
A variation of F handles the emptytree case before the
recursive calls, instead of afterwards, potentially optimizing away
many such calls:
sub print_tree {
my $t = shift;
print_tree($t>left) if $t>left;
print $t>root, "\n";
print_tree($t>right) if $t>right;
}
Eliminating the tail call yields:
sub print_tree {
my $t = shift;
* do {
print_tree($t>left) if $t>left;
print $t>root, "\n";
* $t = $t>right;
* } while $t;
}
=subsubsection Someone Else's Problem
=note XXX WE NEED TO GET PERMISSION HERE!!
Here's a particularly interesting example, taken from pages 237238 of
I, by Orwant, Hietaniemi, and
Macdonald. Given a set of keyvalue pairs (represented as a hash, of
course), it returns the X of that set. This is the set
of all hashes that can be obtained from the original hash by deleting
zero or more of the pairs.
For example, the power set of C<{apple =\> 'red', banana =\> 'yellow',
grape =\> 'purple'}> is
{apple => 'red', banana => 'yellow', grape => 'purple'}
{apple => 'red', banana => 'yellow'}
{apple => 'red', grape => 'purple'}
{apple => 'red'}
{banana => 'yellow', grape => 'purple'}
{banana => 'yellow'}
{grape => 'purple'}
{}
The power set is returned as a hash of hashes. The keys of the return
value are unimportant, and the values are the elements of the power
set. Here's the code that Orwant and the others present:
=listing powerset_recurse0
sub powerset_recurse ($;@) {
my ( $set, $powerset, $keys, $values, $n, $i ) = @_;
if ( @_ == 1 ) { # Initialize.
my $null = { };
$powerset = { $null, $null };
$keys = [ keys %{ $set } ];
$values = [ values %{ $set } ];
$nmembers = keys %{ $set }; # This many rounds.
$i = 0; # The current round.
}
# Ready?
return $powerset if $i == $nmembers;
# Remap.
my @powerkeys = keys %{ $powerset };
my @powervalues = values %{ $powerset };
my $powern = @powerkeys;
my $j;
for ( $j = 0; $j < $powern; $j++ ) {
my %subset = ( );
# Copy the old set to the subset.
@subset{keys %{ $powerset>{ $powerkeys [ $j ] } }} =
values %{ $powerset>{ $powervalues[ $j ] } };
# Add the new member to the subset.
$subset{$keys>[ $i ]} = $values>[ $i ];
# Add the new subset to the powerset.
$powerset>{ \%subset } = \%subset;
}
# Recurse.
powerset_recurse( $set, $powerset, $keys, $values, $nmembers, $i+1 );
}
=endlisting powerset_recurse0
Clearly, the recursive call here is a tail call. Applying the usual
tailcall optimization, we can replace the recursive call with a loop.
The special case initialization for the last five parameters no longer
needs to be a special case; we just take care of the initialization
before we enter the loop. The peculiar C<($;@)> prototype goes away
entirely, or maybe becomes C<($)>.
=listing powerset_recurse1
* sub powerset_recurse ($) {
* my ( $set ) = @_;
my $null = { };
my $powerset = { $null, $null };
my $keys = [ keys %{ $set } ];
my $values = [ values %{ $set } ];
my $nmembers = keys %{ $set }; # This many rounds.
my $i = 0; # The current round.
* until ($i == $nmembers) {
# Remap.
my @powerkeys = keys %{ $powerset };
my @powervalues = values %{ $powerset };
my $powern = @powerkeys;
my $j;
for ( $j = 0; $j < $powern; $j++ ) {
my %subset = ( );
# Copy the old set to the subset.
@subset{keys %{ $powerset>{ $powerkeys [ $j ] } }} =
values %{ $powerset>{ $powervalues[ $j ] } };
# Add the new member to the subset.
$subset{$keys>[ $i ]} = $values>[ $i ];
# Add the new subset to the powerset.
$powerset>{ \%subset } = \%subset;
}
* $i++;
}
* return $powerset;
}
=endlisting powerset_recurse1
Now we can see that C<$i>, the loop counter variable, just runs from 0
up to C<$nmembers1>, so we can rewrite the C loop as a C
loop:
=listing powerset_recurse2
sub powerset_recurse ($) {
my ( $set ) = @_;
my $null = { };
my $powerset = { $null, $null };
my $keys = [ keys %{ $set } ];
my $values = [ values %{ $set } ];
my $nmembers = keys %{ $set }; # This many rounds.
* for my $i (0 .. $nmembers1) {
# Remap.
my @powerkeys = keys %{ $powerset };
my @powervalues = values %{ $powerset };
my $powern = @powerkeys;
my $j;
for ( $j = 0; $j < $powern; $j++ ) {
my %subset = ( );
# Copy the old set to the subset.
@subset{keys %{ $powerset>{ $powerkeys [ $j ] } }} =
values %{ $powerset>{ $powervalues[ $j ] } };
# Add the new member to the subset.
$subset{$keys>[ $i ]} = $values>[ $i ];
# Add the new subset to the powerset.
$powerset>{ \%subset } = \%subset;
}
}
return $powerset;
}
=endlisting powerset_recurse2
Now that we've done this, it appears that the only purpose of C<$i> is
to index C<@$keys> and C<@$values>. Since these are precisely the
keys and values of C<%$set>, we can eliminate all three variables in
favor of a simple C loop:
=listing powerset_recurse3
sub powerset_recurse ($) {
my ( $set ) = @_;
my $null = { };
my $powerset = { $null, $null };
* while (my ($key, $value) = each %$set) {
# Remap.
my @powerkeys = keys %{ $powerset };
my @powervalues = values %{ $powerset };
my $powern = @powerkeys;
my $j;
for ( $j = 0; $j < $powern; $j++ ) {
my %subset = ( );
# Copy the old set to the subset.
@subset{keys %{ $powerset>{ $powerkeys [ $j ] } }} =
values %{ $powerset>{ $powervalues[ $j ] } };
# Add the new member to the subset.
* $subset{$key} = $value;
# Add the new subset to the powerset.
$powerset>{ \%subset } = \%subset;
}
}
return $powerset;
}
=endlisting powerset_recurse3
If we're feeling sharp, we might notice the same thing about C<$j>:
=listing powerset_recurse4
sub powerset_recurse ($) {
my ( $set ) = @_;
my $null = { };
my $powerset = { $null, $null };
while (my ($key, $value) = each %$set) {
* my @newitems;
* while (my ($powerkey, $powervalue) = each %$powerset) {
my %subset = ( );
# Copy the old set to the subset.
* @subset{keys %{ $powerset>{$powerkey} } } =
* values %{ $powerset>{$powervalue} };
# Add the new member to the subset.
$subset{$key} = $value;
# Prepare to add the new subset to the powerset.
* push @newitems, \%subset;
}
* $powerset>{ $_ } = $_ for @newitems;
}
return $powerset;
}
=endlisting powerset_recurse4
=auxtest powerset_recurse_auxtest
sub Same {
my ($a, $b) = @_;
return unless ref($a) eq ref($b);
unless (ref $a && ref $b) {
return $a eq $b;
}
my $art = ref($a);
if ($art eq "ARRAY") {
return unless @$a == @$b;
# But order doesn't matter
my @B_used;
A: for my $i (0 .. $#$a) {
B: for my $j (0 .. $#$b) {
next B if $B_used[$j];
if (Same($a>[$i], $b>[$j])) {
$B_used[$j] = 1;
next A;
}
}
return; # No unused B element corresponds to this A element
}
return 1;
} elsif ($art eq "HASH") {
return unless keys(%$a) == keys (%$b);
for my $k (keys %$a) {
return unless exists $b>{$k} && Same($a>{$k}, $b>{$k});
}
for my $k (keys %$b) {
return unless exists $a>{$k};
}
return 1;
} else {
die "I don't know how to handle references of type '$art'";
}
}
ok(Same([1,4,2,8,5,7], [1,2,4,5,7,8]));
$result = [
{apple => 'red', banana => 'yellow', grape => 'purple'},
{apple => 'red', banana => 'yellow'},
{apple => 'red', grape => 'purple'},
{apple => 'red'},
{banana => 'yellow', grape => 'purple'},
{banana => 'yellow'},
{grape => 'purple'},
{},
];
$source = {apple => 'red', banana => 'yellow', grape => 'purple'};
my $recursed = [ values %{powerset_recurse ( $source )} ];
#use Data::Dumper;
#print Dumper $recursed;
#print Dumper $result;
# this is failing. The deep contents of the structure _are_ the
# same. Moving on!
ok( Same( $recursed, $result ));
=endtest powerset_recurse_auxtest
=test powerset_recurse
# use Iterator_Utils 'Iterator';
do 'powerset_recurse0';
do 'powerset_recurse_auxtest';
do 'powerset_recurse1';
do 'powerset_recurse_auxtest';
do 'powerset_recurse2';
do 'powerset_recurse_auxtest';
do 'powerset_recurse3';
do 'powerset_recurse_auxtest';
do 'powerset_recurse4';
do 'powerset_recurse_auxtest';
=endtest powerset_recurse
Getting rid of the unnecessary recursion made the state changes of the
variables clearer and kicked off a series of simplifications that left
the function with about 1/3 less code.
=subsection Creating Tail Calls
Often, a function that doesn't have a tail call can be easily
converted into one that does. For example, consider the
decimaltobinary conversion function of R:
=inline binary
Here the recursive call isn't in the tail position. The return value
from the recursive call isn't returned directly, but rather is
concatenated to C<$b>.
The general technique for converting such a function to one that does a
tail call is to add an auxiliary parameter that records the return
value so far. When the other parameters indicate that the recursion
is complete, the function returns the returnvalue parameter. Instead
of making a recursive call, waiting for the return value, modifying
it, and returning the result, the modified version takes the return
value parameter, modifies it appropriately, and passes it along. When
we apply this idea to the F function, we get this:
=auxtest binaryauxtest
for (0..50) {
my $bin = sprintf "%b", $_;
my $b2 = binary($_);
is($b2, $bin, "$_ => binary");
}
=endtest
=startlisting binary1
sub binary {
my ($n, $RETVAL) = @_;
$RETVAL = "" unless defined $RETVAL;
my $k = int($n/2);
my $b = $n % 2;
$RETVAL = "$b$RETVAL";
return $RETVAL if $n == 0  $n == 1;
binary($k, $RETVAL);
}
=endlisting binary1
C<$RETVAL> records the bit sequence computed so far; if unspecified,
it defaults to the empty string. On each call, we append a new bit to
this bit string. If C<$n> is 0 or 1, that's the base case, and we
just return the bit string; otherwise, we make a recursive call with
the new value of C<$n> and the new bit string.
Applying the tailcall optimization to this version of F
yields:
=startlisting binary2
sub binary {
my ($n, $RETVAL) = @_;
* $RETVAL = "";
* while (1) {
my $k = int($n/2);
my $b = $n % 2;
$RETVAL = "$b$RETVAL";
return $RETVAL if $n == 0  $n == 1;
* $n = $k;
}
}
=endlisting binary2
and then optimizing away the unnecessary C<$k>:
=startlisting binary3
sub binary {
my ($n, $RETVAL) = @_;
$RETVAL = "";
while (1) {
my $b = $n % 2;
$RETVAL = "$b$RETVAL";
return $RETVAL if $n == 0  $n == 1;
* $n = int($n/2);
}
}
=endlisting binary3
=test binary123 204
print "#binary\n";
do 'binary';
do 'binaryauxtest';
print "#binary1\n";
do 'binary1';
do 'binaryauxtest';
print "#binary2\n";
do 'binary2';
do 'binaryauxtest';
print "#binary3\n";
do 'binary3';
do 'binaryauxtest';
=endtest
Adding an extra parameter to the F function transforms
this:
=startlisting factorial0
sub factorial {
my ($n) = @_;
return 1 if $n == 0;
return factorial($n1) * $n;
}
=endlisting factorial0
into this:
=startlisting factorial1
sub factorial {
my ($n, $product) = @_;
$product = 1 unless defined $product;
return $product if $n == 0;
return factorial($n1, $n * $product);
}
=endlisting factorial1
Then we can eliminate the tail call:
=startlisting factorial2
sub factorial {
my ($n) = @_;
my $product = 1;
until ($n == 0) {
$product *= $n;
$n;
}
return $product;
}
=endlisting factorial2
=auxtest factorialauxtest
my @fact = (1, 1, 2, 6, 24, 120, 720, 5040, 40320);
for (0 .. $#fact) {
my $fact = factorial($_);
is($fact, $fact[$_], "$_!");
}
=endtest factorialauxtest
=test factorial012
do 'factorial0';
do 'factorialauxtest';
do 'factorial1';
do 'factorialauxtest';
do 'factorial2';
do 'factorialauxtest';
=endtest
=subsection Explicit Stacks
When we last left the F example, it looked like this:
sub print_tree {
my $t = shift;
do {
print_tree($t>left) if $t>left;
print $t>root, "\n";
$t = $t>right;
} while $t;
}
The original function had two recursive calls, one of which was a tail
call, and was eliminated in this version. The other call remains.
To get rid of a recursive call embedded in the middle of a function
may require heavy machinery. The heaviest machinery is to explicitly
simulate the same stack operations that Perl normally performs
implicitly on function call and return. Making a recursive call
records the function's current state on the stack, and returning from
a call pops the stack. The function's current state, as we saw
earlier, may in general include all of its local variables and
parameters.
The state of F comprises nothing more than C<$t>, the tree
argument itself. So our statesaving operation will be simple.
We replace the recursive call Cleft)> with a stack push:
sub print_tree {
my $t = shift;
* my @STACK;
do {
* push(@STACK, $t), $t = $t>left if $t>left;
and then, in place of the function return, we add a stack pop and a
jump back to the line right after the recursive call:
* RETURN:
print $t>root, "\n";
$t = $t>right;
} while $t;
* return unless @STACK;
* $t = pop @STACK;
* goto RETURN;
}
(Or, if the stack is empty, then we return from the function instead
of popping.)
One objection to this is likely to be that it uses X>, which
people think is naughty. We can get rid of the goto by transforming
the code to this:
sub print_tree {
my $t = shift;
my @STACK;
* RIGHT: {
push(@STACK, $t), $t = $t>left while $t>left;
* do {
print $t>root, "\n";
$t = $t>right;
* redo RIGHT if $t;
return unless @STACK;
$t = pop @STACK;
* } while 1;
}
}
This is really the same thing, except we have cosmetically disguised
the F as a C loop, and turned the old C loop
into a C. Loop control statements such as C, C, and
C are no more than Cs in disguise, of course, and in fact
so are loops.
=subsubsection Eliminating Recursion from F
Let's apply the same process to the Fibonacci function:
=startlisting fib0
sub fib {
my $n = shift;
if ($n < 2) { return $n }
fib($n2) + fib($n1);
}
=endlisting fib0
There are no tail calls here. The C looks like it might
be, but it isn't, because it's not the very last thing the function
does before it returns; the addition is. So we can't use tail call
elimination. Instead, we'll roll out the heavy guns and manage the
stack explicitly.
The state tracked by F is more complicated than in the
F example. The parameter C<$n> is clearly part of the
state, but there is some additional state that isn't so obvious.
Since there are two recursive calls to C, after we return from a
recursive call, we have to remember how to pick up where we left off:
Were we about to make the second call, or were we about to perform the
addition? Moreover, during the second recursive call, the function's
state must include the result from the first recursive call.
In difficult cases, the first step in eliminating recursive calls is
to make this state explicit. We rewrite F as follows:
=startlisting fib1
sub fib {
my $n = shift;
if ($n < 2) {
return $n;
} else {
* my $s1 = fib($n2);
* my $s2 = fib($n1);
* return $s1 + $s2;
}
}
=endlisting fib1
The second step is introduce a loop to separate the initialization of
the function from the body:
=startlisting fib2
sub fib {
my $n = shift;
* while (1) {
if ($n < 2) {
return $n;
} else {
my $s1 = fib($n2);
my $s2 = fib($n1);
return $s1 + $s2;
}
* }
}
=endlisting fib2
Eventually, we'll have a stack that simulates Perl's call stack; the
loop we just introduced is simulating Perl itself.
Third, break the body into chunks, each of which contains the code
from the end of one recursive call to the beginning of the next.
Breaks may occur in the middle of a statement. For example, in C, the C<$n1> is computed before the call, but the
assignment is done after the call, in a separate chunk. Put each
chunk in a separate branch of an C tree:
=startlisting fib3
sub fib {
my $n = shift;
* my ($s1, $s2, $return);
while (1) {
if ($n < 2) {
return $n;
} else {
* if ($BRANCH == 0) {
* $return = fib($n2);
* } elsif ($BRANCH == 1) {
* $s1 = $return;
* $return = fib($n1);
* } elsif ($BRANCH == 2) {
* $s2 = $return;
* $return = $s1 + $s2;
}
}
}
}
=endlisting fib3
Because a statement like C<$s1 = fib($n2)> was split across chunks,
I've introduced a temporary value, C<$return>, to hold the return
value from C until it can be assigned to C<$s1>. I've also
moved the declaration of C<$s1> and C<$s2> up to the top of the
function. Our new F function is effectively simulating the
behavior of the old one, and C<$s1> and C<$s2> represent information
about the functions' internal state that are normally traced
internally by Perl. They are therefore global to the function itself.
Similarly, C<$BRANCH> will record where in the function we left off to
make a recursive call. This is another thing Perl normally tracks
internally. Initially, it's 0, indicating that we want to start at
the top of the body. When we simulate a return from a recursive call,
it will be 1 or 2, telling us to pick up later on in the body where we
left off.
=startlisting fib4
sub fib {
my $n = shift;
my ($s1, $s2, $return);
* my $BRANCH = 0;
while (1) {
if ($n < 2) {
return $n;
} else {
if ($BRANCH == 0) {
$return = fib($n2);
} elsif ($BRANCH == 1) {
$s1 = $return;
$return = fib($n1);
} elsif ($BRANCH == 2) {
$s2 = $return;
$return = $s1 + $s2;
}
}
}
}
=endlisting fib4
Returning directly from the middle of the C loop is
inappropriate, because the simulated stack might not be empty. So
we'll convert any remaining Cs into assignments to
C<$return>. Later on in the function, we'll return the contents of
C<$return> if the simulated stack is empty:
=startlisting fib5
sub fib {
my $n = shift;
my ($s1, $s2, $return);
my $BRANCH = 0;
while (1) {
if ($n < 2) {
* $return = $n;
} else {
if ($BRANCH == 0) {
$return = fib($n2);
} elsif ($BRANCH == 1) {
$s1 = $return;
$return = fib($n1);
} elsif ($BRANCH == 2) {
$return = $s1 + $s2;
}
}
}
}
=endlisting fib5
Step 5 is the important one: Replace all the recursive calls with code
that pushes the function state onto the synthetic stack and then
transfers control back to the top of the function.
=startlisting fib6
sub fib {
my $n = shift;
my ($s1, $s2, $return);
my $BRANCH = 0;
* my @STACK;
while (1) {
if ($n < 2) {
$return = $n;
} else {
if ($BRANCH == 0) {
* push @STACK, [ $BRANCH, $s1, $s2, $n ];
* $n = 2;
* $BRANCH = 0;
* next;
} elsif ($BRANCH == 1) {
$s1 = $return;
* push @STACK, [ $BRANCH, $s1, $s2, $n ];
* $n = 1;
* $BRANCH = 0;
* next;
} elsif ($BRANCH == 2) {
$s2 = $return;
$return = $s1 + $s2;
}
}
}
}
=endlisting fib6
Since this is important, let's look at one of the calls in detail.
When F calls C, it saves all its state and then
transfers control back to the top of F, which starts up just as
before, but with argument C<$n2> instead of C<$n>. The code we put
in is doing exactly that. It saves the current state on the stack:
push @STACK, [ $BRANCH, $s1, $s2, $n ];
Then it adjusts the value of the argument from C<$n> to C<$n2>:
$n = 2;
Then it adjusts the value of C<$BRANCH> to say that control should
continue from the top of the function, not the middle:
$BRANCH = 0;
(This was unnecessary in this case, since $BRANCH was already 0, but
I left it in for symmetry with the second branch, where it is needed.)
Finally, we transfer control back up to the top:
next;
We're almost done. We've simulated the recursive calls, and the last
thing we need to do is simulate the returns. The function's desired
return value is in C<$return>. To simulate a function return, check
to see if the synthetic stack is empty. If so, then the function is
really returning to its caller, and should just return C<$return>.
Otherwise, we pop the saved state off the stack and resume execution
where we left off:
=startlisting fib7
sub fib {
my $n = shift;
my ($s1, $s2, $return);
my $BRANCH = 0;
my @STACK;
while (1) {
if ($n < 2) {
$return = $n;
} else {
if ($BRANCH == 0) {
push @STACK, [ $BRANCH, $s1, $s2, $n ];
$n = 2;
$BRANCH = 0;
next;
} elsif ($BRANCH == 1) {
$s1 = $return;
push @STACK, [ $BRANCH, $s1, $s2, $n ];
$n = 1;
$BRANCH = 0;
next;
} elsif ($BRANCH == 2) {
$s2 = $return;
$return = $s1 + $s2;
}
}
* return $return unless @STACK;
* ($BRANCH, $s1, $s2, $n) = @{pop @STACK};
* $BRANCH++;
}
}
=endlisting fib7
We increment C<$BRANCH> so that execution will resume with the chunk
I the one we were in when we made the call.
And amazingly, we're now done. The function above does indeed compute
Fibonacci numbers.
Because I was showing a general transformation of a recursive into a
nonrecursive function, the result has some unnecessary code. For
example, I included an unnecessary C<$BRANCH = 0> line for symmetry.
In branch 1, we assigned C<$s1> from C<$return> and then immediately
push its value onto the stack; we may as well push C<$return> directly
onto the stack without the intervening assignment. In branch 0, we
push C<$s1> into the stack, but its value is always undefined at this
point, so we may as well just push C<0> directly.
=startlisting fib8
sub fib {
my $n = shift;
my ($s1, $s2, $return);
my $BRANCH = 0;
my @STACK;
while (1) {
if ($n < 2) {
$return = $n;
} else {
if ($BRANCH == 0) {
* push @STACK, [ $BRANCH, 0, $s2, $n ];
$n = 2;
next;
} elsif ($BRANCH == 1) {
* push @STACK, [ $BRANCH, $return, $s2, $n ];
$n = 1;
$BRANCH = 0;
next;
} elsif ($BRANCH == 2) {
$s2 = $return;
$return = $s1 + $s2;
}
}
return $return unless @STACK;
($BRANCH, $s1, $s2, $n) = @{pop @STACK};
$BRANCH++;
}
}
=endlisting fib8
Performing the same sort of eliminations for C<$s2> as we did for
C<$s1>, we discover that C<$s2> is I unnecessary.
The only place it's used is in branch 2, and it's used
immediately after it's assigned.
=startlisting fib9
sub fib {
my $n = shift;
* my ($s1, $return);
my $BRANCH = 0;
my @STACK;
while (1) {
if ($n < 2) {
$return = $n;
} else {
if ($BRANCH == 0) {
* push @STACK, [ $BRANCH, 0, $n ];
$n = 2;
next;
} elsif ($BRANCH == 1) {
* push @STACK, [ $BRANCH, $return, $n ];
$n = 1;
$BRANCH = 0;
next;
} elsif ($BRANCH == 2) {
* $return += $s1;
}
}
return $return unless @STACK;
* ($BRANCH, $s1, $n) = @{pop @STACK};
$BRANCH++;
}
}
=endlisting fib9
We might also optimize branch 0 a little. In branch 0, we push the
stack, decrement C<$n> by 2, and pass control back to the top of the
function. Typically, we then come back immediately and do it again,
forming a loop. We can tighten up the loop:
=startlisting fib10
sub fib {
my $n = shift;
my ($s1, $return);
my $BRANCH = 0;
my @STACK;
while (1) {
if ($n < 2) {
$return = $n;
} else {
if ($BRANCH == 0) {
* push (@STACK, [ $BRANCH, 0, $n ]), $n = 2 while $n >= 2;
* $return = $n;
} elsif ($BRANCH == 1) {
push @STACK, [ $BRANCH, $return, $n ];
$n = 1;
$BRANCH = 0;
next;
} elsif ($BRANCH == 2) {
$return += $s1;
}
}
return $return unless @STACK;
($BRANCH, $s1, $n) = @{pop @STACK};
$BRANCH++;
}
}
=endlisting fib10
Since that tight loop is more efficient than the large main loop, we'd
like to do it as often as possible. As it is, though, we only do it
about M times. Since it doesn't matter whether F makes the
C or the C call first, we can exchange the first
and second chunks, giving us:
=startlisting fib11
sub fib {
my $n = shift;
my ($s1, $return);
my $BRANCH = 0;
my @STACK;
while (1) {
if ($n < 2) {
$return = $n;
} else {
if ($BRANCH == 0) {
* push (@STACK, [ $BRANCH, 0, $n ]), $n = 1 while $n >= 2;
$return = $n;
} elsif ($BRANCH == 1) {
push @STACK, [ $BRANCH, $return, $n ];
* $n = 2;
$BRANCH = 0;
next;
} elsif ($BRANCH == 2) {
$return += $s1;
}
}
return $return unless @STACK;
($BRANCH, $s1, $n) = @{pop @STACK};
$BRANCH++;
}
}
=endlisting fib11
This is a little faster than the previous version.
We can also clean up one more line of code by eliminating C<$BRANCH++>
at the bottom. Instead of pushing the old value of C<$BRANCH> onto
the stack and then incrementing it after we pop it again, we'll just
push the value of C<$BRANCH> that we want to have when we return:
=note the BRANCH argument is analogous to a continuation;
one might even say that it *is* a continuation.
=startlisting fib12
sub fib {
my $n = shift;
my ($s1, $return);
my $BRANCH = 0;
my @STACK;
while (1) {
if ($n < 2) {
$return = $n;
} else {
if ($BRANCH == 0) {
* push (@STACK, [ 1, 0, $n ]), $n = 1 while $n >= 2;
$return = $n;
} elsif ($BRANCH == 1) {
* push @STACK, [ 2, $return, $n ];
$n = 2;
$BRANCH = 0;
next;
} elsif ($BRANCH == 2) {
$return += $s1;
}
}
return $return unless @STACK;
($BRANCH, $s1, $n) = @{pop @STACK};
}
}
=endlisting fib12
=auxtest fibauxtest
my @fib = (1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377);
for (0..$#fib) {
is(fib($_), $fib[$_], "fib element $_");
}
=endtest
=test fib012
for my $f (0..12) {
do "fib$_";
do 'fibauxtest';
}
=endtest fib012
There are several things we can learn from all of this. Most
important, it affords us a detailed look into what is really required
to implement recursive calls. Many of the small tweaks and
optimizations we applied at the end of the conversion process are
directly analogous to optimizations that compilers and interpreters
can perform internally.
Recursion elimination may also be useful in reducing the memory
footprint of a function. With Perl's builtin recursion, you don't
get a choice about what state is saved on the stack: absolutely
everything is saved. Once we have the stack represented explicitly in
the program, it may become clear that not everything needs to be saved
on every call, and we may be able to reduce stack usage, as we did by
eliminating C<$s2>.
Finally, in some cases it will turn out that the iterative version of
the code is faster or simpler than the recursive version. In these
cases, such as the power set function above, the simplifications
suggested by recursion elimination may lead to a cascade of further
simplifications.
=stop
Ins and outs of derecursion via stacks
* Motivator: Web robot doesn't work (DFS)
* partition problem (find one share)
** partitioning sets completely into several shares (requires backtracking)
** don't modify subtractive list; pass an index to it
* partitionsofaninteger problem (Goff's)
* medialpairs problem
** memoryhogging version
** anecdote about it going berserk first time I ran it
** solution 1: delayed node construction (synthetic version)
** solution 2: delayed node construction via thunks
** solution 3: state in global variables which is saved and restored
** solution 4: linked list for additive sets
* GlobWalker
* BFS to find GPG signature chain
* Reread 20001018 notes
* Maybe pattern matching as an example?
LocalWords: Hoefler's startlisting endlisting imap subsubsection upto gcd
LocalWords: bulletedlist endbulletedlist startpicture endpicture powerset