Monday, July 20, 2009

Pascal's triangle, Perl, and homework

Frequently, an evil professor gives to his students homework and sometimes it may be a request to write code which prints Pascal's triangle. Naturally, the student cranks up Firefox (that is synonym for web browser) and goes googling around for examples. A nice elegant solution comes up in the form of:

sub pascal { 
my @row;
foreach (1 .. shift) {
push @row => 1;
$row [$_] += $row [$_ - 1] for reverse 1 .. @row - 2;
print "@row\n";
}
}


I picked up that one on http://www.perlmonks.org/?node=175586, naturally very few students will understand, right away, what is happening in this code. In order to make the code shorter the author did some Perl tricks, which made it difficult to read and understand the code. In the first transformation I will introduce one unnecessary variable and also make function call more C or Java like.

sub pascal {
my $size = shift();
my @row;
foreach (1 .. $size) {
push (@row, 1);
$row [$_] += $row [$_ - 1] for reverse 1 .. @row - 2;
print "@row\n";
}
}


Already looks better. Inner loop still looks odd and we are not quite sure what it does and which $_ is used, one from foreach or one from for reverse. In order to see, let us add one debug loop:

sub pascal {
my $size = shift();
my @row;
foreach (1 .. $size) {
push (@row, 1);
for (reverse 1 .. @row - 2){
print $row [$_]," + ",$row [$_ - 1]," store in \$row[$_]\n";
}
$row [$_] += $row [$_ - 1] for reverse 1 .. @row - 2;
print "@row\n";
}
}


Output will look like this:

1 
1 1
1 + 1 store in $row[1]
1 2 1
1 + 2 store in $row[2]
2 + 1 store in $row[1]
1 3 3 1
1 + 3 store in $row[3]
3 + 3 store in $row[2]
3 + 1 store in $row[1]
1 4 6 4 1


Now it is obvious that outer loop just pushes ones and inner loop does all difficult work.
One small thing is left, printout doesn't look nice, it is somehow skew. To remedy that we add some pretty printing:
sub pascal {
my $size = shift;
my $count;
my $margin;
my @row;
foreach (1 .. $size) {
push (@row, 1);
$row [$_] += $row [$_ - 1] for reverse 1 .. @row - 2;
$margin = " " x ($size - $count - 1);
print "$margin";
$count++;
foreach (@row){
printf "%6d",$_;
}
print "\n";
}


If we call it and pass 16 as parameter it will print this:

                                                 1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
1 9 36 84 126 126 84 36 9 1
1 10 45 120 210 252 210 120 45 10 1
1 11 55 165 330 462 462 330 165 55 11 1
1 12 66 220 495 792 924 792 495 220 66 12 1
1 13 78 286 715 1287 1716 1716 1287 715 286 78 13 1
1 14 91 364 1001 2002 3003 3432 3003 2002 1001 364 91 14 1
1 15 105 455 1365 3003 5005 6435 6435 5005 3003 1365 455 105 15 1


Not quite equilateral, one would say.

1 comment: