Why is my Perl loop off by one at the end?
-
25-09-2019 - |
Question
I have this program which does not work as expected. Help me.
I want to print a row heading.
If input is 4
, I want 1|2|3|4
to be output.
It does not work as all, if I hard-code $count
value it works partially but the last number is missing.
sub printC {
my $count = @_;
# count = 4 # works partially only prints 1|2|3
for(my $i=1;$i<$count;$i++) {
print "$i|";
}
print $i;
}
$count = 2;
&printC($count);
print "\n";
Solution
The problem is here:
my $count = @_;
The assignment is happening in a scalar context which assigns the number of elements in array @_
to $count
, which your case is 1
, as you are passing 1
argument to the function.
To fix this you can do:
my ($count) = @_;
or
my $count = $_[0];
here is another problem:
for(my $i=1.....
^^
By using my
you've made $i
local to the body of for
and it'll not be available outside it. So your final print
outside the for
is not printing anything. To fix this move the declaration of $i
outside the loop:
sub printC {
my ($count) = @_;
my $i;
....
Always make it a point to write
use strict;
at the top of your program which enables you to catch such bugs.
The perl way of doing what your function does is:
print join('|',1..$count);
OTHER TIPS
A real Perl hacker might write something like this:
sub printC {
my $count = shift;
print join "|", (1 .. $count);
}
Once you understand how this works, you'll find you've learned some more about Perl. :-)
$i
does not exist once you've left the for
loop because of where it's declared.
You could do
sub printC {
my ($count) = @_;
my $i;
for ($i = 1; $i < $count; $i++) {
print "$i|";
}
print $i;
}
Even simpler:
sub printC {
my ($count) = @_;
print join("|", 1 .. $count) . "\n";
}
One other bit of "being more Perlish" is to not use the C-style for
loop. There is almost never a need to use a C-style for
in Perl.
Instead of
for(my $i=1;$i<$count;$i++) { ... }
use
for my $i (1 .. $count) { ... }
They're almost equivalent, except the latter version is both more easily readable and more resistant to off-by-one errors. (The reason your code was only printing 1|2|3
instead of 1|2|3|4
is because the test in your C-style for
was checking $i<$count
when it should have been $i<=$count
- this is a very common mistake with C-style for
loops which for (list)
avoids completely.)
Also, don't prefix your sub calls with &
. It's a holdover from Perl 4 which is no longer needed in Perl 5 and has side effects which you probably aren't aware of and probably don't want. Just use printC($count)
instead of &printC($count)
.
But, yeah, in this particular case, join
is probably a better approach than for
anyhow.