Wie kann ich schnell alle Zahlen in einer Datei summieren?


194

Ich habe eine Datei, die mehrere tausend Zahlen enthält, jede in einer eigenen Zeile:

34
42
11
6
2
99
...

Ich möchte ein Skript schreiben, das die Summe aller Zahlen in der Datei druckt. Ich habe eine Lösung, aber sie ist nicht sehr effizient. (Die Ausführung dauert einige Minuten.) Ich suche nach einer effizienteren Lösung. Irgendwelche Vorschläge?


5
Was war Ihre langsame Lösung? Vielleicht können wir Ihnen helfen, herauszufinden, was daran langsam war. :)
Brian D Foy

4
@brian d foy, es ist mir zu peinlich, es zu posten. Ich weiß, warum es langsam ist. Das liegt daran, dass ich "cat filename | head -n 1" aufrufe, um die Top-Nummer zu erhalten, sie zu einer laufenden Summe hinzuzufügen und "cat filename | tail ..." aufrufe, um die oberste Zeile für die nächste Iteration zu entfernen ... I. habe viel über das Programmieren zu lernen !!!
Mark Roberts

6
Das ist ... sehr systematisch. Sehr klar und direkt, und ich liebe es trotz allem, dass es ein schrecklicher Gräuel ist. Ich nehme an, er wurde aus den Werkzeugen gebaut, die Sie zu Beginn kannten, oder?
dmckee --- Ex-Moderator Kätzchen


@ MarkRoberts Es muss lange gedauert haben, bis du das herausgefunden hast. Es ist eine sehr spalterische Problemlösungstechnik und ach so falsch. Es sieht aus wie ein klassischer Fall von Überdenken. Einige der Shell-Scripting-Lösungen von Glen Jackman (und zwei sind reine Shell-Lösungen, die keine Dinge wie awkund verwenden bc). Diese alle haben in weniger als 10 Sekunden eine Million Zahlen addiert. Schauen Sie sich diese an und sehen Sie, wie es in reiner Schale gemacht werden kann.
David W.

Antworten:


113

Für einen Perl-Einzeiler ist es im Grunde dasselbe wie die awkLösung in Ayman Houriehs Antwort :

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

Wenn Sie neugierig sind, was Perl-Einzeiler tun, können Sie sie trennen:

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

Das Ergebnis ist eine ausführlichere Version des Programms in einer Form, die niemand alleine schreiben würde:

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

Nur zum Kichern habe ich dies mit einer Datei versucht, die 1.000.000 Zahlen enthält (im Bereich von 0 bis 9.999). Auf meinem Mac Pro kehrt es praktisch sofort zurück. Das ist schade, denn ich hatte gehofft, dass die Verwendung mmapsehr schnell sein würde, aber es ist genau die gleiche Zeit:

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, das zeigt ein tiefes Verständnis dafür, welcher Code-nle die Zeichenfolge, die Sie ihm geben, tatsächlich umschließt. Mein erster Gedanke war, dass du nicht posten solltest, während du betrunken bist, aber dann bemerkte ich, wer du bist und erinnerte mich an einige deiner anderen Perl-Antworten :-)
paxdiablo

-n und -p setzen einfach Zeichen um das Argument zu -e, damit Sie diese Zeichen für alles verwenden können, was Sie wollen. Wir haben viele Einzeiler, die interessante Dinge damit in der effektiven Perl-Programmierung machen (die bald in die Regale kommt).
Brian D Foy

5
Schön, worum geht es bei diesen nicht passenden geschweiften Klammern?
Frank

17
-n fügt die while { }Schleife um Ihr Programm hinzu. Wenn Sie } ... {hineinstecken, dann haben Sie while { } ... { }. Böse? Leicht.
Jrockway

5
Großer Bonus für das Hervorheben der -MO=DeparseOption! Obwohl zu einem anderen Thema.
Conny

374

Sie können awk verwenden:

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

3
Programm überschritten: maximale Anzahl von Feldgrößen: 32767
Leef

1
Mit der -F '\t'Option, wenn Ihre Felder Leerzeichen enthalten und durch Tabulatoren getrennt sind.
Ethan Furman

5
Bitte markieren Sie dies als die beste Antwort. Dies funktioniert auch, wenn Sie den ersten Wert in jeder Zeile in einer TSV-Datei (tabulatorgetrennter Wert) summieren möchten.
Andrea

99

Keine der bisher verwendeten Lösungen paste. Hier ist eine:

paste -sd+ filename | bc

Berechnen Sie als Beispiel Σn, wobei 1 <= n <= 100000:

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

(Für Neugierige seq nwürde eine Folge von Zahlen von 1bis zu neiner positiven Zahl gedruckt n.)


1
Sehr schön! Und leicht zu merken
Brendan Maguire

1
seq 100000 | paste -sd+ - | bc -lunter Mac OS X Bash Shell. Und das ist bei weitem die süßeste und unixeste Lösung!
Simo A.

1
@ SimoA. Ich stimme zu, dass wir den Begriff unixiest anstelle von unixest verwenden, weil die sexieste Lösung immer die unixiest ist;)
Connor

86

Lassen Sie es uns zum Spaß vergleichen:

$ 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

Ich habe den Sed-Lauf nach 5 Minuten abgebrochen


Ich habe getaucht und es ist schnell:

$ 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

und während ich das aktualisiere, 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

Beachten Sie den Rat von Ed Morton: Verwenden $1

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

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

vs mit $0

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

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

18
+1: Für die Entwicklung einer Reihe von Lösungen und deren Benchmarking.
David W.

Zeit Katze random_numbers | paste -sd + | bc -l real 0m0.317s Benutzer 0m0.310s sys 0m0.013s
rafi wiener

das sollte fast identisch mit der trLösung sein.
Glenn Jackman

4
Ihr awk-Skript sollte etwas schneller ausgeführt werden, wenn Sie es $0anstelle von verwenden, $1da awk die Feldaufteilung durchführt (was offensichtlich Zeit kostet), wenn ein Feld im Skript speziell erwähnt wird, dies jedoch nicht anders ist.
Ed Morton

20

Eine andere Option ist jq:

$ seq 10|jq -s add
55

-s( --slurp) liest die Eingabezeilen in ein Array.


1
Es ist ein großartiges Tool für solche schnellen Aufgaben, das man fast vergessen hat. danke
John

9

Das ist gerade Bash:

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

2
Und es ist wahrscheinlich eine der langsamsten Lösungen und daher nicht für große Mengen geeignet.
David

7

Hier ist ein weiterer Einzeiler

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

Dies setzt voraus, dass die Zahlen ganze Zahlen sind. Wenn Sie Dezimalstellen benötigen, versuchen Sie es

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

Stellen Sie 2 auf die Anzahl der benötigten Dezimalstellen ein.


6

Ich bevorzuge die Verwendung von GNU-Datamash für solche Aufgaben, da es prägnanter und lesbarer ist als Perl oder Awk. Beispielsweise

datamash sum 1 < myfile

wobei 1 die erste Datenspalte bezeichnet.


1
Dies scheint keine Standardkomponente zu sein, da ich sie in meiner Ubuntu-Installation nicht sehe. Ich würde es aber gerne als Benchmark sehen.
Steven der leicht amüsierte

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

5

Ich bevorzuge es, R dafür zu verwenden:

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

Ich bin ein Fan von R für andere Anwendungen, aber es ist nicht gut für die Leistung auf diese Weise. Datei-E / A ist ein Hauptproblem. Ich habe getestet, wie Argumente an ein Skript übergeben werden, das mit dem vroom-Paket beschleunigt werden kann. Ich werde weitere Details veröffentlichen, wenn ich einige andere Skripte auf demselben Server verglichen habe.
Tom Kelly

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

(wie brian d foys antwort, ohne 'END')


Ich mag das, aber könnten Sie die geschweiften Klammern erklären? Es ist komisch, ohne zu sehen und umgekehrt.
Trommelfeuer

1
@drumfire siehe die Antwort von @brian d foy oben mit, um perl -MO=Deparsezu sehen, wie Perl das Programm analysiert. oder die Dokumente für perlrun: perldoc.perl.org/perlrun.html (Suche nach -n). Perl umschließt Ihren Code mit {}, wenn Sie -n verwenden, damit er zu einem vollständigen Programm wird.
edibleEnergy

4

Prägnanter:

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

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

Die Konvertierung in Float scheint auf meinem System etwa doppelt so schnell zu sein (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

Nur zum Spaß, machen wir es mit PDL , Perls Array-Mathe-Engine!

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

rcolsliest Spalten in eine Matrix (in diesem Fall 1D) und sumsummiert (überraschend) das gesamte Element der Matrix.


Wie kann PDL.pm in @INC nicht gefunden werden (möglicherweise müssen Sie das PDL-Modul installieren) (@INC enthält: / etc / perl /usr/local/lib/x86_64-linux-gnu/perl/5.22.1? )) zum Spaß natürlich =)
Fortran

1
Sie müssen zuerst PDL installieren, es ist kein natives Perl-Modul.
Joel Berger

3

Hier ist eine Lösung mit Python mit einem Generatorausdruck. Getestet mit einer Million Nummern auf meinem alten, groben Laptop.

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
Ein einfaches Listenverständnis mit einer benannten Funktion ist ein map()map(float, sys.stdin)
guter

3

Ich konnte nicht einfach vorbeikommen ... Hier ist mein Haskell Einzeiler. Es ist eigentlich gut lesbar:

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

Leider gibt es keine Möglichkeit, es ghci -eeinfach auszuführen, daher benötigt es die Hauptfunktion, Drucken und Kompilieren.

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

Zur Verdeutlichung lesen wir die gesamte Eingabe ( getContents), teilen sie durch lines, readals Zahlen und sum. <$>ist fmapOperator - wir verwenden ihn anstelle der üblichen Funktionsanwendung, da dies alles sicher in IO geschieht. readbraucht eine zusätzliche fmap, da es auch in der Liste ist.

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

Hier ist ein seltsames Upgrade, damit es mit Floats funktioniert:

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


2

Ausführen von R-Skripten

Ich habe ein R-Skript geschrieben, um Argumente eines Dateinamens zu übernehmen und die Zeilen zu summieren.

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

Dies kann mit dem Paket "data.table" oder "vroom" wie folgt beschleunigt werden:

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

Benchmarking

Gleiche Benchmarking-Daten wie @glenn jackman .

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

Im Vergleich zum obigen R-Aufruf ist das Ausführen von R 3.5.0 als Skript mit anderen Methoden vergleichbar (auf demselben Linux-Debian-Server).

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

R-Skript mit readLines

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

R-Skript mit data.table

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

R-Skript mit vroom

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

Vergleich mit anderen Sprachen

Als Referenz hier einige andere Methoden, die auf derselben Hardware vorgeschlagen wurden

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 (Clang-Version 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

Update mit weiteren Sprachen

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) muss in bash zeitgesteuert sein und ist nicht mit zsh kompatibel

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

sed (4.4) muss in bash zeitgesteuert sein und ist nicht mit zsh kompatibel

$  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

Hinweis: sed-Aufrufe scheinen auf Systemen mit mehr verfügbarem Speicher schneller zu funktionieren (beachten Sie kleinere Datensätze, die für das Benchmarking von sed verwendet werden).

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

Beachten Sie, dass Datei-E / A-Methoden wie in R eine unterschiedliche Leistung aufweisen.


2

C ++ "Einzeiler":

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

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

1

Ein anderer zum Spaß

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

oder nur eine andere Bash

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

Aber awk Lösung ist wahrscheinlich am besten, da es am kompaktesten ist.


1

C gewinnt immer für Geschwindigkeit:

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

Timing für 1M-Nummern (gleiche Maschine / Eingabe wie meine Python-Antwort):

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

1
Beste Antwort! Beste Geschwindigkeit)
Fortran

1

Mit Ruby:

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

Eine andere Option (wenn die Eingabe von STDIN erfolgt) ist ruby -e'p readlines.map(&:to_f).reduce(:+)'.
Nisetama

0

Ich weiß nicht, ob Sie viel besser werden können, wenn man bedenkt, dass Sie die gesamte Datei durchlesen müssen.

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

1
Sehr gut lesbar. Für Perl. Aber ja, es muss so etwas sein ...
dmckee --- Ex-Moderator Kätzchen

$_ist die Standardvariable. Der Zeileneingabeoperator <>gibt das Ergebnis standardmäßig dort ein, wenn Sie <>in verwenden while.
Brian D Foy

1
@Mark, $_ist die Themenvariable - sie funktioniert wie das 'es'. In diesem Fall wird ihm <> jede Zeile zugewiesen. Es wird an mehreren Stellen verwendet, um Code-Unordnung zu reduzieren und beim Schreiben von Einzeilern zu helfen. Das Skript sagt: "Setzen Sie die Summe auf 0, lesen Sie jede Zeile und addieren Sie sie zur Summe. Drucken Sie dann die Summe aus."
Daotoad

1
@Stefan, ohne Warnungen und Einschränkungen können Sie das Deklarieren und Initialisieren überspringen $sum. Da dies so einfach ist, können Sie sogar einen Anweisungsmodifikator verwenden while:$sum += $_ while <>; print $sum;
daotoad

0

Ich habe dies nicht getestet, aber es sollte funktionieren:

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

Möglicherweise müssen Sie dem String vor bc "\ n" hinzufügen (wie über Echo), wenn bc EOF und EOL nicht behandelt ...


2
Es funktioniert nicht. bcgibt einen Syntaxfehler aufgrund des nachgestellten "+" und des Mangels an Zeilenumbruch am Ende aus. Dies funktioniert und verhindert die nutzlose Verwendung von cat: { tr "\n" "+" | sed 's/+$/\n/'| bc; } < numbers2.txt oder <numbers2.txt tr "\n" "+" | sed 's/+$/\n/'| bc
Bis auf weiteres angehalten.

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

0

Hier ist ein anderes:

open(FIL, "a.txt");

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

close(FIL);

print "Sum = $sum\n";

0

Sie können dies mit Alacon tun - dem Befehlszeilenprogramm für die Alasql- Datenbank.

Es funktioniert mit Node.js, daher müssen Sie Node.js und dann das Alasql- Paket installieren :

Um die Summe aus der TXT-Datei zu berechnen, können Sie den folgenden Befehl verwenden:

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

0

Es ist nicht einfacher, alle neuen Zeilen durch zu ersetzen +, eine hinzuzufügen 0und an den RubyDolmetscher zu senden ?

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

Wenn Sie nicht haben irb, können Sie es an senden bc, aber Sie müssen alle Zeilenumbrüche außer dem letzten (von echo) entfernen . Es ist besser, trdies zu verwenden, es sei denn, Sie haben einen Doktortitel 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)
}

Was ist "64"? "10" ist wohl Basis?
Peter K

Ja, 10 ist die Basis. 64 ist die Anzahl der Bits. Wenn das resultierende int nicht mit so vielen Bits dargestellt werden kann, wird ein Fehler zurückgegeben. Siehe golang.org/pkg/strconv/#ParseInt
dwurf

0

Bash-Variante

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 der Shell mit awk habe ich das folgende Skript verwendet, um dies zu tun:

    #!/bin/bash


total=0;

for i in $( awk '{ print $1; }' <myfile> )
do
 total=$(echo $total+$i | bc )
 ((count++))
done
echo "scale=2; $total " | bc
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.