• Publicidad

Extracción de combinaciones de palabras

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

Extracción de combinaciones de palabras

Notapor jorcaes » 2006-07-12 02:45 @156

Hola a todos!

Este es mi primer mensaje en el foro, ya que soy un aprendiz de Perl.

Mi problema es el siguiente:

Debo encontrar la forma (si es que existe) de extraer de varios textos las combinaciones entre palabras de una lista dada de palabras. Estamos hablando de 140 textos y una lista de alrededor de 1.300 palabras. Se trataría de ver cómo esos términos se repiten/combinan entre ellos en dichos textos.

El primer paso sería almacenar en una tabla Hash el listado de las palabras y las posibles combinaciones entre ellas.

Había pensado en realizar una aplicación con la siguiente estructura (os la pongo es pseudo-código):

Código: Seleccionar todo
for each file {
      for each word in current file {
      search for this word in keyword list hash table
           search for previous word in keyword list hash table
              if both are keywords {
                   search for this combination in list of combinations, and increase count by one
                   }
      }
   }



Os lo pongo en inglés porque lo tengo que desarrollar en dicho idioma, si alguien tiene algún problema que me lo diga :wink:

Espero que me aconsejeis sobre la mejor manera de realizarlo, y si estoy equivocado en el planteamiento de la estructura, admito sugerencias, recomendaciones, ayudas...

Un saludo y muchas gracias!!!
jorcaes
Perlero nuevo
Perlero nuevo
 
Mensajes: 26
Registrado: 2006-07-12 02:35 @149

Publicidad

Notapor explorer » 2006-07-12 04:45 @240

Pero, ¿cómo han de ser las combinaciones?
Según el pseudo-código que has puesto...
* Si la palabra del fichero actual es una principal, y
* La palabra anterior también lo era, entonces
* Buscar esta combinación de dos palabras en la lista de combinaciones...

1.- ¿Cómo son esas combinaciones?
2.- Sólo miramos de dos en dos palabras, luego ¿las combinaciones son de todas las posibles de 1.300 palabras tomadas de dos en dos? ¿O de otra forma?
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

Notapor jorcaes » 2006-07-12 06:18 @304

Fallo mío.

He calculado, que, si estamos buscando combinaciones de dos palabras, en el peor de los casos necesitaré unos 30Mb (asumiendo una longitud de 10 carácteres y sólamente almacenando el número de ocurrencias 1300*1300*(10+10), pero de todas formas dependerá del número de palabras que contegan los ficheros.

Espero que ahora haya quedado un poco más claro...

Un saludo y gracias!!!
jorcaes
Perlero nuevo
Perlero nuevo
 
Mensajes: 26
Registrado: 2006-07-12 02:35 @149

Notapor explorer » 2006-07-12 09:45 @448

Perl viene muy bien para resolver estos problemas...

El truco consiste en guardar la lista de palabras en un hash, que nos servirá para saber rápidamente si la palabra es clave o no.
Y otro hash para ir guardando las combinaciones que vamos encontrando...
Código: Seleccionar todo
    1 #!/usr/bin/perl
    2
    3 # Leemos lista de palabras keywords
    4 my %keywords;
    5 while ( <DATA> ) {
    6     chomp;
    7     $keywords{lc($_)} = 1;
    8 }
    9
   10 # Para cada fichero...
   11 foreach my $nombre_fichero ( </usr/lib/perl5/5.8.7/pod/perld*.pod> ) {
   12
   13     # Nos leemos todo el fichero y lo dividimos en palabras
   14     my @palabras;
   15     {
   16         my $fichero = do { local $/; open FH, "<$nombre_fichero"; <FH> };
   17         @palabras = $fichero =~ /(\b[^\W_\d][\w'-]+\b)/g;
   18     };
   19
   20     #print "Número de palabras del fichero $nombre_fichero: ", scalar @palabras,"\n";
   21
   22     # Para todas las palabras del fichero
   23     my %combinaciones;
   24     my $palabra;
   25     my $palabra_anterior = '';
   26     foreach my $palabra_org ( @palabras ) {
   27         $palabra = lc($palabra_org);
   28         if ( $keywords{$palabra} && $keywords{$palabra_anterior} ) {
   29             $combinaciones{"$palabra_anterior|$palabra"}++;
   30         }
   31         $palabra_anterior = $palabra;
   32     }
   33
   34     # Sacamos el resultado, si lo hay
   35     if ( keys %combinaciones ) {
   36         print "Fichero $nombre_fichero:\n";
   37         foreach my $combinacion ( sort keys %combinaciones ) {
   38             print "\t$combinacion :", $combinaciones{$combinacion},"\n";
   39         }
   40     }
   41 }
   42
   43 __DATA__
   44 Perl
   45 can't
   46 can
   47 from

Fichero /usr/lib/perl5/5.8.7/pod/perldata.pod:
        from|perl :1
Fichero /usr/lib/perl5/5.8.7/pod/perldebguts.pod:
        perl|from :1
Fichero /usr/lib/perl5/5.8.7/pod/perldebug.pod:
        perl|can :1
Fichero /usr/lib/perl5/5.8.7/pod/perldiag.pod:
        perl|can :2
        perl|can't :5
Aquí voy leyendo las palabras desde la sección DATA (al final del programa), pero podrían estar en un fichero aparte.
Luego lo que sigue es una traducción literal de tu pseudo-código...
El truco está en usar de clave hash a las dos palabras claves encontradas, y sumarle uno cada vez (línea 29).
Última edición por explorer el 2006-07-19 12:11 @549, editado 3 veces en total
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

Notapor jorcaes » 2006-07-19 09:34 @440

Hola de nuevo, me está siendo de gran ayuda el código que posteaste, pero tengo algunos problemas con su ejecución desde Windows, ya que aún no consigo obtener los ficheros desde el directorio de mi disco duro.

¿cómo sería el código correspondiente en Windows?

Código: Seleccionar todo
     #!C:\perl\bin\perl.exe -w
     use warnings;
     use strict;
   
     # Leemos lista de palabras keywords
     my %keywords;
     while ( <DATA> ) {
         chomp;
         $keywords{$_} = 1;
     }
      foreach ( keys %keywords ) {
          print "$_\n";
      }
   
     # Para cada fichero...
     foreach my $nombre_fichero ( "C:\Perl\lib\Pod\perld*.pod" ) {
        print "Hola desde el foreach\n";
         # Nos leemos todo el fichero y lo dividimos en palabras
         my @palabras = do {
             my $fichero = do { local $/; open FH, "<$nombre_fichero"; <FH> };
             $fichero =~ /(\w+)/g;
         };
   
         print 'Número de palabras del fichero: ', scalar @palabras,"\n";
   
         # Para todas las palabras del fichero
         my %combinaciones;
         my $palabra_anterior = '';
         foreach my $palabra ( @palabras ) {
             if ( $keywords{$palabra} && $keywords{$palabra_anterior} ) {
                 $combinaciones{"$palabra_anterior|$palabra"}++;
             }
             $palabra_anterior = $palabra;
         }
   
         # Sacamos el resultado, si lo hay
         if ( keys %combinaciones ) {
             print "Fichero $nombre_fichero:\n";
             foreach my $combinacion ( sort keys %combinaciones ) {
                 print "\t$combinacion :", $combinaciones{$combinacion},"\n";
             }
         }
     }
   
     __DATA__
     Perl
     can
     from


En qué partes debería cambiar alguna cosa más?

Un saludo y muchisimas gracias por todo!!!
jorcaes
Perlero nuevo
Perlero nuevo
 
Mensajes: 26
Registrado: 2006-07-12 02:35 @149

Notapor explorer » 2006-07-19 10:04 @461

El problema está en esta línea:
foreach my $nombre_fichero ( "C:\Perl\lib\Pod\perld*.pod" )

Estás usando dobles comillas en un literal y para Perl significa que hay que intentar interpretar lo que hay entre esas dobles comillas. Eso quiere decir que ha intentado saber lo que es \P, \l y \p.

Tienes dos soluciones, pero el primer consejo es que te fijes en mi código... yo uso <...> para leer la lista de ficheros.

Como en windows se trabaja con el '\' en lugar de '/' del unix, hay que tener cuidado cuando se pone la ruta de los ficheros.

La primera solución es 'escapar' las barras inclinadas:
"C:\\Perl\\lib\\Pod\\perld*.pod"

y la otra solución, es, que como este literal no tiene ninguna variable que interpolar, la podemos tratar como un literal tal cual:
'C:\Perl\lib\Pod\perld*.pod'

Pero repito, te falta el operador <...>.

Prueba con <C:\\Perl\\lib\\Pod\\perld*.pod> .
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

Notapor jorcaes » 2006-07-19 10:48 @492

Hola de nuevo, gracias por responder tan rápidamente, pero sigo teniendo problemas...

El script funciona correctamente, pero en el fichero perldiag.pod encuentra 6 ocurrencias de Perl | can, pero realmente tan sólo debería mostrar una, ya que el resto son combinaciones de Perl | can't.

Por otra parte, cuando añado la palabra can't al resto de palabras en la sección DATA al final del código, me ignora las diferentes combinaciones existentes...

¿Cómo podría resolver ésto? Es decir, que tenga en cuenta palabras formadas por carácteres especiales como ( ' - )

Otra cosa: cuando se introduce Perl, ¿cómo puedo hacer que también busque la palabra "perl"? Es decir, que NO haga distinción entre mayúsculas y minúsculas?

De nuevo muchas gracias!!!
jorcaes
Perlero nuevo
Perlero nuevo
 
Mensajes: 26
Registrado: 2006-07-12 02:35 @149

Notapor explorer » 2006-07-19 11:46 @532

A ver ahora...

Para corregir el problema de la división de palabras, he usado la expresión regular que esta en el perlfaq6.

Y para el tema de mayúsculas, he optado por pasar todas las palabras a minúsculas con la función lc(), tanto para las palabras clave como en la búsqueda.
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

Notapor jorcaes » 2006-07-25 14:19 @638

Hola de nuevo!

He estado viendo las modificaciones que has añadido al código y me han servido de mucho, ya que me resuelven todos los problemas que tenía, pero ahora se me presenta otro problema, os explico...

Necesito hacer lo que viene haciendo dicho código, pero en lugar de mostrar los resultados de las combinaciones fichero a fichero, necesito que me contemple todos los fichero como un único fichero, es decir, que muestre los resultados globales de todas las combinaciones acumuladas en cada uno de los ficheros...

No sé si me he explicado con sufiente claridad, pero lo que he propuesto es que haga lo mismo que hace, pero a nivel más global, indicando el directorio de los ficheros de texto y leyendolos uno a uno, guardando las combinaciones existentes y si al pasar a otro fichero existe la misma combinación añadirle uno más. Finalmente sacaría el resultado de todas las combinaciones de todos los textos.

*Hago una pequeña pregunta aparte, ¿cómo puedo mostrar los resultados ordenados por el número de ocurrencias que aparece, de mayor a menor frecuencia?

Muchas gracias por todo!!!
jorcaes
Perlero nuevo
Perlero nuevo
 
Mensajes: 26
Registrado: 2006-07-12 02:35 @149

Notapor explorer » 2006-07-25 15:13 @676

Para sacar las combinaciones de todos los ficheros sólo hay que hacer dos modificaciones muy sencillas:
1.- Saca la línea 23 my %combinaciones; y cólocala antes de la línea 11. De esa forma %combinaciones no se resetea por cada fichero.
2.- Saca todo el bucle de resultados, de la línea 34 a la 40, y lo colocas después de la llave de la línea 41. De esa forma los resultados no salen más que al final. Naturalmente, la línea 36 sobra.

Para sacarlo ordenado por número de apariciones... hay que modificar la línea
Código: Seleccionar todo
foreach my $combinacion ( sort keys %combinaciones ) {
por
Código: Seleccionar todo
foreach my $combinacion ( sort { $combinaciones{$a} <=> $combinaciones{$b} } keys %combinaciones ) {
Lo que hacemos es el mismo sort que antes, pero ahora lo hacemos de forma numérica (<=>) y sólo a los valores contenidos en %combinaciones, usando las variables especiales $a y $b (es más claro leer la documentación de sort, claro).

Ahora lo que me sale es esto:
Código: Seleccionar todo
        perl|from :1
        from|perl :1
        perl|can :3
        perl|can't :5
que coincide con la solución anterior.
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

Siguiente

Volver a Básico

¿Quién está conectado?

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

cron