2012-05-08 24 views
7

Mam pytanie dotyczące bloków kodu w perlu. Biorąc pod uwagę następujący kod:bloki kodu perl

my @newArr = sort { $a <=> $b } @oldArr; 

używa bloku kodu jako argumentu.

mógłbym przepisać jako:

sub sortFunc { 
     return $a <=> $b; 
    } 
    my @newArr = sort sortFunc @oldArr; 

Próbuję dowiedzieć się, jak działa ten mechanizm. Obecnie muszę zaimplementować rodzaj złożonej funkcji sortowania, która wyglądałaby niechlujnie w bloku kodu, ale zależy to od niektórych zmiennych lokalnych. Na przykład:

foreach my $val (@values){ 
     my @newArr = sort { $hash{$a}{$val}<=> $hash{$b}{$val} } @oldArr; 
     ... 
    } 

ale pozwala zakładać, że funkcja sortowania jest bardziej skomplikowane, więc przyzwyczajenie to zgrabnie pasuje do kodu powyżej.

Jeśli spróbuję użyć funkcji (zdefiniowanej lokalnie w zasięgu pętli for), otrzymuję "Wykorzystanie niezainicjowanej wartości w elemencie hash".

Zakładam, że to dlatego, że podrzędny jest parsowany raz, a nie odtwarzany w celu powtórzenia iteracji pętli for. Próbuję zrozumieć, jak zaimplementować blok kodu, który byłby reinterpretowany w każdej iteracji, a może w jaki sposób przekazywać parametry.

+0

'moja @newArr = sort {$ a <=> $ b} @oldArr;' co robi @oldArr znaczy zgodnie z tym? –

+0

"ale pozwala założyć, że funkcja sortowania jest bardziej złożona" -> może powinieneś opublikować swój kod dla tej funkcji sortowania. – TLP

Odpowiedz

9

Nie przedstawiają kodu problematyczne dla jakiegoś powodu, ale myślę, że to coś

for my $val (@values) { 
    sub sort_func { 
     return $hash{$a}{$val} <=> $hash{$b}{$val}; 
    } 

    my @newArr = sort sort_func @oldArr; 
} 
Próbuję dowiedzieć się, jak działa ten mechanizm. [...] Zakładam, że to dlatego, że sub jest sparsowany raz, a nie odtworzony w celu powtórzenia iteracji pętli for.

Niezupełnie. Poniższe tylko analizującym i kompiluje sub raz, ale to działa:

for my $val (@values) { 
    my $cmp_func = sub { 
     return $hash{$a}{$val} <=> $hash{$b}{$val}; 
    }; 

    my @newArr = sort $cmp_func @oldArr; 
} 

Liczy się to, gdy co $val zostaje schwytany. $val jest przechwytywany po oszacowaniu wartości sub { ... }.Pamiętając, że

sub foo { ... } 

jest taka sama jak następujące w związku z tym,

BEGIN { *foo = sub { ... }; } 

W moim kodu, to oddaje $val z pętli foreach. W twoim, przechwytuje on podczas kompilacji, więc przechwytuje $val, który istniał w czasie kompilacji. I to nie jest ta zmienna, której potrzebujesz.

Teraz, gdy już wiesz, jak to zrobić, możesz przenieść złożony kod z drogi (z pętli) według własnego uznania.

sub make_cmp_func { 
    my ($hash, $val) = @_; 
    return sub { 
     return $hash->{$a}{$val} <=> $hash{$b}{$val}; 
    }; 
} 

for my $val (@values) { 
    my $cmp_func = make_cmp_func(\%hash, $val); 
    my @newArr = sort $cmp_func @oldArr; 
} 

Alternatywnie, można przekazać odpowiednie wartości do funkcji porównania zamiast ich uchwycenie.

sub cmp_func { 
    my ($hash, $val, $a, $b) = @_; 
    return $hash->{$a}{$val} <=> $hash{$b}{$val}; 
} 

for my $val (@values) { 
    my @newArr = sort { cmp_func(\%hash, $val, $a, $b) } @oldArr; 
} 
+0

Przebacz długość, ale zapytałeś się, jak to działa, nie tylko dla rozwiązania. – ikegami

+0

Dzięki, Rozważałem użycie odwołania do kodu zamiast funkcji, ale nie byłem pewien, czy zrobiłoby to coś innego. – Smartelf

+0

W twoim ostatnim przykładzie, czy musisz przekazać i ponownie zadeklarować '$ a' i' $ b'? – TLP

8

Chcesz użyć funkcji, która pobiera argumenty oprócz $a i $b.

sub my_sort_func { 
    my ($val, $a, $b) = @_; 
    return $hash{$a}{$val} <=> $hash{$b}{$val}; 
} 

foreach my $val (@values) { 
    my @newArr = sort { my_sort_func($val,$a,$b) } @oldArr; 
    ... 
} 

mechanizm Perl za korzystanie blok kodu z sort jest dość szczególny, i nie jest łatwo powielane w czystej Perl.

5

Rozszerzając odpowiedź moby, jest to jedna z "sprytnych, ale niekoniecznie inteligentnych" odmian. Jeśli sprzeciwisz się dodatkowemu parametrowi, możesz zamiast tego użyć currying.

sub make_sorter { 
    my ($hashref, $val) = @_; 
    return sub { 
      $hashref->{$a}{$val} <=> $hashref->{$b}{$val} 
    }; 
} 

for my $val (@values) { 
    my $sorter = make_sorter(\%hash, $val); 
    my @newArr = sort $sorter @oldArr; 
} 

To nie jest bardziej wydajny, bardziej czytelne, lub bardziej wartościowe w naprawdę żaden sposób, ale to może być interesujące wiedzieć o technikę gdzieś, gdzie jest rzeczywiście użyteczne.

+0

Zacząłem też tę ścieżkę i zmieniłem swoją odpowiedź, gdy dostałem ją do "sort {make_sorter ($ hash, $ val) -> ($ a, $ b)} @ oldArr' ... – mob