Somando a cada 5 linhas de inteiros

1

Estou escrevendo um analisador e tenho que fazer algumas coisas extravagantes. Eu estou tentando não usar python, mas eu posso ter que neste momento.

Dado um STDOUT que se parece com isto:

1
0
2
3
0
0
1
0
0
2
0
3
0
4
0
5
0
2
.
.
.

Por 100.000 linhas. O que eu preciso fazer é somar a cada 5, assim:

1 - start
0 |
2 | - 6 
3 |
0 - end
0 - start
1 |
0 | - 3
0 |
2 - end
0 - start
3 |
0 | - 7
4 |
0 - end
5 
0 
2 
. 
.
.

Os - , | , start , end são todos para representação visual, só preciso disso em uma lista de colunas:

 6
 3
 7
 .
 .
 .

Atualmente, tenho um método de fazer isso usando% incrementalhead -n $i e tail -n 5 para cortar 5 linhas fora da lista, então eu uso paste -sd+ - | bc para somar todos os valores. Mas isso é muito lento porque há 100.000 linhas.

Como posso fazer isso melhor?

    
por Tsangares 22.06.2017 / 02:26

6 respostas

2
cat numbers.txt | awk '{sum += $1; if (NR % 5 == 0) {print sum; sum=0}} END {if (NR % 5 != 0) print sum}'

sum começa como 0 no awk. Cada quinta linha imprime a soma atual dos números, redefine a soma para zero e passa para as próximas cinco linhas. O END no final manipula o caso de borda do número de linhas no arquivo não sendo um múltiplo de cinco, por exemplo, se houver 18 linhas no arquivo, ele imprimirá a soma das últimas 3 linhas. Ele também lida com o caso de borda de não imprimir um zero indesejado quando o número de linhas é um múltiplo de cinco.

    
por 22.06.2017 / 03:14
2

e um loop de shell (sem verificação de erros; assume um múltiplo par de 5 números):

while read a; do read b; read c; read d; read e; echo $((a+b+c+d+e)); done < input
    
por 22.06.2017 / 03:25
2
sed '$!N;$!N;$!N;$!N;s/\n/+/g' list.txt | bc

Demonstração:

$ seq 33 | sed '$!N;$!N;$!N;$!N;s/\n/+/g'
1+2+3+4+5
6+7+8+9+10
11+12+13+14+15
16+17+18+19+20
21+22+23+24+25
26+27+28+29+30
31+32+33
$ seq 33 | sed '$!N;$!N;$!N;$!N;s/\n/+/g' | bc
15
40
65
90
115
140
96
$ 

Esta abordagem tem várias vantagens:

  1. Simples
  2. Rápido
  3. Separa de forma clara o agrupamento de números da matemática
  4. Lida corretamente com casos de 0 linhas, 1 linha, 5n linhas e não várias de 5 linhas

Se você quiser descartar extras (por exemplo, descartar 31 a 33 no exemplo acima), você poderá eliminar todas as condições $! ; este é o comando mais simples e foi minha primeira tentativa:

$ seq 33 | sed 'N;N;N;N;s/\n/+/g'
1+2+3+4+5
6+7+8+9+10
11+12+13+14+15
16+17+18+19+20
21+22+23+24+25
26+27+28+29+30
$ 
    
por 22.06.2017 / 02:50
2

Se seus dados tiverem um múltiplo exato de 5 linhas, você poderá modificar o comando head / tail shell loop + paste para usar paste sozinho, por exemplo.

yourcommand | paste -d+ - - - - | bc

Uma opção melhor pode ser usar o pr paginator para organizar a saída em 5 colunas separadas por +

yourcommand | pr -ats+ -5 | bc
  • -a imprime colunas em vez de para baixo
  • -t omitir cabeçalhos e rodapés de página
  • -s+ define o separador da coluna como + no lugar da guia padrão

Ao contrário de paste , pr faz a coisa certa no caso de linhas incompletas, por exemplo usando cat STDOUT para simular sua saída de comando

$ cat STDOUT | pr -ats+ -5
1+0+2+3+0
0+1+0+0+2
0+3+0+4+0
5+0+2

de onde

$ cat STDOUT | pr -ats+ -5 | bc
6
3
7
7

O comando rs parece outro bom candidato, mas o óbvio rs -C+ 0 5 parece querer adicionar delimitadores finais.

    
por 22.06.2017 / 05:23
1

em primeiro lugar porque isso só exige xargs:

<file xargs -rn5 perl -e 'foreach(@ARGV){$a+$_};print $a,"\n"'

mas para velocidade você realmente quer minimizar o número de invocações de intérpretes, então eu usaria

perl -e 'while(!eof(ARGV){print <>+<>+<>+<>+<>,"\n"}' file
    
por 22.06.2017 / 03:07
1

Você pode converter a lista de valores em linhas com 5 valores com column .
Assumindo guias são 8 espaços (e valores são menores que isso):

$ column -c 40 -x infile
1       2       3       4       5
6       7       8       9       10
1       0       2       3       0
0       1       0       0       2
0       3       0       4       0
5       0       2

E traduza cada guia para um + para alimentar bc:

$ column -c 40 -x infile | tr '\t' '+'
1+2+3+4+5
6+7+8+9+10
1+0+2+3+0
0+1+0+0+2
0+3+0+4+0
5+0+2

$ column -c 40 -x infile | tr '\t' '+' | bc
15
40
6
3
7
7

Talvez o awk seja melhor:

$ column -c 40 -x infile | awk '{print($1+$2+$3+$4+$5)}'
15
40
6
3
7
7

Essa é apenas uma chamada para cada programa, ambos os programas serão iniciados ao mesmo tempo devido ao pipe e funcionarão em paralelo.

O tempo me dá (em uma CPU muito lenta) apenas 0.350 segundos para um arquivo de 100.000 linhas.

E apenas 0,148 segundo se o resultado for para um arquivo (não para a tela).

Sim, isso lida com linhas incompletas, um arquivo que termina em 1, 2, 3 ou 4 números. Isso funcionará corretamente se os números forem menores que 1.000.000.

Números maiores (até uma soma de (2 ^ 62) -1 9223372036854775807 ~ 9e18 em um sistema de 64 bits) precisarão de algo como:

$ xargs -n 5 -a infile| awk '{print($1+$2+$3+$4+$5)}'

Mas isso é bem mais lento (38,8 segundos), mudando para o awk puro

$ awk '{s+=$0};NR%5{next};!(NR%5){print(s);s=0}END{if(s){print(s)}}' infile

Ou, se você precisar de um pipe, use:

$ cat infile | awk '{s+=$0};NR%5{next};!(NR%5){print(s);s=0}END{if(s){print(s)}}'

Leva cerca de 0,150 segundos, o que parece rápido o suficiente para mim.

    
por 22.06.2017 / 04:51