Eu tenho um arquivo de texto com 2 milhões de linhas. Cada linha tem um número inteiro positivo. Estou tentando formar um tipo de tabela de frequências.
Arquivo de entrada:
3
4
5
8
A saída deve ser:
3
7
12
20
Como faço para fazer isso?
Eu tenho um arquivo de texto com 2 milhões de linhas. Cada linha tem um número inteiro positivo. Estou tentando formar um tipo de tabela de frequências.
Arquivo de entrada:
3
4
5
8
A saída deve ser:
3
7
12
20
Como faço para fazer isso?
Respostas:
Com awk
:
awk '{total += $0; $0 = total}1'
$0
é a linha atual. Portanto, para cada linha, eu adiciono ao total
, defino a linha como nova total
e, em seguida, o final 1
é um atalho inativo - ele imprime a linha atual para todas as condições verdadeiras e 1
como uma condição é avaliada como verdadeira.
print
também pode ser usada?
print total}
vez de$0 = total}1
{print(total += $0)}
Em um script python:
#!/usr/bin/env python3
import sys
f = sys.argv[1]; out = sys.argv[2]
n = 0
with open(out, "wt") as wr:
with open(f) as read:
for l in read:
n = n + int(l); wr.write(str(n)+"\n")
add_last.py
Execute-o com o arquivo de origem e o arquivo de saída direcionado como argumentos:
python3 /path/to/add_last.py <input_file> <output_file>
O código é bastante legível, mas em detalhes:
Abrir arquivo de saída para gravar resultados
with open(out, "wt") as wr:
Arquivo de entrada aberto para leitura por linha
with open(f) as read:
for l in read:
Leia as linhas, adicionando o valor da nova linha ao total:
n = n + int(l)
Escreva o resultado no arquivo de saída:
wr.write(str(n)+"\n")
Apenas por diversão
$ sed 'a+p' file | dc -e0 -
3
7
12
20
Isso funciona de forma pendente +p
para cada linha da entrada e depois passa o resultado para a dc
calculadora onde
+ Pops two values off the stack, adds them, and pushes the result.
The precision of the result is determined only by the values of
the arguments, and is enough to be exact.
então
p Prints the value on the top of the stack, without altering the
stack. A newline is printed after the value.
O -e0
argumento entra 0
na dc
pilha para inicializar a soma.
real 0m4.234s
No Bash:
#! /bin/bash
file="YOUR_FILE.txt"
TOTAL=0
while IFS= read -r line
do
TOTAL=$(( TOTAL + line ))
echo $TOTAL
done <"$file"
real 0m53.116s
, quase um minuto, em 1,3 milhões de linhas :)
Para imprimir somas parciais de números inteiros fornecidas na entrada padrão, uma por linha:
#!/usr/bin/env python3
import sys
partial_sum = 0
for n in map(int, sys.stdin):
partial_sum += n
print(partial_sum)
Se por algum motivo o comando for muito lento; você poderia usar o programa C:
#include <stdint.h>
#include <ctype.h>
#include <stdio.h>
int main(void)
{
uintmax_t cumsum = 0, n = 0;
for (int c = EOF; (c = getchar()) != EOF; ) {
if (isdigit(c))
n = n * 10 + (c - '0');
else if (n) { // complete number
cumsum += n;
printf("%ju\n", cumsum);
n = 0;
}
}
if (n)
printf("%ju\n", cumsum + n);
return feof(stdin) ? 0 : 1;
}
Para criar e executar, digite:
$ cc cumsum.c -o cumsum
$ ./cumsum < input > output
UINTMAX_MAX
é 18446744073709551615
.
O código C é várias vezes mais rápido que o comando awk na minha máquina para o arquivo de entrada gerado por:
#!/usr/bin/env python3
import numpy.random
print(*numpy.random.random_integers(100, size=2000000), sep='\n')
accumulate()
itertool
Você provavelmente quer algo assim:
sort -n <filename> | uniq -c | awk 'BEGIN{print "Number\tFrequency"}{print $2"\t"$1}'
Explicação do comando:
sort -n <filename> | uniq -c
classifica a entrada e retorna uma tabela de frequência| awk 'BEGIN{print "Number\tFrequency"}{print $2"\t"$1}'
transforma a saída em um formato melhorExemplo:
Arquivo de Entrada list.txt
:
4
5
3
4
4
2
3
4
5
O comando:
$ sort -n list.txt | uniq -c | awk 'BEGIN{print "Number\tFrequency"}{print $2"\t"$1}'
Number Frequency
2 1
3 2
4 4
5 2
Você pode fazer isso no vim. Abra o arquivo e digite as seguintes teclas:
qaqqayiwj@"<C-a>@aq@a:wq<cr>
Observe que <C-a>
na verdade é ctrl-a e <cr>
é retorno de carro , ou seja, o botão Enter.
Veja como isso funciona. Primeiro, queremos limpar o registro 'a' para que não tenha efeitos colaterais na primeira vez. Isto é simplesmente qaq
. Em seguida, fazemos o seguinte:
qa " Start recording keystrokes into register 'a'
yiw " Yank this current number
j " Move down one line. This will break the loop on the last line
@" " Run the number we yanked as if it was typed, and then
<C-a> " increment the number under the cursor *n* times
@a " Call macro 'a'. While recording this will do nothing
q " Stop recording
@a " Call macro 'a', which will call itself creating a loop
Depois que a macro recursiva termina, chamamos simplesmente :wq<cr>
para salvar e sair.
One-liner Perl:
$ perl -lne 'print $sum+=$_' input.txt
3
7
12
20
Com 2,5 milhões de linhas de números, leva cerca de 6,6 segundos para processar:
$ time perl -lne 'print $sum+=$_' large_input.txt > output.txt
0m06.64s real 0m05.42s user 0m00.09s system
$ wc -l large_input.txt
2500000 large_input.txt
real 0m0.908s
, até legal.
Uma linha simples do Bash:
x=0 ; while read n ; do x=$((x+n)) ; echo $x ; done < INPUT_FILE
x
é a soma acumulada de todos os números da linha atual e acima.
n
é o número na linha atual.
Passamos por todas as linhas n
de INPUT_FILE
e adicionamos seu valor numérico à nossa variável x
e imprimimos essa soma durante cada iteração.
O Bash é um pouco lento aqui, porém, você pode esperar que isso seja executado em torno de 20 a 30 segundos para um arquivo com 2 milhões de entradas, sem imprimir a saída no console (que é ainda mais lenta, independentemente do método usado).
Semelhante à resposta do @ steeldriver, mas com o pouco menos misterioso bc
:
sed 's/.*/a+=&;a/' input | bc
O bom de bc
(e dc
) é que elas são calculadoras de precisão arbitrárias, portanto nunca transbordarão ou sofrerão falta de precisão sobre números inteiros.
A sed
expressão transforma a entrada em:
a+=3;a
a+=4;a
a+=5;a
a+=8;a
Isso é então avaliado por bc
. A a
variável bc é inicializada automaticamente em 0. Cada linha é incrementada a
e a imprime explicitamente.
real 0m5.642s
em 1,3 milhões de linhas. sed é realmente lento nisso.