• Publicidad

Match con un conjunto de expresiones regulares

Así que programas sin strict y las expresiones regulares son otro modo de hablar. Aquí encontrarás respuestas de nivel avanzado, no recomendable para los débiles de corazón.

Match con un conjunto de expresiones regulares

Notapor init_perl » 2008-08-21 17:08 @755

Hola. Antes que todo quiero decir que no sé si hacer o no la consulta en "Experto", ya que por un lado yo soy principiante y por otro se trata de expresiones regulares... así que si me equivoco de lugar, les pido me disculpen.

Aquí está mi problema. Tengo dos archivos de entrada (ejemplos simplificados):

Primero (fecha, id1, id2, estado, duración, tipo), llamado "entrada.dat":
Código: Seleccionar todo
19/08/2008-10:26:45 C,14,219,1,0,1
19/08/2008-10:27:46 C,13,222,2,0,3
...


Segundo (expresión regular, identificador), llamado "regexp.dat":
Código: Seleccionar todo
/^1/,OPERADOR1
/^2/,OPERADOR2
/^3/,OPERADOR2
/^4/,OPERADOR3
/^5/,OPERADOR1
/^6/,OPERADOR1
/^7/,OPERADOR2
/^8/,OPERADOR3
/^9/,OPERADOR1


A partir de estos dos archivos de entrada, debo generar un tercero (digamos "salida.dat") que contenga todas las lineas de "entrada.dat", pero con una columna adicional correspondiente a campo identificador del archivo "regexp.dat".

Para buscar el identificador debo leer cada línea de "entrada.dat" y hacer match del campo id1 con la expresión regular de "regexp.dat" (observar que los identificadores de "regexp.dat" pueden tener más de una expresión regular asociada).

Aquí pego lo que he estado tratando de hacer, pero quedo pegada en la forma de recorrer las expresiones regulares cuando estoy recorriendo "entrada.dat":

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
#!/usr/bin/perl

use strict;
use warnings;
use diagnostics;

#Guarda las expresiones regulares en la matriz regexp
open ( RE, 'regexp.dat' );
while ( <RE> ) {
  chomp;
  @line_re = split ( ",", $_ );
  $operator = @linea_re[1];
  $regexp{$operator} = @linea_re[0];
}
close ( RE );


open ( SALIDA, '>salida.dat');

open ( ENTRADA, 'entrada.dat' ) or die "$!\n";
while ( <ENTRADA> ) {
  ($date,$id1,$id2,$st,$dur,$typ) = split ( ",", $_ );

  #Aqui debo hacer match entre $id1 y algo asi como
  #el contenido de $regexp{ ...  } (que es la exp. regular)
  #Si hay match, debe imprimirse en SALIDA + ",la key de regexp

}

close ( SALIDA );
close ( ENTRADA );
Coloreado en 0.003 segundos, usando GeSHi 1.0.8.4


Lo que les presento aquí es una forma que pensé que podía almacenar las reg. exp. en una matriz asociativa para recorrerla, pero al ir codificando me di cuenta que o bien no sabía cómo hacer o bien... no se puede... :oops:

También podría almacenar el archivo de regexp en una lista, pero nuevamente no sé cómo recorrerla para hacer el match.

Les agradecería mucho su ayuda.

Saludos,

Init_perl
init_perl
Perlero nuevo
Perlero nuevo
 
Mensajes: 9
Registrado: 2008-08-21 16:36 @733

Publicidad

Notapor explorer » 2008-08-21 18:28 @811

Bienvenido a los foros de Perl en Español, init_perl.

Los hash siempre dan buenas soluciones. Pero en este caso, he supuesto varias cosas...

* Realmente, no necesitamos aplicar expresiones regulares, porque regexp.dat contiene una entrada por cada dígito, por lo que podemos hacer una traslación de ese dígito a su correspondiente identificador.

* Y también suponemos que el id1 está bien construido (es un campo, con comas como delimitadores), y del que solo nos interesa su primer dígito (viendo como son las exp. reg. del ejemplo, se deduce que solo nos interesa el primer dígito).

Con esas simplificaciones, se puede construir la siguiente solución:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
#!/usr/bin/perl
use strict;
use warnings;
use diagnostics;

## Leemos el regexp.dat y la almacenamos en la hash
my %id_de_la_cifra;

open my $REGFILE, '<', 'regexp.dat' or die "$!\n";

while (my $linea = <$REGFILE>) {
    my ($cifra, $id) = $linea =~ m/ (\d) .* , (\w+) /simox;
    $id_de_la_cifra{$cifra} = $id;
}

close $REGFILE;

## Procesamos el fichero de entrada
open my $INFILE,  '<', 'entrada.dat' or die "$!\n";
open my $OUTFILE, '>', 'salida.dat'  or die "$!\n";

while (my $linea = <$INFILE>) {
    chomp $linea;
    print $OUTFILE $linea, " ", $id_de_la_cifra{substr((split q{,}, $linea)[1], 0, 1)}, "\n";
}

close $INFILE;
close $OUTFILE;

__END__
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
Sale:
Código: Seleccionar todo
19/08/2008-10:26:45 C,14,219,1,0,1 OPERADOR1
19/08/2008-10:27:46 C,13,222,2,0,3 OPERADOR1
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

Respecto de los supuestos...

Notapor init_perl » 2008-08-22 09:13 @425

¡Hola Explorer! De veras que muchas gracias por tu respuesta.

Antes de comenzar a revisarla me gustaría decir respecto de los supuestos que los ejemplos que envié están simplificados. En particular en lo que se refiere a:
- Id1 está simplificado, pero en la realidad se trata de números de teléfonos.
- Las expresiones regulares corresponden a los rangos de numeración de operadoras y por lo tanto en términos reales son muchos más complejas.

Lamentablemente, en estos momentos no tengo ejemplos reales de los archivos de entrada así que no puedo enviar, pero puedo decirte que respecto de las expresiones regulares éstas son muchas más y también más complejas.

Ahora paso a revisar el código que enviaste. Gracias de nuevo. :D
init_perl
Perlero nuevo
Perlero nuevo
 
Mensajes: 9
Registrado: 2008-08-21 16:36 @733

Notapor explorer » 2008-08-22 10:20 @472

En ese caso sí que hay que ejecutar las expresiones regulares...
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

Con un ejemplo mas real ...

Notapor init_perl » 2008-08-22 10:32 @480

Hola. Sigo sin datos reales, pero creo que estos datos son más cercanos de la realidad.

entrada.dat:
Código: Seleccionar todo
19/08/2008-10:00:40 C,98123,100,0,11,1
19/08/2008-10:01:41 C,97834,102,0,12,2
19/08/2008-10:02:42 C,98312,104,1,0,1
19/08/2008-10:03:43 C,99836,106,2,0,3
19/08/2008-10:04:44 C,97632,108,3,0,3


regexp.dat
Código: Seleccionar todo
/^98/,OPERADOR1
/^983/,OPERADOR2
/^97[05]/,OPERADOR3
/^97[69]/,OPERADOR2


salida.dat (luego de ejecución):
Código: Seleccionar todo
19/08/2008-10:00:40 C,98123,100,0,11,1 OPERADOR2
19/08/2008-10:01:41 C,97834,102,0,12,2 OPERADOR2
19/08/2008-10:02:42 C,98312,104,1,0,1 OPERADOR2
19/08/2008-10:03:43 C,99836,106,2,0,3 OPERADOR2
19/08/2008-10:04:44 C,97632,108,3,0,3 OPERADOR2


Ahora, sé que el error en la identificación del "operador" se da por las suposiciones originales, sin embargo, este ejemplo sirve para indicar cuál es la salida esperada:

Código: Seleccionar todo
19/08/2008-10:00:40 C,98123,100,0,11,1 OPERADOR1
19/08/2008-10:01:41 C,97834,102,0,12,2 OPERADOR2
19/08/2008-10:02:42 C,98312,104,1,0,1 OPERADOR2
19/08/2008-10:03:43 C,99836,106,2,0,3 NOT_FOUND
19/08/2008-10:04:44 C,97632,108,3,0,3 OPERADOR2


Se puede observar que la regexp /^983/ tiene precedencia sobre /^98/ por ser más larga y también que cuando el id1 no hace match con ninguna regexp debiera indicar algo como NOT_FOUND.

Ahora, quisiera, explorer, consultar un par de dudas respecto del código enviado:

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
# $linea contiene la información de una línea de regexp.dat y en ella
# parecieras buscar un patrón para hacer un reemplazo, pero no logro
# ver completamente el significado:
my ($cifra, $id) = $linea =~ m/ (\d) .* , (\w+) /simox;

# $linea de entrada.dat se imprime junto con el identificador de
# <operador>, pero no logro ver cómo se hace el <span style="font-style: italic">match</span> en el hash
# id_de_la_cifra ... pues no entiendo el significado de
# (split q{,}, $linea)[1]
print $OUTFILE $linea, " ", $id_de_la_cifra{substr((split q{,}, $linea)[1], 0, 1)}, "\n";
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4


Gracias por sus respuestas.
¡Saludos!
init_perl
Perlero nuevo
Perlero nuevo
 
Mensajes: 9
Registrado: 2008-08-21 16:36 @733

Re: Con un ejemplo mas real ...

Notapor explorer » 2008-08-22 13:03 @585

init_perl escribiste:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
# $linea contiene la información de una línea de regexp.dat y en ella
# parecieras buscar un patrón para hacer un reemplazo, pero no logro
# ver completamente el significado:
my ($cifra, $id) = $linea =~ m/ (\d) .* , (\w+) /simox;

# $linea de entrada.dat se imprime junto con el identificador de
# <operador>, pero no logro ver cómo se hace el <span style="font-style: italic">match</span> en el hash
# id_de_la_cifra ... pues no entiendo el significado de
# (split q{,}, $linea)[1]
print $OUTFILE $linea, " ", $id_de_la_cifra{substr((split q{,}, $linea)[1], 0, 1)}, "\n";
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4


En la primera línea hacemos una búsqueda de un patrón, en la $linea. Capturamos un dígito (\d) y una palabra (\w+), y las guardamos en las variables $cifra e $id. No estamos reemplazando, sino solo buscando.

En la segunda línea no hacemos ninguna búsqueda de patrón. El proceso que hacemos es:
* A partir de la $linea que hemos leído de entrada.dat,
* la partimos (split()) por las comas. El resultado es una lista de valores,
* del que nos quedamos con el segundo valor ([1]).
* De ese segundo elemento, con substr(), extraeremos el (1), primer (0) carácter
* Usaremos ese primer carácter como clave dentro %id_de_la_cifra para extraer la cadena a mostrar.

No hemos hecho ninguna expresión regular por lo que no vale para lo que necesitas.
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: Con un ejemplo mas real ...

Notapor init_perl » 2008-08-22 15:36 @692

Hola otra vez. Gracias por la explicación anterior.

He seguido tratando de averiguar primero cómo guardar mi data de expresiones regulares (antes de comenzar con cualquier otra cosa).

regexp.dat:
Código: Seleccionar todo
/^98/,OPERADOR1
/^983/,OPERADOR2
/^97[05]/,OPERADOR3
/^97[69]/,OPERADOR2


Y escribí este pequeño código:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
#!/usr/bin/perl

use warnings;
use diagnostics;

#Guarda las expresiones regulares en la lista regexp
open ( REGEXP, 'regexp.dat' );
while ( <REGEXP> ) {
  chomp;
  ( $re, $operator ) = split ( ",", $_ );
  $regexp{$operator} = $re;
}

foreach ( %regexp ) {
  print " Operador $_:  RE $regexp{$_}\n";
}
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4


La salida de la ejecución fue la siguiente:
Código: Seleccionar todo
$ ./cg_regexp_v1.pl
 Operador OPERADOR3:  RE /^97[05]/
Use of uninitialized value in concatenation (.) or string at ./cg_regexp_v1.pl
        line 15, <REGEXP> line 4 (#1)
    (W uninitialized) An undefined value was used as if it were already
    defined.  It was interpreted as a "" or a 0, but maybe it was a mistake.
    To suppress this warning assign a defined value to your variables.

    To help you figure out what was undefined, perl tells you what operation
    you used the undefined value in.  Note, however, that perl optimizes your
    program and the operation displayed in the warning may not necessarily
    appear literally in your program.  For example, "that $foo" is
    usually optimized into "that " . $foo, and the warning will refer to
    the concatenation (.) operator, even though there is no . in your
    program.

 Operador /^97[05]/:  RE
 Operador OPERADOR2:  RE /^97[69]/
 Operador /^97[69]/:  RE
 Operador OPERADOR1:  RE /^98/
 Operador /^98/:  RE


Como puede verse, imprimió cualquier cosa... aunque estoy segura que esa "cualquier cosa" es problema mío... sorry por eso.:oops:

¿Podrías por favor darme una pista respecto de lo que estoy haciendo mal?

Muchas gracias.
init_perl
Perlero nuevo
Perlero nuevo
 
Mensajes: 9
Registrado: 2008-08-21 16:36 @733

Notapor explorer » 2008-08-23 08:50 @409

En el bucle foreach quieres recorrer las claves del hash, pero en realidad estás recorriendo las claves y los valores.

Debes agregar 'keys' delante del hash.

Otra cosa. He encontrado una nueva solución.

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
#!/usr/bin/perl
use strict;
use warnings;
use diagnostics;

## Leemos el regexp.dat y la almacenamos en la hash
my %regex;

open my $REGFILE, '<', 'regexp.dat' or die "$!\n";
while (my $linea = <$REGFILE>) {
    my ($regex, $operador) = split q{,}, $linea;
    $regex =~ s{/}{}g;                               # Quitamos los '/' (no hacen falta)
    $regex{$regex} = $operador;
}
close $REGFILE;

## Procesamos el fichero de entrada
open my $INFILE,  '<', 'entrada.dat' or die "$!\n";
open my $OUTFILE, '>', 'salida.dat'  or die "$!\n";

while (my $linea = <$INFILE>) {
    chomp $linea;

    my $numero = (split q{,}, $linea)[1];                # El segundo campo de la $linea

    ## Hacemos un bucle por todas las regex,
    ## buscando aquella que coincida con el $número
    ## y sea de mayor longitud de coincidencia
    my $regex_encontrada;
    my $regex_mayor = 0;
    for my  $regex (keys %regex ) {
        if ($numero =~ m/$regex/) {
            if (length $& > $regex_mayor) {
                $regex_encontrada = $regex;
                $regex_mayor      = length $&;
            }
        }
    }

    ## Pintamos el resultado
    print $OUTFILE $linea, ' ', ($regex_encontrada) ? $regex{$regex_encontrada} : "NOT_FOUND\n";
}

close $INFILE;
close $OUTFILE;

__END__
Coloreado en 0.002 segundos, usando GeSHi 1.0.8.4
Última edición por explorer el 2008-08-27 15:21 @681, editado 1 vez 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

El orden en la matriz asociativa ...

Notapor init_perl » 2008-08-25 09:53 @453

¡Hola Explorer!
Deja que te diga que tu solución funcionó muy bien y estoy haciendo pruebas para ver si puedo aprender yo un poco más de todo esto, pues parece ser muy poderoso.

Dentro de las cosas que he observado es que la matriz con las expresiones regulares debe ser recorrida completamente por cada registro del archivo entrada.dat.

Lo anterior podría (no tengo certeza de cómo ande Perl en esto) ser muy lento si entrada.dat y regexp.dat son muy grandes; así que se me ocurrió pensar que si las expresiones se revisan según precedencia (más largas a más cortas) se podría evitar recorrer completa la matriz cada vez, pues a la primera coincidencia se sale del ciclo.

Me puse entonces a imprimir algunos datos de tu código, para ver en que orden se recorrían las expresiones regulares y, ¡sorpresa!... sin orden... Mira esto:

Las expresiones son leídas del archivo en este orden:
Código: Seleccionar todo
/^98/,OPERADOR1
/^983/,OPERADOR2
/^97[05]/,OPERADOR3
/^97[69]/,OPERADOR2


y la lectura en el ciclo es en este orden:
Código: Seleccionar todo
regex ^97[69]
regex ^97[05]
regex ^98
regex ^983


Si te fijas no es ni al revés ni al derecho... sino... combinado. Leí algo de esto, pero no tengo claro cómo puedo hacer para obligar a leer las expresiones regulares en un orden determinado.

¡Una ayuda con este tema se agradecerá!

Saludos.
init_perl
Perlero nuevo
Perlero nuevo
 
Mensajes: 9
Registrado: 2008-08-21 16:36 @733

Notapor explorer » 2008-08-25 11:47 @532

Los hash no almacenan los valores en el orden en que se guardaron (primer párrafo de perldata). Si quieres ese efecto, tienes dos opciones (básicamente).

Una, usar un array en lugar de un hash.

Y dos, usar algún módulo que sí permite guardar las claves en el orden en que se crean. En CPAN hay unos cuantos, como por ejemplo Tie::Hash::Sorted.

Yo lo haría con un array... aunque estando en el foro Experto... usaría otra característica poderosa de Perl: la creación de código en tiempo de ejecución.
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 Avanzado

¿Quién está conectado?

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