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?
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;
Sie können awk verwenden:
awk '{ sum += $1 } END { print sum }' file
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.)
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
Das funktioniert:
{ tr '\n' +; echo 0; } < file.txt | bc
Eine weitere Option ist die Verwendung von jq
:
$ seq 10|jq -s add
55
-s
(--Slurp
) liest die Eingabezeilen in ein Array.
Das ist gerade Bash:
sum=0
while read -r line
do
(( sum += line ))
done < file
echo $sum
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.
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.
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.
Ich ziehe es vor, R dafür zu verwenden:
$ R -e 'sum(scan("filename"))'
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
cat nums | Perl -ne '$sum += $_ } { print $sum'
(Gleiche Antwort von Brian D Foy, ohne 'END')
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")))'
sed ':a;N;s/\n/+/;ta' file|bc
$ Perl -MList::Util=sum -le 'print sum <>' nums.txt
say sum lines
~$ Perl6 -e '.say for 0..1000000' > test.in
~$ Perl6 -e 'say sum lines' < test.in
500000500000
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))
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
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
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.
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.
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.
Mit Ruby:
Ruby -e "File.read('file.txt').split.inject(0){|mem, obj| mem += obj.to_f}"
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
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')"
Hier ist ein anderes:
open(FIL, "a.txt");
my $sum = 0;
foreach( <FIL> ) {chomp; $sum += $_;}
close(FIL);
print "Sum = $sum\n";
Ich weiß nicht, ob Sie wesentlich besser werden können, wenn Sie die gesamte Datei durchlesen.
$sum = 0;
while(<>){
$sum += $_;
}
print $sum;
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