Pegue uma soma baseada em colunas específicas

2

Eu tenho uma enorme pilha de dados - cerca de 30 a 40 milhões de itens de dados. Precisamos processar esses arquivos e enviá-los para outra equipe de interface.

Abaixo está o meu formato de arquivo que recebemos

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

Eu preciso imprimir no meu arquivo de saída todas as colunas. Como isso está relacionado ao uso do GPRS, precisamos agrupar por c1 - c4 e, se tudo estiver correspondendo, precisamos somar o c5 e imprimir tudo na saída arquivo.

Abaixo está um exemplo de arquivo de saída.

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

Eu não tenho muitos recursos do Unix ou do Perl na minha equipe ou equipe externa.

Por favor me ajude nisso. Também ouvi dizer que esse fluxo de trabalho funciona muito mais rápido no Perl do que no script Unix. Se houver outro método ou método melhor, por favor, sugira-o para mim.

    
por user2769015 07.05.2014 / 17:17

4 respostas

1

Outra solução perl , semelhante à resposta de @ terdon, mas com melhor saída de formato:

$ 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
    
por 07.05.2014 / 18:10
1

Em relação à escolha de ferramentas: normalmente, quanto mais especializada for uma ferramenta, mais rápido ela é. Portanto, os tubos que envolvem tr , cut , grep , sort , etc. tendem a ser mais rápidos que sed , que tende a ser mais rápido que awk , que tende a ser mais rápido que perl , python , ruby . Mas isso, claro, depende muito da tarefa também. Se você ler que Perl é mais rápido, então você interpretou mal ou a comparação foi contra um loop de shell que processa uma linha de cada vez (que definitivamente será lenta para arquivos com milhões de linhas).

Se a sua entrada estiver em um formato onde as linhas a serem mescladas sejam consecutivas, o awk é uma boa aposta (não existe uma maneira sã de executar adições no 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 
'

Se as linhas não forem consecutivas, você pode classificá-las dessa forma. Implementações típicas de sort são altamente otimizadas e mais rápidas do que manipular estruturas de dados em linguagens de alto nível.

sort | awk …

Isso pressupõe que os delimitadores de coluna sejam consistentes, por exemplo, sempre uma aba. Se não estiverem, pré-processe a entrada para torná-la assim ou use sort -k1,1 -k2,2 -k3,3 -k4,4 para comparar esses campos específicos sem considerar os delimitadores.

    
por 08.05.2014 / 02:09
0

Isso pode ajudar você a começar:

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

Não imprime a última coluna, pois você não especificou o que fazer com ela. Além disso, ele não manipula a linha de cabeçalho corretamente, mas deve ser fácil de corrigir.

    
por 07.05.2014 / 17:29
0

Se eu entendi corretamente, você quer algo como isto:

$ 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

Isso não manterá suas colunas alinhadas. Não sei se isso é um problema para você. No entanto, ele lidará com o cabeçalho corretamente e deverá produzir a saída de que você precisa.

Explicação

  • perl -lane : O -l remove as novas linhas do final de cada string e as adiciona a cada instrução print . O a divide cada linha de entrada em campos no espaço em branco e salva os campos na matriz @F . O n significa ler o arquivo de entrada linha a linha e aplicar o script fornecido por -e .

Aqui está o mesmo one-liner na forma de script comentada:

#!/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"
}
    
por 07.05.2014 / 17:45