web-dev-qa-db-fra.com

Comment puis-je additionner rapidement tous les nombres d'un fichier?

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?

162
Mark Roberts

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;
94
brian d foy

Vous pouvez utiliser awk:

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

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.)

87
devnull

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

76
glenn jackman

Cela marche:

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

Une autre option consiste à utiliser jq:

$ seq 10|jq -s add
55

-s (--Slurp) lit les lignes d'entrée dans un tableau.

14
nisetama

C'est droit Bash:

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

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.

7
lhf

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.

4
hertzsprung

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.

3
Joel Berger

Je préfère utiliser R pour cela:

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

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
3
dwurf
cat nums | Perl -ne '$sum += $_ } { print $sum'

(identique à la réponse de brian d foy, sans 'FIN')

3
edibleEnergy

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")))'
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

Exécuter des scripts R

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

Benchmarking

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

Comparaison avec d'autres langues

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

Mise à jour avec des langues supplémentaires

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.

1
Tom Kelly

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 ...

1
DVK

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.

1
nickjb

Avec rubis:

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

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

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')"
0
agershun

En voici un autre:

open(FIL, "a.txt");

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

close(FIL);

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

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;
0
Stefan Kendall

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
0
Daniel Porumbel