Ni bien ni mal, sino regular

Matrices asociativas (hashes) y expresiones regulares.

A trancos y barrancos llegamos a un punto en que el tema se pone interesante. Si todavía estás con nosotros, agárrate, que ahora viene lo gordo: hacer un índice onomástico, es decir, los nombres de las gentes que aparecen en la novela y dónde diablos aparecen. Lo bueno es que allá por el siglo XVIII la gente era muy educada y a todo el mundo lo trataban de Don, así que un nombre será algo en mayúsculas después de un Don. Ahí vamos

my $fichero_a_procesar = shift                                                       (1)
  || die "Uso: $0 <nombre de fichero>n";
open my $fh, "<", $fichero_a_procesar  || die "No puedo abrir el fichero. Error $!\n";

my %indice;                                                         (2)
while(<$fh>) {
  if ( /[Dd]on ([A-Z][a-záéíóúñ]+)/ ) {                                                  (3)
    $indice{$1} .= "$.";                                              (2)
  }
}

for (sort {$a cmp $b} keys %indice ) {                                           (2)
  print "*Don $_\n\t$indice{$_}\n";
}
(1)
He aquí una forma alternativa de leer los argumentos de la línea de comandos. Sin dejar de usar @ARGV, en este caso echamos mano de shift, que, si no tiene argumento al que hincarle el diente, lo hace directamente sobre él. O séase, la línea anterior sería exactamente igual que:
my $fichero_a_procesar = shift @ARGV
.
(2)
Los diccionarios son útiles entre otras cosas para equilibrar una mesa, pero también para almacenar definiciones. Son muy rápidos para buscar información: vas directamente a la letra y buscas secuencialmente. Los vectores no lo son: tienes que ir examinando uno por uno todos los elementos para encontrar lo que buscas, si no sabes su índice. Los vectores son útiles para almacenar información a la que se va a acceder secuencialmente, sin embargo, los diccionarios sirven para almacenar información a la que se va a acceder usando una palabra clave. En Perl, los diccionarios se llaman hashes o variables asociativas y a la palabra clave que se usa para acceder a su contenido, se le llama key o clave[1]. Los hashes tienen un % delante, que es lo más parecido a una K si se mira de lejos y con los ojos entrecerrados. Eso es lo que declaramos en esta referencia; sin embargo, cada uno de los contenidos de estos hashes son escalares y se usa la misma convención que en los vectores, como sucede en el resto de las líneas marcadas.

En este programa se va creando un hash con los nombres que se van encontrando y el contenido del mismo son las líneas en las que aparece el nombre (para las que usamos la variable predefinida $.).

El índice se imprime una vez recorrido el fichero, en las últimas líneas del programa. Para empezar, el comienzo del bucle es de esos que le dan al Perl un mal nombre, pero recorriéndolo de derecha a izquierda podemos irlo decodificando. A la derecha está el nombre del hash; y un poco más allá la función keys, que devuelve un vector con las claves del hash. Recorrer una matriz es fácil: empiezas por 0 y acabas por el último vector, pero para recorrer un hash necesitas saber cuáles son las claves que tiene. Y resulta más útil si lo recorres siguiendo un orden determinado. En este caso,

sort {$a cmp $b}
clasifica (sort) usando un bloque ($a cmp $b). $a y $b representan los dos elementos que se comparan en la clasificación y cmp es una comparación alfabética, que devuelve -1, 0 o 1 dependiendo de si el primero es mayor, son iguales o lo es el segundo. sort toma como argumento un vector y lo devuelve clasificado, numéricamente si no se le indica ninguna expresión y usando la expresión si la hay. Por ejemplo, si quisiéramos que recorriera el hash por orden de número de apariciones (que equivalen a la longitud de la cadena que las representa), podríamos poner
sort {length($indice{$a}) cmp length($indice{$b})
.

Dentro del bucle, nada inesperado: la variable por defecto $_ toma el valor de cada una de las claves del hash y se escribe tal clave (con el Don por delante, que no falte) y el contenido de la misma ($indice{$_}).

Nota

Una vez más, como aconsejamos para los vectores, se puede ampliar información escribiendo perldoc perldata

(3)
Si algo caracteriza al lenguaje Perl, son las expresiones regulares. Posiblemente fue el primer lenguaje de programación que las introdujo de forma nativa, hasta el punto que lenguajes posteriores han adoptado su formato. Una expresión regular es una forma sintética de expresar la estructura de una cadena alfanumérica. Las expresiones regulares usan símbolos para representar grupos de caracteres (por ejemplo, números, o espacios en blanco) y repeticiones de los mismos (que aparezcan 1, n o más veces) y, lo que es más importante, qué parte de la cadena nos interesa para hacer algo con ella (por ejemplo, extraerla o sustituirla por otra).

En Perl, la forma más común de encontrarse las expresiones regulares entre dos slash //. En la expresión regular siguiente (a veces abreviada como regexp o regex):

/[Dd]on ([A-ZÁÉÍÓÚ][a-záéíóúñ]+)/
La primera parte concidirá con cualquier cadena que empiece por don o Don; [Dd] coincide con un solo carácter que esté entre el grupo entre corchetes. Y algo similar es la expresión entre paréntesis (que indican que es la parte que queremos guardar para luego), comienza con [A-ZÁÉÍÓÚ], que es una serie de caracteres que comienzan por A y terminan por Z (más las mayúsculas acentuadas, aunque en este texto no sirven de mucho), lo que vienen siendo las mayúsculas y luego cualquier letra minúscula (inclusive letras con acento y la ñ, que no están dentro del alfabeto anglosajón), pero que aparezcan una o más veces (de ahí el +; un * habría significado 0 o más veces y una ? un elemento opcional, que puede aparecer o no). En resumen, esta expresión regular coincidirá con Don Cleofás o don Domingo, pero no con Sir James, ni con don dinero (la segunda palabra no está en mayúsculas), ni siquiera con Don Dinero (porque tiene dos espacios, aunque no se vean). Coincidirá precisamente con lo que queremos que coincida.

Una forma fácil de visualizar las expresiones regulares es el programa kregexpeditor, que aparece abajo con la expresión regular anterior.

Figura 9. Programa kregexpeditor presentando la expresión regular ([A-Z][a-záéíóúñ]+), y mostrando subrayado en rojo una cadena que cumple la expresión. En el panel superior se muestra una explicación de los diferentes elementos de la regex.

Nota

Una vez más, la documentación incluida en perl es nuestra amiga: perldoc perlrequick es una referencia rápida,perldoc perlretut un tutorial más extenso y perldoc perlre un manual de referencia.

Ejecutado sobre el diablo cojuelo, este programa dará una salida tal que así:

*Don Adolfo
	258 293 476 3487 3652 5864 9392 
*Don Agustín
	3794 5462 8955 
*Don Alfonso
	8830 
*Don Alonso
	2858 3562 3676 6061 6771 
*Don Alonsos
	1198 
*Don Alvaro
	1233 2043 3534 
*Don Ambrosio
	7703 
*Don Américo
	5513 
*Don Antonio
	2110 2611 2657 2983 3472 5335 5460 5800 6133 9048 
*Don Apolo
	3219 
*Don Baltasar
	2647 2653 
*Don Beltrane
	7439 7457 
*Don Beltrán
	7436 7451 
*Don Bueso
	6906 
*Don Carlos
	2671 4238 
*Don Cayetano
	3463 
*Don Clarian
	5846 
*Don Claudio
	2697 
*Don Cleofas
	323 
*Don Cleofás
	658 694 719 727 735 763 782 792 807 810 825 844 860 869 914 936 950 961 979 993 1007 1020 1045 1052 1061 1109 1114 1156 1160 1172 1183 1190 1219 1235 1280 1292 1302 1323 1339 1382 1442 1452 1589 1611 1626 1639 1648 1694 1698 1704 1711 1716 1721 1740 1746 1751 1757 1798 1847 1856 1938 1947 1955 1957 1958 1963 1970 1980 1993 1998 2002 2055 2115 2126 2144 2169 2224 2251 2318 2335 2340 2348 2350 2358 2370 2373 2381 2437 2479 2488 2549 2738 2746 2754 2768 2780 2793 2811 2827 2833 2838 2868 2876 2907 2971 2975 2988 2996 3010 3020 3037 3039 3053 3124 3158 3168 3175 3182 3195 3365 3368 3446 3852 4206 5452 5625 5640 6417 8274 8504 8609 9003 9430 
*Don Cristóbal
	2894 3459 8950 
, donde queda bastante claro quién es el prota (aparte del Diablo Cojuelo, que por ser diablo no tiene Don). Y que no poner un acento pasa hasta en las mejores familias.

Pero la utilidad de matrices asociativas y expresiones regulares no acaba ahí. Ni la del dominio público: vamos a modificar El Diablo Cojuelo para sustituir unos cuantos nombres, a gusto del consumidor. Y lo haremos con el siguiente programa:

use File::Slurp;

my %roles = (
	     Madrid => 'el foro',
	     'Cleof[áa]s' => 'El prota',
	     '[Ee]l [Dd]iablo [Cc]ojuelo' => 'Su Satánica Majestad' ,
	    );

my $fichero_a_procesar = shift 
  || die "Uso: $0 <nombre de fichero>n";

my $texto=read_file($fichero_a_procesar);

for ( keys %roles ) {
  $texto =~ s/$_/$roles{$_}/g;                                               (2)
}

print $texto;
(1)
Aquí definimos los cambios. Más claro no puede estar: lo que está a la izquierda de la flechica, se convertirá en lo que hay a la derecha de la flechica. Y lo definimos usando un hash, que usan un paréntesis tal como los vectores. Las claves están a la izquierda y si son amazacotadas (sin espacios ni caracteres raros en medio; de hecho, si tienen la misma sintaxis que un nombre de variable en Perl), se les pueden quitar las comillas (como pasa con Madrid. La última coma, por cierto, no es necesaria, pero si conveniente.

La coma gorda (=>) sustituye a la coma normal cuando se definen expresiones regulares; adicionalmente, se pueden omitir las comillas. Por supuesto, también se pueden definir como las matrices normales, sin más que poner a cada clave seguida por su valor o definición.

(2)
Las sustituciones se hacen precisamente en esta línea, usando el escueto comando s///, que tiene una sintaxis un tanto curiosa, procedente del ignoto comando de Unix sed. Por lo menos usa la s de sustituir. s/esto/aquello/xyz vendría a ser algo así como sustituye(esto,aquello,xyz, donde el último argumento son una serie de opciones que modifican su comportamiento. El primer argumento puede y debe ser una expresión regular; el segundo puede ser cualquier cosa, pero si ponemos una expresión que incluya variables, tenemos que añadir al final la opción e para que la evalúe. Y la g es para que no se pare en la primera sustitución, sino que siga hasta que llegue al final del texto.

Y el resultado (parte de él), sería algo así como esto:

--¿Quién es aquí Su Satánica Majestad? Que he tenido soplo que está aquí en
este garito de los pobres, y no me ha de salir ninguno deste aposento
hasta reconocellos a todos, porque me importa hacer esta prisión.

Los pobres y las pobras se escarapelaron viendo la justicia en su
garito, y el verdadero Diablo Cojuelo, como quien deja la capa al toro,
dejó a Cienllamas cebado con el pobrismo, y por el caracolillo se
volvieron a salir del garito él y don El prota.
que no es que sea perfecto, pero es un comienzo.

Importante

Ejercicios. Las humildes palabras, que no tienen título, también pueden y deben ser contadas, para llevar una contabilidad exacta. Así que contemos todas las palabras en minúscula que aparecen en un texto, y hagamos un ránking de las 50 palabras más comunes. Por ejemplo.

Notas

[1]

En realidad, los vectores serían un tipo especial de diccionarios, que sólo admitirían números como palabra clave; así es, además, como se implementan internamente en Perl