The following snippet of Perl is supposed to print the first 5 items of an array referenced by a hash value, or fewer if the array is shorter.
while ( my ($key,$value) = each %groups ) {
print "$key: \n";
my @list = grep defined, @{$value};
my @slice = grep defined, @list[0..4];
foreach my $item ( @slice ) {
print " $item \n";
}
print " (", scalar @slice, " of ", scalar @list, ")\n";
}
I don't think the first grep defined
is necessary, but it can't do any harm, and it should guarantee that there are no undefined array members before the slice. The second grep defined
is to remove undefined array members in the result of slice
when @list
is shorter than 5.
%groups
has been populated by repeated invocations of:
$groups{$key} = () unless defined $groups{$key};
push @{$groups{$key}}, $value;
Most of the time it works fine:
key1:
value1
value2
value3
value4
value5
(5 of 100)
But sometimes -- and I haven't worked out under what circumstances -- I see:
key2:
value1
(1 of 5)
key3:
value1
value2
(2 of 5)
I expect the length of the printed list, and x
from (x of y)
to be min(5,y)
What could cause this behaviour?
Using grep
with an array slice for @list
autovivifies the elements and extends the array.
@foo = (1,2,3);
@bar = @foo[0..9999];
print scalar @foo; # => 3
@foo = (1,2,3);
@bar = grep 1, @foo[0..9999];
print scalar @foo; # => 10000
This also happens in other contexts where Perl wants to loop over an array slice.
@foo = (1,2,3);
foreach (@foo[0..9999]) { }
print scalar @foo; # => 10000
@foo = (1,2,3);
@bar = map { } @foo[0..9999];
print scalar @foo; # => 10000
So what are the workarounds?
Use a more complicated expression for the range or the grep
operand
@bar = grep 1, @foo[0..(@foo>=9999?9999:$#foo)];
@bar = grep 1, @foo>=9999 ? @foo[0..9999] : @foo;
Use a temporary array variable
@bar = grep 1, @tmp=@foo[0..9999]
(suggested by @FMc) use map
to set up an intermediate array
@bar = grep 1, map { $list[$_] } 0..9999;
work with array indices rather than directly with the array
@bar_indices = grep defined($foo[$_]), 0..9999;
@bar = @foo[@bar_indices];
@bar = @foo[ grep defined($foo[$_]), 0..9999 ];
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With