Tome una suma basada en columnas particulares

Tome una suma basada en columnas particulares

Tengo una enorme cantidad de datos: entre 30 y 40 millones de elementos de datos. Necesitamos procesar estos archivos y enviarlos a otro equipo de interfaz.

A continuación se muestra el formato de mi archivo que recibimos.

c1  c2  c3  c4  c5  c6
A   B   C   D   5   s
A   B   C   D   4   s
A   B   E   F   5   s
A   B   E   F   8   S
C   D   E   F   9   S

Necesito imprimir en mi archivo de salida todas las columnas. Dado que esto está relacionado con el uso de GPRS, debemos agrupar porc1-c4y luego, si todo coincide, debemos sumar losc5e imprima todo en el archivo de salida.

A continuación se muestra un archivo de salida de muestra.

c1  c2  c3  c4  c5  c6
A   B   C   D   9   s
A   B   E   F   13  s
C   D   E   F   9   s

También escuché que este flujo de trabajo funciona mucho más rápido en Perl que en los scripts de Unix.

Respuesta1

Otra perlsolución, similar a la respuesta de @terdon pero con mejor formato de salida:

$ perl -alne '
    (print && next) if $. == 1;   
    $h{"@F[0..3]"}{s} += $F[4];
    $h{"@F[0..3]"}{t}  = $F[5];
    END {
        for (keys %h) {
            printf "%-4s%-4s%-4s%-4s%-4s%-4s",split(" ",$_),$h{$_}{s},$h{$_}{t};                        
            printf "\n";
        }
    }' file
c1  c2  c3  c4  c5  c6
A   B   E   F   13  S   
A   B   C   D   9   s   
C   D   E   F   9   S

Respuesta2

En cuanto a la elección de herramientas: normalmente, cuanto más especializada es una herramienta, más rápida es. Entonces, las tuberías que involucran tr, cut, grep, sort, etc. tienden a ser más rápidas que sedlas que tienden a ser más rápidas que awklas que tienden a ser más rápidas que perl, python, ruby. Pero eso, por supuesto, también depende mucho de la tarea. Si lee que Perl es más rápido, entonces o leyó mal o la comparación fue con un bucle de shell que procesa una línea a la vez (eso definitivamente será lento para archivos con millones de líneas).

Si su entrada está en un formulario en el que las líneas a fusionar son consecutivas, awk es una buena apuesta (no existe una forma sensata de realizar adiciones en sed).

awk -v OFS='\t' '                      # use tabs to separate output fields
    NR==1 {print; next}                # keep the first line intact
    function flush () {                # function to print a completed sum
        if (key != "") print previous, sum, more;
        sum=0
    }
    {key = $1 OFS $2 OFS $3 OFS $4}    # break out the comparison key
    key!=previous {flush()}            # if the comparison key has changed, print the accumulated sum
    {previous=key; sum+=$5; more=$6}   # save the current line
    END {flush()}                      # print the last 
'

Si las líneas no son consecutivas, puedes hacerlo ordenándolas. Las implementaciones típicas sortestán altamente optimizadas y son más rápidas que la manipulación de estructuras de datos en lenguajes de alto nivel.

sort | awk …

Esto supone que los delimitadores de sus columnas son consistentes, por ejemplo, siempre una pestaña. Si no es así, preprocese la entrada para que así sea o utilícela sort -k1,1 -k2,2 -k3,3 -k4,4para comparar estos campos específicos sin tener en cuenta los delimitadores.

Respuesta3

Esto podría ayudarte a comenzar:

perl -ane '$h{"@F[0 .. 3]"} += $F[4] }{ print "$_ $h{$_}\n" for keys %h' input-file

No imprime la última columna porque no especificó qué hacer con ella. Además, no maneja correctamente la línea del encabezado, pero debería ser fácil de solucionar.

Respuesta4

Si te entiendo correctamente, quieres algo como esto:

$ perl -lane 'if($.>1){$k{"@F[0..3]"}{sum}+=$F[4]; $k{"@F[0..3]"}{last}=$F[5]}
              else{print "@F"}
              END{
                foreach (keys(%k)){ print "$_ $k{$_}{sum} $k{$_}{last}"}
              }' file
c1 c2 c3 c4 c5 c6
C D E F 9 S
A B E F 13 S
A B C D 9 s

Esto no mantendrá sus columnas alineadas. No sé si esto es un problema para usted. Sin embargo, tratará el encabezado correctamente y debería producir el resultado que necesita.

Explicación

  • perl -lane: -lElimina nuevas líneas del final de cada cadena y las agrega a cada printdeclaración. Divide acada línea de entrada en campos en espacios en blanco y guarda los campos en la matriz @F. Los nsignificadoslea el archivo de entrada línea por línea y aplique el script proporcionado por-e.

Aquí está la misma frase en forma de guión comentado:

#!/usr/bin/env perl

## This is the equivalent of perl -ne
## in the one-liner. It iterates through
## the input file.
while (<>) {
    
    ## This is what the -a flag does
    my @F=split(/\s+/);
    ## $. is the current line number.
    ## This simply tests whether we are on the
    ## first line or not.
    if ($.>1) {
    ## @F[0..3] is an array slice. It holds fields 1 through 4.
    ## The slice is used as a key for the hash %k and the 5th
    ## field is summed to $k{slice}{sum} while the last column is 
    ## saved as $k{slice}{last}.
    $k{"@F[0..3]"}{sum}+=$F[4]; $k{"@F[0..3]"}{last}=$F[5];
    }

    ## If this is the first line, print the fields.
    ## I am using print "@F" instead of a simple print 
    ## so that all lines are formatted in the same way.
    else {
    print "@F\n";
    }
}

## This is the same as the END{} block
## in the one liner. It will be run after
## the whole file has been read.

## For each of the keys of the hash %k
foreach (keys(%k)){ 
    ## Print the key ($_, a special variable in Perl),
    ## the value of $k{$key}{sum} (the summed values),
    ## and the last column.
    print "$_ $k{$_}{sum} $k{$_}{last}\n"
}


    

información relacionada