Неожиданно короткие ломтики Perl

следующий фрагмент Perl должен печатать первые 5 элементов массива, на которые ссылается хэш-значение,или меньше, если массив короче.

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";
}

я не думаю, что первый grep defined необходимо, но это не может причинить никакого вреда, и это должно гарантировать, что перед срезом нет неопределенных членов массива. Второй grep defined - удалить неопределенные члены массива в результате slice, когда @list короче 5.

%groups была населенный повторяющимися призывами:

  $groups{$key} = () unless defined $groups{$key};
  push @{$groups{$key}}, $value;

большую часть времени он работает нормально:

key1:
   value1
   value2
   value3
   value4
   value5
   (5 of 100)
key2:
   value1
   (1 of 5)

key3:
   value1
   value2
   (2 of 5)

я ожидаю длину печатного списка, и x С (x of y) на min(5,y)

что могло вызвать такое поведение?

1 ответов


используя grep с фрагментом массива для @list autovivifies элементы и расширяет массив.

@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

это также происходит в других контекстах, где Perl хочет выполнить цикл над срезом массива.

@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

Итак, каковы обходные пути?

  1. использовать более сложное выражение для диапазона или grep операнд

    @bar = grep 1, @foo[0..(@foo>=9999?9999:$#foo)];
    @bar = grep 1, @foo>=9999 ? @foo[0..9999] : @foo;
    
  2. использовать временный массив переменная

    @bar = grep 1, @tmp=@foo[0..9999]
    
  3. (предложено @FMc) использовать map настройка промежуточного массива

    @bar = grep 1, map { $list[$_] } 0..9999;
    
  4. работа с индексами массива, а не напрямую с блока

    @bar_indices = grep defined($foo[$_]), 0..9999;
    @bar = @foo[@bar_indices];
    
    @bar = @foo[  grep defined($foo[$_]), 0..9999 ];