Melhor maneira de iterar por meio de uma matriz Perl

94

Qual é a melhor implementação (em termos de velocidade e uso de memória) para iterar por meio de um array Perl? Existe alguma maneira melhor? ( @Arraynão precisa ser retido).

Implementação 1

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

Implementação 2

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

Implementação 3

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

Implementação 4

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

Implementação 5

map { SubRoutine($_) } @Array ;
Jean
fonte
2
Por que haveria um "melhor"? Especialmente porque não temos ideia de como você compararia um com o outro (a velocidade é mais importante do que o uso da memória? É mapuma resposta aceitável? Etc.)
Max Lybbert
2
Dois dos três que você postou me fariam dizer "WTH ?!" a menos que haja um contexto circundante adicional para torná-los alternativas sensatas. Em qualquer caso, essa pergunta está no nível de “ Qual é a melhor maneira de somar dois números? ” Na maioria das vezes, há apenas uma maneira. Então, há aquelas circunstâncias em que você precisa de uma maneira diferente. Votando para fechar.
Sinan Ünür
4
@ SinanÜnür Tenho empatia com sua opinião (que só existe uma maneira de somar dois números), mas a analogia não é forte o suficiente para ser usada com desdém. Obviamente, existe mais de uma maneira, e o OP quer entender o que é uma boa ideia e o que não é.
CodeClown42
2
O capítulo 24 da terceira edição de Programming Perl tem uma seção sobre eficiência que é uma boa leitura. Ele aborda os diferentes tipos de eficiência, como tempo, programador, mantenedor. A seção começa com a declaração "Observe que a otimização para o tempo pode às vezes custar-lhe espaço ou eficiência do programador (indicado pelas dicas conflitantes abaixo). Esses são os pontos fracos."
1
Uma maneira de somar dois números? Não se você olhar para chamadas / implementações de nível inferior ... pense em carry lookahead, carry save
adders

Respostas:

76
  • Em termos de velocidade: # 1 e # 4, mas não muito na maioria dos casos.

    Você poderia escrever um benchmark para confirmar, mas eu suspeito que você descobrirá que os itens 1 e 4 são um pouco mais rápidos porque o trabalho de iteração é feito em C em vez de Perl, e nenhuma cópia desnecessária dos elementos do array ocorre. ( $_tem um alias para o elemento em # 1, mas # 2 e # 3 na verdade copiam os escalares da matriz.)

    # 5 pode ser semelhante.

  • Em termos de uso de memória: Eles são todos iguais, exceto o # 5.

    for (@a)é especial para evitar o achatamento da matriz. O loop itera sobre os índices da matriz.

  • Em termos de legibilidade: # 1.

  • Em termos de flexibilidade: # 1 / # 4 e # 5.

    # 2 não suporta elementos falsos. # 2 e # 3 são destrutivos.

ikegami
fonte
3
Uau, você adicionou um monte de informações em frases curtas e simples.
Jaypal Singh
1
# 2 é bom quando você faz filas (por exemplo, pesquisas em largura):my @todo = $root; while (@todo) { my $node = shift; ...; push @todo, ...; ...; }
ikegami
A implementação 4 não cria uma matriz intermediária de índices, que pode apresentar uma grande quantidade de memória a ser usada? Nesse caso, parece que não se deve usar essa abordagem. stackoverflow.com/questions/6440723/… rt.cpan.org/Public/Bug/Display.html?id=115863
Thorsten Schöning
@ikegami Fiel ao seu estilo de campeão - ótima resposta :)
skeetastax
26

Se você se preocupa apenas com os elementos de @Array, use:

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

ou

Se os índices importam, use:

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

Ou, a partir de perl5.12.1, você pode usar:

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

Se você precisar do elemento e de seu índice no corpo do loop, eu esperaria usando each para ser o mais rápido, mas entãovocê estará desistindo da compatibilidade com o pré-5.12.1 perls.

Algum padrão diferente desses pode ser apropriado em certas circunstâncias.

Sinan Ünür
fonte
Eu esperaria eachque fosse o mais lento. Ele faz todo o trabalho dos outros menos um alias, mais uma atribuição de lista, duas cópias escalares e duas limpezas escalares.
ikegami
E, com o melhor da minha capacidade de medição, você está certo. Cerca de 45% mais rápido com a foriteração sobre os índices de um array e 20% mais rápido com a iteração sobre os índices de uma referência de array (eu acesso $array->[$i]no corpo), em vez de usar eachem conjunto com while.
Sinan Ünür
3

IMO, a implementação # 1 é típica e ser curta e idiomática para Perl supera as outras apenas por isso. Um benchmark das três opções pode oferecer a você uma percepção da velocidade, pelo menos.

JRFerguson
fonte
2

1 é substancialmente diferente de 2 e 3, pois deixa o array intacto, enquanto os outros dois o deixam vazio.

Eu diria que o nº 3 é bem maluco e provavelmente menos eficiente, então esqueça isso.

O que deixa você com o nº 1 e o nº 2, e eles não fazem a mesma coisa, então um não pode ser "melhor" que o outro. Se o array for grande e você não precisar mantê-lo, geralmente o escopo tratará dele ( mas veja a NOTA ), então geralmente , # 1 ainda é o método mais claro e simples. Desligar cada elemento não acelera nada. Mesmo se houver necessidade de liberar a matriz da referência, eu simplesmente diria:

undef @Array;

quando terminar.

  • NOTA : A sub-rotina que contém o escopo da matriz, na verdade, mantém a matriz e reutiliza o espaço na próxima vez. Geralmente , isso deve estar bem (ver comentários).
CodeClown42
fonte
@Array = ();não libera a matriz subjacente. Nem mesmo sair do escopo faria isso. Se você quisesse liberar o array subjacente, você deveria usar 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
O QUE??? Eu pensei que todo o ponto de GC era uma vez uma contagem de ref == 0, a memória envolvida se torna reciclável.
CodeClown42
@ikegami: Entendo o que é ()vs undef, mas se sair do escopo não libera a memória usada por um array local para esse escopo, isso não torna o perl um desastre de vazamento? Isso não pode ser verdade.
CodeClown42
Eles também não vazam. O sub ainda os possui e irá reutilizá-los na próxima vez que o sub for chamado. Otimizado para velocidade.
ikegami
1

Em uma única linha para imprimir o elemento ou array.

imprimir $ _ para (@array);

NOTA: lembre-se de que $ _ se refere internamente ao elemento de @array no loop. Quaisquer alterações feitas em $ _ refletirão em @array; ex.

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

saída: 2 4 6

Sandeep_black
fonte
0

A melhor maneira de decidir questões como esta para compará-las:

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 executando isso em perl 5, versão 24, subversão 1 (v5.24.1) construído para x86_64-linux-gnu-thread-multi

Eu recebo:

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)

Portanto, o 'foreach (@Array)' é cerca de duas vezes mais rápido que os outros. Todos os outros são muito semelhantes.

@ikegami também aponta que existem algumas diferenças nessas implementações além da velocidade.

G. Allen Morris III
fonte
1
A comparação $index < $#arraydeveria ser $index <= $#arrayporque $#arraynão é o comprimento da matriz, mas o último índice dela.
Joseph