Soma no bloco com AWK (reinicia a soma quando o padrão muda)

5

Eu tenho um arquivo como este:

A 100
A 200
A 300 #sum=600
B 400
B 500 #sum=900
A 600
A 700
A 800 #sum=2100

Eu gostaria que a saída fosse:

A 600
B 900
A 2100
C sum_of_C
D sum_of_D

Eu posso fazer isso com for , sed , grep e awk .

No entanto, como estou aprendendo awk , gostaria de escrever um script awk . Até agora eu tenho:

if (${NR {print $1}} == ${NR-1 {print $1}}) 
  sum+=$2
  print $0"\t"sum
else
  sum=$2
  print $0"\t"sum

awk -f awkscript file não foi bem-sucedido. Qual é a solução?

    
por Ooker 07.07.2014 / 11:38

2 respostas

6

Não tenho certeza absoluta do que seu if está tentando fazer lá. NR é o número de registros; use NF para o número de campos, se é isso que você está procurando. Você não pode colocar {} blocos no meio de coisas assim.

Acho que o que você está procurando é comparar o valor de um campo nesta linha com um campo na linha anterior, imprimindo a soma quando chegarmos a um novo "grupo" de dados. Se for esse o caso, este script fará o que você quer e eu acho que equivale ao que você queria:

{
    if (last && $1 != last) {
        print last, sum
        sum = 0
    }
    sum = sum + $2
    last = $1
}
END {
    print last, sum
}

Criamos uma nova variável last para manter o valor do primeiro campo ( $1 ) na linha anterior. Usaremos isso para rastrear qual grupo estamos analisando.

  • Para cada linha (porque temos { ... } no nível superior), primeiro testamos se a) last está definido (porque não queremos imprimir nada na primeira linha) eb) o valor do primeiro campo é diferente de last . Se estiver, imprimimos o valor de last , um espaço (por causa de , ) e o sum que calculamos. (Se você quiser uma aba, use "\t" entre aspas como você tinha)
  • Após a impressão, redefinimos sum para zero.
  • De qualquer forma, adicionamos o valor do segundo campo ( $2 ) a sum .
  • Para cada linha, salvamos o primeiro campo (nosso grupo) em last , para que possamos usá-lo para comparação na próxima linha.
  • Finalmente, queremos imprimir o último grupo também. Para isso, usamos um bloco END { ... } . Ele é executado no final do programa quando ficamos sem dados. Imprimimos a soma e o grupo com o qual estamos trabalhando, exatamente como fizemos antes.

Se eu correr:

awk -f sum.awk < data

com seu arquivo de dados, recebo esta saída:

A 600
B 900
A 2100

conforme desejado.

Existem maneiras mais simples de fazer isso, tanto no awk quanto no contrário. Em particular, podemos substituir o corpo acima por:

last && $1 != last {
    print last, sum
    sum = 0
}
{
    sum = sum + $2
    last = $1
}

Aqui usamos a sintaxe de bloqueio condicional do awk em vez de um teste if explícito: o comportamento deste programa é idêntico ao acima, mas é mais idiomático. Não é muito diferente neste exemplo, mas é útil saber se você está aprendendo o awk.

Se o exemplo de arquivo que você forneceu for literalmente o que é, com #sum= linhas (ou similar), você pode usar este script:

{
    sum = sum + $2
    if (NF == 3) {
        print $1, sum
        sum = 0
    }
}

Para cada linha, isso adiciona o valor do segundo campo à variável sum . Em linhas que têm exatamente três campos ( NF == 3 ), imprimimos nosso total e redefinimos sum para zero.

    
por 07.07.2014 / 12:06
2

Se o seu arquivo for pequeno o suficiente para que todas as somas caibam na memória, você pode fazer algo tão simples como:

$ awk '{sum[$1]+=$2}END{for(pat in sum){print pat,sum[pat]}}' file 
A 2700
B 900

Aqui está a mesma coisa que um script awk comentado:

#!/usr/bin/awk -f

{
    ## Here, we use $1 as the key of an associative array
    ## and increment its current value by $2. The result of 
    ## this will be an array element for each different $1 in 
    ## the file whose value will be the sum of all associated $2s.
    sum[$1]+=$2
}

## The END{} block is exacuted after the entire file
## has been processed.
END{
    ## Iterate through the keys of the array (the $1s),
    ## saving each as 'pat'. Then, print the current value of
    ## 'pat' as well as the associated value (the sum) from
    ## the array.
    for(pat in sum){
        print pat,sum[pat]
    }
}

O único problema possível com essa abordagem é se você tiver tantas linhas que manter a matriz de $1 s fará com que fique sem memória. Isso não é muito provável em um sistema moderno. Por outro lado, essa abordagem também funciona quando as linhas do seu arquivo não estão em ordem, pois ele pode lidar com arquivos não classificados.

    
por 07.07.2014 / 12:36

Tags