Como faço para gerar um total acumulado em execução de números em um arquivo de texto?

9

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?

Monty Harder
fonte
1
No seu texto, você diz que deseja uma tabela de frequências . Sua amostra de saída é uma lista. Você pode esclarecer isso?
Wayne_Yux 02/02
Na verdade a sua saída não é uma tabela de frequência
don.joey
Eu sinto Muito. Eu quis dizer uma tabela de frequência cumulativa. Modificou a pergunta. Obrigado.
Não é muito legal, mas geralmente faço coisas assim em uma planilha.
John U
@ JohnU Normalmente, mas o arquivo que tenho tem 1 milhão de números.

Respostas:

20

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 totale, em seguida, o final 1é um atalho inativo - ele imprime a linha atual para todas as condições verdadeiras e 1como uma condição é avaliada como verdadeira.

muru
fonte
Por favor, você poderia explicar seu código?
George Udosen
A palavra printtambém pode ser usada?
George Udosen
Sim, em print total}vez de$0 = total}1
muru
1
@ George ah, não.
muru
9
Uma maneira mais curta e talvez mais compreensível de escrever o script awk seria{print(total += $0)}
Miles
9

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")

Usar

  • Copie o script em um arquivo vazio, salve-o como 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>
    

Explicação

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")
    
Jacob Vlijm
fonte
3
Não se trata de falta de desempenho ou tempo (milhões de linhas não são big data). O código na sua resposta não é Python idiomático. Minha resposta é apenas uma versão mais pitônica da sua.
jfs
8
@JFSebastian se a versão mais idiomática é mais lenta, por que alguém preferiria? Não há nada de especial em ser "pythonic", que é apenas uma convenção que ajuda os desenvolvedores python a compartilhar código e padrões de legibilidade. Se a versão mais idiomática for menos eficiente (mais lenta), ela não deverá ser usada, a menos que você esteja trabalhando em um ambiente em que a padronização seja mais importante que o desempenho (o que me parece uma idéia horrível).
terdon
2
@terdon há algo a ser dito sobre otimização prematura. A legibilidade pode ser importante devido à capacidade de manutenção a longo prazo.
muru
4
@uru certo, mas isso é perfeitamente legível. O único crime é não ser "pitônico". Sem mencionar que estamos falando de 7 linhas de código, não de um projeto gigante. Sacrificar a eficiência em nome das convenções de estilo parece ser a abordagem errada.
terdon
9

Apenas por diversão

$ sed 'a+p' file | dc -e0 -
3
7
12
20

Isso funciona de forma pendente +ppara cada linha da entrada e depois passa o resultado para a dccalculadora 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 -e0argumento entra 0na dcpilha para inicializar a soma.

chave de aço
fonte
Algo como isso pode realmente ser o mais rápido ao longo de um grande conjunto de dados
Trauma Digital
@DigitalTrauma em um 1,3 milhões de linhas, na verdade, quase o mais lento:real 0m4.234s
Jacob Vlijm
diversão é tudo o que precisa para uma upvote: D peculiar é o suficiente também: D: D
Rinzwind
Por favor, explique um pouco.
AmanicA
8

No Bash:

#! /bin/bash

file="YOUR_FILE.txt"

TOTAL=0
while IFS= read -r line
do
    TOTAL=$(( TOTAL + line ))
    echo $TOTAL
done <"$file"
Julen Larrucea
fonte
bash é extremamente lento sobre isso: real 0m53.116s, quase um minuto, em 1,3 milhões de linhas :)
Jacob Vlijm
O traço do @JacobVlijm é duas vezes mais rápido, o busybox ash e o zsh (no modo sh) 1,5 vezes, mas é claro que até o traço é 5 vezes mais lento que o python.
muru
6

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)

Exemplo executável .

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

Exemplo executável .

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')
jfs
fonte
2
Pode ser também vale a pena mencionar o accumulate()itertool
David Z
5

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 melhor

Exemplo:
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
Wayne_Yux
fonte
Eu gosto disso, o lançamento é bom
:)
5

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.

James
fonte
1
+1 por quebrar o encantamento mágico e explicar todas as partes. Muito raro em torno dessas partes.
John U
5

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
Sergiy Kolodyazhnyy
fonte
real 0m0.908s, até legal.
Jacob Vlijm
@JacobVlijm que está em um arquivo bem pequeno. Eu adicionei um pequeno teste com 2,5 milhões de linhas de arquivo. 6,64 segundos
Sergiy Kolodyazhnyy 02/02
1
Corri 1,3 milhões de linhas em um antigo sistema
Jacob Vlijm
3

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 nde INPUT_FILEe adicionamos seu valor numérico à nossa variável xe 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).

Byte Commander
fonte
3

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 sedexpressão transforma a entrada em:

a+=3;a
a+=4;a
a+=5;a
a+=8;a

Isso é então avaliado por bc. A avariável bc é inicializada automaticamente em 0. Cada linha é incrementada ae a imprime explicitamente.

Trauma Digital
fonte
real 0m5.642sem 1,3 milhões de linhas. sed é realmente lento nisso.
Jacob Vlijm