Mostrando entradas con la etiqueta lenguaje. Mostrar todas las entradas
Mostrando entradas con la etiqueta lenguaje. Mostrar todas las entradas

sábado, 5 de octubre de 2013

Perl para los nuevos en Perl 2


Perl para los nuevos en Perl 2

El tutorial siguiente es la versión libre en español del artículo "Perl for Newbies" (parte 2) escrita por Shlomi Fish y que podés leer en su versión original en la dirección web http://perl-begin.org/tutorials/perl-for-newbies/

El bucle for

El bucle for tiene otra sintaxis que se puede utilizar para encapsular los bucles parecidos a while pero en una forma más compacta, lo que trae algunas ventajas.
La sintaxis de este bucle for es for ({Sentencias inicio}; {Condición}; {Comandos de iteración}) { ... }, donde {Condición} es una expresión booleana y {Sentencias inicio} y {Comandos de iteración} pueden contener una o más sentencias separadas por comas.
Cuando el intérprete se encuentra con un bucle for ejecuta sus sentencias de inicio, y luego ejecuta las sentencias dentro de su bloque mientras se cumpla la condición. {Comandos de iteración} se ejecuta al final de cada iteración.
El siguiente ejemplo imprime la tabla de multiplicar:


for ($fila = 1 ; $fila <= 10 ; $fila++)
{
    for ($columna = 1 ; $columna <= 10 ; $columna++)
    {
        $result = $fila*$columna;
        $pad = ( " "  x  (4-length($result)) );
        print $pad, $result;
    }
    print "\n";
}



Comportamiento de next en el bucle for

Una diferencia importante entre el bucle for y el bucle while es el comportamiento de next en su interior. En un bucle for next salta el resto de la iteración pero aún así ejecuta los comandos de iteración.
Por ejemplo el siguiente programa imprime los primeros 200 números primos:



MAIN_LOOP: for(
    @primos=(2), $i=3 ;
    scalar(@primos) < 200 ;
    $i++
    )
{
    foreach $p (@primos)
    {
        if ($i % $p == 0)
        {
            next MAIN_LOOP;
        }
    }
    push @primos, $i;
}
print join(", " , @primos), "\n";

Y ahora el programa equivalente que utiliza un bucle while:



@primos = (2);
$i = 3;
MAIN_LOOP:
while (scalar(@primos) < 200)
{
    foreach $p (@primos)
    {
        if ($i % $p == 0)
        {
            next MAIN_LOOP;
        }
    }
    push @primos, $i;
    $i++;
}
print join(", " , @primos), "\n";




Se cuelga. Por qué?



Cuál sintaxis de for usar?

Hasta ahora hemos aprendido acerca de tres tipos diferentes de bucles, por lo que probablemente te estás preguntando cuándo utilizar cada uno.
Una diferencia sustancial entre la notación for ($i = 0; $ i < $límite; $i++) y for $i (0 .. ($límite-1)) es que la primera se tiene que acomodar a los cambios que haya en $límite, mientras que la segunda se repetirá durante un número constante de veces (incluso si $limite se modifica dentro del bucle). Por supuesto podés hacer que la primera sea similar a la segunda mediante la asignación de $limite a una tercera variable que se mantenga constante. Sin embargo, la segunda notación es por lo general más segura en ese sentido.
El bucle foreach $item (@array) es muy útil, pero a veces cuando se itera a través de un arreglo de ítems el índice del arreglo se vuelve importante. En ese caso uno puede preferir utilizar un bucle for normal.



Hashes

Los hashes se pueden usar para mapear un conjunto de valores claves a sus valores asociados. Usando una variables hash podemos obtener un valor asociado a una clave u obtener la lista completa de claves presentes en el hash.
Para asignar o recuperar el valor de la clave $miClave en el hash $miHash se utiliza la convención $miHash{$miClave}. Para comprobar si existe una clave en un hash debería tipear exists($miHash{$miClave}) que devuelve un valor booleano que corresponde a su existencia.
Para obtener un arreglo cuyos elementos son las claves presentes en el hash puede tipear keys(%miHash). He aquí un pequeño ejemplo, que corre una operación bancaria simple, que ilustrará esta funcionalidad:



# Inicializo las operaciones válidas en un hash
$ops{'crear'} = 1;
$ops{'depositar'} = 1;
$ops{'ver estado'} = 1;
$ops{'salir'} = 2;

while (1)
{
    # Obtener una entrada válida del usuario
    while (1)
    {
        print "Por favor, escriba lo que desea hacer:\n";
        print "(" . join(", ", keys(%ops)) . ")\n";

        $function = <>;
        chomp($function);

        if (exists($ops{$function}))
        {
            last;
        }
        print "Función desconocida!\nPor favor inténtelo de nuevo.\n\n"
    }

    if ($function eq "salir")
    {
        last;
    }

    print "Entre el nombre de la cuenta:\n";
    $account = <>;
    chomp($account);
    if ($function eq "crear")
    {
        if (! exists($bank_accounts{$account}))
        {
            $bank_accounts{$account} = 0;
        }
    }
    elsif ($function eq "ver estado")
    {
        if (! exists ($bank_accounts{$account}) )
        {
            print "Error! La cuenta no existe.\n";
        }
        else
        {
            print "Hay " . $bank_accounts{$account} .
                " pesos en la cuenta.\n";
        }
    }
    elsif ($function eq "depositar")
    {
        if (exists($bank_accounts{$account}) )
        {
            print "Cuanto desea depositar?\n";
            $how_much = <>;
            chomp($how_much);
            $bank_accounts{$account} += $how_much;
        }
    }
    print "\n";
}



El ejemplo siguiente, que es considerablemente más corto, utiliza un hash para averiguar si una lista de cadenas contiene sólo cadenas únicas:



while($string = <>)
{
    chomp($string);
    if (exists($myhash{$string}))
    {
        print "La cadena \"" . $string . "\" ya estaba en la lista!";
        last;
    }
    else
    {
        $myhash{$string} = 1;
    }
}




Funciones sobre hash

delete()

La función delete() se puede utilizar para eliminar una clave o un conjunto de claves de un hash. Por ejemplo:



$myhash{"hola"} = "mundo";
$myhash{"Perl"} = "TMTOWTDI";
$myhash{"autor"} = "diego";

if (exists($myhash{"Perl"}))
{
    print "La clave Per existe!\n";
}
else
{
    print "La clave Perl no existe!\n";
}

delete($myhash{"Perl"});

if (exists($myhash{"Perl"}))
{
    print "La clave Per existe!\n";
}
else
{
    print "La clave Perl no existe!\n";
}




La coma en los hashes

La coma se puede usar para combinar dos arreglos en uno más grande. Teniendo en cuenta el hecho de que un mini-hash con una clave y un valor se puede especificar utilizando la notación $clave => $valor (que es esencialmente equivalente a $clave,$valor) un hash se puede inicializar en una sentencia. He aquí un ejemplo:



%hash1 = (
    "lorenzo jovanotti" => "ti porto via con me",
    "manel" => "aniversari",
    "diego" => "maradona"
    );

%hash2 = (
    "negramaro" => "blucobalto",
    "diego" => "mañas",
    "rem" => "shiny"
    );

%combinado = (%hash1, %hash2);

foreach $key (keys(%combinado))
{
    print $key, " = ", $combinado{$key}, "\n";
}




Si los dos hashes combinados contienen varias claves idénticas, entonces los valores del último hash van a ganar y quedarán en el hash combinado.




hashes



Declarar variables locales con "my"

Las variables pueden hacerse locales a su propio alcance declarándolas con la palabra clave "my". Cuando el intérprete abandona el alcance, la variable será restaurada a su valor original.
Por ejemplo:



$x = 5;
$y = 1000;
{
    my ($y);
    for ($y=0;$y<10;$y++)
    {
        print $x, "*", $y, " = ", ($x*$y), "\n";
    }
}

print "Ahora, \$y es ", $y, "\n";




Si querés declarar más de una variable local se debe utilizar un conjunto de paréntesis rodeando a la lista de variables. Por ejemplo: my (@arreglo, $escalar1, %hash, $escalar2). Si querés declarar sólo una variable, entonces los paréntesis son opcionales.



Usa "strict", Luke!

Perl tiene un pragma (una directiva relacionada con el intérprete) conocida como use strict, que, entre otras cosas, asegura que todas las variables que se utilizan sean declaradas con my. Si hace referencia a una variable que no fue declarada con my va a generar un error.
Usar use strict es, en la mayoría de los casos, una buena idea, ya que minimiza el número de errores a causa de errores tipográficos. Sólo tipeas use strict; al comienzo de tu programa y puedes ir a dormir tranquilo todas las noches.
Como ejemplo, aquí está el programa de números primos usando strict:



use strict;
use warnings;

my (@primos, $i);

MAIN_LOOP: for(
    @primos=(2), $i=3 ;
    scalar(@primos) < 200 ;
    $i++
    )
{
    foreach my $p (@primos)
    {
        if ($i % $p == 0)
        {
            next MAIN_LOOP;
        }
    }

    push @primos, $i;
}

print join(", " , @primos), "\n";




Observe el uso de my en la declaración del bucle foreach. Tales declaraciones just-in-time, inspiradas de C++, son posibles en Perl.



Usa "warnings", Luke!

Hay otro pragma que se llama y se escribe use warnings;, que hace que el intérprete emita muchas advertencias en la consola de comandos en caso de que estés haciendo las cosas mal (como usar undef en una expresión). También es muy útil declararla en el comienzo del programa, además de use strict;. Así que, de ahora en adelante, nuestros programas las incluirán también.



Use warnings




Funciones

Ya hemos comentado algunas de las funciones predefinidas de Perl. Perl permite definir nuestras propias funciones mediante código Perl. Cada vez que se utiliza una pieza de código más de una vez en un programa es una buena idea para convertirla en una función. De esta manera, si la pieza de código cambia, tienes que cambiarla en un solo lugar.
En Perl cada función acepta un arreglo de argumentos y devuelve un arreglo de valores como retorno. Los argumentos (también conocidos como "parámetros") pueden ser encontrados en la variable especial @_. Esta variable es mágica y no necesita ni debe ser declarada con my. Con el fin de devolver valores desde una función se puede utilizar la palabra clave return.
Para declarar una función tipeamos sub nombre_funcion { al principio y } al final. Todo lo del medio es el cuerpo de la función.
He aquí un ejemplo de una función que devuelve el valor máximo y el mínimo de un conjunto de números:



use strict;
use warnings;

sub min_and_max
{
    my (@numbers);

    @numbers = @_;

    my ($min, $max);

    $min = $numbers[0];
    $max = $numbers[0];

    foreach my $i (@numbers)
    {
        if ($i > $max)
        {
            $max = $i;
        }
        elsif ($i < $min)
        {
            $min = $i;
        }
    }

    return ($min, $max);
}

my (@test_array, @ret);
@test_array = (123, 23 , -6, 7 , 80, 300, 45, 2, 9, 1000, -90, 3);

@ret = min_and_max(@test_array);

print "El mínimo es ", $ret[0], "\n";
print "El máximo es ", $ret[1], "\n";




Y aquí hay otro ejemplo para una función que calcula la hipotenusa de un triángulo rectángulo de acuerdo con sus otros lados:



use strict;
use warnings;

sub pitagoras
{
    my ($x, $y);

    ($x, $y) = @_;

    return sqrt($x**2 + $y**2);
}

print "La hipotenusa de un triángulo rectángulo con lado 3 y 4 es ",
    pitagoras(3,4), "\n";



Funciones recursivas

Las funciones pueden llamar a otras funciones. De hecho, una función puede llamarse a sí misma, ya sea directa o indirectamente. Cuando una función se llama a sí misma se dice que hay recursión, recursividad o que la función es recursiva.
He aquí un ejemplo en el que se utiliza la recursividad para encontrar todas las permutaciones de dividir 10 entre tres números:



use strict;
use warnings;

sub mysplit
{
    my ($total, $num_elems, @accum) = @_;

    if ($num_elems == 1)
    {
        push @accum, $total;
        print join(",", @accum), "\n";

        return;
    }

    for (my $item=0 ; $item <= $total ; $item++)
    {
        my @new_accum = (@accum, $item);
        mysplit($total-$item, $num_elems-1, @new_accum);
    }
}

mysplit(10,3);



Es necesario tener en cuenta un par de cosas. En primer lugar, Perl no maneja la cola de recursión muy bien, al menos no en la encarnación actual del compilador. Si tu función se puede hacer sin recursividad, usando un bucle simple, mejor usar el bucle.
En segundo lugar, algunos sistemas (tales como Microsoft Windows) limitan al ejecutable a una cierta cantidad de pila, en la medida en que tales lenguajes como ensamblador o C se refiere. Esto no debe ser de ningún interés para hackers Perl, porque el intérprete de Perl no traduce una llamada de función Perl en una llamada a la función C. (Por no hablar de que el intérprete Perl en esos sistemas se compila con suficiente pila para sí mismo).
A veces la recursividad es útil, pero si ves que tu recursividad es demasiado profunda deberías considerar el uso de una pila propia dedicada (que se puede implementar con un arreglo). Es una buena práctica de programación.



El uso de "shift" en funciones

Se puede utilizar la función shift() para extraer argumentos del arreglo de argumentos. Dado que este uso es tan común, entonces simplemente escribiendo shift sin argumentos hace exactamente eso.
Aquí está el programa de dividir 10 entre tres números de nuevo, que fue reescrito usando shift:



use strict;
use warnings;

sub mysplit
{
    my $total = shift;
    my $num_elems = shift;
    my @accum = @_;

    if ($num_elems == 1)
    {
        push @accum, $total;
        print join(",", @accum), "\n";

        return;
    }

    for (my $item = 0 ; $item <= $total ; $item++)
    {
        my @new_accum = (@accum, $item);
        mysplit($total-$item, $num_elems-1, @new_accum);
    }
}

mysplit(10,3);



Archivos de Entrada/Salida

A estas alturas probablemente te preguntes cómo Perl se puede utilizar para interactuar con el mundo exterior, y aquí es donde Archivos de Entrada/Salida entra en escena.
En Perl los archivos de E/S son manejados usando sesiones: vas a abrir un archivo para lectura o escritura (o ambos), haces con él lo que quieres, y luego lo cierras. En Perl los manejadores de archivo son implementados con algo llamado globs que se colocan en un espacio de nombres independiente del de las variables. En general se marcan con un asterisco (*) al principio del nombre, que se puede omitir si la primera letra del nombre es mayúscula.
Para abrir un archivo se utiliza la notación open my $file_handle, $modo, $path_file; y para cerrar un archivo se utiliza close($file_handle);. El $modo determina si el archivo será abierto para lectura, escritura, agregado o alguna combinación. La siguiente tabla te da una referencia rápida:



> Escritura (El archivo original será borrado antes que la función comience)
< (o nada) Lectura
>> Agregación (El archivo no será sobreescrito, el puntero apunta al final del archivo)
+< Lectura/Escritura o solo escribir sin truncar el archivo



$path_file es la ruta de acceso al archivo que se desea abrir en relación con el directorio de trabajo actual del programa (CWD). Por ejemplo, el comando open I, "<", "../hello.txt"; abre en modo lectura el archivo "hello.txt" encontrado un directorio por encima del directorio actual.



Usando "print" con archivos

El comando print que ya conocemos también puede servir para enviar la salida a un archivo. De hecho la pantalla en sí es un manejador de archivo especial, cuyo nombre es STDOUT, pero ya que su uso es tan común Perl permite omitirlo.
La sintaxis para escribir en archivos es print File $s1, $s2, ...
He aquí un pequeño ejemplo que prepara un archivo con una pirámide adentro:



use strict;
use warnings;

my $piramideLado = 20;

open my $out, ">", "piramide.txt";
for (my $i=1 ; $i <= $piramideLado ; $i++)
{
    my $fila= "L" x $i;
    print $out $fila,"\n";
}
close($out);



Para imprimir en más de un archivo a la vez hay que utilizar dos sentencias de impresión separadas. He aquí un ejemplo que imprime en un archivo la secuencia 0, 0.1, 1, 1.1, 2, 2.1, 3, 3.1... y en el otro la secuencia 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5...



use strict;
use warnings;

open my $seq1, ">", "seq1.txt";
open my $seq2, ">", "seq2.txt";

for (my $x=0 ; $x < 100 ; $x++)
{
    print $seq1 $x, "\n";
    print $seq2 $x, "\n";
    print $seq1 ($x+0.1);
    print $seq2 ($x+0.5);
    print $seq1 "\n";
    print $seq2 "\n";
}

close($seq1);
close($seq2);



El operador <> para archivos

Al igual que print se generaliza para archivos, también se puede generalizar el <> que vimos antes. Si se coloca el nombre del manejador de archivo dentro del operador se leerá una línea desde el archivo abierto por este manejador.
He aquí un ejemplo, vamos a añadir los números de línea a un archivo determinado:



use strict;
use warnings;

my ($lineaNumero, $linea);

$lineaNumero= 0;
open my $in, "<", "input.txt";
open my $out, ">", "output.txt";

while ($linea = <$in>)
{
    # No agregamos el \n porque en la lectura no usamos la funcion chomp()
    print $out $lineaNumero, ": ", $linea;
    $lineaNumero++;
}

close ($in);
close ($out);



Y en el siguiente ejemplo contamos el número de líneas en un archivo que comienzan con la letra "A" (mayúsculas y minúsculas).



use strict;
use warnings;

my ($filename, $lineaNumero, $linea, $c);

$lineaNumero = 0;
$filename = "input.txt";
open my $in,  "<", $filename;
while ($linea = <$in>)
{
    $c = substr($linea, 0, 1);
    if (lc($c) eq "a")
    {
        $lineaNumero++;
    }
}
close($in);

print "En " , $filename, " hay ",
    $lineaNumero, " lineas que comienzan con \"A\".\n";

El comando join("", <FILEHANDLE>) devuelve el contenido del archivo a partir de la posición actual, y puede llegar a ser útil. Veremos un ejemplo en la siguiente sección.

El arreglo @ARGV

Se puede utilizar la variable @ARGV para acceder a los argumentos pasados ​​al script de Perl en la línea de comandos. He aquí un ejemplo que hace una copia de seguridad del archivo especificado como argumento:



use strict;
use warnings;

my $filename = $ARGV[0];

open my $in, "<", $filename;
open my $out, ">", $filename.".bak";
print $out join("",<$in>);
close($in);
close($out);


Usar la línea de comandos para especificar parámetros para el programa suele ser más útil que el uso de los archivos o modificar el programa cada vez.
Tenga en cuenta que a menudo es conveniente usar shift para obtener los argumentos de @ARGV de a uno. Cuando se utiliza sin parámetros y fuera de las funciones shift extrae argumentos de @ARGV.




funciones




Expresiones Regulares




Las expresiones regulares son una de las características más potentes de Perl, pero también una de las que los principiantes encuentran más difíciles de entender. No dejes que te desanime el hecho: definitivamente vale la pena el esfuerzo.
Básicamente, una expresión regular le dice al intérprete que busque una cadena para un patrón. El patrón puede ser simple como la palabra "hola" o ser complejo como la palabra "hola" seguido de cualquier número de apariciones de la palabra "uno", seguido de la palabra "triple". Esto último se puede escribir como hola(?:uno)*triple dentro de una expresión regular.
Los operadores de Perl te permiten hacer tres cosas con una expresión regular:
-Comprobar si existe en alguna parte de la cadena.
-Recuperar varias capturas de ella.
-Recuperar todas sus ocurrencias.
-Sustituir la primera ocurrencia o la totalidad de sus ocurrencias con alguna otra cadena o expresión.
Algunas funciones de Perl como split y grep (que se describe más adelante) pueden utilizar expresiones regulares para realizar otras tareas.



Sintaxis

La sintaxis para comprobar si una cadena contiene una expresión regular dada es $cadena =~ /$regexp/. Por ejemplo el siguiente programa comprueba si una cadena determinada contiene la palabra "hola" (en minúsculas):



use strict;
use warnings;

my $string = shift(@ARGV);

if ($string =~ /hola/)
{
    print "La cadena contiene la palabra \"hola\".\n";
}
else
{
    print "La cadena no contiene la palabra \"hola\".\n";
}



Ten en cuenta que los caracteres alfanuméricos en expresiones regulares se representan a sí mismos, y que el comportamiento está garantizado de no cambiar en futuras versiones de Perl. Otros caracteres pueden necesitar ser precedidos de una barra invertida (\) si se colocan dentro de una expresión regular.



El "." representa cualquier carácter

Poniendo el carácter "." en una expresión regular estamos diciendo que puede coincidir con cualquier carácter, menos el carácter de nueva línea. Por ejemplo el siguiente script busca coincidencia con palabras de 5 letras que comienzan con 'l' y terminan con 'x':



use strict;
use warnings;

my $string = lc(shift(@ARGV));

if ($string =~ /l...x/)
{
    print "Verdadero\n";
}
else
{
    print "Falso\n";
}




Los corchetes especifican opciones para un carácter

Cuando aparecen entre corchetes, se puede especificar más de un carácter dentro de ellos, como opción para coincidencias. Si el primer carácter es ^ entonces ellos coinciden con todo lo que no sea uno de esos caracteres.
Se puede especificar un rango de caracteres con el guión. Por ejemplo, el patrón [a-zA-Z0-9_] coincide con cualquier carácter alfanumérico incluyendo el guión bajo.
He aquí un ejemplo que comprueba si un identificador válido para una variable Perl está presente en la cadena:



use strict;
use warnings;

my $string = lc(shift(@ARGV));

if ($string =~ /\$[A-Za-z_]/)
{
    print "Verdadero\n";
}
else
{
    print "Falso\n";
}




El "*" y sus amigos

Con el fin de repetir un carácter más de una vez, varios patrones de repetición se pueden utilizar. El asterisco (*) se puede utilizar para indicar que el carácter puede repetirse cero o más veces. El signo más (+) se puede utilizar para indicar que el carácter debe repetirse una o más veces. En cuanto al signo de interrogación (?) indica que un caracter debe aparecer cero o una vez.
El siguiente patrón /ab*c/ coincide con "a", seguido de la cantidad de veces de "b" que nos guste (incluyendo el cero), y luego "c". El patrón /dijo h?ola/ coincide con ambas frases: "dijo hola" y "dijo ola". El patrón /mundo +loco/ coincide con la palabra "mundo", seguido de la palabra "loco", con unos espacios en blanco en el medio o al menos uno.



Agrupamientos

Uno puede utilizar la notación de agrupación en clúster (:? ...) o la notación de agrupación de captura (...) para agrupar varios caracteres tal que al grupo se le pueda añadir un patrón de repetición +, ? *.
La diferencia entre la agrupación en clúster y de captura, es que con la captura el intérprete de Perl también realiza un seguimiento del texto que coincide y lo guarda en las variables $1, $2, $3, etc. El modo de clúster no, por eso es más rápido y menos intrusivo.
Por ejemplo el siguiente programa Perl acepta una cadena como argumento y comprueba si es una frase completa que comienza con "la" y termina con "no":



use strict;
use warnings;

my $string = lc(shift(@ARGV));

if ($string =~ /la(?: +[a-z]+)* +no/)
{
    print "Verdadero\n";
}
else
{
    print "Falso\n";
}



Un ejemplo de entrada que dé Verdadero es 'la pelota no'.



Es posible que los agrupamientos se aniden, así como el ejemplo siguiente que busca número separados por comas encerrados entre llaves que están separadas por punto y coma encerrados entre corchetes:



use strict;
use warnings;

my $string = lc(shift(@ARGV));

if ($string =~ /\[\{[0-9]+(?:,[0-9]+)+\}(?:;\{(?:[0-9]+(?:,[0-9]+)+)\})+\]/)
{
    print "Verdadero\n";
}
else
{
    print "Falso\n";
}



Por lo tanto coincide con cadenas como [{54,129};{236,78}] y [{54,129};{236,78};{78,100,808};{72,1009,8}]

Alternando con "|"

Se pueden especificar varios patrones como opciones para comprobar si una expresión coincide con alguno de ellos, usando el |. Por ejemplo, perro|gato|rata buscará coincidencia en la expresión con "perro" o "gato" o "rata". Pero, naturalmente, las opciones pueden ser ellas mismas expresiones regulares tan complejas como soporte Perl.
El operador de alternancia intentará emparejar tanto de la expresión como pueda, por lo que se recomienda el uso de agrupación en clúster para limitarlo. Por ejemplo: (?:perro|gato|raton)
El siguiente patrón coincide con una secuencia de espacios en blanco que separan conjuntos de caracteres o de dígitos:



(?:[a-zA-Z]+|[0-9]+)(?: )+(?:[a-zA-Z]+|[0-9]+)+



Buscar al principio o al final de una cadena

En una expresión regular un símbolo de intercalación (^) en el comienzo de la expresión coincide con el principio de la cadena y un signo de pesos ($) al final de la expresión coincide con el final de la cadena.
Por ejemplo, el siguiente programa comprueba si una cadena se compone enteramente de letras A:



use strict;
use warnings;

my $string = shift;

if ($string =~ /^[Aa]*$/)
{
    print "La cadena introducida tiene solo letras A! \n";
}
else
{
    print "Al menos uno de los caracteres de la cadena " . "introducida no es \"A\". \n";
}



Es mucho más corto que con el uso de un bucle y también mucho más rápido (hice la prueba!).



He aquí otro ejemplo. Vamos a ver si una cadena termina con puntos suspensivos (somos permisivos y puede haber espacios al final):



use strict;
use warnings;

my $string = shift;

if ($string =~ /\.\.\. *$/)
{
    print "La cadena introducida termina con puntos suspensivos.\n";
}
else
{
    print "La cadena introducida no termina con puntos suspensivos.\n";
}



Sustituir usando expresiones regulares

Uno puede usar expresiones regulares para sustituir una aparición de una expresión regular que se encuentra dentro de una cadena a otra cosa. El operador que se utiliza para esto es s/$exp_buscada/$exp_sustituta/. El siguiente y sencillo programa reemplaza un número con la cadena "Algún Número":



use strict;
use warnings;

my $string = shift;

$string =~ s/[0-9]+/Algún Número/;

print $string, "\n";



Observe que en este caso será sustituida sólo la primera coincidencia del patrón en la cadena. El interruptor g que se tratará en breve nos permite sustituir todas las coincidencias.
Uno puede incluir las capturas que se corresponden en la expresión como parte de la cadena de sustitución. La primera captura es $1, la segunda $2, etc.
He aquí un ejemplo que invierte el orden de dos palabras al principio de una frase:



use strict;
use warnings;

my $string = shift;

$string =~ s/^([A-Za-z]+) *([A-Za-z]+)/$2 $1/;

print $string, "\n";



Nota
Las variables $1, $2, ... también están disponibles después de ejecutar la expresión regular o sustitución, y se pueden utilizar en el código del programa.



El interruptor "e"

Si una "e" se añade al final de la orden de sustitución, entonces la parte derecha se trata como una expresión Perl normal y no como un patrón, esto le da la capacidad de utilizar operadores y funciones.
En el siguiente ejemplo sustituimos la primera palabra de la frase con su longitud:



use strict;
use warnings;

my $string = shift;

$string =~ s/^([A-Za-z]+)/length($1)/e;

print $string, "\n";



Y el ejemplo siguiente convierte a mayúsculas la primera palabra que comience con la letra "S":


use strict;
use warnings;

my $string = shift;

$string =~ s/([Ss][A-Za-z]*)/uc($1)/e;

print $string, "\n";



Búsqueda no codiciosa con *? y amigos

De forma predeterminada los operadores de repetición de expresiones regulares como *, + y otros son codiciosos. Por lo tanto, van a tratar de igualar lo más posible de la cadena como puedan.
Si querés buscar para que coincidan lo menos posible, agregá un signo de interrogación (?) después de ellos. He aquí un ejemplo:



use strict;
use warnings;

my $cadena = "HolaBuenas
"; my $codicioso = $cadena; $codicioso =~ s/.*<\/html>/SUSTITUTO/; my $humilde = $cadena; $humilde =~ s/.*?<\/html>/SUSTITUTO/; print $cadena, "\n", 'Codicioso: ', $codicioso, "\n", 'No codicioso: ', $humilde, "\n";

Interruptores útiles

Hay otros interruptores aparte de "e" que pueden ser añadidos al final de la búsqueda o sustitución.
Una "i" al final de la expresion regular provoca que la expresión sea insensible a mayúsculas y minúsculas. Así, por ejemplo, una "A" coincide tanto con "a" y con "A". Ten en cuenta que las cadenas colocadas en $1, $2, etc. aún mantendrán sus letras originales.
Una "g" hace que la búsqueda o sustitución para una cadena coincida con todas las ocurrencias, no sólo con una. Si se utiliza en un contexto de arreglo (por ejemplo: @ocurrencias = ($cadena =~ /$regexp/g);) recuperará todas las coincidencias, y si se utiliza con sustitución, sustituye todas las ocurrencias en la cadena.
En este ejemplo vemos que se reemplazan todas las apariciones de la palabra "hola" por el índice de aparición:



use strict;
use warnings;

my $index = 0;
sub put_index
{
    $index++;
    return $index;
}

my $cadena = shift;

$cadena =~ s/hola/put_index()/gei;

print $cadena, "\n";



Secuencias de escape útiles

Perl tiene varias secuencias de escape que son específicas de las expresiones regulares. Aquí está una lista de algunas de las más útiles:
\w - coincide con una palabra, lo que equivale a [A-Za-z_].
\W - coincide con una no-palabra.
\s - coincide con un carácter en blanco, por lo general el espacio o el tab.
\S - coincide con un carácter que no sea en blanco.
\d - coincide con un dígito. Equivale a [0-9].
\D - coincide con un no-dígito.
\b - coincide con un límite de palabra. Esta secuencia, de hecho, tiene una anchura cero pero sin embargo es de gran utilidad.
\B - concuerda con algo que no es un límite de palabra.
Este ejemplo muestra las secuencias de escape en acción:



use strict;
use warnings;

my @ocurrencias;
my $cadena= "A 40 km de X7z";
print $cadena,"\n";
print "Digito y no digito\n";
@ocurrencias = ($cadena =~ /\d/g);
foreach my $o (@ocurrencias){
   print "'",$o,"'\n";
}
@ocurrencias = ($cadena =~ /\D/g);
foreach my $o (@ocurrencias){
   print "\t'",$o,"'\n";
}
print "Palabra y no palabra\n";
@ocurrencias = ($cadena =~ /\w+/g);
foreach my $o (@ocurrencias){
   print "'",$o,"'\n";
}
@ocurrencias = ($cadena =~ /\W+/g);
foreach my $o (@ocurrencias){
   print "\t'",$o,"'\n";
}
print "Limite y no limite\n";
@ocurrencias = ($cadena =~ /\b/g);
foreach my $o (@ocurrencias){
   print "'",$o,"'\n";
}
@ocurrencias = ($cadena =~ /\B/g);
foreach my $o (@ocurrencias){
   print "\t'",$o,"'\n";
}
print "Blanco y no blanco\n";
@ocurrencias = ($cadena =~ /\s+/g);
foreach my $o (@ocurrencias){
   print "'",$o,"'\n";
}
@ocurrencias = ($cadena =~ /\S+/g);
foreach my $o (@ocurrencias){
   print "\t'",$o,"'\n";
}



El siguiente paso

Esto fue sólo una introducción a las expresiones regulares de Perl. Hay mucha más funcionalidad disponible y te voy a referenciar las siguientes fuentes para obtener más información:





expresiones regulares



Interpolación de cadenas

Perl soporta la inserción de variables en las constantes de cadena con sólo colocar su nombre junto con el signo pesos en su interior. He aquí un ejemplo:



use strict;
use warnings;

my $name;
print "Introduzca su nombre:\n";
$name = <>;
chomp($name);
print "Hola $name!\n";



Ten en cuenta que una vez que Perl se encuentra con un signo $ tratará de usar tantos caracteres como sea posible de la cadena para usarlos como nombre de la variable, incluso si una variable con ese nombre no existe. Por lo tanto, si escribes $xy dentro de una cadena, no va a buscar el valor de $x y añadir la cadena "y" después! Para superar esta limitación, se puede limitar el nombre de la variable usando llaves: "Hola ${chico}y${chica}".
En cualquier caso, la interpolación es especialmente útil para la construcción de expresiones regulares, ya que la cadena puede contener caracteres de control.



Funciones útiles

Perl tiene muchas funciones que permiten operaciones por lotes en arreglos. Estas funciones no sólo son convenientes, sino que también son más rápidas que desarrollarlas en bucles for y foreach.
Este comportamiento a veces se conoce como la tubería Perl porque se asemeja a las tuberías utilizadas por las consolas estilo LINUX.
Veremos a continuación: split, map, sort y grep.



split

La función split puede ser utilizada para dividir una cadena de acuerdo con una expresión regular. La sintaxis de la función split es la siguiente:
@componentes = split (/$regexp/, $cadena);
He aquí un ejemplo simple que recupera la identificación del usuario de un determinado nombre de usuario:


use strict;
use warnings;

my ($linea, @parte);

my $user_name = shift;

open my $in, "<", "/etc/passwd";
while ($linea = <$in>)
{
    @parte = split(/:/, $linea);
    if ($parte[0] eq $user_name)
    {
        print "El ID de $user_name es " . $parte[2] . "\n";
        exit(0);
    }
}
close($in);

print "El ID de $user_name no fue encontrado!\n";
exit(-1);



En el siguiente código se divide una frase en palabras y las imprime:



use strict;
use warnings;

my $frase = shift;

my @palabras = split(/\s+/, $frase);

my $i;
for($i=0;$i<scalar(@palabras);$i++)
{
    print "$i: " . $palabras[$i] . "\n";
}



Ahora tomamos un párrafo y lo dividimos en oraciones:



use strict;
use warnings;

my $parrafo = "La circulación de las gentes trae como consecuencia natural 
la circulación de dinero, y, lo que es más importante, la de las ideas. 
Cambiar de horizonte, cambiar de método de vida y de atmósfera, es 
provechoso a la salud y a la inteligencia. Hay algunos que no salen de 
la ciudad buscando en el campo la calma y el sosiego como contraste a 
su perpetua agitación. Adoradores de un ídolo, corren a rendirle culto 
adonde se trasladan sus sacerdotes. Esclavos de la moda y las exigencias 
sociales, cambian de decoración; pero van a los puntos en que se reúne el 
mundo elegante a continuar representando la misma escena.";

my @oraciones = split(/\./, $parrafo);

my $i;
for($i=0;$i<scalar(@oraciones);$i++)
{
    print "$i: " . $oraciones[$i] . "\n";
}




map

La función map atraviesa un arreglo y mapea cada elemento a uno o más (o cero) elementos en una nueva matriz. Tiene una sintaxis poco ortodoxa que se escribe como @nuevo_arreglo = (map { <Alguna Expresión con $_> } @arreglo) .
Para cada elemento del arreglo @arreglo, se asigna su valor a la variable $_ y dentro de los corchetes se puede poner una expresión que depende de ella.
El siguiente ejemplo multiplica cada elemento de un arreglo por 2:



use strict;
use warnings;

my @arreglo = (20, 3, 1, 9, 100, 88, 75);

my @nuevo_arreglo = (map { $_*2; } @arreglo);

print join(",", @nuevo_arreglo), "\n";



Usar map es generalmente más rápido que usar un bucle foreach $elem (@arreglo) {... push @nuevo_arreglo, $nuevo_elem; }, al menos cuando la función realizada es una relativamente simple.
El siguiente programa decodifica una codificación comprimida por longitudes, en la que cada elemento del arreglo es un número o palabra seguido por el número de veces que se debe repetir:



use strict;
use warnings;

my $default_input_string = "oro-4,2-9,1-2,8-1,4-7,20-9,cromo-3,16-9";

my $input_string = shift || $default_input_string;

my @componentes = split(/,/, $input_string);
my @secuencia_expandida = (map
    {
        my ($cosa, $veces) = split(/-/, $_);
        (($cosa) x $veces);
    }
    @componentes
    );

print join(",", @secuencia_expandida), "\n";



Como puedes ver, la última expresión entre llaves puede ser un arreglo con más de un elemento. También puede ser un arreglo vacío, lo que significa que algunos elementos pueden ser filtrados.



sort

La función sort puede ordenar un arreglo basado en una expresión de comparación. Al igual que con la función map, esta expresión puede ser tan compleja como desee y puede incluir en realidad una llamada a una función específica.
Dentro del bloque de comparación, las variables $a y $b indican los dos elementos que se comparan. Si la expresión devuelve un valor negativo significa que $a está antes que $b. Si es positivo, significa que $b viene antes $a. Si es cero, significa que no importa quien va primero.
El siguiente ejemplo ordena un arreglo de enteros numéricamente:




use strict;
use warnings;

my @arreglo = (100,5,8,92,-7,34,29,58,8,10,24);

my @sorted_arreglo =
    (sort
        {
            if ($a < $b)
            {
                return -1;
            }
            elsif ($a > $b)
            {
                return 1;
            }
            else
            {
                return 0;
            }
        }
        @arreglo
    );

print join(",", @sorted_arreglo), "\n";



Operadores <=> y cmp

Perl tiene dos operadores, <=> y cmp, que son muy útiles cuando se desea ordenar arreglos. $x <=> $y devuelve -1 si $x es numéricamente menor que $y, 1 si es mayor, y 0 si son iguales.
cmp hace lo mismo para la comparación de cadenas. Por ejemplo, el ejemplo anterior puede reescribirse como:



use strict;
use warnings;

my @arreglo = (100,5,8,92,-7,34,29,58,8,10,24);

my @sorted_arreglo = (sort { $a <=> $b } @arreglo);

print join(",", @sorted_arreglo), "\n";



Mucho más civilizado ¿no es cierto? En el siguiente ejemplo ordena un arreglo de cadenas a la inversa:



use strict;
use warnings;

my @input = (
    "Hola Mundo!",
    "Tú eres todo lo que necesito",
    "Ser o no ser",
    "Hay más de una manera de hacerlo",
    "Absolutamente Fabuloso",
    "Ci vis pacem, para belum",
    "Linux - Because software problems should not cost money",
    "Dadme la libertad o dadme la muerte",
);

# Hace un ordenamiento insensible a mayúsculas/minúsculas
my @sorted = (sort { lc($b) cmp lc($a); } @input);

print join("\n", @sorted), "\n";

grep

La función grep se puede utilizar para filtrar los elementos de un arreglo sobre la base de una expresión booleana o una expresión regular. La sintaxis para el uso de una expresión booleana es similar a map, mientras que la sintaxis para el uso de expresiones regulares es similar a split.
He aquí un ejemplo que toma un archivo y filtra sólo los comentarios de Perl cuya longitud es menor de 80 caracteres:


use strict;
use warnings;

my (@lineas, $linea);

my $filename = shift;

open my $in, "<", $filename;
while ($linea = <$in>)
{
    chomp($linea);
    push @lineas, $linea;
}
close($in);

# Filtrar los comentarios
my @comentarios = grep(/^#/, @lineas);
# Descartar los comentarios muy largos
my @short_comentarios = (grep { length($_) <= 80 ; } @comentarios);

print join("\n", @short_comentarios), "\n";



Y así es como grep puede ayudarnos a encontrar los primeros 100 números primos:



use strict;
use warnings;

my @primos = (2);
for(my $n=3 ; scalar(@primos) < 100 ; $n++)
{
    if (scalar(grep { $n % $_ == 0 ; } @primos) == 0)
    {
        push @primos, $n;
    }
}
print join(", ", @primos), "\n";



Un ejemplo que filtra los números pares de un arreglo:


use strict;
use warnings;

my @arreglo = (20, 3, 1, 9, 100, 88, 75);

my @nuevo_arreglo = (grep { ! ($_%2); } @arreglo);

print join(",", @nuevo_arreglo), "\n";



Referencias

Perl permite que uno se refiera a la ubicación de una cierta variable en la memoria. Una expresión que almacena tal ubicación se llama referencia. Los que están familiarizados con los punteros de C o Pascal pueden pensar en referencias como punteros. Sin embargo, existen dos diferencias fundamentales:
-No hay aritmética de punteros en Perl. Si, por ejemplo, una referencia apunta al quinto elemento de un arreglo y a continuación le sumo 1 a ella, no apuntará el sexto elemento. De hecho, la adición o sustracción de números enteros de referencias es posible, pero sin sentido.
-Una referencia está garantizada de seguir siendo la misma incluso si un arreglo o una cadena cambian de tamaño. En C, realocar un arreglo produce un puntero completamente diferente.
Perl hace diferencia entre un arreglo o un hash y una referencia a ellos. La referencia de un arreglo cualquiera puede ser tomada, y una referencia a un arreglo siempre puede ser usada para acceder a sus elementos, pero todavía hay una diferencia en la funcionalidad.
La mejor manera de cambiar una variable en un alcance diferente (por ejemplo, dentro de una función diferente) es pasar su referencia a la función. La función llamada puede entonces referenciar a la variable para acceder o modificar su valor.



Ejemplo: Las Torres de Hanoi

En este ejemplo, que tiene por objeto dar una idea de las capacidades de las referencias, vamos a resolver el bien conocido problema de las Torres de Hanoi (consulte el enlace para obtener más información sobre el problema, también explicado para programadores en La Torre). El número de discos se puede introducir desde la línea de comandos. Las propias torres se pueden representar como un arreglo de tres elementos, cada uno de los cuales es una referencia a un arreglo.


Vamos a utilizar la solución recursiva en la que con el fin de mover una columna de $N discos, primero movemos los $N - 1 discos de la parte superior de la columna, luego movemos el disco de más abajo, y entonces pasamos los $N - 1 discos a la parte superior de ese disco.



Aquí va:




use strict;
use warnings;

my $num_disks = shift || 9;

my @towers = (
    [ reverse(1 .. $num_disks) ],  # Un [ ... ] es una referencia anónima
    [ ],                           # a un arreglo
    [ ]
    );

sub print_towers
{
    for(my $i=0 ; $i < 3 ; $i++)
    {
        print ": ";
        print join(" ", @{$towers[$i]}); # Des-referenciamos la torre
        print "\n";
    }
    print "\n\n";
}

sub move_column
{
    my $source = shift;
    my $dest = shift;
    my $how_many = shift;

    if ($how_many == 0)
    {
        return;
    }
    # Busca la tercera columna
    my $intermediate = 0+1+2-$source-$dest;
    move_column($source, $intermediate, $how_many-1);
    # Imprime el estado actual
    print_towers();
    # Mueve un disco. Observe la des-referenciación
    # usando @{$ref}.
    push @{$towers[$dest]}, pop(@{$towers[$source]});
    move_column($intermediate, $dest, $how_many-1);
}

# Mueve la columna completa
move_column(0,1,$num_disks);
print_towers();



Si no lo habías hecho antes:
Prueba el script con 1, 2 y 3 discos para ver cómo funciona. Si llamamos al script "hanoi.pl" las llamadas serían:
hanoi.pl 1
hanoi.pl 2
hanoi.pl 3
Como el problema es exponencial, si probamos por ejemplo con 25 discos, el script tardará bastante tiempo en terminar XD.



Tomando una referencia a una variable existente

En Perl la barra invertida (\) es un operador que devuelve la referencia a una variable existente. También puede devolver una referencia dinámica a una constante.



He aquí un ejemplo que utiliza una referencia para actualizar una suma:




use strict;
use warnings;

my $suma = 0;

sub update_sum
{
    my $ref_suma = shift;
    foreach my $item (@_)
    {
        # El ${ ... } des-referencia la variable
        ${$ref_suma} += $item;
    }
}

print "\$suma es ", $suma, "\n";

update_sum(\$suma, 5, 4, 9, 10, 11);
update_sum(\$suma, 100, 80, 7, 24);

print "\$suma es ahora ", $suma, "\n";



Como se puede ver, debido a que se utiliza la referencia a $suma, su valor cambia durante todo el programa.



[@arreglo] - una referencia dinámica a un arreglo

Un arreglo encerrado entre corchetes ([@arreglo]) devuelve una referencia dinámica a un arreglo. Esta referencia no afecta directamente a los valores de @arreglo, por lo que se llama dinámica.
Ya hemos encontrado estas referencias dinámicas a arreglos en el ejemplo de las Torres de Hanoi. Sin embargo, su uso no se limita a lo que vimos allí. Como una referencia a un arreglo es un escalar, puede servir como un valor de un hash y por lo tanto, servir como un miembro de objeto (como se verá más adelante en estos tutoriales).



En este ejemplo, una función recibe dos arreglos y devuelve un arreglo que es la suma vectorial de ambos:


use strict;
use warnings;

sub vector_suma
{
    my $v1_ref = shift;
    my $v2_ref = shift;

    my @ret;

    my @v1 = @{$v1_ref};
    my @v2 = @{$v2_ref};

    if (scalar(@v1) != scalar(@v2))
    {
        return undef;
    }
    for(my $i=0;$i<scalar(@v1);$i++)
    {
        push @ret, ($v1[$i] + $v2[$i]);
    }

    return [ @ret ];
}

my $ret = vector_suma(
    [ 5, 9, 24, 30 ],
    [ 8, 2, 10, 20 ]
);

print join(", ", @{$ret}), "\n";



Nota
La diferencia fundamental entre el uso de \@miarreglo en una variable existente denominada @miarreglo a usar [@miarreglo] es la siguiente: la última forma crea una copia dinámica de @miarreglo y si cambia la copia, @miarreglo no va a cambiar con ella. Por otro lado, los cambios realizados en una referencia generada por la barra invertida, afectarán a la variable original.
Tenga en cuenta que si los miembros de @miarreglo son en sí mismos referencias, entonces la segunda forma no hará una copia en profundidad de ellos. Por lo tanto, se pueden modificar también incluso en la segunda forma.



Veamos la diferencia en este ejemplo:



use strict;
use warnings;

my @vida= (1..12);
my $ret= [@vida];

print join(", ", @{$ret}), "\n";
print join(", ", @vida), "\n";

@{$ret}[5]= '*';

print join(", ", @{$ret}), "\n";
print join(", ", @vida), "\n";



{ %hash } - una referencia dinámica a un hash

En una forma similar a los corchetes, colocar un hash entre llaves ({...}) construye una referencia a un hash. Como en la referencia a arreglos, esta referencia es un escalar y se puede utilizar como un elemento de un arreglo o de un valor de hash. Además, sus valores pueden ser referencias a otros arreglos o hashes.
Para demostrar esto vamos a ver el código de una parte de la tabla de contenidos de este mismo tutorial para demostrar las capacidades de la estructura de datos de múltiples niveles de Perl:




use strict;
use warnings;

my $contenidos =
{
    'titulo' => "Contenidos",
    'subs' =>
    [
        {
            'url' => "for",
            'titulo' => "El bucle for",
            'subs' =>
            [
                {
                    'url' => "next.html",
                    'titulo' => "Comportamiento de next en el bucle for",
                },
                {
                    'url' => "whence_for.html",
                    'titulo' => "Cual sintaxis de for usar?",
                },
            ],
        },
        {
            'url' => "hashes",
            'titulo' => "Hashes",
            'subs' =>
            [
                {
                    'url' => "functions.html",
                    'titulo' => "Funciones sobre hash",
                },
            ],
        },
        {
            'url' => "my",
            'titulo' => "Declarar variables locales con \"my\"",
            'subs' =>
            [
                {
                    'url' => "use_strict.html",
                    'titulo' => "usa \"strict\", Luke!",
                },
            ],
        },
        {
            'url' => "argv.html",
            'titulo' => "El arreglo \@ARGV",
        },
    ],
    'imagenes' =>
    [
        {
            'url' => 'style.css',
            'type' => 'text/css',
        }
    ],
};

sub get_contents
{
    return $contenidos;
}



El operador ->

Una flecha (->), seguida de corchetes o llaves, puede ser usada para acceder directamente a los elementos de un arreglo o hash apuntados por una referencia. Por ejemplo: $arreglo_ref->[5] recupera el quinto elemento del arreglo apuntado por $arreglo_ref.
Como ejemplo vamos a imprimir una parte del árbol de contenidos que escribimos en el código anterior:



use strict;
use warnings;

do "lol.pl";         # Carga el archivo anterior

my $cont = get_contents();

print $cont->{'titulo'}, "\n";
print $cont->{'subs'}->[0]->{'url'}, "\n";
print $cont->{'subs'}->[0]->{'subs'}->[1]->{'titulo'}, "\n";



Ten en cuenta que las flechas después de la primera flecha son opcionales porque Perl ve que la intención del programador es acceder al subsiguiente ítem. Sin embargo, la primera es obligatoria pues la expresión $arreglo_ref{'elem'} busca en la variable %arreglo_ref.
Por lo tanto el código anterior es equivalente a:


use strict;
use warnings;

do "lol.pl";         # Carga el archivo anterior

my $cont = get_contents();

print $cont->{'titulo'}, "\n";
print $cont->{'subs'}[0]{'url'}, "\n";
print $cont->{'subs'}[0]{'subs'}[1]{'titulo'}, "\n";




Desreferenciación

La estructura completa de datos o el escalar apuntado por la referencia pueden ser recuperados por la desreferenciación de la referencia. Se realiza mediante el uso de un $, un @ o un % (dependiendo de si la referencia se refiere a un escalar, un arreglo o un hash, respectivamente), y a continuación la referencia dentro de llaves.



Aquí hay algunos ejemplos sencillos:




use strict;
use warnings;

my $ds1 =
{
    'h' => [5,6,7],
    'y' => { 't' => 'u', 'o' => 'p' },
    'hello' => 'up',
};

my $arreglo_ref = [5, 6, 7, 10, 24, 90, 14];
my $x = "Hola Mundo!";
my $y = \$x;

print "\$arreglo_ref:\n";

print join(", ", @{$arreglo_ref}), "\n";


print "\n\n\$ds1->{'h'}:\n";

print join(", ", @{$ds1->{'h'}}), "\n";

my %hash = %{$ds1->{'y'}};

print "\n\n\%hash:\n";

foreach my $k (keys(%hash))
{
    print $k,  " => ", $hash{$k}, ' ';
}


print "\n\n\$\$y:\n";

print ${$y}, "\n";



Si la expresión que da la referencia es sencilla se pueden omitir las llaves (por ejemplo: @$arreglo_ref o $$ref). Sin embargo, suponiendo que utilizas las llaves, la expresión en su interior puede ser tan compleja como desees.



Uso del depurador de Perl

Incluso si el código Perl compila y se ejecuta, no significa que esté libre de errores. Muy a menudo vas a escribir programas que cuando se les da una entrada específica se comportan de manera muy diferente de su comportamiento previsto. Perl proporciona al usuario con un depurador que puede ayudar en la captura de estos errores en el programa.
El depurador se invoca simplemente añadiendo un switch "-d" antes del nombre del script. Por ejemplo: perl -d hello.pl. Después de tipear "Intro" en la línea de comandos del shell, tendrás una sesión de depuración interactiva a tu disposición. En esta sesión se puede controlar la ejecución del programa, así como introducir cualquier comando de Perl que te guste.
El siguiente capítulo cubre los fundamentos del uso del depurador de Perl y te da consejos sobre dónde encontrar más información.



Pasando por encima y entrando en

El depurador muestra la instrucción de Perl que está a punto de ser ejecutada en la ventana de visualización, y debajo del símbolo del sistema. Se ve algo como esto:



diego:~/bin# perl -d add_cr.pl
Default die handler restored.

Loading DB routines from perl5db.pl version 1.07
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(add_cr.pl:5): my ($contents);
  DB<1>



El comando más utilizado en el depurador es tener que ejecutar una (y sólo una) sentencia de Perl. Esto se llama stepping y hay dos tipos: stepping over (pasar por encima) y stepping in (entrar en).
La diferencia es que "pasar por encima" no entra en las funciones de Perl que fueron llamadas en la expresión que se evalúa. "Entrando en", por otro lado, sí entra en esas funciones.
Para pasar por encima tipea "n" (abreviatura de "next"), seguido de Intro, y verás el siguiente comando de Perl. Para entrar en ella tipea "s" (abreviatura de "step").
Y he aquí cómo se verá la pantalla después de unos pocos pasos:



diego:~/bin# perl -d add_cr.pl test.txt
Default die handler restored.

Loading DB routines from perl5db.pl version 1.07
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(add_cr.pl:5): my ($contents);
  DB<1> n
main::(add_cr.pl:7): foreach my $file (@ARGV)
main::(add_cr.pl:8): {
  DB<1> n
main::(add_cr.pl:9):     $contents = "";
  DB<1> n
main::(add_cr.pl:11):     open my $in, "<", $file;
  DB<1>

Nota que a veces una expresión tomará más de un paso para pasar. Las operaciones como map, sort y amigas son especialmente conocidas por eso. Los puntos de interrupción que se tratarán en la siguiente sección ofrecen una solución a este problema.



Establecer puntos de interrupción y continuar

Cuando el usuario coloca un punto de interrupción en una determinada línea de un programa, le indica al depurador que detenga la ejecución del programa cuando se alcanza esa línea. A continuación se puede hacer que el programa siga ejecutando libremente con el comando "continue".
Los puntos de interrupción generalmente ahorran mucho tiempo del paso a paso. Para establecer un punto de interrupción tipea "b NUM_LINEA" o "b FUNCION", donde NUM_LINEA es el número de línea y FUNCION es el nombre de la función. Un punto de interrupción en una función detiene la ejecución del programa tan pronto como entra en la función.
Para continuar la ejecución del programa hasta que se alcance otro punto de interrupción (o hasta que el programa termine) tipea "c". Aquí hay un ejemplo de sesión con el mismo programa:




diego:~/bin# perl -d add_cr.pl test.txt
Default die handler restored.

Loading DB routines from perl5db.pl version 1.07
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(add_cr.pl:5): my ($contents);
  DB<1> b 11
  DB<2> c
main::(add_cr.pl:11):     open my $in, "<", $file;
  DB<2>



Se puede establecer un punto de interrupción condicional escribiendo "b [línea] [expresión Perl]". Una expresión condicional es una que detiene la ejecución del programa sólo si la expresión Perl se evalúa como verdadera. La expresión Perl puede ser tan compleja como quieras y puede incluir espacios en blanco.
Para eliminar punto de interrupción tipea "d [línea]". Después de eliminar un punto de interrupción ya no afecta a la ejecución del programa.



Ejecución de comandos Perl en el depurador

Se pueden ejecutar comandos Perl dentro del depurador. Por ejemplo si se escribe print $x en el símbolo del depurador imprimirá el valor de la variable $x. También se pueden modificar los valores de las variables de esta forma, o ejecutar funciones, etc.
Un comando especial que también es útil es "x". "x $variable" muestra de una manera jerárquica a $variable, lo que es muy útil para las listas de listas, arreglos de arreglos, hashes de hashes, etc. El módulo Data::Dumper que está disponible en CPAN ofrece una funcionalidad similar desde dentro de los programas Perl.



Cómo obtener más información

El comando h en el depurador muestra la lista de comandos de depuración disponibles.
La información sobre el uso del depurador de Perl está disponible en el documento perldebug.
Hay algunos front-ends para el depurador de Perl. Uno de ellos, que se ejecuta en Linux, es ddd.



Final


Me acuerdo del día en que mi hija entró, miró por encima de mi hombro en algún código de Perl 4, y me dijo: "¿Qué es eso, estás maldiciendo?"
Larry Wall (el padre de Perl) en <199806181642.JAA10629@wall.org>

Pero por suerte el próximo back-end de Perl 5 - Parrot soportará front-ends de sintaxis múltiples, así que uno va a ser capaz de escribir en Perl con una sintaxis alternativa que no tenga todos estos sigils molestos. La pregunta es: ¿por qué alguien va a querer hacer eso :-)?
No, en serio. Esperamos que hayas disfrutado de esta serie de tutoriales, pero recuerda que no te enseña todo lo que debes saber sobre Perl, y mucho menos la forma de entender el código de otras personas.
A estas alturas, es probable que puedas entender los documentos básicos de Perl, por lo que te recomendamos que los leas en caso de que estés buscando obtener más información. También puedes consultar el sitio Principiantes Perl para más fuentes de información, así como formas de obtener ayuda en línea.
Esperamos continuar con estos tutoriales para cubrir temas como: módulos, objetos y referencias a funciones en Perl. Hasta entonces, mantén la calma y que el signo pesos siga contigo!
























lunes, 27 de mayo de 2013

Corolario I


Un juego en Perl

Como corolario de la Parte I de "Perl for newbies" vamos a programar un juego muy simple. Se trata de adivinar qué número está pensando la computadora. Se le pide al usuario que adivine el número y le informamos si acertó o si su número es mayor o menor. Manos a la obra!

print '*'x78;
print "\n";
print 'Adivina mi numero';
print "\n";
print '*'x78;
print "\n";

print 'Estoy pensando un numero',"\n";
my $miNumero= 5;
print 'Quieres adivinar mi numero? (si/no)',"\n";
my $respuesta= <>;
chomp($respuesta);
if ('si'eq$respuesta) {
   print 'Dime que numero estoy pensando',"\n";
   my $numero= <>;
   chomp($numero);
   if ($miNumero==$numero){
      print 'Si! Acertaste! Mi numero es el ', $miNumero, '!!!',"\n";
   } elsif($miNumero<$numero) {
      print 'No. Mi numero es menor que ', $numero, "\n";
   } else {
      print 'No. Mi numero es mayor que ', $numero, "\n";
   }
} else {
   print 'Esta bien, no quieres adivinar mi numero',"\n";
}

print 'Gracias por jugar conmigo',"\n";
print '-'x78;
print "\n";

Las posibles salidas del programa son:

***************************************************************************
Adivina mi numero
***************************************************************************
Estoy pensando un numero
Quieres adivinar mi numero? (si/no)
si
Dime que numero estoy pensando
5
Si! Acertaste! Mi numero es el 5!!!
Gracias por jugar conmigo
---------------------------------------------------------------------------

***************************************************************************
Adivina mi numero
***************************************************************************
Estoy pensando un numero
Quieres adivinar mi numero? (si/no)
si
Dime que numero estoy pensando
2
No. Mi numero es mayor que 2
Gracias por jugar conmigo
---------------------------------------------------------------------------

***************************************************************************
Adivina mi numero
***************************************************************************
Estoy pensando un numero
Quieres adivinar mi numero? (si/no)
si
Dime que numero estoy pensando
9
No. Mi numero es menor que 9
Gracias por jugar conmigo
---------------------------------------------------------------------------

***************************************************************************
Adivina mi numero
***************************************************************************
Estoy pensando un numero
Quieres adivinar mi numero? (si/no)
no
Esta bien, no quieres adivinar mi numero
Gracias por jugar conmigo
---------------------------------------------------------------------------
Usamos el operador x para imprimir una cantidad fija de caracteres, en este caso '*' y '-'. También usamos <> para recibir la respuesta del usuario, chomp() para limpiarla, print para enviarle mensajes y la estructura de control if para tomar decisiones según el valor ingresado. El problema es que el usuario puede arriesgar una sola vez el valor, además ya sabemos que el número elegido será el 5 así que el juego se torna algo monótono y aburrido. Deberíamos darle al usuario un número distinto cada vez y la oportunidad de adivinarlo con cierta cantidad de intentos, digamos 3. Hagamos las modificaciones para que quede mejor este juego.

Modificaciones

print '*'x78;
print "\n";
print 'Adivina mi numero';
print "\n";
print '*'x78;
print "\n";

print 'Estoy pensando un numero del 0 al 9',"\n";
my $miNumero= int(rand(10));
print 'Quieres adivinar mi numero? (si/no)',"\n";
my $respuesta= <>;
chomp($respuesta);
my $miNumero;
if ('si'eq@respuesta) {
   for my $i (1..3){
      print 'Dime que numero estoy pensando',"\n";
      my $numero= <>;
      chomp($numero);
      if ($miNumero==$numero){
         print 'Si! Acertaste! Mi numero es el ', $miNumero, '!!!', "\n";
         print "Y usaste $i intentos \n";
         last;
      } elsif($miNumero<$numero) {
         print 'No. Mi numero es menor que ', $numero, "\n";
         if (3==$i){ print "Mi numero era el $miNumeroo \n"; }
      } else {
         print 'No. Mi numero es mayor que ', $numero, "\n";
         if (3==$i){ print "Mi numero era el $miNumeroo \n"; }
      }
   }
} else {
   print 'Esta bien, no quieres adivinar mi numero',"\n";
}

print 'Gracias por jugar conmigo',"\n";
print '-'x78;
print "\n";

Usamos la función rand() para obtener un número aleatorio entre 0 y el argumento, en este caso 10, y aunque el número aleatorio podría ser el 0 nunca va a ser 10, pero como son números de coma flotante también usamos la función int() para que nos deje la parte entera del número. Usamos un bucle for que va de 1 a 3 gracias al operador .. que nos devuelve un arreglo de números consecutivos. Usamos last para salir del bucle cuando el usuario adivina el número. Pero el programa no funciona como queremos. Cuando el usuario escribe 'si' el programa termina sin informarnos nada anormal. Para que Perl nos dé más información sobre los posibles errores tenemos que usar dos pragmas muy útiles: strict y warnings. Veamos la diferencia. Cuando agregamos al comienzo del programa
use strict;
use warnings;
Nos informa lo siguiente:
Global symbol "@respuesta" requires explicit package name at adivina2.pl
Global symbol "$miNumeroo" requires explicit package name at adivina2.pl
Hay dos errores de tipeo, primero el @ en la variables $respuesta, luego la 'o' repetida al final de $miNumero. Corregimos y nos dice lo siguiente:
"my" variable $miNumero masks earlier declaration in same scope at adivina2.pl
Parece que declaramos dos veces la misma variable. Borramos la segunda declaración que es la incorrecta en este caso y corremos el programa. Funciona!
use warnings;
use strict;

print '*'x78;
print "\n";
print 'Adivina mi numero';
print "\n";
print '*'x78;
print "\n";

print 'Estoy pensando un numero del 0 al 9',"\n";
my $miNumero= int(rand(10));
print 'Quieres adivinar mi numero? (si/no)',"\n";
my $respuesta= <>;
chomp($respuesta);
if ('si'eq$respuesta) {
   for my $i (1..3){
      print 'Dime que numero estoy pensando',"\n";
      my $numero= <>;
      chomp($numero);
      if ($miNumero==$numero){
         print 'Si! Acertaste! Mi numero es el ', $miNumero, '!!!', "\n";
         print "Y usaste $i intentos \n";
         last;
      } elsif($miNumero<$numero) {
         print 'No. Mi numero es menor que ', $numero, "\n";
         if (3==$i){ print  "Mi numero era el $miNumero \n"; }
      } else {
         print 'No. Mi numero es mayor que ', $numero, "\n";
         if (3==$i){ print "Mi numero era el $miNumero \n"; }
      }
   }
} else {
   print 'Esta bien, no quieres adivinar mi numero',"\n";
}

print 'Gracias por jugar conmigo',"\n";
print '-'x78;
print "\n";

Y esta es mi jugada:

***************************************************************************
Adivina mi numero
***************************************************************************
Estoy pensando un numero del 0 al 9
Quieres adivinar mi numero? (si/no)
si
Dime que numero estoy pensando
7
No. Mi numero es menor que 7
Dime que numero estoy pensando
2
No. Mi numero es mayor que 2
Dime que numero estoy pensando
4    
No. Mi numero es mayor que 4
Mi numero era el 5  
Gracias por jugar conmigo
---------------------------------------------------------------------------

Más modificaciones

Podríamos seguir modificando el programa. Por ejemplo dejar que el usuario elija cuantos intentos puede tener, o que luego de un juego el usuario pueda seguir jugando en vez de terminar el programa, o que el programa elija un número de 2 cifras, o que en vez de elegir un número aleatorio sea una letra aleatoria. Cómo podemos asignar una letra aleatoria a una variable?
my $letra= ('a'..'z')[rand(26)];