La mejor forma de iterar a través de una matriz de Perl

94

¿Cuál es la mejor implementación (en términos de velocidad y uso de memoria) para iterar a través de una matriz Perl? ¿Hay alguna forma mejor? ( @Arrayno es necesario conservarlo).

Implementación 1

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

Implementación 2

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

Implementación 3

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

Implementación 4

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

Implementación 5

map { SubRoutine($_) } @Array ;
Vaquero
fuente
2
¿Por qué habría un "mejor"? Especialmente dado que no tenemos idea de cómo se medirían unos contra otros (¿la velocidad es más importante que el uso de la memoria? ¿Es mapuna respuesta aceptable?, Etc.)
Max Lybbert
2
Dos de los tres que publicaste me harían decir "¡¿POR QUÉ ?!" a menos que exista un contexto circundante adicional para que sean alternativas sensatas. En cualquier caso, esta pregunta está al nivel de " ¿Cuál es la mejor manera de sumar dos números? " La mayoría de las veces, solo hay una forma. Luego, están esas circunstancias, en las que necesitas una forma diferente. Votación para cerrar.
Sinan Ünür
4
@ SinanÜnür Siento empatía con tu opinión (que solo hay una forma de sumar dos números), pero la analogía no es lo suficientemente fuerte como para usarla con desdén. Obviamente, hay más de una forma, y ​​el OP quiere entender qué es una buena idea y qué no.
CodeClown42
2
El capítulo 24 de la tercera edición de Programming Perl tiene una sección sobre eficiencia que es una buena lectura. Aborda los diferentes tipos de eficiencia como tiempo, programador, mantenedor. La sección comienza con la declaración "Tenga en cuenta que optimizar el tiempo a veces puede costarle en espacio o eficiencia del programador (indicado por sugerencias contradictorias a continuación). Ellos son los descansos".
1
¿Una forma de sumar dos números? No si nos fijamos en menores llamadas Nivel / implementaciones .... cree que llevan búsqueda hacia delante, llevar Guardar sumadores etc.
WorkWise

Respuestas:

76
  • En términos de velocidad: # 1 y # 4, pero no mucho en la mayoría de los casos.

    Podría escribir un punto de referencia para confirmar, pero sospecho que encontrará que el n. ° 1 y el n. ° 4 son un poco más rápidos porque el trabajo de iteración se realiza en C en lugar de Perl, y no se produce una copia innecesaria de los elementos de la matriz. ( $_tiene el alias del elemento en el n. ° 1, pero el n. ° 2 y el n. ° 3 en realidad copian los escalares de la matriz).

    El número 5 podría ser similar.

  • En términos de uso de memoria: todos son iguales excepto el n. ° 5.

    for (@a)tiene una carcasa especial para evitar aplanar la matriz. El ciclo itera sobre los índices de la matriz.

  • En términos de legibilidad: # 1.

  • En términos de flexibilidad: # 1 / # 4 y # 5.

    # 2 no admite elementos falsos. # 2 y # 3 son destructivos.

ikegami
fuente
3
Vaya, agregaste un montón de información en oraciones cortas y simples.
Jaypal Singh
1
# 2 es bueno cuando haces colas (por ejemplo, búsquedas en amplitud):my @todo = $root; while (@todo) { my $node = shift; ...; push @todo, ...; ...; }
ikegami
¿No crea la implementación 4 una matriz intermedia de índices, que podría introducir una gran cantidad de memoria para ser utilizada? Si es así, parece que uno no debería usar ese enfoque. stackoverflow.com/questions/6440723/… rt.cpan.org/Public/Bug/Display.html?id=115863
Thorsten Schöning
@ikegami Fiel a tu estilo de campeón - gran respuesta :)
skeetastax
26

Si solo te preocupan los elementos de @Array, usa:

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

o

Si los índices son importantes, use:

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

O, a partir de perl5.12.1, puede utilizar:

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

Si necesita tanto el elemento como su índice en el cuerpo del ciclo, Yo esperaría utilizando each ser el más rápido, pero luegorenunciará a la compatibilidad con versiones anteriores a 5.12.1 perls.

Algún otro patrón diferente a estos podría ser apropiado en determinadas circunstancias.

Sinan Ünür
fuente
Espero que eachsea ​​el más lento. Hace todo el trabajo de los demás menos un alias, más una asignación de lista, dos copias escalares y dos claros escalares.
ikegami
Y, en lo mejor de mi capacidad de medición, tienes razón. Aproximadamente un 45% más rápido al foriterar sobre los índices de una matriz, y un 20% más rápido al iterar sobre los índices de una referencia de matriz (accedo $array->[$i]en el cuerpo), sobre el uso eachjunto con while.
Sinan Ünür
3

En mi opinión, la implementación n. ° 1 es típica y ser breve e idiomático para Perl supera a los demás solo por eso. Un punto de referencia de las tres opciones podría ofrecerle una idea de la velocidad, al menos.

JRFerguson
fuente
2

1 es sustancialmente diferente de 2 y 3, ya que deja la matriz intacta, mientras que los otros dos la dejan vacía.

Yo diría que el número 3 es bastante loco y probablemente menos eficiente, así que olvídalo.

Lo que te deja con el # 1 y el # 2, y no hacen lo mismo, por lo que uno no puede ser "mejor" que el otro. Si la matriz es grande y no necesita conservarla, generalmente el alcance se ocupará de ella ( pero vea la NOTA ), por lo que , en general , el # 1 sigue siendo el método más claro y simple. Desactivar cada elemento no acelerará nada. Incluso si existe la necesidad de liberar la matriz de la referencia, simplemente iría:

undef @Array;

cuando termine.

  • NOTA : La subrutina que contiene el alcance de la matriz realmente mantiene la matriz y reutiliza el espacio la próxima vez. Generalmente , eso debería estar bien (ver comentarios).
CódigoClown42
fuente
@Array = ();no libera la matriz subyacente. Ni siquiera salir del alcance haría eso. Si quisiera liberar la matriz subyacente, tendría uso undef @Array;.
ikegami
2
Manifestación; 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
¿¿¿QUÉ??? Pensé que el objetivo de GC era una vez que un recuento de referencias == 0, la memoria involucrada se vuelve reciclable.
CodeClown42
@ikegami: Veo lo que pasa con ()vs undef, pero si salir del alcance no libera la memoria utilizada por una matriz local en ese alcance, ¿eso no convierte a Perl en un desastre con fugas? Eso no puede ser verdad.
CodeClown42
Tampoco tienen fugas. El submarino todavía los posee y los reutilizará la próxima vez que se llame al submarino. Optimizado para velocidad.
ikegami
1

En una sola línea para imprimir el elemento o matriz.

imprimir $ _ para (@array);

NOTA: recuerde que $ _ se refiere internamente al elemento de @array en bucle. Cualquier cambio realizado en $ _ se reflejará en @array; ex.

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

salida: 2 4 6

Sandeep_black
fuente
0

La mejor manera de decidir preguntas como esta para compararlas:

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,
});

Y ejecutando esto en perl 5, versión 24, subversion 1 (v5.24.1) construido para x86_64-linux-gnu-thread-multi

Yo obtengo:

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)

Entonces, el 'foreach (@Array)' es aproximadamente el doble de rápido que los demás. Todos los demás son muy similares.

@ikegami también señala que hay bastantes diferencias en estas implicaciones además de la velocidad.

G. Allen Morris III
fuente
1
La comparación $index < $#arraydebería ser en realidad $index <= $#arrayporque $#arrayno es la longitud de la matriz sino el último índice de la misma.
Josch