NAME

perllol - Manipulación de array de array en Perl *** DOCUMENTO SIN REVISAR ***

DESCRIPCIÓN

Declaración y acceso de los array de array

La estructura de datos a dos niveles más simple que se puede generar en Perl es un array de array, llamada en algunas ocasiones lista de listas. Es razonablemente fácil de entender, y casi todo lo que se aplica aquí también se aplicará más tarde con las estructuras de datos más elaborados.

Un array de un array es justo un array @AoA normal sobre la que se puede aplicar dos subíndices, como $AoA[3][2]. Aquí hay una declaración del array:

    use 5.010;  # so we can use say()

    # asignar a nuestro array, un array de referencias a array
    @AoA = (
	   [ "fred", "barney", "pebbles", "bambam", "dino", ],
	   [ "george", "jane", "elroy", "judy", ],
	   [ "homer", "bart", "marge", "maggie", ],
    );
    say $AoA[2][1];
  bart

Ahora, debe tener mucho cuidado de que el paréntesis externo debe ser eso: un paréntesis. Esto es así porque está asignando a un @array, por lo que necesita usar paréntesis. Si no quiere que sea un @AoA, sino una referencia a él, puede hacer algo más parecido a esto:

    # asignar una referencia a un array de referencias de array
    $ref_a_AoA = [
	[ "fred", "barney", "pebbles", "bambam", "dino", ],
	[ "george", "jane", "elroy", "judy", ],
	[ "homer", "bart", "marge", "maggie", ],
    ];
    say $ref_to_AoA->[2][1];
  bart

Observe que el paréntesis más exterior ha cambiado, por lo que nuestra sintaxis de acceso ha cambiado también. Eso es porque, a diferencia de C, en perl no puede intercambiar libremente los array y referencias a ellos. $ref_to_AoA es una referencia a un array, mientras que @AoA es un array. Del mismo modo, $AoA[2] no es un array, si no una referencia a un array. Así que ¿cómo entonces puede escribir lo siguiente

$AoA[2][2]
$ref_to_AoA->[2][2]

en lugar de tener que escribir lo siguiente?

$AoA[2]->[2]
$ref_to_AoA->[2]->[2]

Bueno, eso es porque la regla dice que sólo en los corchetes o llaves adyacentes, es libre de omitir la flecha de referencias. Pero no puede hacerlo para el primer caso si es un escalar que contenga una referencia, lo que significa que $ref_to_AoA siempre lo necesita.

Creciendo

Todo eso está bien y es bueno para la declaración de una estructura de datos fijos, pero ¿y si quisiera añadir nuevos elementos sobre la marcha, o construirlo desde cero?

En primer lugar, echemos un vistazo a la lectura desde un archivo. Esto es algo así como la adición de una fila cada vez. Vamos a suponer que hay un archivo de texto plano en el que cada línea es una fila y cada palabra un elemento. Si está tratando de desarrollar un array @AoA conteniendo todo eso, esta es la manera correcta de hacerlo:

    while (<>) {
	@tmp = split;
	push @AoA, [ @tmp ];
    }

Es posible que también lo haya cargado a partir de una función:

    for $i ( 1 .. 10 ) {
	$AoA[$i] = [ algunafuncion($i) ];
    }

O podría haber usado una variable temporal con el array en ella.

    for $i ( 1 .. 10 ) {
	@tmp = algunafuncion($i);
	$AoA[$i] = [ @tmp ];
    }

Es importante que esté seguro de que está utilizando el constructor de referencia a array []. Porque si no, lo siguiente no funcionará:

$AoA[$i] = @tmp;   # ¡MAL!

La razón de que no hace lo que usted quiere que haga es porque la asignación de un array con nombre como si fuera un escalar es tomado como si se tomara un escalar de un array en contexto escalar, lo que significa que sólo obtenemos el número de elementos que hay en @tmp.

Si está ejecutando bajo use strict (y si no lo está haciendo, ¿por qué demonios no?), tendrá que agregar algunas declaraciones para hacerle feliz:

    use strict;
    my(@AoA, @tmp);
    while (<>) {
	@tmp = split;
	push @AoA, [ @tmp ];
    }

Por supuesto, no necesita el array temporal para nada:

    while (<>) {
	push @AoA, [ split ];
    }

Tampoco tiene por qué usar push(). Puede hacer una asignación directa si sabe dónde quiere ponerlo:

    my (@AoA, $i, $linea);
    for $i ( 0 .. 10 ) {
	$linea = <>;
	$AoA[$i] = [ split " ", $linea ];
    }

o incluso sólo

    my (@AoA, $i);
    for $i ( 0 .. 10 ) {
	$AoA[$i] = [ split " ", <> ];
    }

Debe, por lo general, desconfiar del uso de funciones que podrían devolver listas en contexto escalar sin constancia explícita de ello. Esto será más claro para el lector casual:

    my (@AoA, $i);
    for $i ( 0 .. 10 ) {
	$AoA[$i] = [ split " ", scalar(<>) ];
    }

Si quería tener una variable $ref_to_AoA como una referencia a un array, tendrá que hacer algo como esto:

    while (<>) {
	push @$ref_to_AoA, [ split ];
    }

Ahora puede agregar nuevas filas. ¿Qué pasa con la adición de nuevas columnas? Si está tratando con matrices justo, a menudo es más fácil usar una asignación simple:

    for $x (1 .. 10) {
	for $y (1 .. 10) {
	    $AoA[$x][$y] = func($x, $y);
	}
    }

    for $x ( 3, 7, 9 ) {
	$AoA[$x][20] += func2($x);
    }

No importa qué elementos estén allí o no: serán, con mucho gusto, creados para usted, estableciendo elementos intermedios a undef cuando sea necesario.

Si sólo quiere añadir a una fila, tendrá que hacer algo un poco más divertido:

# añadir nuevas columnas a una fila existente
push @{ $AoA[0] }, "wilma", "betty";   # desreferencia explícita

Acceso e impresión

Ahora es el momento de imprimir la estructura de datos. ¿Cómo va a hacer eso? Bueno, si desea sólo uno de los elementos, es trivial:

print $AoA[0][0];

Si desea imprimir toda el conjunto, sin embargo, no se puede decir

print @AoA;		# MAL

porque solo va a conseguir un listado de referencias, y perl nunca desreferencia de forma automática. En su lugar, tiene que hacer un bucle o dos. Esto muestra toda la estructura, utilizando la construcción de un bucle al estilo del for() de la shell para ciclar por el conjunto externo de subíndices.

    for $aref ( @AoA ) {
	say "\t [ @$aref ],";
    }

Si quisiera hacer un seguimiento de los subíndices, podría hacer lo siguiente:

    for $i ( 0 .. $#AoA ) {
	say "\t elt $i is [ @{$AoA[$i]} ],";
    }

o tal vez incluso así. Observe el bucle interior.

    for $i ( 0 .. $#AoA ) {
	for $j ( 0 .. $#{$AoA[$i]} ) {
	    say "elt $i $j is $AoA[$i][$j]";
	}
    }

Como puede ver, se está haciendo un poco complicado. Es por eso que a veces es más fácil tomar un descanso en su marcha campo a través:

    for $i ( 0 .. $#AoA ) {
	$aref = $AoA[$i];
	for $j ( 0 .. $#{$aref} ) {
	    say "elt $i $j is $AoA[$i][$j]";
	}
    }

Hmm ... que aún es un poco feo. ¿Qué le parece esto?

    for $i ( 0 .. $#AoA ) {
	$aref = $AoA[$i];
	$n = @$aref - 1;
	for $j ( 0 .. $n ) {
	    say "elt $i $j is $AoA[$i][$j]";
	}
    }

Cuando se canse de escribir una impresión personalizada de las estructuras de datos, podría mirar los módulos estándar Dumpvalue o Data::Dumper. El primero es el que el depurador de Perl utiliza, mientras que el segundo genera código Perl interpretable de forma directa. Por ejemplo:

 use v5.14;     # usando el prototipo +, nuevo en v5.14

 sub show(+) {
	require Dumpvalue;
	state $bellamente = new Dumpvalue::
			    tick        => q("),
			    compactDump => 1,  # descomentar estas dos líneas
                                               # (sacarlas fuera)
			    veryCompact => 1,  # si quiere un mayor
                                               # volcado
			;
	dumpValue $bellamente @_;
 }

 # Asigna una lista de referencias de array a un array.
 my @AoA = (
	   [ "pedro", "pablo" ],
	   [ "george", "jane", "elroy" ],
	   [ "homer", "marge", "bart" ],
 );
 push @{ $AoA[0] }, "wilma", "betty";
 show @AoA;

imprimirá en pantalla:

0  0..3  "pedro" "pablo" "wilma" "betty"
1  0..2  "george" "jane" "elroy"
2  0..2  "homer" "marge" "bart"

Mientras que si comenta las dos líneas que le he dicho antes, entonces se lo muestra de esta manera:

0  ARRAY(0x8031d0)
   0  "pedro"
   1  "pablo"
   2  "wilma"
   3  "betty"
1  ARRAY(0x803d40)
   0  "george"
   1  "jane"
   2  "elroy"
2  ARRAY(0x803e10)
   0  "homer"
   1  "marge"
   2  "bart"

Porciones

Si desea obtener una porción (parte de una fila) en una matriz multidimensional, va a tener que hacer algunos juegos malabares con los subíndices. Eso es porque mientras que nosotros tenemos un sinónimo sencillo para los elementos individuales a través de la flecha puntero de desreferencia, no existe tal conveniencia para las porciones.

Aquí está cómo hacer una operación con un bucle. Vamos a suponer una variable @AoA, como antes.

    @part = ();
    $x = 4;
    for ($y = 7; $y < 13; $y++) {
	push @part, $AoA[$x][$y];
    }

Ese mismo bucle podría ser sustituido por una operación de corte:

@part = @{$AoA[4]}[7..12];

o un poco más espaciado:

@part = @{ $AoA[4] } [ 7..12 ];

Pero como se podrá imaginar, esto puede ser bastante duro para el lector.

¡Ah!, pero ¿qué pasa si yo quisiera un porción bidimensional, ¿cómo hacer que $x vaya de 4 al 8 y $y de 7 al 12? Hmm ... aquí está la forma más sencilla:

    @newAoA = ();
    for ($startx = $x = 4; $x <= 8; $x++) {
	for ($starty = $y = 7; $y <= 12; $y++) {
	    $newAoA[$x - $startx][$y - $starty] = $AoA[$x][$y];
	}
    }

Podemos reducir algunos de los bucles usando porciones

    for ($x = 4; $x <= 8; $x++) {
	push @newAoA, [ @{ $AoA[$x] } [ 7..12 ] ];
    }

Si está en una transformada Schwartziana, es probable que haya elegido map para esto

@newAoA = map { [ @{ $AoA[$_] } [ 7..12 ] ] } 4 .. 8;

Pero su jefe le puede acusar de estar buscando un trabajo de seguridad (o rápida inseguridad) usando código inescrutable, y le será difícil excusarse. :-) Si yo fuera usted, lo pondría en una función:

    @newAoA = splice_2D( \@AoA, 4 => 8, 7 => 12 );
    sub splice_2D {
	my $lrr = shift; 	# ¡ref. a un array de array de ref.!
	my ($x_lo, $x_hi,
	    $y_lo, $y_hi) = @_;

	return map {
	    [ @{ $lrr->[$_] } [ $y_lo .. $y_hi ] ]
	} $x_lo .. $x_hi;
    }

VEA TAMBIÉN

perldata, perlref, perldsc

AUTOR

Tom Christiansen <tchrist@perl.com>

Última actualización: Martes, 26 de abril 18:30:55 MDT 2011

TRADUCTORES

  • Joaquín Ferrero (Tech Lead)

  • Enrique Nell (Language Lead)