Come posso sommare rapidamente tutti i numeri in un file?


196

Ho un file che contiene diverse migliaia di numeri, ognuno sulla propria riga:

34
42
11
6
2
99
...

Sto cercando di scrivere uno script che stamperà la somma di tutti i numeri nel file. Ho una soluzione, ma non è molto efficiente. (Ci vogliono diversi minuti per correre.) Sto cercando una soluzione più efficiente. Eventuali suggerimenti?


5
Qual è stata la tua soluzione lenta? Forse possiamo aiutarti a capire cosa è stato lento al riguardo. :)
brian d foy,

4
@brian d foy, sono troppo imbarazzato per pubblicarlo. So perché è lento. È perché chiamo "cat nomefile | head -n 1" per ottenere il numero più alto, lo aggiungo a un totale parziale e chiamo "nome file cat | coda ..." per rimuovere la riga superiore per la prossima iterazione ... I ho molto da imparare sulla programmazione !!!
Mark Roberts,

6
È ... molto sistematico. Molto chiaro e diretto, e lo adoro per tutto ciò che è un orribile abominio. Costruito, presumo, dagli strumenti che sapevi quando hai iniziato, giusto?
dmckee --- ex gattino moderatore


@MarkRoberts Deve aver impiegato molto tempo per risolverlo. È una tecnica di risoluzione dei problemi molto semplice, e oh così sbagliato. Sembra un classico caso di over think. Molte delle soluzioni di Glen Jackman shell scripting solutions (e due sono pure shell che non usano cose come awke bc). Tutto ciò ha finito per aggiungere un milione di numeri in meno di 10 secondi. Dai un'occhiata a quelli e vedi come si può fare in puro guscio.
David W.

Risposte:


113

Per un one-liner Perl, è sostanzialmente la stessa cosa della awksoluzione nella risposta di Ayman Hourieh :

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

Se sei curioso di sapere cosa fanno le battute singole Perl, puoi depilarle:

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

Il risultato è una versione più dettagliata del programma, in una forma che nessuno avrebbe mai scritto da solo:

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

Solo per risatine, ho provato questo con un file contenente 1.000.000 di numeri (nell'intervallo 0 - 9.999). Sul mio Mac Pro, ritorna praticamente istantaneamente. È un peccato, perché speravo che l'uso mmapsarebbe stato molto veloce, ma è solo la stessa volta:

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
Wow, questo dimostra una profonda comprensione di quale codice-avvolgimento effettivamente avvolge la stringa che gli dai. Il mio pensiero iniziale era che non
dovevi

-n e -p mettono semplicemente i caratteri attorno all'argomento a -e, in modo da poterli usare per quello che vuoi. Abbiamo un sacco di line-line che fanno cose interessanti con quello in Effective Perl Programming (che sta per colpire gli scaffali).
brian d foy,

5
Bene, di cosa trattano queste parentesi graffe non corrispondenti?
Frank,

17
-n aggiunge il while { }ciclo attorno al tuo programma. Se metti } ... {dentro, allora hai while { } ... { }. Il male? Leggermente.
jrockway,

5
Grande bonus per evidenziare l' -MO=Deparseopzione! Anche se su un argomento separato.
Conny

375

Puoi usare awk:

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

4
programma superato: numero massimo di dimensioni del campo: 32767
leef

1
Con l' -F '\t'opzione se i campi contengono spazi e sono separati da schede.
Ethan Furman,

5
Si prega di contrassegnare questa come la migliore risposta. Funziona anche se vuoi sommare il primo valore in ogni riga, all'interno di un file TSV (valore separato da tabulazioni).
Andrea,

100

Nessuna soluzione finora utilizzata paste. Eccone uno:

paste -sd+ filename | bc

Ad esempio, calcola wheren dove 1 <= n <= 100000:

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

(Per i curiosi, seq nstampa una sequenza di numeri da 1a cui ndare un numero positivo n.)


1
Molto bella! E facile da ricordare
Brendan Maguire,

1
seq 100000 | paste -sd+ - | bc -lsulla shell Bash di Mac OS X. E questa è di gran lunga la soluzione più dolce e più semplice!
Simo A.

2
@SimoA. Voto che usiamo il termine unixest al posto di unixest perché per la soluzione più sexy è sempre il più unix;)
Connor

86

Solo per divertimento, facciamo un benchmark:

$ 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

Ho interrotto la corsa sed dopo 5 minuti


Mi sono tuffato a , ed è veloce:

$ 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 mentre lo aggiorno, 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

Il consiglio di Heed Ed Morton: usare $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: per aver trovato un sacco di soluzioni e confrontarle con loro.
David W.

time cat random_numbers | incolla -sd + | bc -l real 0m0.317s user 0m0.310s sys 0m0.013s
rafi wiener

quello dovrebbe essere quasi identico alla trsoluzione.
Glenn Jackman,

4
Il tuo script awk dovrebbe essere eseguito un po 'più veloce se lo usi $0invece che $1poiché awk fa la suddivisione dei campi (che ovviamente richiede tempo) se qualsiasi campo è specificamente menzionato nello script ma non diversamente.
Ed Morton,

21

Un'altra opzione è utilizzare jq:

$ seq 10|jq -s add
55

-s( --slurp) legge le righe di input in un array.


1
È uno strumento fantastico per compiti rapidi come quello, quasi dimenticato. grazie
Giovanni

9

Questo è dritto Bash:

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

2
Ed è probabilmente una delle soluzioni più lente e quindi non adatta a grandi quantità di numeri.
David,

7

Ecco un altro one-liner

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

Questo presuppone che i numeri siano numeri interi. Se hai bisogno di decimali, prova

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

Regola 2 in base al numero di decimali necessari.


6

Preferisco usare il datamash GNU per tali compiti perché è più conciso e leggibile di perl o awk. Per esempio

datamash sum 1 < myfile

dove 1 indica la prima colonna di dati.


1
Questo non sembra essere un componente standard in quanto non lo vedo nella mia installazione di Ubuntu. Mi piacerebbe vederlo confrontato, però.
Steven the Easy Amused

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

5

Preferisco usare R per questo:

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

Sono un fan di R per altre applicazioni, ma non è buono per le prestazioni in questo modo. L'I / O dei file è un grosso problema. Ho provato a passare args a uno script che può essere velocizzato usando il pacchetto vroom. Pubblicherò ulteriori dettagli quando ho confrontato alcuni altri script sullo stesso server.
Tom Kelly,

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

(uguale alla risposta di brian d foy, senza "END")


Mi piace questo, ma potresti spiegare le parentesi graffe? È strano vedere} senza {e viceversa.
drumfire

1
@drumfire vedi la risposta di @brian d foy sopra con perl -MO=Deparseper vedere come perl analizza il programma. o la documentazione per perlrun: perldoc.perl.org/perlrun.html (ricerca di -n). perl racchiude il tuo codice con {} se usi -n in modo che diventi un programma completo.
edibleEnergy,

4

Più succinto:

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

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

La conversione in float sembra essere circa due volte più veloce sul mio 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

Solo per divertimento, facciamolo con PDL , il motore matematico array di Perl!

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

rcolslegge le colonne in una matrice (1D in questo caso) e sum(sorpresa) somma tutti gli elementi della matrice.


Come risolvere Impossibile individuare PDL.pm in @INC (potrebbe essere necessario installare il modulo PDL) (@INC contiene: / etc / perl /usr/local/lib/x86_64-linux-gnu/perl/5.22.1? )) per divertimento ovviamente =)
Fortran,

1
Devi prima installare PDL, non è un modulo nativo Perl.
Joel Berger,

3

Ecco una soluzione che usa python con un'espressione di generatore. Testato con un milione di numeri sul mio vecchio laptop rozzo.

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
Una semplice comprensione dell'elenco con una funzione denominata è un buon caso d'uso per map():map(float, sys.stdin)
sevko

3

Non potevo semplicemente passare ... Ecco la mia copertina di Haskell. In realtà è abbastanza leggibile:

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

Sfortunatamente non è ghci -enecessario eseguirlo, quindi ha bisogno della funzione principale, della stampa e della compilazione.

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

Per chiarire, leggiamo l'intero input ( getContents), diviso per lines, readcome numeri e sum. <$>è un fmapoperatore - lo usiamo al posto della normale applicazione di funzione perché sicuramente questo accade in I / O. readha bisogno di un ulteriore fmap, perché è anche nell'elenco.

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

Ecco uno strano aggiornamento per farlo funzionare con i float:

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


2

Esecuzione di script R.

Ho scritto uno script R per prendere argomenti sul nome di un file e sommare le righe.

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

Questo può essere velocizzato con il pacchetto "data.table" o "vroom" come segue:

#! /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))

Analisi comparativa

Stessi dati di benchmarking di @glenn jackman .

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

Rispetto alla chiamata R sopra, l'esecuzione di R 3.5.0 come script è paragonabile ad altri metodi (sullo stesso server Debian Linux).

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

Script R con readLines

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

Script R con data.table

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

Script R con Vroom

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

Confronto con altre lingue

Per riferimento qui come alcuni altri metodi suggeriti sullo stesso 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

Rubino (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 (clang versione 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

Aggiornamento con altre lingue

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 essere temporizzato in bash, non compatibile con zsh

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

sed (4.4) deve essere temporizzato in bash, non compatibile con 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

nota: le chiamate sed sembrano funzionare più velocemente sui sistemi con più memoria disponibile (notare set di dati più piccoli utilizzati per il benchmarking di 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

Si noti che, come in R, i metodi di I / O dei file hanno prestazioni diverse.


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

Un altro per divertimento

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

o solo un altro bash

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

Ma la soluzione Awk è probabilmente la migliore perché è la più compatta.


1

C vince sempre per la velocità:

#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;
}

Temporizzazione per numeri 1M (stessa macchina / input della mia risposta python):

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

1
Migliore risposta! Miglior velocità)
Fortran,

1

Con Ruby:

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

Un'altra opzione (quando l'input proviene da STDIN) è ruby -e'p readlines.map(&:to_f).reduce(:+)'.
nisetama,

0

Non so se puoi ottenere molto meglio di questo, considerando che devi leggere l'intero file.

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

1
Molto leggibile Per Perl. Ma sì, dovrà essere qualcosa del genere ...
dmckee --- ex gattino moderatore

$_è la variabile predefinita. L'operatore di input di linea,, <>inserisce il suo risultato per impostazione predefinita quando lo usi <>in while.
brian d foy,

1
@Mark, $_è la variabile argomento - funziona come 'it'. In questo caso <> assegna ciascuna riga ad essa. Viene utilizzato in diversi punti per ridurre il disordine del codice e aiutare con la scrittura di una riga. Lo script dice "Imposta la somma su 0, leggi ogni riga e aggiungila alla somma, quindi stampa la somma".
daotoad,

1
@Stefan, con avvisi e restrizioni disattivati, puoi saltare la dichiarazione e l'inizializzazione $sum. Dato che è così semplice, puoi persino usare un modificatore di istruzioni while:$sum += $_ while <>; print $sum;
daotoad

0

Non l'ho testato ma dovrebbe funzionare:

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

Potrebbe essere necessario aggiungere "\ n" alla stringa prima di bc (come via echo) se bc non tratta EOF ed EOL ...


2
Non funziona bcgenera un errore di sintassi a causa del "+" finale e della mancanza di newline alla fine. Questo funzionerà ed elimina un uso inutile di cat: { tr "\n" "+" | sed 's/+$/\n/'| bc; } < numbers2.txt oppure <numbers2.txt tr "\n" "+" | sed 's/+$/\n/'| bc
In pausa fino a nuovo avviso.

tr "\n" "+" <file | sed 's/+$/\n/' | bc
ghostdog74

0

Eccone un altro:

open(FIL, "a.txt");

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

close(FIL);

print "Sum = $sum\n";

0

Puoi farlo con Alacon - utility da riga di comando per Alasql database .

Funziona con Node.js, quindi è necessario installare Node.js e quindi Alasql pacchetto :

Per calcolare la somma dal file TXT è possibile utilizzare il seguente comando:

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

0

Non è più facile sostituire tutte le nuove righe con +, aggiungere un 0e inviarlo Rubyall'interprete?

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

Se non lo hai irb, puoi inviarlo a bc, ma devi rimuovere tutte le nuove righe tranne l'ultima (di echo). È meglio usare trper questo, a meno che tu non abbia un dottorato di ricerca in sed.

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

0

In 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)
}

Che cos'è "64"? "10" Suppongo sia base?
Peter K

Sì, 10 è la base. 64 è il numero di bit, se l'int risultante non può essere rappresentato con quel numero di bit, viene restituito un errore. Vedi golang.org/pkg/strconv/#ParseInt
dwurf il

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

In shell usando awk, ho usato lo script seguente per farlo:

    #!/bin/bash


total=0;

for i in $( awk '{ print $1; }' <myfile> )
do
 total=$(echo $total+$i | bc )
 ((count++))
done
echo "scale=2; $total " | bc
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.