Operadores de Listas

El operador grep

Utilice el operador grep para seleccionar una sublista de una lista dada. El operador tiene dos formas de uso. El siguiente ejemplo, muestra la primera forma de uso: crea la lista @withnumbers a partir de una lista @lines formada por aquellos elementos que contienen uno o más dígitos.

@withnumbers =  grep /\d+/, @lines;
grep recibe como primer argumento una expresión regular y como segundo una lista. En Perl las expresiones regulares se escriben entre barras de división. La notación \d casa con cualquier dígito decimal. Así pues el cierre positivo casará con cualquier número entero sin signo. Para saber más sobre expresiones regulares, estudie el capítulo 3.

El siguiente código corresponde a la segunda forma de uso: guarda en $n_undef el número de items undef en el array v

$n_undef = grep { !defined($_)} @v;

Obsérvese que en esta segunda forma de uso no hay coma de separación entre el bloque y el array. En general, el primer argumento de grep es un bloque en el cual se utiliza $_ como referente a cada elemento de la lista, y se devuelve un valor lógico. Los restantes argumentos constituyen la lista de items sobre la que hay que buscar. El operador grep evalúa la expresión una vez para cada item en la lista, como si se tratara de un bucle foreach. Cuando el bloque esta constituido por una única expresión se puede escribir simplemente la expresión, separándola mediante una coma de la lista.

Ejercicio 1.13.3   Explique la interpretación que hace Perl de los siguientes fragmentos de código. Consulte 1.13.2 y el manual de Perl para entender el uso de la función grep:

@b = grep {not $_ % 5} @s;

El operador map

Si lo que se quiere es construir un array transformado del array inicial, se debe usar map:

@sizes = map {-s $_ } @files;
El operador -s retorna el tamaño de un fichero. Este código genera un array conteniendo los tamaños de los ficheros especificados en @files. La forma de uso anterior utiliza la sintáxis map bloque array. Observe la ausencia de coma.

Ejercicio 1.13.4  
  1. Explique que salida produce la transformación:
    @a = grep { defined } map { /(\d+)/; $1 } glob('/tmp/*')
    
    El operador glob produce una lista con los ficheros descritos por la expresión shell que se le pasa como argumento (vea perldoc -f glob). La expresión regular /(\d+)/ casa con números enteros sin signo. Al estar paréntizada la cadena que ha casado queda automáticamente guardada en la variable especial $1. El valor devuelto por un bloque es la última sentencia evaluada, en este caso es el valor de $1.

    Cuando un casamiento con una expresión regular tiene lugar en un contexto de lista se devuelve una lista con las cadenas que han casado con los paréntesis:

      DB<0> $a = "hola 123 b 56 c"
      DB<1> @a = $a =~ m{(\d+)\s(\w+)}
      DB<2> x @a
    0  123
    1  'b'
      DB<3> @a = $a =~ m{(\d+)\s(\w+)}g
      DB<4> x @a
    0  123
    1  'b'
    2  56
    3  'c'
    
    Quizá le ayude a entender la salida la siguiente sesión con el depurador:

      DB<0> x glob('/tmp/*.pl')
    0  '/tmp/cgisearch.pl'
    1  '/tmp/ficha.pl'
    2  '/tmp/uploadpractica.config.pl'
    3  '/tmp/uploadpractica.pl'
      DB<1> x map { m{/([^u/]+)$}; $1 } glob('/tmp/*.pl')
    0  'cgisearch.pl'
    1  'ficha.pl'
    2  undef
    3  undef
      DB<2> x grep { defined} map { m{/([^u/]+)$}; $1 } glob('/tmp/*.pl')
    0  'cgisearch.pl'
    1  'ficha.pl'
    

  2. La expresión
    @a = map { /(\d+)/ } glob('/tmp/*')
    
    produce el mismo resultado que la anterior. ¿Porqué? ¿Que retorna cada una de las evaluaciones individuales del bloque? ¿En que contexto lista o escalar se evalúa /(\d+)/?

    Observe este comando en el depurador:

     DB<1> x @a = map { /(\d+)/ } ("a123", "b", "c42")
    0  123
    1  42
    

  3. ¿Que salida produce la siguiente variante de la expresión?
    @a = map { scalar(/(\d+)/) } glob('/tmp/*')
    

push, pop, shift, unshift y splice

Perl proporciona las funciones push, pop, shift y unshift que permiten trabajar el array o lista como si de una pila o cola se tratase.

La función push tiene la sintáxis:

push(ARRAY,LIST)

empuja el valor de LIST en el ARRAY. La longitud del ARRAY se incrementa en la longitud de LIST. Es lo mismo que hacer:

    for $value (LIST) {
            $ARRAY[++$#ARRAY] = $value;
    }

La función pop tiene la sintáxis:

pop(ARRAY)
pop ARRAY

pop ARRAY tiene el mismo efecto que:

        $tmp = $ARRAY[$#ARRAY]; $#ARRAY--

Si no hay elementos en ARRAY, devuelve el valor undef.

Las funciones shift y unshift actuán de manera similar a push y pop pero utilizando el comienzo de la lista en vez del final de la misma.

Las funciones push, pop, shift y unshift son un caso particular de la función splice, la cual cambia los elementos de un ARRAY. La función splice toma 4 argumentos: el ARRAY a modificar, el índice OFFSET en el cual es modificado, el número de elementos a suprimir LENGTH y la lista de elementos extra a insertar.

splice(ARRAY,OFFSET,LENGTH,LIST)
splice(ARRAY,OFFSET,LENGTH)
splice(ARRAY,OFFSET)

La función splice devuelve los elementos suprimidos del ARRAY. Si se omite LENGTH se suprime todo desde OFFSET hacia adelante. Se cumplen las siguientes equivalencias

        push(@a,$x)           splice(@a,$#a+1,0,$x)
        pop(@a)               splice(@a,-1)
        shift(@a)             splice(@a,0,1)
        unshift(@a,$x)        splice(@a,0,0,$x)
        $a[$x] = $y           splice(@a,$x,1,$y);

No se puede acortar un array asignando undef a sus elementos del final. Para acortarlo se debe asignar $#a o utilizar un operador como pop ó splice.

@a = 1 .. 10;
$a[9] = undef;  # @a = (1 ..9, undef)
$x = pop @a;    # @a = (1 ..9)
splice @a, -2;  # @a = (1 ..7) OFFSET = -2. Como se ha suprimido 
                # LENGTH, se suprime desde el penúltimo elemento
                #  hacia adelante
$#a = 4;        # @a = (1 ..5)

La Función join

La función join convierte un arreglo en un escalar La llamada a join EXPR, LIST concatena las cadenas en LIST separadas por EXPR:

@a = ('a'..'e');
$a = join ":", @a  # $a queda con "a:b:c:d:e";

La Función split

La función split es la inversa de join: convierte un escalar en un arreglo. split /PATTERN/,EXPR,LIMIT retorna el arreglo resultante de partir la cadena EXPR. Si se especifica LIMIT indica el número máximo de campos en los que se divide.

 
lhp@nereida:~/projects/perl/src$ perl -wde 0
main::(-e:1):   0
  DB<1> $a = 'a:b:c:d:e'
  DB<2> @a = split /:/, $a
  DB<3> x @a
0  'a'
1  'b'
2  'c'
3  'd'
4  'e'
  DB<4> @a = split /:/, $a, 3
  DB<5> x @a
0  'a'
1  'b'
2  'c:d:e'
  DB<6> $a = 'a:b:c:d::'
  DB<7> @a = split /:/, $a
  DB<8> x @a # Los nulos finales se suprimen
0  'a'
1  'b'
2  'c'
3  'd'
  DB<9> @a = split /:/, $a, -1 
  DB<10> x @a # No si LIMIT es negativo
0  'a'
1  'b'
2  'c'
3  'd'
4  ''
5  ''
Observe que en split el primer parámetro es una expresión regular mientras que en join es un carácter.

La función sort

El operador sort toma una lista de valores y los ordena según el alfabeto ASCII. Por ejemplo:

@rocks = qw/ bedrocks slate rubble granite /;
@sorted = sort (@rocks); # bedrocks granite rubble slate
El operador sort de Perl utiliza los operadores de cadenas por defecto:

:~/perl/src> perl -de 0
main::(-e:1):   0
  DB<1> print "a" == "b"
1
  DB<2> @x = sort (10, 2);
  DB<3> p "@x"
10 2

El Bloque de Comparación

La función sort admite como primer argumento un bloque que determina la función de comparación. Dicho bloque depende de dos variables ''especiales'' a y b.

El valor retornado por un bloque es el valor asociado con la última sentencia del bloque. En este caso sort espera que el bloque devuelva -1, 0 ó 1. Dicho valor es utilizado como elemento de comparación. Por ejemplo:

lhp@nereida:~/Lperl/src$ perl -dwe 0
main::(-e:1):   0
  DB<1> @a = (4, 7, 9, 12, -1)
  DB<2> p 4 <=> 7
-1
  DB<3> p 9 <=> 2
1
  DB<4> p 13 <=> 12+1
0
  DB<5> @a = sort { $a <=> $b } @a;
  DB<6> p "@a"
-1 4 7 9 12
  DB<7> @a = sort { $b <=> $a } @a;
  DB<8> p "@a"
12 9 7 4 -1

Ejemplo de Ordenación

El siguiente ejemplo ordena los usuarios de un sistema unix atendiendo a su uid:

lhp@nereida:~/projects/perl/src$ cat -n sortperuid.pl
 1  #!/usr/bin/perl -w
 2  use strict;
 3  my @user =  grep { $_ !~ /^#/ } `cat /etc/passwd`;
 4  my (@name, @uid, $x);
 5
 6  for (my $i=0; $i < @user; $i++) {
 7    ($name[$i], $x, $uid[$i]) = split ':', $user[$i];
 8  }
 9
10  @name = @name[
11    sort {$uid[$a] <=> $uid[$b]} 0..$#name
12  ];
13
14  local $" = "\n";
15  print "@name\n";
En la línea 3 se obtiene en @user la lísta de líneas no comentadas en /etc/passwd. en las líneas 6-8 se inicializan los arrays @name y @uid a los nombres (login) y uid de los usuarios (campos primero y tercero de /etc/passwd). Las líneas 10-12 ordenan primero la lista 0..$#name de acuerdo con el valor de uid. El nuevo conjunto ordenado de índices es utilizado para reindexar el array name.

El operador sort en un contexto escalar devuelve undef.

Los Módulos List::Util y List::MoreUtils

El módulo List::Util provee un conjunto de funciones para el manejo de listas. Veamos algunos ejemplos de uso de List::Util:

  DB<1> use List::Util qw(first max maxstr min minstr reduce shuffle sum)
  DB<2> @a = map { int(rand 20) } 1..5
  DB<3> x @a
0  8
1  9
2  4
3  16
4  5
  DB<4> x min @a
0  4
  DB<5> x max @a
0  16
  DB<6>x first { $_ > 8 } @a
0  9
  DB<7> x sum @a
0  42
  DB<8> x shuffle @a
0  8
1  5
2  16
3  4
4  9
  DB<9> x reduce { $a." $b" } @a
0  '8 9 4 16 5'

Existe también un módulo List::MoreUtils que provee mas funciones para el manejo de listas. He aqui un fragmento de la sección SYNOPSIS de la documentación:

          use List::MoreUtils qw(any all none notall true false firstidx first_index
                                  lastidx last_index insert_after insert_after_string
                                  apply after after_incl before before_incl indexes
                                  firstval first_value lastval last_value each_array
                                  each_arrayref pairwise natatime mesh zip uniq minmax);

Veamos algunos ejemplos de uso de List::MoreUtils:

lhp@nereida:~/Lperl/src/XSUB/h2xsexample/Coord/script$ perl -de 0
main::(-e:1):   0
  DB<1> use List::MoreUtils qw(:all) # Importar todas las funciones
  DB<2> @a = (1..8,-2,-3)
  DB<4> print "@a" if any { $_ > 0 } @a
1 2 3 4 5 6 7 8 -2 -3
  DB<5> print "@a" if all { $_ > 0 } @a

  DB<6> print (false {$_ > 0} @a),"\n" # Número de elementos para los que es falsa la condición
2
  DB<7> print (firstidx {$_ < 0} @a),"\n"
8
  DB<8> @a = (1..3,2..5,3..6)
  DB<9> x uniq(@a) # Array con los elementos distintos
0  1
1  2
2  3
3  4
4  5
5  6
  DB<8> @a = 1..5; @b = 'a'..'e'; @c = 10..14
  DB<9> x mesh @a, @b, @c # Mezcla los 3 arrays
0  1
1  'a'
2  10
3  2
4  'b'
5  11
6  3
7  'c'
8  12
9  4
10  'd'
11  13
12  5
13  'e'
14  14

El Array Especial @ARGV

El array especial @ARGV contiene la lista de argumentos del programa.

El array @ARGV es usado como array por defecto cuando se realiza una operación de arrays dentro del programa principal:

nereida:~/etc> perl -wde 0 one two three
main::(-e:1):   0
  DB<1> p "@ARGV"
one two three
  DB<2> p $^X # La variable mágica $^X tiene el camino al intérprete Perl utilizado
/usr/bin/perl
  DB<3> $a = shift # No se especifica el arrray: es @ARGV
  DB<4> x $a
0  'one'
  DB<5> $b = pop
  DB<6> x $b
0  'three'
  DB<7> x $^O # La variable $^O contiene el nombre del sistema operativo.
0  'linux'
  DB<8> x $0  # La variable mágica $0 contiene el nombre del programa
0  '-e'

Allanamiento de las listas

En contra de lo que quizá algunos podrían esperar, una lista que contiene a otra lista:

@virtues = ("faith", "hope", ("love", "charity"));

no produce una jerarquía de listas, sino que la lista es aplanada y es lo mismo que si nunca se hubieran puesto los paréntesis. Esto es, es equivalente a:

@virtues = ("faith", "hope", "love", "charity");



Subsecciones
Casiano Rodríguez León
2009-10-04