Il modo migliore per iterare attraverso un array Perl


94

Qual è la migliore implementazione (in termini di velocità e utilizzo della memoria) per iterare attraverso un array Perl? C'è un modo migliore? ( @Arraynon è necessario conservarlo).

Attuazione 1

foreach (@Array)
{
      SubRoutine($_);
}

Implementazione 2

while($Element=shift(@Array))
{
      SubRoutine($Element);
}

Implementazione 3

while(scalar(@Array) !=0)
{
      $Element=shift(@Array);
      SubRoutine($Element);
}

Attuazione 4

for my $i (0 .. $#Array)
{
      SubRoutine($Array[$i]);
}

Attuazione 5

map { SubRoutine($_) } @Array ;

2
Perché dovrebbe esserci un "migliore"? Soprattutto dato che non abbiamo idea di come misurereste l'uno rispetto all'altro (la velocità è più importante dell'uso della memoria? È mapuna risposta accettabile? Ecc.)
Max Lybbert

2
Due dei tre che hai postato mi farebbero dire "WTH ?!" a meno che non ci sia un contesto circostante aggiuntivo per renderli alternative ragionevoli. In ogni caso, questa domanda è a livello di " Qual è il modo migliore per aggiungere due numeri? " La maggior parte delle volte, c'è solo un modo. Poi ci sono quelle circostanze in cui hai bisogno di un modo diverso. Votazioni per chiudere.
Sinan Ünür

4
@ SinanÜnür Sono in empatia con la tua opinione (che esiste un solo modo per aggiungere due numeri), ma l'analogia non è abbastanza forte per essere usata in modo sprezzante. Ovviamente, c'è più di un modo e l'OP vuole capire cosa è una buona idea e cosa non lo è.
CodeClown42

2
Il capitolo 24 della terza edizione di Programming Perl ha una sezione sull'efficienza che è una buona lettura. Affronta i diversi tipi di efficienza come tempo, programmatore, manutentore. La sezione inizia con l'affermazione "Tieni presente che l'ottimizzazione del tempo a volte può costarti spazio o efficienza del programmatore (indicato da suggerimenti contrastanti di seguito). Sono loro le pause".

1
Un modo per aggiungere due numeri? No, se si esaminano le chiamate / implementazioni di livello inferiore ... si pensi a carry lookahead, carry save
adders

Risposte:


76
  • In termini di velocità: # 1 e # 4, ma non di molto nella maggior parte dei casi.

    Potresti scrivere un benchmark per confermare, ma sospetto che troverai # 1 e # 4 leggermente più veloci perché il lavoro di iterazione è fatto in C invece che Perl, e non si verifica alcuna copia inutile degli elementi dell'array. ( $_È un alias per l'elemento a # 1, ma # 2 e # 3 in realtà copiare i scalari dalla matrice.)

    # 5 potrebbe essere simile.

  • In termini di utilizzo della memoria: sono tutti uguali tranne il n. 5.

    for (@a)è speciale per evitare di appiattire l'array. Il ciclo itera sugli indici dell'array.

  • In termini di leggibilità: # 1.

  • In termini di flessibilità: # 1 / # 4 e # 5.

    # 2 non supporta elementi falsi. # 2 e # 3 sono distruttivi.


3
Wow, hai aggiunto camion carichi di informazioni in frasi brevi e semplici.
jaypal singh

1
# 2 va bene quando fai le code (ad es. Ricerche in ampiezza):my @todo = $root; while (@todo) { my $node = shift; ...; push @todo, ...; ...; }
ikegami

L'implementazione 4 non crea un array intermedio di indici, che potrebbe introdurre una grande quantità di memoria da utilizzare? Se è così, sembra che non si dovrebbe usare quell'approccio. stackoverflow.com/questions/6440723/... rt.cpan.org/Public/Bug/Display.html?id=115863
Thorsten Schöning

@ikegami Fedele al tuo stile da campione - ottima risposta :)
skeetastax

26

Se ti interessano solo gli elementi di @Array, usa:

for my $el (@Array) {
# ...
}

o

Se gli indici sono importanti, usa:

for my $i (0 .. $#Array) {
# ...
}

Oppure, a partire da perl5.12.1, puoi utilizzare:

while (my ($i, $el) = each @Array) {
# ...
}

Se hai bisogno sia dell'elemento che del suo indice nel corpo del ciclo, Mi aspetterei utilizzando each per essere il più veloce, ma poirinuncerai alla compatibilità con le versioni precedenti alla 5.12.1 perl.

Qualche modello diverso da questi potrebbe essere appropriato in determinate circostanze.


Mi aspetto eachche sia il più lento. Fa tutto il lavoro degli altri meno un alias, più un'assegnazione di lista, due copie scalari e due cancellazioni scalari.
ikegami

E, al meglio delle mie capacità di misurazione, hai ragione. Circa il 45% più veloce con l' foriterazione sugli indici di un array e il 20% più veloce quando l'iterazione sugli indici di un riferimento di array (accedo $array->[$i]nel corpo), rispetto all'uso eachin combinazione con while.
Sinan Ünür

3

IMO, l'implementazione # 1 è tipica ed essere breve e idiomatica per Perl supera gli altri solo per questo. Un punto di riferimento delle tre scelte potrebbe offrirti almeno una visione della velocità.


2

1 è sostanzialmente diverso da 2 e 3, poiché lascia intatto l'array, mentre gli altri due lo lasciano vuoto.

Direi che il numero 3 è piuttosto stravagante e probabilmente meno efficiente, quindi dimenticalo.

Il che ti lascia con # 1 e # 2, e non fanno la stessa cosa, quindi uno non può essere "migliore" dell'altro. Se l'array è grande e non è necessario mantenerlo, generalmente se ne occuperà l'ambito ( ma vedi NOTA ), quindi in genere il metodo # 1 è ancora il più chiaro e semplice. Spegnere ogni elemento non accelererà nulla. Anche se è necessario liberare l'array dal riferimento, vorrei semplicemente andare:

undef @Array;

quando fatto.

  • NOTA : La subroutine che contiene l'ambito della matrice mantiene effettivamente la matrice e riutilizza lo spazio la volta successiva. In generale , dovrebbe andare bene (vedi commenti).

@Array = ();non libera l'array sottostante. Nemmeno uscire dall'ambito lo farebbe. Se volessi liberare l'array sottostante, dovresti usare undef @Array;.
ikegami

2
Demo; perl -MDevel::Peek -e'my @a; Dump(\@a,1); @a=qw( a b c ); Dump(\@a,1); @a=(); Dump(\@a,1); undef @a; Dump(\@a,1);' 2>&1 | grep ARRAY
ikegami

CHE COSA??? Avevo pensato che l'intero punto di GC fosse una volta un conteggio rif == 0, la memoria coinvolta diventa riciclabile.
CodeClown42

@ikegami: Capisco il problema di ()vs undef, ma se uscire dall'ambito non rilascia la memoria utilizzata da un array locale a tale ambito, non rende perl un disastro? Non può essere vero.
CodeClown42

Non perdono neanche. Il sottomarino li possiede ancora e li riutilizzerà la prossima volta che verrà chiamato. Ottimizzato per la velocità.
ikegami

1

In una sola riga per stampare l'elemento o l'array.

stampa $ _ per (@array);

NOTA: ricorda che $ _ si riferisce internamente all'elemento di @array in loop. Qualsiasi modifica effettuata in $ _ si rifletterà in @array; ex.

my @array = qw( 1 2 3 );
for (@array) {
        $_ = $_ *2 ;
}
print "@array";

uscita: 2 4 6


0

Il modo migliore per decidere domande come questa per valutarle:

use strict;
use warnings;
use Benchmark qw(:all);

our @input_array = (0..1000);

my $a = sub {
    my @array = @{[ @input_array ]};
    my $index = 0;
    foreach my $element (@array) {
       die unless $index == $element;
       $index++;
    }
};

my $b = sub {
    my @array = @{[ @input_array ]};
    my $index = 0;
    while (defined(my $element = shift @array)) {
       die unless $index == $element;
       $index++;
    }
};

my $c = sub {
    my @array = @{[ @input_array ]};
    my $index = 0;
    while (scalar(@array) !=0) {
       my $element = shift(@array);
       die unless $index == $element;
       $index++;
    }
};

my $d = sub {
    my @array = @{[ @input_array ]};
    foreach my $index (0.. $#array) {
       my $element = $array[$index];
       die unless $index == $element;
    }
};

my $e = sub {
    my @array = @{[ @input_array ]};
    for (my $index = 0; $index <= $#array; $index++) {
       my $element = $array[$index];
       die unless $index == $element;
    }
};

my $f = sub {
    my @array = @{[ @input_array ]};
    while (my ($index, $element) = each @array) {
       die unless $index == $element;
    }
};

my $count;
timethese($count, {
   '1' => $a,
   '2' => $b,
   '3' => $c,
   '4' => $d,
   '5' => $e,
   '6' => $f,
});

E eseguendolo su perl 5, versione 24, subversion 1 (v5.24.1) compilato per x86_64-linux-gnu-thread-multi

Ottengo:

Benchmark: running 1, 2, 3, 4, 5, 6 for at least 3 CPU seconds...
         1:  3 wallclock secs ( 3.16 usr +  0.00 sys =  3.16 CPU) @ 12560.13/s (n=39690)
         2:  3 wallclock secs ( 3.18 usr +  0.00 sys =  3.18 CPU) @ 7828.30/s (n=24894)
         3:  3 wallclock secs ( 3.23 usr +  0.00 sys =  3.23 CPU) @ 6763.47/s (n=21846)
         4:  4 wallclock secs ( 3.15 usr +  0.00 sys =  3.15 CPU) @ 9596.83/s (n=30230)
         5:  4 wallclock secs ( 3.20 usr +  0.00 sys =  3.20 CPU) @ 6826.88/s (n=21846)
         6:  3 wallclock secs ( 3.12 usr +  0.00 sys =  3.12 CPU) @ 5653.53/s (n=17639)

Quindi "foreach (@Array)" è circa il doppio più veloce degli altri. Tutti gli altri sono molto simili.

@ikegami sottolinea anche che ci sono alcune differenze in queste impiantazioni oltre alla velocità.


1
Il confronto $index < $#arraydovrebbe effettivamente essere $index <= $#arraydovuto al fatto che $#arraynon è la lunghezza dell'array ma l'ultimo indice di esso.
Josch
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.