• Publicidad

Optimizar una búsqueda de datos repetidos

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

Optimizar una búsqueda de datos repetidos

Notapor MARKO » 2012-01-13 21:26 @935

¿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.

Sintáxis: [ Descargar ] [ Ocultar ]
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
Coloreado en 0.000 segundos, usando GeSHi 1.0.8.4


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.

Sintáxis: [ Descargar ] [ Ocultar ]
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.  
Coloreado en 0.004 segundos, usando GeSHi 1.0.8.4


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.

Sintáxis: [ Descargar ] [ Ocultar ]
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.  
Coloreado en 0.014 segundos, usando GeSHi 1.0.8.4

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
Razón: Formateado de código con Perltidy
MARKO
Perlero nuevo
Perlero nuevo
 
Mensajes: 86
Registrado: 2012-01-10 22:34 @982

Publicidad

Re: Optimizar una búsqueda de datos repetidos

Notapor explorer » 2012-01-14 11:05 @503

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:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
@vector = map { [ split /\t/, $_ ] } @vector;
Coloreado en 0.002 segundos, usando GeSHi 1.0.8.4

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í:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
print "$vector[$i]->[2]\n";
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
o, de forma abreviada:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
print "$vector[$i][2]\n";
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4

Al final del proceso, tienes que hacer el proceso inverso:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
@vector = map { join "\t", @{$_} } @vector;
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4


Aún más... podríamos crear una estructura bidimensional con solo la información que nos interesa:
Sintáxis: [ Descargar ] [ Ocultar ]
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;
Coloreado en 0.002 segundos, usando GeSHi 1.0.8.4
Estas líneas sacan una estructura como esta:
Sintáxis: [ Descargar ] [ Ocultar ]
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'
            ]
...
Coloreado en 0.000 segundos, usando GeSHi 1.0.8.4
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 & 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: Optimizar una búsqueda de datos repetidos

Notapor MARKO » 2012-01-17 07:35 @357

Realicé las operaciones y todo va muy bien, solo tengo duda con la utilización de

explorer escribiste:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
use Data::Dumper::Simple;
warn Dumper @vector;
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4


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


explorer escribiste: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.
MARKO
Perlero nuevo
Perlero nuevo
 
Mensajes: 86
Registrado: 2012-01-10 22:34 @982

Re: Optimizar una búsqueda de datos repetidos

Notapor explorer » 2012-01-17 11:51 @535

MARKO escribiste:Realicé las operaciones y todo va muy bien, solo tengo duda con la utilización de
explorer escribiste:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
use Data::Dumper::Simple;
warn Dumper @vector;
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
¿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 escribiste:
explorer escribiste: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 escribiste:¿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 escribiste: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:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
push @{ $fechas{ $campo[8] } }, $x;
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4

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 escribiste: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):
Sintáxis: [ Descargar ] [ Ocultar ]
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
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: Optimizar una búsqueda de datos repetidos

Notapor MARKO » 2012-01-21 00:36 @066

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.
MARKO
Perlero nuevo
Perlero nuevo
 
Mensajes: 86
Registrado: 2012-01-10 22:34 @982


Volver a Básico

¿Quién está conectado?

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

cron