J'ai un fichier qui contient plusieurs milliers de numéros, chacun sur sa propre ligne:
34
42
11
6
2
99
...
Je cherche à écrire un script qui imprimera la somme de tous les nombres du fichier. J'ai une solution, mais ce n'est pas très efficace. (Cela prend plusieurs minutes.) Je cherche une solution plus efficace. Aucune suggestion?
Pour un one-liner Perl, c'est fondamentalement la même chose que la solution awk
dans Réponse d'Ayman Hourieh :
% Perl -nle '$sum += $_ } END { print $sum'
Si vous êtes curieux de savoir ce que Perl one-liners fait, vous pouvez les séparer:
% Perl -MO=Deparse -nle '$sum += $_ } END { print $sum'
Le résultat est une version plus verbeuse du programme, sous une forme que personne ne pourrait écrire seul:
BEGIN { $/ = "\n"; $\ = "\n"; }
LINE: while (defined($_ = <ARGV>)) {
chomp $_;
$sum += $_;
}
sub END {
print $sum;
}
-e syntax OK
Juste pour rire, j’ai essayé cela avec un fichier contenant 1 000 000 numéros (de 0 à 9 999). Sur mon Mac Pro, le retour est quasi instantané. C'est dommage, car j'espérais que mmap
serait très rapide, mais c'est exactement la même chose:
use 5.010;
use File::Map qw(map_file);
map_file my $map, $ARGV[0];
$sum += $1 while $map =~ m/(\d+)/g;
say $sum;
Vous pouvez utiliser awk:
awk '{ sum += $1 } END { print sum }' file
Aucune des solutions utilisées jusqu’à présent n’utilise paste
. En voici un:
paste -sd+ filename | bc
Par exemple, calculez Σn où 1 <= n <= 100000:
$ seq 100000 | paste -sd+ | bc -l
5000050000
(Pour les curieux, seq n
imprime une séquence de nombres de 1
à n
avec un nombre positif n
.)
Juste pour le plaisir, mesurons cela:
$ 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
J'ai avorté la course après 5 minutes
Cela marche:
{ tr '\n' +; echo 0; } < file.txt | bc
Une autre option consiste à utiliser jq
:
$ seq 10|jq -s add
55
-s
(--Slurp
) lit les lignes d'entrée dans un tableau.
C'est droit Bash:
sum=0
while read -r line
do
(( sum += line ))
done < file
echo $sum
Voici un autre one-liner
( echo 0 ; sed 's/$/ +/' foo ; echo p ) | dc
Cela suppose que les nombres sont des entiers. Si vous avez besoin de nombres décimaux, essayez
( echo 0 2k ; sed 's/$/ +/' foo ; echo p ) | dc
Ajustez 2 au nombre de décimales nécessaires.
Je préfère utiliser GNU datamash pour ces tâches, car il est plus succinct et lisible que Perl ou awk. Par exemple
datamash sum 1 < myfile
où 1 désigne la première colonne de données.
Juste pour le plaisir, faisons-le avec PDL , le moteur de calcul matriciel de Perl!
Perl -MPDL -E 'say rcols(shift)->sum' datafile
rcols
lit les colonnes dans une matrice (1D dans ce cas) et sum
(surprise) additionne tous les éléments de la matrice.
Je préfère utiliser R pour cela:
$ R -e 'sum(scan("filename"))'
Voici une solution utilisant python avec une expression génératrice. Testé avec un million de numéros sur mon vieux portable grossier.
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'
(identique à la réponse de brian d foy, sans 'FIN')
Plus succinct:
# 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
J'ai écrit un script R pour prendre les arguments d'un nom de fichier et additionner les lignes.
#! /usr/local/bin/R
file=commandArgs(trailingOnly=TRUE)[1]
sum(as.numeric(readLines(file)))
Cela peut être accéléré avec le paquet "data.table" ou "vroom" comme suit:
#! /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))
Mêmes données de référence que @ glenn jackman .
for ((i=0; i<1000000; i++)) ; do echo $RANDOM; done > random_numbers
Par rapport à l'appel R ci-dessus, exécuter R 3.5.0 en tant que script est comparable à d'autres méthodes (sur le même serveur Linux Debian).
$ time R -e 'sum(scan("random_numbers"))'
0.37s user
0.04s system
86% cpu
0.478 total
Script R avec readLines
$ time Rscript sum.R random_numbers
0.53s user
0.04s system
84% cpu
0.679 total
Script R avec data.table
$ time Rscript sum.R random_numbers
0.30s user
0.05s system
77% cpu
0.453 total
Script R avec vroom
$ time Rscript sum.R random_numbers
0.54s user
0.11s system
93% cpu
0.696 total
Pour référence ici comme d'autres méthodes suggérées sur le même matériel
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
Rubis (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) doit être chronométré en bash, non compatible avec zsh
$time { { tr "\n" + < random_numbers ; echo 0; } | bc; }
real 0m0.494s
user 0m0.488s
sys 0m0.044s
sed (4.4) doit être chronométré en bash, non compatible avec 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
remarque: les appels sed semblent fonctionner plus rapidement sur les systèmes disposant de plus de mémoire disponible (notez les jeux de données plus petits utilisés pour l'analyse comparative 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
Notez que, comme dans R, les méthodes d’E/S sur fichier ont des performances différentes.
Je n'ai pas testé cela mais ça devrait marcher:
cat f | tr "\n" "+" | sed 's/+$/\n/' | bc
Vous devrez peut-être ajouter "\ n" à la chaîne avant BC (comme via echo) si BC ne traite pas EOF et EOL ...
Un autre pour le plaisir
sum=0;for i in $(cat file);do sum=$((sum+$i));done;echo $sum
ou une autre bash seulement
s=0;while read l; do s=$((s+$l));done<file;echo $s
Mais la solution awk est probablement la meilleure car elle est la plus compacte.
Avec rubis:
Ruby -e "File.read('file.txt').split.inject(0){|mem, obj| mem += obj.to_f}"
C gagne toujours pour la vitesse:
#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;
}
Chronométrage pour les nombres 1M (même machine/entrée que ma réponse python):
$ gcc sum.c -o sum && time ./sum < numbers
5003371677.000000
real 0m0.188s
user 0m0.180s
sys 0m0.000s
Vous pouvez le faire avec Alacon - utilitaire de ligne de commande pour Alasql database.
Cela fonctionne avec Node.js, vous devez donc installer Node.js puis Alasql package:
Pour calculer la somme à partir du fichier TXT, vous pouvez utiliser la commande suivante:
> node alacon "SELECT VALUE SUM([0]) FROM TXT('mydata.txt')"
En voici un autre:
open(FIL, "a.txt");
my $sum = 0;
foreach( <FIL> ) {chomp; $sum += $_;}
close(FIL);
print "Sum = $sum\n";
Je ne sais pas si vous pouvez obtenir beaucoup mieux que cela, étant donné que vous devez lire l'intégralité du fichier.
$sum = 0;
while(<>){
$sum += $_;
}
print $sum;
Il n’est pas plus facile de remplacer toutes les nouvelles lignes par +
, d’ajouter un 0
et de l’envoyer à l’interprète Ruby
?
(sed -e "s/$/+/" file; echo 0)|irb
Si vous n'avez pas irb
, vous pouvez l'envoyer à bc
, mais vous devez supprimer toutes les nouvelles lignes à l'exception de la dernière (de echo
). Il vaut mieux utiliser tr
pour cela, sauf si vous avez un doctorat sur sed
.
(sed -e "s/$/+/" file|tr -d "\n"; echo 0)|bc