Perl en Español

  1. Home
  2. Tutoriales
  3. Foro
  4. Artículos
  5. Donativos
  6. Publicidad
 
Índice general » Mundo Perl » Básico » Optimizar una búsqueda de datos repetidos  RESUELTO Responder al tema
Nuevo tema


Página 1 de 1  [ 5 mensajes ] 
 
Nota 2012-01-13 21:26 @935

Perlero Nuevo
Registrado: 2012-01-10 22:34 @982
Mensajes: 23
Optimizar una búsqueda de datos repetidos
¿Qué tal, Perl en español? Acudo a Uds. con una duda de optimización de un proceso.

Creé una aplicación usando el módulo SpreadSheet::ParseExcel que me permite guardar en .txt los datos desde una hoja de Excel y también guardar en variables los parámetros que me servirán para realizar cálculos y finalmente generar un reporte.

A continuación les pongo una muestra de los datos, solo son cuatro columnas: la 0, 1, 2 y 3; aunque en la aplicación real son once columnas con las que trabajo.

Las columnas importantes son la 1 y la 2, o sea, la de la letra i y la de las fechas con horas.

Syntax: [ Download ] [ Hide ]
Using text Syntax Highlighting
1       i       01/09/2011 00:00:00     2011-Sep-01 06:00:00.000
2       i       01/09/2011 00:15:00     2011-Sep-01 06:15:00.000
3       i       01/09/2011 00:30:00     2011-Sep-01 06:30:00.000
4       i       01/09/2011 00:45:00     2011-Sep-01 06:45:00.000
5       i       01/09/2011 01:00:00     2011-Sep-01 07:00:00.000
6       i       01/09/2011 01:15:00     2011-Sep-01 07:15:00.000
7       i       01/09/2011 01:30:00     2011-Sep-01 07:30:00.000
8       i       01/09/2011 01:45:00     2011-Sep-01 07:45:00.000
9       i       01/09/2011 02:00:00     2011-Sep-01 08:00:00.000
10      i       01/09/2011 02:15:00     2011-Sep-01 08:15:00.000


Este archivo lo transformé de .xls a .txt y luego lo moví a un arreglo llamado @vector.

Bueno, la primera cuestión que quiero comentarles es que necesito buscar en el archivo completo si las fechas están en intervalos de 15 minutos. Si es así, dejar la columna 1 con la letra i y si no cambiar la i por una n.

Yo solucioné lo anterior con un ciclo for() que recorre el arreglo completo haciendo que la línea "actual" compare su fecha y hora con la fecha y hora de la línea "siguiente" y si es mayor de 15 minutos el intervalo pues que coloque una n en vez de la i en la línea "siguiente".

Además ingresará al vector @rango el número de linea que está fuera de rango y aumentará un contador de datos de fuera de rango "$contrango" en +1.

Aquí muestro mi código.

Syntax: [ Download ] [ Hide ]
Using perl Syntax Highlighting
  1. #ANÁLISIS DE FECHA Y HORA
  2.  
  3. use strict;
  4. use Date::Calc qw(:all);
  5.  
  6. my $numero = scalar(@vector);
  7. for ( my $x = 0; $x < $numero - 1; $x++ ) {
  8.     my $linea    = $vector[$x];
  9.     my @palabras = split( "\t", $linea );
  10.     my $tiempo   = $palabras[2];
  11.     my $dia1     = substr( $tiempo, 0, 2 );
  12.     my $mes1     = substr( $tiempo, 3, 2 );
  13.     my $anio1    = substr( $tiempo, 6, 4 );
  14.     my $hora1    = substr( $tiempo, 11, 2 );
  15.     my $min1     = substr( $tiempo, 14, 2 );
  16.     my $seg1     = substr( $tiempo, 17, 2 );
  17.  
  18.     $linea = $vector[ $x + 1 ];
  19.     my @palabrass = split( "\t", $linea );
  20.     $tiempo = $palabrass[2];
  21.     my $dia2  = substr( $tiempo, 0,  2 );
  22.     my $mes2  = substr( $tiempo, 3,  2 );
  23.     my $anio2 = substr( $tiempo, 6,  4 );
  24.     my $hora2 = substr( $tiempo, 11, 2 );
  25.     my $min2  = substr( $tiempo, 14, 2 );
  26.     my $seg2  = substr( $tiempo, 17, 2 );
  27.  
  28.     # cálculo del intervalo entre las dos fechas
  29.  
  30.     my ( $Dd, $Dh, $Dm, $Ds )
  31.         = Delta_DHMS( $anio1, $mes1, $dia1, $hora1, $min1, $seg1, $anio2, $mes2, $dia2, $hora2, $min2, $seg2 );
  32.  
  33.     if ( $palabrass[1] eq "i" ) {
  34.         if ( $Dm == 15 and $Dd == 0 and $Dh == 0 and $Ds == 0 ) {
  35.             $palabrass[1] = "i";
  36.         }
  37.         else {
  38.             $contrango++;              #contador de fuera de rango
  39.             $palabrass[1] = "n";
  40.             push( @rango, $palabrass[0] . "\n" );
  41.         }
  42.         $linea = join( "\t", @palabrass );
  43.         $vector[ $x + 1 ] = $linea;
  44.     }
  45. }
  46.  
  47.  


La segunda parte de la validación se compone de un proceso que se encarga de comprobar si hay fechas idénticas.

Esto lo solucioné con la ayuda de un for() "anidado" que se encarga de comparar la fecha-hora de la línea "actual" con TODAS las fechas-horas de las líneas que le siguen y si encuentra alguna igual pues que le colocará * en vez de la i a la línea actual y a la línea que está igual y también colocará una n de falla de rango a la línea que está después de la línea que es copia de la "actual".

También aumentará los contadores de repetidos "$contbis" y de rango "$contrango" e ingresará el número de registro de los datos repetidos al vector @repetidos y el número de registro del dato fuera de rango a @rango.

A continuación mi código.

Syntax: [ Download ] [ Hide ]
Using perl Syntax Highlighting
  1. #ANÁLISIS DE REPETIDOS
  2. my $numero = scalar(@vector);
  3. for ( my $x = 0; $x < $numero - 1; $x++ ) {
  4.     my @variables = split( "\t", $vector[$x] );
  5.     my $fechaactual = $variables[2];
  6.  
  7.     for ( my $y = $x + 1; $y < $numero; $y++ ) {
  8.         my @variabless = split( "\t", $vector[$y] );
  9.         my $fecha = $variabless[2];
  10.  
  11.         if ( $fechaactual eq $fecha ) {
  12.             if ( $variables[1] eq "*" and $variabless[1] eq "*" ) {
  13.                 next;
  14.             }
  15.             else {
  16.                 $variables[1] = "*";
  17.                 $vector[$x] = join( "\t", @variables );
  18.                 $variabless[1] = "*";
  19.                 $contbis++;
  20.                 $contbis++;
  21.                 $vector[$y] = join( "\t", @variabless );
  22.                 my $temp = $variables[0] . " con el " . $variabless[0] . "\n";
  23.                 push( @repetidos, $temp );
  24.                 my @variablesss = split( "\t", $vector[ $y + 1 ] );
  25.  
  26.                 if ( $variablesss[1] ne "*" ) {
  27.                     $variablesss[1] = "n";
  28.                     $contrango++;
  29.                     $vector[ $y + 1 ] = join( "\t", @variablesss );
  30.                     push( @rango, $variablesss[0] . "\n" );
  31.                 }
  32.             }
  33.         }
  34.         else {
  35.             next;
  36.         }
  37.     }
  38.  
  39.     # i dato valido.
  40.     # * dato duplicado.
  41.     # n dato invalidado por rango de tiempo.
  42. }
  43. open( ARCHIVO, '>validado.txt' ) or die "no se encuentra\n";
  44. print ARCHIVO @vector;
  45. close ARCHIVO;
  46.  

Realizado lo anterior ya estamos listos para analizar los datos para crear el reporte :D

Quiero comentarles que mi programa funciona perfectamente pero los archivos que debo analizar se componen de un mínimo de 2400 registros.

Al realizar el análisis completo de los archivos por completo pero en "partes" (primero transformación xls, luego chequeo de rangos, luego chequeo de repetidos, luego análisis de datos, luego reporte) me di cuenta que la rutina que más tiempo lleva es la rutina de chequeo de repetidos(): casi 12 minutos para un archivo de 2880 registros lo cual es demasiado para un registro tan "pequeño" si lo comparamos con los registros que se acostumbran analizar en el lugar en el que estoy desarrollando la aplicación (800000 en un mal día).

Mi pregunta es si alguien me puede orientar hacia la forma de optimizar la rutina de chequeo de repetidos.

Gracias por adelantado :wink:


Última edición por explorer el 2012-01-14 07:42 @362, editado 1 vez en total
Formateado de código con Perltidy


Nota 2012-01-14 11:05 @503
Avatar de Usuario
Administrador
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España
Mensajes: 10268
Re: Optimizar una búsqueda de datos repetidos
Si te das cuenta, en el primer código, puedes meter las líneas 20 a 31 detrás del if() de la 33...

Se ahorraría mucho tiempo si eliminaras todas las operaciones split() y join(). Para ello, solo tienes que transformar la estructura de líneas a una estructura bidimensional.

Por ejemplo:
Syntax: [ Download ] [ Hide ]
Using perl Syntax Highlighting
@vector = map { [ split /\t/, $_ ] } @vector;

A partir de esa línea, @vector ya es una estructura bidimensional. Cada elemento es una referencia a otro arreglo, que contiene todas las columnas ya separadas. Para acceder a la columna 2 de la línea $i, por ejemplo, sería así:
Syntax: [ Download ] [ Hide ]
Using perl Syntax Highlighting
print "$vector[$i]->[2]\n";
o, de forma abreviada:
Syntax: [ Download ] [ Hide ]
Using perl Syntax Highlighting
print "$vector[$i][2]\n";

Al final del proceso, tienes que hacer el proceso inverso:
Syntax: [ Download ] [ Hide ]
Using perl Syntax Highlighting
@vector = map { join "\t", @{$_} } @vector;


Aún más... podríamos crear una estructura bidimensional con solo la información que nos interesa:
Syntax: [ Download ] [ Hide ]
Using perl Syntax Highlighting
@vector = map {
    my @campos = split /\t/, $_;
    my @fechas = split /\D/, $campos[2];
    [ @fechas, @campos ]
} @vector;

use Data::Dumper::Simple;
warn Dumper @vector;
Estas líneas sacan una estructura como esta:
Syntax: [ Download ] [ Hide ]
Using text Syntax Highlighting
...
            [
              '01',
              '09',
              '2011',
              '02',
              '15',
              '00',
              '10',
              'i',
              '01/09/2011 02:15:00',
              '2011-Sep-01 08:15:00.000'
            ]
...
Como ves, ya tienes todos los elementos que necesitas para hacer las búsquedas y comparaciones. Los primeros seis campos tienen los componentes de la fecha. El octavo es la letra, que cambiaremos o no. Y para rehacer la línea original, solo tenemos que unir los campos siete a diez.

Esto te servirá para el primer caso, el de detectar que los tiempos están cada cuarto de hora. Para el segundo caso, puedes aprovechar la estructura ya creada, porque la fecha está en el campo nueve. Y para evitar el doble bucle, puedes usar la técnica de los hash: metes las fechas en un hash, como clave, a medida de que las vas leyendo, y como valor, guardas el número de línea en donde está, en forma de arreglo (push()). Así, por cada fecha, guardas todas las líneas en las que aparece esa fecha.

Solo un bucle más, donde recorremos el hash. Si, para una fecha, se repite más de una vez (su arreglo tiene más de un valor), actualizamos el valor de las letras y asteriscos de esos números de línea (ponemos asterisco en todos los valores, y 'n' en la línea siguiente, salvo que ya sea un asterisco).

Ya solo queda rehacer las líneas originales, sacándolas de la estructura bidimensional.

_________________
JF^D Perl programming


Nota 2012-01-17 07:35 @357

Perlero Nuevo
Registrado: 2012-01-10 22:34 @982
Mensajes: 23
Re: Optimizar una búsqueda de datos repetidos
Realicé las operaciones y todo va muy bien, solo tengo duda con la utilización de

explorer escribió:
Syntax: [ Download ] [ Hide ]
Using perl Syntax Highlighting
use Data::Dumper::Simple;
warn Dumper @vector;


¿Cuál es el objeto de usar este par de líneas?


explorer escribió:
puedes aprovechar la estructura ya creada, porque la fecha está en el campo nueve. Y para evitar el doble bucle, puedes usar la técnica de los hash: metes las fechas en un hash, como clave, a medida de que las vas leyendo, y como valor, guardas el número de línea en donde está, en forma de arreglo (push()). Así, por cada fecha, guardas todas las líneas en las que aparece esa fecha.


Me perdí en lo del push(), no sé si estoy en lo correcto pero ¿¿lo que me sugieres es que haga un Hash de Arrays?? Donde cada fecha sea una llave y cada "llave/fecha" se relacione con un arreglo al cual le "pusheare" los números de línea en que aparece dicha "fecha/llave".

¿Si son 2880 registros sería un hash de 2880 llaves hacia 2880 arreglos?

Si estoy en lo correcto... ¿una ayuda con la sintaxis del push() para el hash de arrays y para contar los elementos en cada array del hash?

Y si no te entendí bien, por favor ¿una ayuda con la sintaxis correcta?

Gracias por adelantado.


Nota 2012-01-17 11:51 @535
Avatar de Usuario
Administrador
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España
Mensajes: 10268
Re: Optimizar una búsqueda de datos repetidos  RESUELTO
MARKO escribió:
Realicé las operaciones y todo va muy bien, solo tengo duda con la utilización de
explorer escribió:
Syntax: [ Download ] [ Hide ]
Using perl Syntax Highlighting
use Data::Dumper::Simple;
warn Dumper @vector;
¿Cuál es el objeto de usar este par de líneas?
La de sacar en pantalla (más bien, en la salida de error estándar) la estructura del array @vector.

MARKO escribió:
explorer escribió:
puedes aprovechar la estructura ya creada, porque la fecha está en el campo nueve. Y para evitar el doble bucle, puedes usar la técnica de los hash: metes las fechas en un hash, como clave, a medida de que las vas leyendo, y como valor, guardas el número de línea en donde está, en forma de arreglo (push()). Así, por cada fecha, guardas todas las líneas en las que aparece esa fecha.
Me perdí en lo del push(), no sé si estoy en lo correcto pero ¿¿lo que me sugieres es que haga un Hash de Arrays?? Donde cada fecha sea una llave y cada "llave/fecha" se relacione con un arreglo al cual le "pusheare" los números de línea en que aparece dicha "fecha/llave".
Eso es. Guardar, para cada fecha, en qué líneas aparece esa fecha.

MARKO escribió:
¿Si son 2880 registros sería un hash de 2880 llaves hacia 2880 arreglos?
2880, o menos, para el caso de que existan fechas repetidas.

MARKO escribió:
Si estoy en lo correcto... ¿una ayuda con la sintaxis del push() para el hash de arrays y para contar los elementos en cada array del hash?
Sería algo así. Supongamos que guardamos en el hash %fechas las fechas que estamos viendo. Suponemos también que la fecha de la línea $x la tenemos en $campo[8]. Entonces hacemos un bucle por todo el fichero y hacemos:
Syntax: [ Download ] [ Hide ]
Using perl Syntax Highlighting
push @{ $fechas{ $campo[8] } }, $x;

De esa manera, vamos creando y aumentando un array, apuntado por el elemento $fechas{$campo[8]}, en donde estarán los números de línea donde aparece esa fecha.

MARKO escribió:
Y si no te entendí bien, por favor ¿una ayuda con la sintaxis correcta?
Luego, hacemos un bucle por todas las fechas encontradas, y actuamos según el número de líneas en las que aparece (no probado):
Syntax: [ Download ] [ Hide ]
Using perl Syntax Highlighting
  1. for my $fecha (keys %fechas) {
  2.     next if @{$fechas{$fecha}} == 1;               # si solo hay una línea, es línea normal, saltamos a la siguiente
  3.  
  4.     for my $linea_mala ( @{$fechas{$fecha}} ) {    # si no es así, recorremos todas las líneas con fecha repetida
  5.         $vector[$linea_mala][7] = '*';             # y las marcamos como malas
  6.     }
  7.  
  8.     my $linea_fuera_rango = $fechas{$fecha}[-1];   # último número de línea con fechas que se repiten
  9.  
  10.     $vector[$linea_fuera_rango][7] == 'n'          # la marcamos como fuera de rango
  11.         if $vector[$linea_fuera_rango][7] ne '*';  # salvo que ya esté marcada como apestada

_________________
JF^D Perl programming


Nota 2012-01-21 00:36 @066

Perlero Nuevo
Registrado: 2012-01-10 22:34 @982
Mensajes: 23
Re: Optimizar una búsqueda de datos repetidos
Buenísimo, explorer. Te cuento que ahora la validación de mis datos se hace en menos de un segundo :D

Ahora el proceso completo de cada archivo se realiza en 1 minuto 45 segundos :D (ahora es la conversión la que se tarda más)

Te agradezco la ayuda.


Responder al tema  [ 5 mensajes ] 

Reglas del Foro
No puedes abrir nuevos temas en este Foro
No puedes responder a temas en este Foro
No puedes editar tus mensajes en este Foro
No puedes borrar tus mensajes en este Foro
No puedes enviar adjuntos en este Foro

Publicidad

Socializa

Síguenos por Twitter

Suscríbete GRATUITAMENTE al Boletín de Perl en Español

Saltar a:  
cron
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
Traducción al español por Huan Manwë para phpbb-es.com
phpBB SEO