• Publicidad

Cómo automatizar adición de etiquetas

¿Apenas comienzas con Perl? En este foro podrás encontrar y hacer preguntas básicas de Perl con respuestas aptas a tu nivel.

Cómo automatizar adición de etiquetas

Notapor RGP_perl » 2011-08-29 04:18 @221

¡Hola!

Supongamos que tengo un texto al que se han añadido etiquetas morfológicas/sintácticas y otro semejante que hay que etiquetar. Combino ambos textos en formato interlineal, una línea debajo de la otra. Así:


Sintáxis: [ Descargar ] [ Ocultar ]
  1. El(A) día(S) comenzó(V) con(P) viento(S-CCM) y(C) lluvia(S-CCM) en(P) Madrid(S-CCL). 
  2.     El día había comenzado bien, con lluvia en Madrid. 
  3. La(A) jornada(S) se presentaba(V) ideal(A-CCM) para(P) salir(V) a(P) buscar(V) setas(S-OD). 
  4.     La jornada pintaba muy bien para salir al campo. 


La segunda línea (o sea, el texto sin etiquetar) siempre comienza con el símbolo de espacio y tabulación (para distinguirla de la anterior). Además, pueden ignorarse perfectamente los signos de puntuación, así como la distinción entre mayúsculas y minúsculas y los acentos. Con lo cual, por ejemplo, solo = sólo y Solo = solo. Además de eso, puede darse el caso de que en la primera línea alguna palabra no esté etiquetada.

A partir de esta especie de pseudo-código, y dado que mis conocimientos de Perl son básicos, decido utilizar una serie de expresiones regulares para ir extrayendo las etiquetas con los códigos (siempre entre paréntesis) en la primera línea e insertándolos en la segunda línea, siempre que las palabras sean las mismas.

Mi código actual es este:

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. use strict;
  2. use warnings;
  3.  
  4. while ( <DATA> )
  5. {
  6. s/(^\w+)(\(\w+\))?(.+\r)(\s\t)(\1)/$1$2$3$4$5$2/g; #Etiqueto la 1ª palabra
  7. de la segunda línea (si coincide con la 1ª de la primera).
  8. s/(^\w+)(\(\w+\))?\s(\w+)(\(\w+\))?(.+\r)(\s\t)(\1\2)\s(\3)/$1$2 $3$4$5$6$7 $8$4/g; # Etiqueto la 2ª palabra de la segunda línea (si coincide con la 2ª de la primera.
  9. # Y así sucesivamente...
  10.  
  11. print;
  12. }
  13.  
  14.  
  15.  
  16. __DATA__
  17. El(A) día(S) comenzó(V) con(P) viento(S-CCM) y(C) lluvia(S-CCM) en(P) Madrid(S-CCL).
  18.         El día había comenzado bien, con lluvia en Madrid.
  19. La(A) jornada(S) se presentaba(V) ideal(A-CCM) para(P) salir(V) a(P) buscar(V) setas(S-OD).
  20.         La jornada pintaba muy bien para salir al campo.
Coloreado en 0.002 segundos, usando GeSHi 1.0.8.4


Naturalmente, el resultado que desearía obtener es el siguiente:

Sintáxis: [ Descargar ] [ Ocultar ]
  1. El(A) día(S) comenzó(V) con(P) viento(S-CCM) y(C) lluvia(S-CCM) en(P) Madrid(S-CCL). 
  2.     El(A) día(S) había comenzado bien, con lluvia(S-CCM) en(P) Madrid(S-CCL). 
  3. La(A) jornada(S) se presentaba(V) ideal(A-CCM) para(P) salir(V) a(P) buscar(V) setas(S-OD). 
  4.     La(A) jornada(S) pintaba muy bien para(P) salir(V) al campo. 


Mi problema es doble:

1) El script (donde de momento sólo se sustituyen la primera y la segunda palabras) no funciona como yo quisiera, pese a que creo que las expresiones regulares están bien.

2) No estoy convencido de que la forma de abordar el problema (añadiendo toda una serie de expresiones regulares cada vez más largas y complejas) sea la correcta.

¿Alguien podría aconsejarme sobré qué debería hacer o enseñarme algún camino alternativo para optimizar esta tarea?

Muchas gracias.

RGP
RGP_perl
Perlero nuevo
Perlero nuevo
 
Mensajes: 6
Registrado: 2011-08-26 07:49 @367

Publicidad

Re: Cómo automatizar adición de etiquetas

Notapor explorer » 2011-08-29 12:13 @550

1) El problema es que algunos códigos tienen un '-' dentro, así que ese carácter también hay que buscarlo.

2) La solución es encontrar el patrón que sirve para resolver el caso general. Necesitamos capturar palabras que están seguidas por caracteres-palabra-con-guiones encerrados entre paréntesis. La siguiente expresión puede servir: /\b(\w+)\(([-\w]+)\)/, que quiere decir:
  • \b es el delimitador de palabra (comienzo o final de palabra)
  • capturamos la palabra que le sigue ((\w+))
  • seguido por un par de paréntesis (que escapamos con '\')
  • y dentro de ellos, uno o más (+) caracteres que pueden ser, o un '-' o un carácter-palabra

Esta solución me funciona a mí:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/perl
  2. use strict;
  3. use warnings;
  4. use utf8;
  5. use open qw(:utf8 :std);
  6.  
  7.  
  8. while ( <DATA> ) {
  9.  
  10.     # Iniciamos nuestra "memoria de palabras->código"
  11.     my %codigos;
  12.  
  13.     while ( /\b(\w+)\(([-\w]+)\)/g ) {
  14.         $codigos{$1} = $2;
  15.     }
  16.  
  17.     print;
  18.  
  19.     $_ = <DATA>;
  20.  
  21.     s/\b(\w+)\b/exists $codigos{$1} ? "$1($codigos{$1})" : $1/ge;
  22.  
  23.     print;
  24. }
  25.  
  26. __DATA__
  27. El(A) día(S) comenzó(V) con(P) viento(S-CCM) y(C) lluvia(S-CCM) en(P) Madrid(S-CCL).
  28.         El día había comenzado bien, con lluvia en Madrid.
  29. La(A) jornada(S) se presentaba(V) ideal(A-CCM) para(P) salir(V) a(P) buscar(V) setas(S-OD).
  30.         La jornada pintaba muy bien para salir al campo.
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4

El proceso es el siguiente:
  • vamos leyendo línea a línea
  • con la primera línea, extraemos todas las parejas palabras-código y las guardamos en la memoria %codigos
  • leemos después la segunda línea (en el mismo bucle que la primera)
  • hacemos el proceso de sustitución (s///), de forma repetida (/g), y ejecutando código Perl (/e)
  • esta sustitución busca palabras (\w+) y ejecuta el código Perl, que lo que hace es comprobar si existe un código para esa palabra, y si es así, la sustituye. Si no, sustituye la palabra por sí misma.


Esta es otra solución, con una expresión regular que utiliza una versión extendida de patrones especiales:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/perl
  2. use strict;
  3. use warnings;
  4. use utf8;
  5. use open qw(:utf8 :std);
  6.  
  7. while ( my $linea1 = <DATA> ) {
  8.  
  9.     # Iniciamos nuestra "memoria de palabras->código"
  10.     my %codigos;
  11.  
  12.     # Buscamos todos las palabras(código)
  13.     while ( $linea1 =~ /\b(\w+)\(([-\w]+)\)/g ) {
  14.         $codigos{$1} = $2;
  15.     }
  16.  
  17.     print $linea1;
  18.  
  19.     # Segunda línea
  20.     my $linea2 = <DATA>;
  21.  
  22.     # Evaluamos la sustitución
  23.     # Necesitamos evaluarlo porque se trata de una exp. reg. distinta en cada vuelta
  24.     eval '
  25.        $linea2 =~ s{\b(\w+)\b(?(?{not exists $codigos{$1}})(*FAIL))}
  26.                    {$1($codigos{$1})}g;
  27.    ';
  28.     print $linea2;
  29. }
  30.  
  31. __DATA__
  32. La(A) jornada(S) se presentaba(V) ideal(A-CCM) para(P) salir(V) a(P) buscar(V) setas(S-OD).
  33.         La jornada pintaba muy bien para salir al campo.
  34. El(A) día(S) comenzó(V) con(P) viento(S-CCM) y(C) lluvia(S-CCM) en(P) Madrid(S-CCL).
  35.         El día había comenzado bien, con lluvia en Madrid.
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
En este caso, la expresión es:
  • \b(\w+)\b es lo mismo que antes: buscamos palabras normales
  • (?(?{not defined $codigos{$1}})(*FAIL)) es una comprobación: miramos a ver si la palabra la tenemos en %codigos o no. Si no la tenemos, indicamos un fallo ((*FAIL)) (mejor dicho, indicamos que lo encontrado realmente no era lo que queríamos) y la regex lo intentará con la siguiente palabra.

Seguro que hay más soluciones...
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Re: Cómo automatizar adición de etiquetas

Notapor RGP_perl » 2011-08-29 13:01 @584

Gracias por la explicación, explorer. Tiene que ser una pasada saber tanto Perl :)

La primera solución la sigo mejor, aunque hay algo que no me cuadra.

Verás, yo pretendía insertar la etiqueta cuando la palabra en la línea 2 fuera igual que la de la línea 1 y estuviera en la misma posición (p.ej., ambas ocuparan el cuarto lugar). Esto es lo que quería conseguir con mi expresión regular original. Sin embargo, ahora, cuando ejecuto el script observo que "con(P)" en la línea 1 está en cuarto lugar, y en la línea 2, que ha sido etiquetada, en sexto lugar. En este caso no pasa nada porque el resultado es correcto, pero temo que al ocupar distintas posiciones y ejecutar el programa sobre un texto extenso, pueda causarme problemas.

¿Es posible "fijar" la posición de modo que solamente se añadan las etiquetas si la palabra y la posición que ocupan son las mismas?
RGP_perl
Perlero nuevo
Perlero nuevo
 
Mensajes: 6
Registrado: 2011-08-26 07:49 @367

Re: Cómo automatizar adición de etiquetas

Notapor explorer » 2011-08-29 13:42 @612

En ese caso... lo que yo haría sería meter las palabras encontradas en un array (y en otro array, los códigos, ocupando los mismos puestos). Luego, al tratar la segunda línea, la dividimos en palabras (en otro array), y solo queda sacar elemento de cada array.

Si una palabra del primer array le corresponde un código, mira a ver si en el segundo array está la misma palabra. Si no, comienza a buscarla por el resto de palabras del segundo array, y resolvemos. Pero... si agotamos el array, entonces es que algo ha ido mal, y falla.
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Re: Cómo automatizar adición de etiquetas

Notapor RGP_perl » 2011-08-30 10:03 @460

Gracias. Con un poco de suerte y leyendo mucho supongo que todo esto acabará teniendo sentido. Agradezco de verdad que te hayas tomado la molestia de ayudarme.
RGP_perl
Perlero nuevo
Perlero nuevo
 
Mensajes: 6
Registrado: 2011-08-26 07:49 @367

Re: Cómo automatizar adición de etiquetas

Notapor explorer » 2011-08-31 13:34 @607

Creo que he conseguido encontrar una solución (no ha sido nada fácil).
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/perl
  2. use strict;
  3. use warnings;
  4. use utf8;
  5. use open qw(:utf8 :std);
  6.  
  7. while (my $linea1 = <DATA> ) {  # leemos línea a línea desde __DATA__
  8.     chomp $linea1;
  9.     my $linea2 = <DATA>;        # leemos la segunda línea, también
  10.     chomp $linea2;
  11.  
  12.     print "1:[$linea1]\n";
  13.     print "2:[$linea2]\n";
  14.  
  15.     while ($linea1 =~ m/\G.*?(\w+)(?:\(([-\w]+)\))?/g) {         # buscamos una palabra más código opcional
  16.         my($palabra, $codigo) = ($1, $2);
  17.         my $palabra2;
  18.  
  19.         if ($linea2 =~ m/\G.*?(\w+)/g) {                         # recorremos segunda línea,
  20.             $palabra2 = $1;                                      # buscando la siguiente palabra
  21.  
  22.             if ($codigo  and  $palabra eq $palabra2) {           # si teníamos un código y las palabras coinciden
  23.                 my $anterior = pos $linea2;                      # guardamos la posición de la palabra
  24.                 substr($linea2, $anterior, 0, "($codigo)");      # metemos con calzador el código, en ese lugar
  25.                 pos($linea2) = $anterior + 2 + length $codigo;   # posición para la siguiente búsqueda
  26.             }
  27.         }
  28.     }
  29.  
  30.     print "3:[$linea2]\n";
  31.  
  32.     print '-' x 60, "\n";
  33. }
  34.  
  35. __DATA__
  36. El(A) día(S) comenzó(V) con(P) viento(S-CCM) y(C) lluvia(S-CCM) en(P) Madrid(S-CCL).
  37.         El día había comenzado bien, con lluvia en Madrid.
  38. El(A) día(S) comenzó(V) con(P) viento(S-CCM) y(C) lluvia(S-CCM) en(P) Madrid(S-CCL).
  39.         El comenzó el día bien, con lluvia y viento en Madrid.
  40. La(A) jornada(S) se presentaba(V) ideal(A-CCM) para(P) salir(V) a(P) buscar(V) setas(S-OD).
  41.         La jornada pintaba muy bien para salir al campo.
  42. La(A) jornada se presentaba(V) ideal(A-CCM) para(P) salir(V) a(P) buscar(V) setas(S-OD).
  43.         La jornada pintaba muy bien para salir al campo.
Coloreado en 0.002 segundos, usando GeSHi 1.0.8.4
sale
Sintáxis: [ Descargar ] [ Ocultar ]
Using text Syntax Highlighting
1:[El(A) día(S) comenzó(V) con(P) viento(S-CCM) y(C) lluvia(S-CCM) en(P) Madrid(S-CCL).]
2:[     El día había comenzado bien, con lluvia en Madrid.]
3:[     El(A) día(S) había comenzado bien, con lluvia(S-CCM) en(P) Madrid(S-CCL).]
------------------------------------------------------------
1:[El(A) día(S) comenzó(V) con(P) viento(S-CCM) y(C) lluvia(S-CCM) en(P) Madrid(S-CCL).]
2:[     El comenzó el día bien, con lluvia y viento en Madrid.]
3:[     El(A) comenzó el día bien, con lluvia(S-CCM) y viento en Madrid.]
------------------------------------------------------------
1:[La(A) jornada(S) se presentaba(V) ideal(A-CCM) para(P) salir(V) a(P) buscar(V) setas(S-OD).]
2:[     La jornada pintaba muy bien para salir al campo.]
3:[     La(A) jornada(S) pintaba muy bien para(P) salir(V) al campo.]
------------------------------------------------------------
1:[La(A) jornada se presentaba(V) ideal(A-CCM) para(P) salir(V) a(P) buscar(V) setas(S-OD).]
2:[     La jornada pintaba muy bien para salir al campo.]
3:[     La(A) jornada pintaba muy bien para(P) salir(V) al campo.]
------------------------------------------------------------
Coloreado en 0.000 segundos, usando GeSHi 1.0.8.4

Un poco de explicación.

Usamos un while() para recorrer las palabras de la primera línea, y un if() para las de la segunda línea. Con el uso de /g podemos hacer esas búsquedas repetidas. Observar (importante) que las dos expresiones regulares están en contexto escalar, por lo que lo que devuelven es un valor booleano (verdadero o falso) sobre si han encontrado la siguiente palabra o no.

Y como extraemos una palabra cada vez, de las dos líneas, estamos seguros de que se cumple la condición de que las palabras deben coincidir en posición.

Si, la primera línea, tenía un código, y las dos palabras son iguales, entonces hacemos la operación de modificación de la segunda línea. En el ejemplo indicado usamos substr(). Pero hay un problema: al usar substr(), las expr. reg. "olvidan" la posición de búsqueda, por lo que se "reiniciaría" la búsqueda de la siguiente palabra desde el comienzo, y no desde el final de la anterior búsqueda. Por eso usamos pos() para almacenar temporalmente esa posición. Después de hacer la inserción del código, ajustamos la posición a la que tenía antes, más lo que ocupa ahora el código.

La línea
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1.                 substr($linea2, $anterior, 0, "($codigo)");
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
también se puede escribir como
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1.                 substr($linea2, $anterior, 0) = "($codigo)";
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4



Explicación de las expresiones regulares:

$linea1 =~ m/\G.*?(\w+)(?:\(([-\w]+)\))?/g

De forma repetida (/g), buscamos en $linea1, desde la última posición anterior (\G), cero o más caracteres (.*?) seguidos por una palabra (\w+), que capturaremos, y seguido, opcionalmente seguidas por un par de paréntesis con un código dentro ((?:\(([-\w]+)\))?), de que capturaremos el texto del código.

$linea2 =~ m/\G.*?(\w+)/g

También, de forma repetida (/g), buscamos en $linea2, desde la última posición anterior (\G), cero o más caracteres (.*?) seguidos por una palabra (\w+), que capturaremos.

Los comodines (.*?) son necesarios para saltar los caracteres espacio y de puntuación.

Otra forma de hacer la inserción del código es por medio de una sustitución, pero es algo más lenta (cambiar las líneas 23 a 25 a éstas):
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1.                 my $anterior = pos $linea2;                      # guardamos posición donde la encontramos
  2.                 pos($linea2) -= length $palabra2;                # retrocedemos a donde comienza la palabra
  3.                 $linea2 =~ s/\G.*?\w+\K/($codigo)/;              # y le agregamos el código
  4.                 pos($linea2) = $anterior + 2 + length $codigo;   # reposicionamos para la siguiente búsqueda
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Re: Cómo automatizar adición de etiquetas

Notapor RGP_perl » 2011-08-31 14:05 @628

explorer:

¡Estoy realmente impresionado! Lo he probado con un texto más extenso y funciona bien. Lo único es que no añade los códigos si las palabras se diferencian en algún acento (prueba a poner "dia" en lugar de "día") o una palabra está escrita en mayúsculas y la otra en minúsculas (por ej. "madrid" en vez de "Madrid").

¿Existe algún modo de indicar en el script que no tenga en cuenta estos factores y los códigos se inserten igualmente, tal como decía en mi mensaje original, o al tratarse de unicode esto no es posible?

Muchas gracias de nuevo.

RGP
RGP_perl
Perlero nuevo
Perlero nuevo
 
Mensajes: 6
Registrado: 2011-08-26 07:49 @367

Re: Cómo automatizar adición de etiquetas

Notapor explorer » 2011-08-31 18:07 @797

Bueno, la solución está ajustada a que las palabras sean iguales, pero lo que pides ya es un nivel más de profundidad.

Para solucionar el primer caso (tamaño de caja), una solución clásica es la de normalizar las palabras a un mínimo común, por ejemplo, convertir las dos a minúsculas:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1.             if ($codigo  and  lc($palabra) eq lc($palabra2)) {
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4


pero el problema segundo, el de las tildes, es más peliagudo, porque depende de la codificación que estés usando.

De forma estricta "o" y "ó" son dos letras completamente distintas, así que, hay que hacer alguna transformación de las palabras, para poderlas comparar.

Habría que hacer esto:

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
use Unicode::Normalize 'NFD';     # importamos la función de descomposición normalizada
...
my $p1 = NFD($palabra);           # denormalizamos: convertimos los caracteres tildados
                                  # en caracteres normales más el carácter tilde
$p1 =~ s/[^a-z]//g;               # un poco chapuza, pero quitamos los caracteres tilde,
                                  # con lo que solo quedan los caracteres normales
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4


aunque... como parece que solo vas a tratar documentación en español, podemos hacer una simplificación:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1.             my $p1 = lc $palabra;          # lo pasamos a minúsculas
  2.             my $p2 = lc $palabra2;         # ídem
  3.  
  4.             $p1 =~ tr/áéíóúñçü/aeiouncu/;  # quitamos las tildes
  5.             $p2 =~ tr/áéíóúñçü/aeiouncu/;  # ídem
  6.  
  7.             if ($codigo  and  $p1 eq $p2) {
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España


Volver a Básico

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 0 invitados