2010-03-07 5 views
11

Von perldoc -f each lesen wir:Ist Perls jede Funktion wert, verwendet zu werden?

Es gibt eine für jeden Hash-Single Iterator ist, von allen each, keys geteilt, und values Funktionsaufrufe im Programm; Es kann zurückgesetzt werden, indem alle Elemente aus dem Hash gelesen werden, oder indem keys HASH oder values HASH ausgewertet wird.

Der Iterator wird nicht zurückgesetzt, wenn Sie den Bereich mit dem each() verlassen, und dies kann zu Fehlern führen kann:

my %h = map { $_, 1 } qw(1 2 3); 
while (my $k = each %h) { print "1: $k\n"; last } 
while (my $k = each %h) { print "2: $k\n"  } 

Ausgang:

1: 1 
2: 3 
2: 2 

Was die gemeinsamen Abhilfen für diese sind Verhalten? Und lohnt es sich, each im Allgemeinen zu verwenden?

+5

Ich würde mir vorstellen, dass die üblichen Workarounds die Bewertung von 'Schlüssel HASH' oder' Werte HASH' enthalten. –

Antwort

11

Ich denke, es lohnt sich, solange Sie sich dessen bewusst sind. Es ist ideal, wenn Sie beiden Schlüssel und Wert in Iteration benötigen:

while (my ($k,$v) = each %h) { 
    say "$k = $v"; 
} 

In Ihrem Beispiel können Sie den Iterator zurück, indem keys %h; Hinzufügen wie so:

my %h = map { $_ => 1 } qw/1 2 3/; 
while (my $k = each %h) { print "1: $k\n"; last } 
keys %h; # reset %h 
while (my $k = each %h) { print "2: $k\n" } 

Von Perl 5.12 each auch Iteration auf einer Zulassungs Array.

2

Verwenden Sie die keys() Funktion, um den Iterator zurückzusetzen. Sehen Sie sich die faq für weitere Informationen

8

I each finden sehr praktisch, für Idiome wie folgt aus:

my $hashref = some_really_complicated_method_that_builds_a_large_and_deep_structure(); 
while (my ($key, $value) = each %$hashref) 
{ 
    # code that does stuff with both $key and $value 
} 

Kontrast dieser Code dazu:

my $hashref = ...same call as above 
foreach my $key (keys %$hashref) 
{ 
    my $value = $hashref->{$key}; 
    # more code here... 
} 

Im ersten Fall sind sowohl $key und $value sind sofort für den Körper der Schleife verfügbar. Im zweiten Fall muss $value zuerst abgerufen werden. Darüber hinaus kann die Liste der Schlüssel von $hashref wirklich riesig sein, die Speicher belegt. Dies ist gelegentlich ein Problem. each verursacht keinen solchen Overhead.

Die Nachteile von each sind jedoch nicht sofort ersichtlich: Wenn die Schleife vorzeitig abgebrochen wird, wird der Iterator des Hashs nicht zurückgesetzt. Zusätzlich (und ich finde dieses eine ernstere und noch weniger sichtbar): Sie können nicht keys(), values() oder andere each() von innerhalb dieser Schleife anrufen. Um dies zu tun, würde der Iterator zurückgesetzt, und Sie würden Ihren Platz in der While-Schleife verlieren. Die while-Schleife würde für immer fortfahren, was definitiv ein ernsthafter Fehler ist.

7

jeder ist nicht nur wert, zu verwenden, es ist ziemlich zwingend erforderlich, wenn Sie über einen ganzen gebundenen Hash, der für Gedächtnis zu groß ist, Schleife wiederholen möchten.

Ein void-context keys() (oder Werte, aber Konsistenz ist nett) vor Beginn der Schleife ist die einzige "Workaround" notwendig; Gibt es einen Grund, warum Sie nach einer anderen Problemumgehung suchen?

+0

Ausgezeichneter Punkt! Dies ist der beste (einzige?) Grund, 'jedes' zu verwenden, an das ich denken kann. – daotoad

1

Es ist am besten, wenn verwendet, wie es ist Name: each. Es ist wahrscheinlich die falsche Sache zu verwenden, wenn Sie "geben Sie mir das erste Schlüssel-Wert-Paar" oder "geben Sie mir die ersten beiden Paare" oder was auch immer. Denken Sie daran, dass die Idee flexibel genug ist, dass Sie jedes Mal, wenn Sie es anrufen, das nächste Paar (oder geben Sie einen skalaren Kontext ein).

6

each ist zu gefährlich, um überhaupt zu verwenden, und viele style guides verbieten seine Verwendung vollständig. Die Gefahr besteht darin, dass, wenn ein Zyklus von each vor dem Ende des Hash abgebrochen wird, der nächste Zyklus dort beginnt. Dies kann sehr schwer zu reproduzierende Fehler verursachen; Das Verhalten eines Teils des Programms hängt von einem völlig anderen Teil des Programms ab. Sie könnte each richtig verwenden, aber was ist mit jedem Modul jemals geschrieben, die Ihren Hash (oder Hashref verwenden könnte; es ist das gleiche)?

keys und values sind immer sicher, also benutzen Sie einfach diese. keys macht es einfacher, den Hash in deterministischer Reihenfolge zu durchlaufen, was fast immer nützlicher ist. (for my $key (sort keys %hash) { ... })

+0

Verwenden Sie viele globale Hashes, oder? – ysth

+2

Egal ob global oder nicht. Selbst private Attribute einer Klasse sind für dieses Problem anfällig. Alles, was einen Verweis auf einen Hash zurückgibt, ist betroffen. – jrockway

1

each hat eine Buit-in, versteckte globale Variable, die Sie verletzen kann. Wenn Sie dieses Verhalten nicht benötigen, ist es sicherer, einfach keys zu verwenden.

Betrachten Sie dieses Beispiel, wo wir Gruppe unserer k/v Paare wollen (ja, ich weiß printf würde dies besser tun):

#!perl 

use strict; 
use warnings; 

use Test::More 'no_plan'; 

{ my %foo = map { ($_) x 2 } (1..15); 

    is(one(\%foo), one(\%foo), 'Calling one twice works with 15 keys'); 
    is(two(\%foo), two(\%foo), 'Calling two twice works with 15 keys'); 
} 

{ my %foo = map { ($_) x 2 } (1..105); 

    is(one(\%foo), one(\%foo), 'Calling one twice works with 105 keys'); 
    is(two(\%foo), two(\%foo), 'Calling two twice works with 105 keys'); 
} 


sub one { 
    my $foo = shift; 

    my $r = ''; 

    for(1..9) { 
     last unless my ($k, $v) = each %$foo; 

     $r .= " $_: $k -> $v\n"; 
    } 
    for(10..99) { 
     last unless my ($k, $v) = each %$foo; 

     $r .= " $_: $k -> $v\n"; 
    } 

    return $r; 
} 

sub two { 
    my $foo = shift; 

    my $r = ''; 

    my @k = keys %$foo; 

    for(1..9) { 
     last unless @k; 
     my $k = shift @k; 

     $r .= " $_: $k -> $foo->{$k}\n"; 
    } 
    for(10..99) { 
     last unless @k; 
     my $k = shift @k; 

     $r .= " $_: $k -> $foo->{$k}\n"; 
    } 

    return $r; 
} 

die Fehler in den Tests, die oben in einer realen Anwendung gezeigt Debuggen wären schrecklich schmerzhaft. (Für eine bessere Ausgangs Verwendung Test::Differenceseq_or_diff statt is.)

Natürlich one() kann mit keys befestigt werden, um den Iterator am Anfang und Ende des Unterprogramms zu löschen. Falls du dich erinnerst. Wenn alle Ihre Mitarbeiter sich erinnern. Es ist vollkommen sicher, solange niemand vergisst.

Ich weiß nicht über Sie, aber ich bleibe einfach bei der Verwendung von keys und values.

1

each() kann effizienter sein, wenn Sie einen gebundenen Hash durchlaufen, zum Beispiel eine Datenbank, die Millionen von Schlüsseln enthält; Auf diese Weise müssen Sie nicht alle Schlüssel im Speicher laden.