2013-05-08 5 views
6

Il seguente frammento di Perl deve stampare i primi 5 elementi di un array a cui fa riferimento un valore hash, o meno se l'array è più corto.Fette Perl inaspettatamente brevi

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

non credo che la prima grep defined è necessaria, ma non può fare alcun male, e dovrebbe garantire che non vi siano membri non definiti array prima fetta. Il secondo è quello di rimuovere grep defined membri di matrice non definiti nel risultato di slice quando @list è più corto 5.

%groups è stato popolato da ripetute invocazioni di:

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

più delle volte funziona bene:

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

Ma a volte - e non ho lavorato in quali circostanze - vedo:

key2: 
    value1 
    (1 of 5) 

key3: 
    value1 
    value2 
    (2 of 5) 

mi aspetto che la lunghezza della lista stampata, e x da (x of y) per essere min(5,y)

Che cosa potrebbe causare questo comportamento?

risposta

8

L'utilizzo di grep con una sezione di matrice per @list consente di automatizzare gli elementi ed estendere la matrice.

@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 

Ciò si verifica anche in altri contesti in cui Perl desidera eseguire il loop su una sezione di matrice.

@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 

Quindi quali sono le soluzioni alternative?

  1. utilizzare un'espressione più complicato per l'intervallo o la grep operando

    @bar = grep 1, @foo[0..(@foo>=9999?9999:$#foo)]; 
    @bar = grep 1, @foo>=9999 ? @foo[0..9999] : @foo; 
    
  2. Utilizzare una variabile array temporaneo

    @bar = grep 1, @[email protected][0..9999] 
    
  3. (suggerito da @FMc) utilizzare per impostare map un array intermedio

    @bar = grep 1, map { $list[$_] } 0..9999; 
    
  4. lavoro con indici di array anziché direttamente con la matrice

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

Ahi! C'è un modo idiomatico (o almeno pulito) attorno ad esso? – slim

+0

Ci sto pensando. ['no autovivification'] (http://search.cpan.org/perldoc?autovivification) purtroppo non aiuta. – mob

+0

'print" (", scalare @slice," di ", scalare (grep defined, @list),") \ n ";' ottiene l'output corretto. Potrebbe lasciare un trucco per i futuri manutentori. – slim