Perl permite encontrar una solución rápida, pero eso es lo que dicen los 'expertos' del lenguaje.
Pocas veces se acuerdan de los sufrimientos por los que pasaron mientras aprendieron con este lenguaje de las 'mil caras'.
Los novatos están cansados de escuchar que un problema se puede resolver de diez formas distintas, cuando ellos sólo quieren una para terminar cuanto antes el problema que les ha mandado el jefe (o ellos mismos). Ya aprenderán las otras nueve el mes que viene...
En el siguiente texto intento poner el desarrollo de una de estas soluciones, para más adelante incluir otras que la experiencia va dando.
Problema:
Tengo un fichero de texto compuesto por dos columnas de palabras, separadas por un espacio en blanco, de esta forma:
madrid Antonio
barcelona Xavier
valladolid Joaquin
badajoz Merelo
valladolid Antonio
badajoz Xavier
valladolid Joaquin
Necesito generar un fichero de texto en que cada línea aparezca el nombre de la persona (segunda columna del primer fichero) y a continuación el número de veces que aparece en el fichero.
Así, el resultado con la entrada anterior debería ser:
Antonio 2
Xavier 2
Joaquin 2
Merelo 1
El orden de los nombres no es importante.
Primera solución
Intentamos buscar una primera solución... pero nos faltan datos... ¿Cómo se llaman los ficheros de entrada y de salida? Y nos faltan ideas: ¿cómo hago para llevar la cuenta de los nombres aparecidos?
Para lo primero, nos dicen que el fichero de entrada está en 'lista1.txt' y que el de salida... sea... hummm.... no sé... quizás... 'lista2.txt' (no nos lo han dicho muy convencidos).
Para lo segundo, cómo lo hacemos...
Si no somos muy duchos en esto de Perl, intentaremos buscar una solución clásica (y, ¡ojo!, perfectamente válida mientras funcione). Usaremos un array donde iremos guardando los nombres y en otro iremos contando las veces que van apareciendo.
Empezamos (lo voy a hacer en el joe, que me numera las líneas):
Using perl Syntax Highlighting
1 #!/usr/bin/perl
2 $entra = "lista1.txt"; #cada linea seria, p.e., madrid Antonio
3 $sale = "lista2.txt"; # barna Xavi
4
5 open(UNO, "$lista1");
6 open(DOS, ">$sale");
7
8 foreach $linea (<UNO>)
9 {
10 chop $linea;
11 ($v1,$nombre) = split(/ /,$linea);
12 # ------------------ calculo el $numero de veces que aparece Antonio y Xavi
13 # ------------------ y lo escribo en el fichero de salida
14 print DOS "$nombre $numero\n";
15 }
16
17 close(UNO);
18 close(DOS);
2 $entra = "lista1.txt"; #cada linea seria, p.e., madrid Antonio
3 $sale = "lista2.txt"; # barna Xavi
4
5 open(UNO, "$lista1");
6 open(DOS, ">$sale");
7
8 foreach $linea (<UNO>)
9 {
10 chop $linea;
11 ($v1,$nombre) = split(/ /,$linea);
12 # ------------------ calculo el $numero de veces que aparece Antonio y Xavi
13 # ------------------ y lo escribo en el fichero de salida
14 print DOS "$nombre $numero\n";
15 }
16
17 close(UNO);
18 close(DOS);
Coloreado en 0.003 segundos, usando GeSHi 1.0.8.4
Todavía no está completo, pero no importa. Quiero ejecutarlo para ver si la lectura del fichero es correcta.
- ¡¡¡Ooopsss!!! ¡¡¡Fallos!!! Algo no va bien... ¡¡¡no sale nada en lista2.txt!!!
Repasamos... ¡vaya! Había nombrado el primer fichero en la variable $entra, pero en el momento del open hago referencia a una variable llamada $lista1, que no existe por ningún lado...
¿Cómo evitamos estos problemas? Necesitamos que alguien o algo nos diga que hemos metido la pata con el tema de los nombres de las variables.
Perl nos da una ayuda. Añadiremos 'use warnings;' después de la primera línea. Y volvemos a ejecutar. Ahora sale esto:
Name "main::numero" used only once: possible typo at ./kk.pl line 16.
Name "main::lista1" used only once: possible typo at ./kk.pl line 7.
Name "main::v1" used only once: possible typo at ./kk.pl line 13.
Name "main::entra" used only once: possible typo at ./kk.pl line 4.
Use of uninitialized value in string at ./kk.pl line 7.
readline() on closed filehandle UNO at ./kk.pl line 10.
¡Vaya! Pues sí que estamos listos... a ver...
* Perl me dice que las variables $numero y $v1 sólo se usan una vez, lo cual me parece correcto porque todavía no he hecho nada con ellas...
* Me dice que a $lista1 y $entra les pasa lo mismo... ¡ahí está el error!, pues yo sé que a lo largo del programa las uso más de una vez.
Bien, entonces el error está en la línea 7, que además veo que Perl me dice que estamos usando una variable sin haberla inicializado antes: ¡error cazado!. Cambiamos el código, aparte de que lo dejamos un poco más claro...
Using perl Syntax Highlighting
1 #!/usr/bin/perl
2 use warnings;
3
4 $entra = "lista1.txt"; # fichero de entrada
5 $sale = "lista2.txt"; # fichero de salida
6
7 open(UNO, "<$entra");
8 open(DOS, ">$sale");
9
10 foreach $linea (<UNO>)
11 {
12 chop $linea;
13 ($v1,$nombre) = split(/ /,$linea);
14 # --- calculo el $numero de veces que aparece Antonio y Xavi
15 # --- y lo escribo en el fichero de salida
16 print DOS "$nombre $numero\n";
17 }
18
19 close(UNO);
20 close(DOS);
2 use warnings;
3
4 $entra = "lista1.txt"; # fichero de entrada
5 $sale = "lista2.txt"; # fichero de salida
6
7 open(UNO, "<$entra");
8 open(DOS, ">$sale");
9
10 foreach $linea (<UNO>)
11 {
12 chop $linea;
13 ($v1,$nombre) = split(/ /,$linea);
14 # --- calculo el $numero de veces que aparece Antonio y Xavi
15 # --- y lo escribo en el fichero de salida
16 print DOS "$nombre $numero\n";
17 }
18
19 close(UNO);
20 close(DOS);
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
pero estamos tranquilos, porque es normal: no hemos realizado todavía ningún proceso con las variables $numero y $v1. Y el fallo de la línea 16 es porque estamos intentando sacar a un fichero el valor de $numero... y claro, no tiene ninguno...Name "main::numero" used only once: possible typo at ./kk.pl line 16.
Name "main::v1" used only once: possible typo at ./kk.pl line 13.
Use of uninitialized value in concatenation (.) or string at ./kk.pl line 16, <UNO> line 7.
Use of uninitialized value in concatenation (.) or string at ./kk.pl line 16, <UNO> line 7.
Use of uninitialized value in concatenation (.) or string at ./kk.pl line 16, <UNO> line 7.
Use of uninitialized value in concatenation (.) or string at ./kk.pl line 16, <UNO> line 7.
Use of uninitialized value in concatenation (.) or string at ./kk.pl line 16, <UNO> line 7.
Use of uninitialized value in concatenation (.) or string at ./kk.pl line 16, <UNO> line 7.
Use of uninitialized value in concatenation (.) or string at ./kk.pl line 16, <UNO> line 7.
Vamos a ver el aspecto que tiene lista2.txt:
Todo parece bien... ¡Un momento!... ¡¡¡Hay un Joaquin y un Joaqui!!! Algo pasa... ¿qué será? ¿Por qué ha desaparecido un carácter...? Si repasamos lista1.txt (ver arriba), parece que todo está correcto... ¿quién le ha dicho a Perl que haga eso...?Antonio
Xavier
Joaquin
Merelo
Antonio
Xavier
Joaqui
Antes de pensar en brujas o haber descubierto un fallo increíblemente raro de Perl, pensemos: ¿no habremos sido nosotros? A fin de cuentas, un ordenador no hace nada más que aquello que se la manda...
Si hemos sido nosotros... ¿dónde puede haber sido? Porque... el único sitio donde nosotros le mandamos que corte... es... en... hum... en el chop... y lo hacemos porque queremos quitar el retorno de carro o fin de línea que hay en todas las líneas que leemos, ya que sólo queremos quedarnos con el nombre. A la hora de sacar el resultado, en el print, se lo volvemos a poner...
Sospechas... ¿es que chop ha cortado demasiado? Repasamos la documentación: chop quita el último carácter, sea el que sea... ¡ah! entonces... quizás la 'n' de Joaquin sea el último carácter... Abrimos el fichero y... ¡efectivamente! ¡No hay fin de línea en la última línea del fichero!.
Bueno, pues algo tenemos que hacer. Si se lo ponemos a mano, arreglamos el asunto, pero... ¿vamos a hacerlo todas las veces que el jefe quiera ejecutar el programa? ¿y si el programa se tiene que ejecutar de forma automática cada cinco minutos ya que son las estadísticas de acceso de nuestros comerciales que el jefe quiere que se publiquen en la web de la empresa? Nosotros no vamos a poder estar las 24 horas editando el fichero de entrada... así que estamos obligados a tener en cuenta ese caso... O quizás no...
Perl nació como un lenguaje de procesado de informes... que rápidamente derivó como un sistema de filtrado de ficheros de texto y luego, a todo lo demás. Así que este tema del final de línea debe estar contemplado en algún sitio.
Si leemos la documentación de chop, veremos que al final nos dice que leamos la de otra función muy similar: chomp. Esta función elimina cualquier carácter o conjunto de caracteres que coincidan con el definido como 'fin de línea'. Pues estupendo: nos quitará los finales de línea -si los hay-. Y además otra ventaja: cuando el jefe nos pida trasladar este programa desde el Windows a Linux, no tendremos que hacer ningún cambio... porque aunque los finales de línea hayan cambiado, nuestra lógica de programa no. Por eso se dice que Perl crea programas con una homeostasis muy alta (quiere decir que aguantan muy bien los cambios en el contexto donde se ejecutan... o dicho de otra manera... que resisten los golpes de la vida).
Vale, cambiamos chop por chomp, volvemos a ejecutar, y el tal Joaquin sale completo -menos mal-.
Ahora llegamos a la parte cruda... resolver el problema que nos mandan...
Vale... vamos a hacerlo así.... voy leyendo nombre por nombre... ok...
Tengo que ver si ese nombre ya lo tenía y, en ese caso, sumarle una aparición más.
Si no, agregarlo al array y poner un uno en el apartado de apariciones...
Total, este trabajo es muy parecido a lo que hacemos con los vectores en C...
Voy a probar con algo así:
Using perl Syntax Highlighting
10 foreach $linea (<UNO>)
11 {
12 chomp $linea;
13 ($v1,$nombre) = split(/ /,$linea);
14 # busco $nombre por el array de nombres
15 $numero_nombres = $#nombres + 1;
16 $encontrado = 0;
17 for ($i=0; $i < $numero_nombres; $i++ )
18 {
19 if ( $nombres[$i] eq $nombre ) # encontrado!!!
20 {
21 $veces[$i] = $veces[$i] + 1;
22 $numero = $veces[$i];
23 $encontrado = 1;
24 }
25 }
26 if ( $encontrado == 1 )
27 {
28 # --- y lo escribo en el fichero de salida
29 print DOS "$nombre $numero\n";
30 }
31 else
32 {
33 # --- añado el nuevo nombre
34 push @nombres, $nombre;
35 push @veces, 1;
36 }
37 }
11 {
12 chomp $linea;
13 ($v1,$nombre) = split(/ /,$linea);
14 # busco $nombre por el array de nombres
15 $numero_nombres = $#nombres + 1;
16 $encontrado = 0;
17 for ($i=0; $i < $numero_nombres; $i++ )
18 {
19 if ( $nombres[$i] eq $nombre ) # encontrado!!!
20 {
21 $veces[$i] = $veces[$i] + 1;
22 $numero = $veces[$i];
23 $encontrado = 1;
24 }
25 }
26 if ( $encontrado == 1 )
27 {
28 # --- y lo escribo en el fichero de salida
29 print DOS "$nombre $numero\n";
30 }
31 else
32 {
33 # --- añado el nuevo nombre
34 push @nombres, $nombre;
35 push @veces, 1;
36 }
37 }
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
¡Esto ya tiene buena pinta!Antonio 2
Xavier 2
Joaquin 2
Lo que he hecho es que en las líneas 17 a 25 busco el nombre que estoy mirando entre los ya vistos (que están en el array @nombres). Si lo encuentro (19) le sumo 1 (21) y saco el resultado al fichero (29).
Si no lo he $encontrado (31) lo guardo dentro del array @nombres y también pongo a 1 las veces que lo he visto, en otro array (35).
Pero... ¡un momento! ¿Donde está Merelo? Debería estar con un 1. Algo está mal... Y otra cosa... acabo de darme cuenta de que si existiese un tercer Antonio, por ejemplo, saldrían dos veces en el fichero de salida. Una vez con un '2' y otra con un '3'.
Bueno, pues algo está claro: el print hacia el fichero de salida lo estamos haciendo en mal lugar. Y no estamos sacando todos los nombres. Nos quedamos cortos o nos pasamos, pero no acertamos.
Pensando un poco, llegamos a darnos cuenta de que en realidad, primero debemos leer todo el fichero de entrada y luego, cuando tengamos todas las apariciones, sacamos el resultado. Luego hay que sacar el print de ahí y meterlo en un segundo bucle. Hacemos eso y queda todo nuestro programa así:
Using perl Syntax Highlighting
1 #!/usr/bin/perl
2 use warnings;
3
4 $entra = "lista1.txt"; # fichero de entrada
5 $sale = "lista2.txt"; # fichero de salida
6
7 open(UNO, "<$entra");
8 open(DOS, ">$sale");
9
10 foreach $linea (<UNO>)
11 {
12 chomp $linea;
13 ($v1,$nombre) = split(/ /,$linea);
14 # busco $nombre por el array de nombres
15 $numero_nombres = $#nombres + 1;
16 $encontrado = 0;
17 for ($i=0; $i < $numero_nombres; $i++ )
18 {
19 if ( $nombres[$i] eq $nombre ) # encontrado!!!
20 {
21 $veces[$i] = $veces[$i] + 1;
22 $numero = $veces[$i];
23 $encontrado = 1;
24 }
25 }
26 if ( $encontrado != 1 )
27 {
28 # --- añado el nuevo nombre
29 push @nombres, $nombre;
30 push @veces, 1;
31 }
32 }
33
34 close(UNO);
35
36 # --- y lo escribo en el fichero de salida
37 $numero_nombres = $#nombres + 1;
38 for($i=0; $i< $numero_nombres; $i++)
39 {
40 $nombre = $nombres[$i];
41 $numero = $veces[$i];
42 print DOS "$nombre $numero\n";
43 }
44
45 close(DOS);
2 use warnings;
3
4 $entra = "lista1.txt"; # fichero de entrada
5 $sale = "lista2.txt"; # fichero de salida
6
7 open(UNO, "<$entra");
8 open(DOS, ">$sale");
9
10 foreach $linea (<UNO>)
11 {
12 chomp $linea;
13 ($v1,$nombre) = split(/ /,$linea);
14 # busco $nombre por el array de nombres
15 $numero_nombres = $#nombres + 1;
16 $encontrado = 0;
17 for ($i=0; $i < $numero_nombres; $i++ )
18 {
19 if ( $nombres[$i] eq $nombre ) # encontrado!!!
20 {
21 $veces[$i] = $veces[$i] + 1;
22 $numero = $veces[$i];
23 $encontrado = 1;
24 }
25 }
26 if ( $encontrado != 1 )
27 {
28 # --- añado el nuevo nombre
29 push @nombres, $nombre;
30 push @veces, 1;
31 }
32 }
33
34 close(UNO);
35
36 # --- y lo escribo en el fichero de salida
37 $numero_nombres = $#nombres + 1;
38 for($i=0; $i< $numero_nombres; $i++)
39 {
40 $nombre = $nombres[$i];
41 $numero = $veces[$i];
42 print DOS "$nombre $numero\n";
43 }
44
45 close(DOS);
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
¡Perfecto! ¡Fuera el cava y el champán! ¡vino y rosas! ¡Fiesta, fiesta y fiesta! Llamamos al jefe y todo son parabienes y felicitaciones... el futuro de la empresa está garantizado... la humanidad cuenta con un nuevo benefactor en forma de programador... y esas cosas que se suelen decir cuando algo sale bien (bueno, algunas veces).Antonio 2
Xavier 2
Joaquin 2
Merelo 1
En eso que el jefe se fija en la pantalla y dice: -Oye, Antonio. ¿Por qué sale esa línea en la pantalla? ¿No es un error?
El silencio se apodera de la oficina mientras te fijas que, efectivamente, en la pantalla sale algo raro:
Name "main::v1" used only once: possible typo at ./kk.pl line 13.
Tu sangre empieza a fluir rápidamente por efecto de la adrenalina segregada por la glándula suprarrenal, llega al cerebro y ese pequeño aporte es suficiente para que tus neuronas piensen: "este estúpido Perl no hace más que hablar demasiado".
Te sientas delante del ordenador mientras dices -Nada, nada... son unas pruebas que estaba haciendo.... Con la velocidad del rayo, cargas el programa en el editor, vas a la línea del use warnings; y te la cargas. Grabas el programa y lo vuelves a ejecutar. Tiempo total: 0.9 segundos. Nadie ha visto la trampa.
Excepto tú. Esa noche te acostarás sabiendo que habrás salvado la empresa, la humanidad o quizás el universo... con un programa que... no está bien hecho... -pero al menos, funciona piensas... pero ese remordimiento te seguirá durante los próximos días, semanas y meses. Y con el paso de los años, cada vez que empieces un programa te harás el siguiente juramento: -Esta vez sí, esta vez sí que voy a hacer un buen programa, y no una chapuza, de esas chapuzas que la gente no se entera porque el programa funciona bien... pero que a mi no me dejan dormir...
Cada vez que cierras los ojos, la misma pesadilla se repite una y otra vez: estás como encargado en un restaurante, recibiendo a los clientes...
-Dígame Señor, ¿tienen mesa reservada?.
-Sí, Soy Don Xavier.
-¿Don... Xavier?.
-Sí, Don Xavier, y vengo de Barcelona. Me acompañan mis amigos Melero y Joaquin. Necesitamos una mesa para 4 personas. Enseguida viene...
-No me diga más... el Sr. Antonio.
-Efectivamente...
Te empiezas a poner nervioso porque... es como si esto te sonara del pasado...
-Por favor, pónganse en aquella mesa. Enseguida viene un camarero a atenderles.
Los comensales se sientan y les mandas un camarero. Este habla con ellos y parece que algo anda mal... El camarero se da media vuelta y viene hacia ti.
-¿Qué ocurre?, le preguntas. -¿Falta algo?
-Dicen que la mesa está bien, están todos los cubiertos, hay servilletas, platos... pero que no saben qué hacer con el $v1
-¿$v1?, ¿qué es el $v1?
-No lo sé... me lo han dado y esperan un respuesta. Yo tampoco sé lo que es.
Como responsable de sala de aquel restaurante, tu eres el encargado de absolutamente todos los detalles. Pero... ¿qué era aquello? ¿para qué servía? El camarero, efectivamente, tenía en sus manos un $v1.
-Bueno, les dices que no importa, que enseguida les atendemos.
Coges el $v1 y lo dejas detrás del mostrador (en realidad, lo tiras a la papelera, porque ves que... no sirve para nada).
Al cabo de un rato, llega otro camarero:
-Señor, tenemos un problema con la mesa cuatro.
-¿Qué pasa?
-Los señores que vienen de Francia, dicen que no saben qué hacer con el $v1.
-¿Cómo?
El camarero mantenía en sus manos otro $v1. Y no fue el único. Poco a poco llegaron más y más camareros. Y los clientes empezaban a mirar hacia recepción, mirándote para ver sí podías decir que era aquello... o para qué servía.
Solamente podías hacer una cosa... tirarlos a la basura y decir que no pasaba nada... la comida está buena así que todos los clientes quedan satisfechos... total, tu sólo querías leer un determinado campo de cada línea de un fichero... y necesitaste un $v1... pero... ¿para nada?
Pasaron los años y, con tu relación diaria con Perl, poco a poco empezaste a entender algunas cosas...
Te diste cuenta de que aquellas semanas, meses y años que estuviste aprendiendo lenguajes como Basic, Pascal y C te enseñaron a pensar de forma funcional. Te enseñaron que un ordenador va ejecutando línea a línea las órdenes que tú les das... y que cada orden ha de ser precisa, lógica, casi única, sacada del proceso que tu mente ha creado, de la traducción de un problema en un algoritmo, y que ahora transformas en programas.
Pero ahora, con Perl... hay algo distinto. Sientes que esa realidad que te han contado poco a poco se va desmoronando. Cada vez que ves un programa Perl hecho por otra persona, que lees un libro, que asistes a alguna conferencia, reunión o foro especializado en Perl... cada vez que participas en la comunidad de Perl... ves que hay algo que no encaja en tu forma de trabajar.
Primera revelación
Y un día tienes la primera revelación.
Ves la siguiente línea:
- Código: Seleccionar todo
$name = (split(" ",$line))[1];
Análisis:
* El autor de la línea usa " " como separador, lo cuál le sirve a split para separar los elementos con CUALQUIER número y tipo de espacios que haya entre los campos, lo que aumenta la homeostasis, ergo, es muy bueno...
* ¿Por qué mete al split entre paréntesis? Si ya sabemos que split devuelve una lista de elementos, ¿por qué la mete entre paréntesis? Sabemos que si en Perl metemos algo entre paréntesis, aparte de servirnos para agrupar expresiones matemáticas, tiene la otra función de crear un array... ¡un momento!... ¡eso es!... está convirtiendo la salida de split en un array.
* Y el [1] final lo corrobora: estamos delante de un array... y el [1] nos devuelve el 2º elemento de ese array (hemos tenido que hacer una prueba por nosotros mismos para averiguarlo, pero también así se aprende).
* Y finalmente, como resultado de sacar un único elemento del array, se deja en una variable escalar.
-Ya sé qué hacer ahora con el $v1..., piensas, -lo mandaré al mundo de los olvidados, aquel del que nunca debió salir....
Esa noche duermes profundamente.
Pero hay que aclarar algo importante. Estaba BIEN lo que hiciste. Es AHORA cuando ves OTRA forma de hacerlo.
Y lo más importante. Cuando despiertas por la mañana te das cuenta de un hecho importante: Has necesitado un párrafo de muchas líneas para explicar el análisis de una sola línea de Perl. ¿Dónde queda la sencillez de C? ¿Dónde está la lógica de Pascal? ¿Donde está la inmediatez de Basic?
Y esa es la revelación: Perl tiene un lenguaje muy poderoso... es capaz de almacenar y desplegar mucha 'energía' en poco espacio... y eso es algo que todo programador sueña, porque todo programador es un vago (primera virtud del programador de Perl, según Larry Wall, su inventor). Un programador quiere hacer lo máximo en el menor espacio/tiempo posible.
Días más tarde, ves otras posibilidades:
- Código: Seleccionar todo
(undef,$n) = split " ", $line;
Segunda revelación: el conocimiento de lo básico
Decides darle una oportunidad más a este lenguaje y comienzas de nuevo a repasar la sintásix del lenguaje.
Al llegar a los hash, ves que en un libro les llama 'memoria asociativa'. Sirven para recordar cosas en función de otras. Es como si del aroma del café que nos estamos tomando recordásemos todas esas mañanas tranquilas de nuestra vida en la que hemos tomado café en compañía de nuestra familia y amigos. Lo bien y tranquilos que estábamos. Recordar... recordar... -Perl sirve para recordar..., piensas. -Nunca me lo enseñaron de esa manera. Siempre me dijeron que las variables eran el lugar donde dejamos nuestros saberes, nuestros valores, nuestras cuentas... pero nunca me dijeron que podría guardar mis recuerdos de forma ordenada, bajo un mismo nombre...
-¿Podría ponerme un ejemplo?. ¿En qué problema anterior he tenido la necesidad de recordar...?
Te viene a la mente el viejo problema de las listas de apariciones de personas, el que hemos visto más arriba. Bueno, pues ya sabemos algo... que quizás en %nombres, podamos asociar (recordar) el nombre de cada persona con las veces que le vemos en el fichero. Como además tienes más conocimientos de Perl, decides hacer unos cuantos cambios más. Y el resultado queda:
Using perl Syntax Highlighting
1 #!/usr/bin/perl
2 use warnings;
3
4 $entra = "lista1.txt"; # fichero de entrada
5 $sale = "lista2.txt"; # fichero de salida
6
7 open(UNO, "<$entra");
8 open(DOS, ">$sale");
9
10 foreach $linea (<UNO>)
11 {
12 chomp $linea;
13 (undef,$nombre) = split " ", $linea;
14
15 # --- Sumo uno a las veces que aparece $nombre
16 $veces{ $nombre } += 1;
17 }
18
19 close(UNO);
20
21 # --- y lo escribo en el fichero de salida
22 foreach $nombre ( keys %veces )
23 {
24 print DOS "$nombre $veces{ $nombre }\n";
25 }
26
27 close(DOS);
2 use warnings;
3
4 $entra = "lista1.txt"; # fichero de entrada
5 $sale = "lista2.txt"; # fichero de salida
6
7 open(UNO, "<$entra");
8 open(DOS, ">$sale");
9
10 foreach $linea (<UNO>)
11 {
12 chomp $linea;
13 (undef,$nombre) = split " ", $linea;
14
15 # --- Sumo uno a las veces que aparece $nombre
16 $veces{ $nombre } += 1;
17 }
18
19 close(UNO);
20
21 # --- y lo escribo en el fichero de salida
22 foreach $nombre ( keys %veces )
23 {
24 print DOS "$nombre $veces{ $nombre }\n";
25 }
26
27 close(DOS);
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
Pero atención... es sencillo y claro para TI, y en ESE momento. ¿Qué hubieras pensado si hace un tiempo te hubieran dicho que 18 líneas de tu programa quedan reducidos a sólamente una?. Seguramente pensarías que esa línea es muy complicada. Y en efecto, ES muy complicada. Lo ES para cualquiera que siga ligado a una estructura mental de un lenguaje de programación tradicional. ¿A quien se le puede ocurrir la idea de asociar nombres con veces que aparece esa persona, y sumar uno en cada ocasión?. Pero lo bueno es que una vez que lo aprendes, lo aplicarás toda tu vida, porque sabes que es algo bueno (me refiero a ese sentimiento que aparece cuando en un programa sustituyes 18 líneas por una. Os aseguro que es algo muy especial).
Tercera revelación
La tercera revelación es la búsqueda de la perfección. El karma.
Despúes de un tiempo de programar en Perl, ya sabes más cosas. Sabes que los humanos podemos mantener una conversación sobre un tema sin estar constantemente nombrando ese tema. Una vez iniciado el tema, podemos hablar y hablar sin tener que recordar el nombre del tema. Y Perl no es una excepción. Perl permite que... algunas cosas... queden... sobreentendidas...
Using perl Syntax Highlighting
1 #!/usr/bin/perl
2 use warnings;
3
4 open UNO, "<lista1.txt" or die "ERROR: No pude abrir el fichero de entrada";
5 open DOS, ">lista2.txt" or die "ERROR: No pude abrir el fichero de salida";
6
7 while ( <UNO>) {
8 chomp;
9 (undef,$nombre) = split;
10 $veces{ $nombre }++;
11 }
12
13 foreach ( keys %veces ) {
14 print DOS "$_ $veces{$_}\n";
15 }
16
17 close(UNO);
18 close(DOS);
2 use warnings;
3
4 open UNO, "<lista1.txt" or die "ERROR: No pude abrir el fichero de entrada";
5 open DOS, ">lista2.txt" or die "ERROR: No pude abrir el fichero de salida";
6
7 while ( <UNO>) {
8 chomp;
9 (undef,$nombre) = split;
10 $veces{ $nombre }++;
11 }
12
13 foreach ( keys %veces ) {
14 print DOS "$_ $veces{$_}\n";
15 }
16
17 close(UNO);
18 close(DOS);
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
* En la línea 8, chomp hace su trabajo, cortar. Pero, ¿qué?. ¡Ah!, aquello... ($_)
* En la 9, split divide. ¿El qué? Pues eso... eso de lo que estamos hablando... (otra vez $_), y con el delimitador que queremos.
* En la 10, un cambio cosmético... usamos el post-incremento.
La misma jugada se hace en el bucle de las líneas 13 a 15.... sólo que en esta ocasión si aparece la $_ porque no nos quedaba otro remedio
Otro detalle de perfección: al abrir los ficheros nos aseguramos que lo hemos podido hacer... o el programa morirá.
Y es cuando aparece la perfección. Este programa es lo que querías que siempre hubiese sido. Algo corto que resolviera el problema, pero no tanto como para no entenderlo. Y lo suficientemente largo como para enseñarselo a alguien y que al menos pueda entender, por encima, lo que hace. Tu le vas hablando en lenguaje natural mientras le vas indicando las líneas del programa:
Pues verás... hace esto:
Abrimos los ficheros, de entrada y de salida (4 y 5)
Luego, mientras vamos leyendo línea por línea desde el primer fichero, a cada línea le quitamos el fin de línea que nos molesta, nos quedamos con el segundo campo y lo almacenamos como si le hubieramos visto una vez más. Y así con todas las líneas.
Al final (13), por cada persona vista, sacamos su nombre y las veces que le hemos visto. Cerramos los ficheros y nos vamos.
Ahora estas orgulloso (segunda virtud del programador de Perl), porque tu código no sólo es eficiente. Es incluso hasta traducible al lenguaje natural. Las caras de quienes te rodean miran con asombro el programa que les enseñas. Casi no entienden nada de lo que pone... pero entienden perfectamente lo que dices tu... y mientras ellos se preguntan si aprender Perl puede ser interesante, comienza tu propia transformación.
Ahora es cuando Perl te posee... porque empiezas a pensar que Perl tiene algo más que los lenguajes funcionales, aquellos que eran simples expresiones matemáticas... Perl es un lenguaje imperativo. TU das las órdenes y Perl obedece. Pero claro... eso también lo hace un lenguaje de muy bajo nivel, como el ensamblador... tu das órdenes precisas y la máquina las ejecuta sin rechistar. Perl, en cambio, es de muy alto nivel. Con unas pocas líneas, haces mucho.
Perl es multi-paradigmático. En concreto, a 3 niveles: funcional, imperativo y orientado a objetos. Pero esto es teoría informática. Ahora quizás estés con otras preocupaciones más que investigar la teoría.
Llegas ahora a un nuevo nivel de conocimiento. La abstracción y el conocimiento futuro. La abstracción es clara: vas a traducir tus pensamientos no en instrucciones Perl, sino en órdenes. Es más, vas a hacer que las líneas del programa Perl imiten el lenguaje natural en que has expresado la solución. Si consigues eso, no sólo tu, sino los que te sucedan, entenderán mejor el programa. Y... ¿el futuro?.
Para conducir una bicicleta debes mirar al futuro, porque lo que pasa bajo tus pies es el presente, que se escapa velozmente. El conocimiento del futuro nos plantea preguntas del tipo ¿qué pasaría si...? Y tu te planteas el caso: "¿qué pasaría si las condiciones de funcionamiento del programa cambiasen? ¿qué pasaría si un día, no fuera list1.txt el fichero que tengo que leer? Es más, ¿qué pasaría si no fuera list2.txt el lugar donde quiero depositar el resultado? Y lo mayor pregunta... ¿es que no hay otra cosa que ficheros para la vida de este programa? ¿es que no hay nada más?
Sabes entonces que las respuestas a estas preguntas van más allá de la comprensión de tus jefes. Tu ya no estás pensando con una semana a la vista. Tu estás pensando con meses, con años de adelanto. Quizás estás pensando en la frontera final... hacer que tu programa sea... inmortal: ser capaz de enfrentarse a cualquier situación futura.
Bueno, sabemos que la inmortalidad no se consigue de la noche a la mañana. Todo lo material muere alguna vez por culpa de la segunda ley de la termodinámica. Pero sabemos que lo más cercano que tenemos de la inmortalidad, en informática, es la conversión de lo material en lo inmaterial. En convertir algo tangible en un concepto. Entonces, transformemos nuestro programa a un concepto.
Antes, nuestro programa procesaba un fichero y sacaba otro. Ahora lo que queremos es procesar cualquier cosa que el usuario quiera. Si es necesario, el universo entero:
Using perl Syntax Highlighting
1 #!/usr/bin/perl
2
3 while ( <> ) {
4 chomp;
5 (undef,$nombre) = split " ";
6 $veces{ $nombre }++;
7 }
8
9 foreach ( keys %veces ) {
10 print "$_ $veces{$_}\n";
11 }
2
3 while ( <> ) {
4 chomp;
5 (undef,$nombre) = split " ";
6 $veces{ $nombre }++;
7 }
8
9 foreach ( keys %veces ) {
10 print "$_ $veces{$_}\n";
11 }
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
Ahora, gracias a nuestro platillo volante (<>) somos capaces de absorverlo todo. Y con el print, devolverlo. El qué, dónde y cuándo, se lo dejaremos decidir al usuario.
Ya no hay ficheros que abrir, ni que cerrar. Sólo existe el concepto proceso, entre lo que se le da al programa y lo que este devuelve. Hemos pasado de una herramienta al concepto de "estadística de apariciones de los vendedores".
Ahora, como parte de un concepto, debemos integrarlo en un discurso. Le pediremos al usuario que lo ejecute de forma parecida a esta:
- Código: Seleccionar todo
perl kk.pl < lista1.txt > lista2.txt
Cuarta revelación: -casi- todo ya está hecho
De esta revelación decir que se refiere a CPAN, un lugar donde miles de personas ya han resuelto problemas parecidos y que es posible que ellos hayan dejado sus soluciones, para tí. En caso de problemas muy, muy sencillos, no, porque se supone que sin son sencillos, 'cualquiera' puede resolverlos.
De entre los que más se parecen a nuestro programa, quizás sea Text::Filter. Pero como su aplicación es dentro de la programación orientada a objetos, queda un poco fuera de nuestra disertación.
Quinta revelación: la belleza de lo mínimo
Llegados al punto anterior, deberíamos estar tranquilos, porque ya sabemos bastante de Perl como enfrentarnos a cualquier problema.
La cuestión ahora es que también nosotros nos podemos plantear el siguiente caso: es que el programa que hemos hecho, tan reducido, ¿no puede reducirse más? Si es un concepto, ¿no podemos reducir un concepto a casi, casi, lo inmaterial?.
Desgraciadamente no podemos desmaterializar un programa de Perl, porque entonces necesitaríamos también un ordenador inmaterial que lo pudiese correr. El sentido contrario es lo que están haciendo los creadores de Perl6: lo están materializando.
Pero bueno... al menos... podemos reducir un concepto a sus más básicos elementos:
Using perl Syntax Highlighting
perl -lane '$v{$F[1]}++; END{ print "$_ $v{$_}" for keys %v}' lista1.txt
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
Perl también tiene esta faceta, la de poder ejecutarse en una sóla línea -siempre y cuando sepamos escribir esa línea-.
Saber escribir Perl en una-línea no significa que se sea más o menos versado en Perl. Es sólo un recurso más. Muchos administradores de sistemas usan Perl de esta manera. En vez de escribir un programa para sacar estadísticas de miles de ficheros, la simplicidad de los datos de partida induce a que la solución sea tan sencilla que pueda ser escrita en la misma línea de comandos.
Pero lo que hay arriba es también el compendio de otros saberes muy importantes. Fíjate que después de la palabra perl aparecen las opciones '-lane'. Son cuatro, agrupadas. Y entre las cuatro poseen las cuatro virtudes de nuestro programa conceptual:
* a: un bucle y un split
* n: no pintes lo que recibes, sólo lo que yo quiera
* l: hay un final de línea al final de cada print
* e: ejecuta este programa, el que viene a continuación
Con estas cuatro opciones convertimos a un pequeñísimo programa en un filtro. Un filtro es algo que recibe por una entrada, la transforma, y entrega una salida. Es casi, casi lo que hacía nuestro programa original.
Dentro del nuevo programa vemos alguna parte que reconocemos, como la del $v{...}++; o el pintado del resultado, pero hay cosas nuevas.
* El $F[1] es resultado de la opción '-a'. Ha leído la entrada y la ha partido en palabras. Y todas están en @F. Así que $F[1] es la segunda palabra.
* END. Esto es muy claro: quiere decir que lo que sigue lo va a ejecutar cuando se haya terminado de procesar toda la entrada.
Y ya está. No hay más. Nuestro bucle y nuestro split han desaparecido porque su trabajo lo han asumido esos parámetros. Precísamente existen en Perl: porque es muy frecuente usar Perl para hacer bucles y procesar ficheros de texto.
Mientras que sí es necesario decir que conseguir una línea así requiere tiempo, experiencia y mucha experimentación, son pocos los que lo hacen con el ánimo de conseguir la mínima expresión (ver Perl Golf). Los más, son aquellos que quieren resolver un problema en la misma línea de comandos y cuanto antes.
Y esa es la tercera virtud del programador de Perl: la impaciencia de obtener el resultado lo antes posible.
Y eso es posible porque Perl te lo permite.
Conclusión
¿Quiere esto último decir que tenemos siempre que ir hacia la solución más corta? NO. Tenemos que usar la solución con la cual estemos más a gusto. Perl nos da la libertad de elección, a diferencia de otros lenguajes. Podemos quedarnos en el nivel en que queramos. Podemos usar cualquiera de las soluciones que encontremos, siempre que la entendamos (si no la entendemos, seremos una máquina más dedicada a cortar y pegar código sacado de algún foro).
Y eso también quiere decir que no tenemos que sentirnos minusvalorados si no sabemos algo o vemos conceptos que se nos escapan. Eso sólo quiere decir que nuestro recorrido hacia la iluminación no ha hecho más que empezar.
Fin
P.D. No pongo aquí cómo es la iluminación porque yo no la conozco. De momento, sigo mi camino. Espero encontrarla algún día.