it-swarm.dev

Wie kann ich schnell alle Zahlen in einer Datei summieren?

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

34
42
11
6
2
99
...

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

162
Mark Roberts

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

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

Wenn Sie wissen möchten, was Perl-One-Liner tun, können Sie sie abschaffen:

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

Das Ergebnis ist eine ausführlichere Version des Programms in einer Form, die niemand je 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 mit 1.000.000 Zahlen (im Bereich von 0 - 9.999) versucht. Bei meinem Mac Pro kehrt es praktisch sofort zurück. Das ist zu schade, weil ich gehofft hatte, dass mmap sehr schnell wäre, 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;
94
brian d foy

Sie können awk verwenden:

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

Keine der Lösungen verwendet bisher paste. Hier ist eins:

paste -sd+ filename | bc

Berechnen Sie als Beispiel Σn mit 1 <= n <= 100000:

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

(Für Neugierige würde seq n bei einer positiven Zahl n eine Zahlenfolge von 1 bis n drucken.)

87
devnull

Nur zum Spaß: Benchmarking:

$ 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

76
glenn jackman

Das funktioniert:

{ tr '\n' +; echo 0; } < file.txt | bc
21
Mark L. Smith

Eine weitere Option ist die Verwendung von jq:

$ seq 10|jq -s add
55

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

14
nisetama

Das ist gerade Bash:

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

Hier ist ein weiterer Einzeiler

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

Dies setzt voraus, dass die Zahlen ganze Zahlen sind. Wenn Sie Dezimalzahlen 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.

7
lhf

Ich bevorzuge die Verwendung von GNU -Datamash für solche Aufgaben, weil sie prägnanter und lesbarer ist als Perl oder awk. Zum Beispiel

datamash sum 1 < myfile

dabei bezeichnet 1 die erste Datenspalte.

4
hertzsprung

Zum Spaß machen wir das mit PDL , der Array-Math-Engine von Perl!

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

rcols liest Spalten in eine Matrix (in diesem Fall 1D) und sum (Überraschung) summiert das gesamte Element der Matrix.

3
Joel Berger

Ich ziehe es vor, R dafür zu verwenden:

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

Hier ist eine Lösung, die Python mit einem Generatorausdruck verwendet. Getestet mit einer Million Zahlen 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
dwurf
cat nums | Perl -ne '$sum += $_ } { print $sum'

(Gleiche Antwort von Brian D Foy, ohne 'END')

3
edibleEnergy

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")))'
2
Vidul
sed ':a;N;s/\n/+/;ta' file|bc
2
ghostdog74
$ Perl -MList::Util=sum -le 'print sum <>' nums.txt
2
Zaid

Perl 6

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

~$ Perl6 -e 'say sum lines' < test.in
500000500000
2
Brad Gilbert

Ausführen von R-Skripten

Ich habe ein R-Skript geschrieben, um die 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 Benchmark-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 vergleichbar mit anderen Methoden (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 auf der gleichen Hardware vorgeschlagen

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

Rubin (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 zusätzlichen 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 zeitlich festgelegt sein, nicht kompatibel mit zsh

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

sed (4.4) muss in bash zeitlich festgelegt werden, nicht kompatibel mit 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

hinweis: sed-Aufrufe scheinen auf Systemen mit mehr verfügbarem Speicher schneller zu funktionieren (beachten Sie, dass kleinere Datensätze 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.

1
Tom Kelly

Ich habe das nicht getestet, aber es sollte funktionieren:

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

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

1
DVK

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

Die Lösung von awk ist jedoch am besten geeignet, da sie am kompaktesten ist.

1
nickjb

Mit Ruby:

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

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
0
dwurf

Sie können dies mit Alacon - Befehlszeilenhilfsprogramm für Alasql database tun.

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

Zum Berechnen der Summe aus der Datei TXT können Sie den folgenden Befehl verwenden:

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

Hier ist ein anderes:

open(FIL, "a.txt");

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

close(FIL);

print "Sum = $sum\n";
0
ruben2020

Ich weiß nicht, ob Sie wesentlich besser werden können, wenn Sie die gesamte Datei durchlesen.

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

Es ist nicht einfacher, alle neuen Zeilen durch + zu ersetzen, einen 0 hinzuzufügen und an den Ruby-Interpreter zu senden.

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

Wenn Sie irb nicht haben, können Sie es an bc senden, aber Sie müssen alle neuen Zeilen außer der letzten (von echo) entfernen. Es ist besser, tr zu verwenden, es sei denn, Sie haben einen Doktortitel in sed.

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