Como posso somar rapidamente todos os números em um arquivo?


194

Eu tenho um arquivo que contém vários milhares de números, cada um em sua própria linha:

34
42
11
6
2
99
...

Estou procurando escrever um script que imprima a soma de todos os números no arquivo. Eu tenho uma solução, mas não é muito eficiente. (Demora alguns minutos para ser executado.) Estou procurando uma solução mais eficiente. Alguma sugestão?


5
Qual foi a sua solução lenta? Talvez possamos ajudá-lo a descobrir o que foi mais lento. :)
Brian d foy

4
@ Brian Doy, estou com vergonha de publicá-lo. Eu sei porque é lento. É porque eu chamo "cat filename | head -n 1" para obter o número superior, adicione-o a um total em execução e chamo "cat filename | tail ..." para remover a linha superior da próxima iteração ... tem muito a aprender sobre programação !!!
Mark Roberts

6
Isso é ... muito sistemático. Muito claro e direto, e eu amo isso por tudo que é uma abominação horrível. Construído, presumo, a partir das ferramentas que você sabia quando começou, certo?
dmckee --- ex-moderador gatinho


@ MarkRoberts Deve ter demorado muito tempo para resolver isso. É uma técnica de resolução de problemas muito mais cortante, e oh, tão errada. Parece um caso clássico de pensar demais. Várias das soluções de Glen Jackman oferecem soluções de script de shell (e duas são shell puro que não usa coisas como awke bc). Todos terminaram adicionando um milhão de números em menos de 10 segundos. Dê uma olhada nelas e veja como isso pode ser feito com casca pura.
22413 David W.

Respostas:


113

Para uma linha única de Perl, é basicamente a mesma coisa que a awksolução na resposta de Ayman Hourieh :

 % perl -nle '$sum += $_ } END { print $sum'

Se você estiver curioso sobre o que as linhas de uma linha do Perl fazem, você pode separá-las:

 %  perl -MO=Deparse -nle '$sum += $_ } END { print $sum'

O resultado é uma versão mais detalhada do programa, de uma forma que ninguém jamais escreveria por conta própria:

BEGIN { $/ = "\n"; $\ = "\n"; }
LINE: while (defined($_ = <ARGV>)) {
    chomp $_;
    $sum += $_;
}
sub END {
    print $sum;
}
-e syntax OK

Apenas para rir, tentei isso com um arquivo contendo 1.000.000 de números (no intervalo de 0 a 9.999). No meu Mac Pro, ele retorna virtualmente instantaneamente. Isso é muito ruim, porque eu esperava que o uso mmapfosse muito rápido, mas é exatamente ao mesmo tempo:

use 5.010;
use File::Map qw(map_file);

map_file my $map, $ARGV[0];

$sum += $1 while $map =~ m/(\d+)/g;

say $sum;

4
Uau, isso mostra uma profunda compreensão de qual código -nle realmente envolve a string que você fornece. Meu pensamento inicial era que você não deveria postar embriagado, mas então eu notei quem você era e lembrei-me de algumas de suas outras respostas do Perl :-) #
384

-n e -p apenas colocam caracteres em torno do argumento para -e, para que você possa usá-los para o que quiser. Temos várias linhas que fazem coisas interessantes com isso na Programação Perl Efetiva (que está prestes a chegar às prateleiras).
usar o seguinte código

5
Bom, sobre o que são esses aparelhos cacheados não correspondentes?
Frank

17
-n adiciona o while { }loop ao redor do seu programa. Se você colocar } ... {dentro, então você tem while { } ... { }. Mal? Levemente.
jrockway

5
Grande bônus por destacar a -MO=Deparseopção! Mesmo que em um tópico separado.
conny

374

Você pode usar o awk:

awk '{ sum += $1 } END { print sum }' file

3
programa excedido: número máximo de tamanhos de campo: 32767
leef

1
Com a -F '\t'opção se seus campos contêm espaços e são separados por tabulações.
21714 Ethan Furman

5
Marque esta como a melhor resposta. Também funciona se você deseja somar o primeiro valor em cada linha, dentro de um arquivo TSV (valor separado por tabulação).
Andrea

99

Até agora nenhuma das soluções é utilizada paste. Aqui está um:

paste -sd+ filename | bc

Como exemplo, calcule wheren onde 1 <= n <= 100000:

$ seq 100000 | paste -sd+ | bc -l
5000050000

(Para os curiosos, seq nimprimiria uma sequência de números de 1para num número positivo n.)


1
Muito agradável! E fácil de lembrar
Brendan Maguire

1
seq 100000 | paste -sd+ - | bc -lno shell do Mac OS X Bash. E esta é de longe a solução mais doce e a mais unix!
Simo A.

1
@SimoA. Eu voto que usamos o termo unixiest em vez de unixest porque, para a solução mais sexy, é sempre a unixiest;)
Connor

86

Apenas por diversão, vamos compará-lo:

$ for ((i=0; i<1000000; i++)) ; do echo $RANDOM; done > random_numbers

$ time perl -nle '$sum += $_ } END { print $sum' random_numbers
16379866392

real    0m0.226s
user    0m0.219s
sys     0m0.002s

$ time awk '{ sum += $1 } END { print sum }' random_numbers
16379866392

real    0m0.311s
user    0m0.304s
sys     0m0.005s

$ time { { tr "\n" + < random_numbers ; echo 0; } | bc; }
16379866392

real    0m0.445s
user    0m0.438s
sys     0m0.024s

$ time { s=0;while read l; do s=$((s+$l));done<random_numbers;echo $s; }
16379866392

real    0m9.309s
user    0m8.404s
sys     0m0.887s

$ time { s=0;while read l; do ((s+=l));done<random_numbers;echo $s; }
16379866392

real    0m7.191s
user    0m6.402s
sys     0m0.776s

$ time { sed ':a;N;s/\n/+/;ta' random_numbers|bc; }
^C

real    4m53.413s
user    4m52.584s
sys 0m0.052s

Abortei a corrida após 5 minutos


Eu tenho mergulhado para e é rápido:

$ time lua -e 'sum=0; for line in io.lines() do sum=sum+line end; print(sum)' < random_numbers
16388542582.0

real    0m0.362s
user    0m0.313s
sys     0m0.063s

e enquanto estou atualizando isso, ruby:

$ time ruby -e 'sum = 0; File.foreach(ARGV.shift) {|line| sum+=line.to_i}; puts sum' random_numbers
16388542582

real    0m0.378s
user    0m0.297s
sys     0m0.078s

Preste atenção ao conselho de Ed Morton: usar $1

$ time awk '{ sum += $1 } END { print sum }' random_numbers
16388542582

real    0m0.421s
user    0m0.359s
sys     0m0.063s

vs usando $0

$ time awk '{ sum += $0 } END { print sum }' random_numbers
16388542582

real    0m0.302s
user    0m0.234s
sys     0m0.063s

18
+1: por apresentar várias soluções e compará-las.
22413 David W.

random_numbers gato tempo | colar -sd + | bc -l reais 0m0.317s usuário 0m0.310s sys 0m0.013s
rafi wiener

isso deve ser praticamente idêntico à trsolução.
Glenn Jackman

4
Seu script awk deve ser executado um pouco mais rápido se você usar, em $0vez de o $1awk dividir o campo (o que obviamente leva tempo) se algum campo for mencionado especificamente no script, mas não o fizer.
Ed Morton

20

Outra opção é usar jq:

$ seq 10|jq -s add
55

-s( --slurp) lê as linhas de entrada em uma matriz.


1
É uma ferramenta incrível para tarefas rápidas como essa, quase esqueci. graças
John

9

Este é Bash direto:

sum=0
while read -r line
do
    (( sum += line ))
done < file
echo $sum

2
E é provavelmente uma das soluções mais lentas e, portanto, não é tão adequada para grandes quantidades de números.
David

7

Aqui está outra frase

( echo 0 ; sed 's/$/ +/' foo ; echo p ) | dc

Isso pressupõe que os números são inteiros. Se você precisar de casas decimais, tente

( echo 0 2k ; sed 's/$/ +/' foo ; echo p ) | dc

Ajuste 2 para o número de casas decimais necessárias.


6

Eu prefiro usar o GNU datamash para essas tarefas porque é mais sucinto e legível que o perl ou o awk. Por exemplo

datamash sum 1 < myfile

onde 1 indica a primeira coluna de dados.


1
Este não parece ser um componente padrão, pois não o vejo na minha instalação do Ubuntu. Gostaria de vê-lo comparado, no entanto.
Steven the facilmente divertido

5
$ perl -MList::Util=sum -le 'print sum <>' nums.txt

5

Eu prefiro usar R para isso:

$ R -e 'sum(scan("filename"))'

Sou fã do R para outras aplicações, mas não é bom para o desempenho dessa maneira. O arquivo E / S é um problema importante. Eu testei passando args para um script que pode ser acelerado usando o pacote vroom. Vou postar mais detalhes quando comparar outros scripts no mesmo servidor.
Tom Kelly

4
cat nums | perl -ne '$sum += $_ } { print $sum'

(o mesmo que a resposta de brian d foy, sem 'END')


Eu gosto disso, mas você poderia explicar os colchetes? É estranho ver} sem {e vice-versa.
drumfire 17/06

1
@drumfire veja a resposta de @brian d foy acima com perl -MO=Deparsepara ver como o perl analisa o programa. ou os documentos para perlrun: perldoc.perl.org/perlrun.html (pesquise -n). O perl agrupa seu código com {} se você usar -n para que ele se torne um programa completo.
edibleEnergy

4

Mais sucinto:

# Ruby
ruby -e 'puts open("random_numbers").map(&:to_i).reduce(:+)'

# Python
python -c 'print(sum(int(l) for l in open("random_numbers")))'

Converter para flutuar parece ser duas vezes mais rápido no meu sistema (320 vs 640 ms). time python -c "print(sum([float(s) for s in open('random_numbers','r')]))"
user12719

4

Perl 6

say sum lines
~$ perl6 -e '.say for 0..1000000' > test.in

~$ perl6 -e 'say sum lines' < test.in
500000500000

3

Apenas por diversão, vamos fazê-lo com o PDL , o mecanismo matemático de matriz do Perl!

perl -MPDL -E 'say rcols(shift)->sum' datafile

rcolslê colunas em uma matriz (1D neste caso) e sum(surpresa) soma todo o elemento da matriz.


Como corrigir Não é possível localizar o PDL.pm no @INC (pode ser necessário instalar o módulo PDL) (o @INC contém: / etc / perl /usr/local/lib/x86_64-linux-gnu/perl/5.22.1? )) por diversão, é claro =)
Fortran

1
Você precisa instalar o PDL primeiro, não é um módulo nativo do Perl.
Joel Berger

3

Aqui está uma solução usando python com uma expressão de gerador. Testado com um milhão de números no meu laptop velho e sujo.

time python -c "import sys; print sum((float(l) for l in sys.stdin))" < file

real    0m0.619s
user    0m0.512s
sys     0m0.028s

3
A compreensão simples lista com uma função chamada é um caso de uso agradável para map():map(float, sys.stdin)
Sevko

3

Eu não podia simplesmente passar por aqui ... Aqui está o meu verso de Haskell. Na verdade, é bastante legível:

sum <$> (read <$>) <$> lines <$> getContents

Infelizmente, não há ghci -ecomo executá-lo, por isso ele precisa da função principal, impressão e compilação.

main = (sum <$> (read <$>) <$> lines <$> getContents) >>= print

Para esclarecer, lemos toda a entrada ( getContents), dividimos por lines, readcomo números e sum. <$>é fmapoperador - nós o usamos em vez da aplicação de função usual, porque com certeza isso tudo acontece no IO. readprecisa de um adicional fmap, porque também está na lista.

$ ghc sum.hs
[1 of 1] Compiling Main             ( sum.hs, sum.o )
Linking sum ...
$ ./sum 
1
2
4
^D
7

Aqui está uma atualização estranha para fazê-lo funcionar com carros alegóricos:

main = ((0.0 + ) <$> sum <$> (read <$>) <$> lines <$> getContents) >>= print
$ ./sum 
1.3
2.1
4.2
^D
7.6000000000000005


2

Executando scripts R

Eu escrevi um script R para pegar argumentos de um nome de arquivo e somar as linhas.

#! /usr/local/bin/R
file=commandArgs(trailingOnly=TRUE)[1]
sum(as.numeric(readLines(file)))

Isso pode ser acelerado com o pacote "data.table" ou "vroom" da seguinte maneira:

#! /usr/local/bin/R
file=commandArgs(trailingOnly=TRUE)[1]
sum(data.table::fread(file))
#! /usr/local/bin/R
file=commandArgs(trailingOnly=TRUE)[1]
sum(vroom::vroom(file))

avaliação comparativa

Os mesmos dados de benchmarking que @glenn jackman .

for ((i=0; i<1000000; i++)) ; do echo $RANDOM; done > random_numbers

Em comparação com a chamada R acima, a execução do R 3.5.0 como um script é comparável a outros métodos (no mesmo servidor Debian Linux).

$ time R -e 'sum(scan("random_numbers"))'  
 0.37s user
 0.04s system
 86% cpu
 0.478 total

Script R com readLines

$ time Rscript sum.R random_numbers
  0.53s user
  0.04s system
  84% cpu
  0.679 total

Script R com data.table

$ time Rscript sum.R random_numbers     
 0.30s user
 0.05s system
 77% cpu
 0.453 total

Script R com vroom

$ time Rscript sum.R random_numbers     
  0.54s user 
  0.11s system
  93% cpu
  0.696 total

Comparação com outros idiomas

Para referência aqui, como alguns outros métodos sugeridos no mesmo hardware

Python 2 (2.7.13)

$ time python2 -c "import sys; print sum((float(l) for l in sys.stdin))" < random_numbers 
 0.27s user 0.00s system 89% cpu 0.298 total

Python 3 (3.6.8)

$ time python3 -c "import sys; print(sum((float(l) for l in sys.stdin)))" < random_number
0.37s user 0.02s system 98% cpu 0.393 total

Ruby (2.3.3)

$  time ruby -e 'sum = 0; File.foreach(ARGV.shift) {|line| sum+=line.to_i}; puts sum' random_numbers
 0.42s user
 0.03s system
 72% cpu
 0.625 total

Perl (5.24.1)

$ time perl -nle '$sum += $_ } END { print $sum' random_numbers
 0.24s user
 0.01s system
 99% cpu
 0.249 total

Awk (4.1.4)

$ time awk '{ sum += $0 } END { print sum }' random_numbers
 0.26s user
 0.01s system
 99% cpu
 0.265 total
$ time awk '{ sum += $1 } END { print sum }' random_numbers
 0.34s user
 0.01s system
 99% cpu
 0.354 total

C (versão clang 3.3; gcc (Debian 6.3.0-18) 6.3.0)

 $ gcc sum.c -o sum && time ./sum < random_numbers   
 0.10s user
 0.00s system
 96% cpu
 0.108 total

Atualizar com idiomas adicionais

Lua (5.3.5)

$ time lua -e 'sum=0; for line in io.lines() do sum=sum+line end; print(sum)' < random_numbers 
 0.30s user 
 0.01s system
 98% cpu
 0.312 total

tr (8.26) deve ser cronometrado no bash, não compatível com zsh

$time { { tr "\n" + < random_numbers ; echo 0; } | bc; }
real    0m0.494s
user    0m0.488s
sys 0m0.044s

sed (4.4) deve ser cronometrado no bash, não compatível com zsh

$  time { head -n 10000 random_numbers | sed ':a;N;s/\n/+/;ta' |bc; }
real    0m0.631s
user    0m0.628s
sys     0m0.008s
$  time { head -n 100000 random_numbers | sed ':a;N;s/\n/+/;ta' |bc; }
real    1m2.593s
user    1m2.588s
sys     0m0.012s

note: as chamadas sed parecem funcionar mais rapidamente em sistemas com mais memória disponível (observe conjuntos de dados menores usados ​​para comparar o sed)

Julia (0.5.0)

$ time julia -e 'print(sum(readdlm("random_numbers")))'
 3.00s user 
 1.39s system 
 136% cpu 
 3.204 total
$  time julia -e 'print(sum(readtable("random_numbers")))'
 0.63s user 
 0.96s system 
 248% cpu 
 0.638 total

Observe que, como no R, os métodos de E / S do arquivo têm desempenho diferente.


2

C ++ "one-liner":

#include <iostream>
#include <iterator>
#include <numeric>
using namespace std;

int main() {
    cout << accumulate(istream_iterator<int>(cin), istream_iterator<int>(), 0) << endl;
}

1

Outro por diversão

sum=0;for i in $(cat file);do sum=$((sum+$i));done;echo $sum

ou apenas outra festança

s=0;while read l; do s=$((s+$l));done<file;echo $s

Mas a solução awk é provavelmente a melhor, pois é mais compacta.


1

C sempre ganha por velocidade:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
    ssize_t read;
    char *line = NULL;
    size_t len = 0;
    double sum = 0.0;

    while (read = getline(&line, &len, stdin) != -1) {
        sum += atof(line);
    }

    printf("%f", sum);
    return 0;
}

Tempo para números de 1 milhão (a mesma máquina / entrada que minha resposta em python):

$ gcc sum.c -o sum && time ./sum < numbers 
5003371677.000000
real    0m0.188s
user    0m0.180s
sys     0m0.000s

1
Melhor resposta! Melhor velocidade)
Fortran

1

Com Ruby:

ruby -e "File.read('file.txt').split.inject(0){|mem, obj| mem += obj.to_f}"

Outra opção (quando a entrada é do STDIN) é ruby -e'p readlines.map(&:to_f).reduce(:+)'.
Nisetama

0

Não sei se você pode ficar muito melhor do que isso, considerando que precisa ler o arquivo inteiro.

$sum = 0;
while(<>){
   $sum += $_;
}
print $sum;

1
Muito legível. Para perl. Mas sim, vai ter que ser algo assim ...
dmckee --- ex-moderador gatinho

$_é a variável padrão. O operador de entrada de linha, <>, coloca-o do resultado lá por padrão quando você usa <>no while.
usar o seguinte código

1
@ Mark, $_é a variável de tópico - funciona como o 'it'. Nesse caso, <> atribui cada linha a ela. Ele é usado em vários locais para reduzir a desordem de código e ajudar na escrita de linhas únicas. O script diz "Defina a soma como 0, leia cada linha e adicione-a à soma e depois imprima a soma".
precisa saber é o seguinte

1
@ Stefan, com avisos e restrições desativados, você pode pular a declaração e a inicialização $sum. Como isso é tão simples, você pode até usar um modificador de instrução while:$sum += $_ while <>; print $sum;
daotoad

0

Eu não testei isso, mas deve funcionar:

cat f | tr "\n" "+" | sed 's/+$/\n/' | bc

Talvez você precise adicionar "\ n" à string antes de bc (como via eco) se bc não tratar EOF e EOL ...


2
Isso não funciona. bcemite um erro de sintaxe devido ao "+" à direita e falta de nova linha no final. Isso funcionará e elimina o uso inútil de cat: { tr "\n" "+" | sed 's/+$/\n/'| bc; } < numbers2.txt ou <numbers2.txt tr "\n" "+" | sed 's/+$/\n/'| bc
Pausado até novo aviso.

tr "\n" "+" <file | sed 's/+$/\n/' | bc
precisa saber é o seguinte

0

Aqui está outro:

open(FIL, "a.txt");

my $sum = 0;
foreach( <FIL> ) {chomp; $sum += $_;}

close(FIL);

print "Sum = $sum\n";

0

Você pode fazer isso com o Alacon - utilitário de linha de comando do banco de dados Alasql .

Funciona com o Node.js, então você precisa instalar o Node.js e o pacote Alasql :

Para calcular a soma do arquivo TXT, você pode usar o seguinte comando:

> node alacon "SELECT VALUE SUM([0]) FROM TXT('mydata.txt')"

0

Não é mais fácil substituir todas as novas linhas +, adicionar a 0e enviá-lo ao Rubyintérprete?

(sed -e "s/$/+/" file; echo 0)|irb

Se você não tiver irb, poderá enviá-lo para bc, mas precisará remover todas as novas linhas, exceto a última (de echo). É melhor usar trisso, a menos que você tenha um doutorado sed.

(sed -e "s/$/+/" file|tr -d "\n"; echo 0)|bc

0

Em Go:

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
)

func main() {
    scanner := bufio.NewScanner(os.Stdin)
    sum := int64(0)
    for scanner.Scan() {
        v, err := strconv.ParseInt(scanner.Text(), 10, 64)
        if err != nil {
            fmt.Fprintf(os.Stderr, "Not an integer: '%s'\n", scanner.Text())
            os.Exit(1)
        }
        sum += v
    }
    fmt.Println(sum)
}

O que é "64"? "10" Acho que é base?
Peter K

Sim, 10 é a base. 64 é o número de bits, se o int resultante não puder ser representado com tantos bits, um erro será retornado. Veja golang.org/pkg/strconv/#ParseInt
dwurf

0

Variante Bash

raw=$(cat file)
echo $(( ${raw//$'\n'/+} ))

$ wc -l file
10000 file

$ time ./test
323390

real    0m3,096s
user    0m3,095s
sys     0m0,000s

0

No shell usando o awk, usei o script abaixo para fazer isso:

    #!/bin/bash


total=0;

for i in $( awk '{ print $1; }' <myfile> )
do
 total=$(echo $total+$i | bc )
 ((count++))
done
echo "scale=2; $total " | bc
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.