Неочаквано къси 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)

Какво може да причини това поведение?


person slim    schedule 08.05.2013    source източник


Отговори (1)


Използването на grep със срез на масив за @list автоматично оживява елементите и разширява масива.

@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 ];
    
person mob    schedule 08.05.2013
comment
Ох! Има ли идиоматичен (или поне чист) начин да го заобиколите? - person slim; 08.05.2013
comment
мисля за това no autovivification не помага, за съжаление. - person mob; 08.05.2013
comment
print " (", scalar @slice, " of ", scalar (grep defined, @list), ")\n"; постига правилния резултат. Все пак може да остави проблем за бъдещите поддържащи. - person slim; 08.05.2013
comment
Не често приемам отговор в рамките на 11 минути след публикуването, но в този случай мисля, че е заслужено. Благодаря. - person slim; 08.05.2013
comment
Вариант 4) е най-DWIM според мен. Задаването на секция от масив от пет елемента означава, че човек е сигурен, че масивът има поне 5 елемента. - person Zaid; 09.05.2013