Comment additionner rapidement tous les nombres d'un fichier?

194

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 elle n'est pas très efficace. (Cela prend plusieurs minutes.) Je recherche une solution plus efficace. Aucune suggestion?

Mark Roberts
la source
5
Quelle a été votre solution lente? Nous pouvons peut-être vous aider à comprendre ce qui a été lent à ce sujet. :)
brian d foy
4
@brian d foy, je suis trop gêné pour le poster. Je sais pourquoi c'est lent. C'est parce que j'appelle "cat filename | head -n 1" pour obtenir le numéro supérieur, l'ajoute à un total cumulé et appelle "cat filename | tail ..." pour supprimer la ligne supérieure pour la prochaine itération ... I avoir beaucoup à apprendre sur la programmation !!!
Mark Roberts
6
C'est ... très systématique. Très clair et simple, et je l'aime pour autant que c'est une horrible abomination. Construit, je suppose, à partir des outils que vous connaissiez lorsque vous avez commencé, non?
dmckee --- chaton ex-modérateur
4
duplicata complet: stackoverflow.com/questions/450799/…
codeholic
@MarkRoberts Cela a dû vous prendre beaucoup de temps pour trouver une solution. C'est une technique de résolution de problèmes très fendeuse, et oh si mal. Cela ressemble à un cas classique de réflexion excessive. Plusieurs des solutions de script de shell de Glen Jackman (et deux sont des shell purs qui n'utilisent pas des choses comme awket bc). Ils ont tous fini d'ajouter un million de numéros en moins de 10 secondes. Jetez un oeil à ceux-ci et voyez comment cela peut être fait en pure coque.
David W.

Réponses:

113

Pour un monoplace Perl, c'est essentiellement la même chose que la awksolution dans la réponse d'Ayman Hourieh :

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

Si vous êtes curieux de savoir ce que font les monolignes Perl, vous pouvez les analyser:

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

Le résultat est une version plus verbeuse du programme, sous une forme que personne n'écrirait jamais 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 de numéros (compris entre 0 et 9 999). Sur mon Mac Pro, il revient pratiquement instantanément. C'est dommage, car j'espérais que l'utilisation mmapserait très rapide, mais c'est juste en même temps:

use 5.010;
use File::Map qw(map_file);

map_file my $map, $ARGV[0];

$sum += $1 while $map =~ m/(\d+)/g;

say $sum;
brian d foy
la source
4
Wow, cela montre une compréhension profonde de ce que le code -nle entoure réellement la chaîne que vous lui donnez. Ma pensée initiale était que vous ne deviez pas poster en état d'ébriété, mais j'ai remarqué qui vous étiez et je me suis souvenu de certaines de vos autres réponses Perl :-)
paxdiablo
-n et -p mettent juste des caractères autour de l'argument à -e, donc vous pouvez utiliser ces caractères pour ce que vous voulez. Nous avons beaucoup de monolignes qui font des choses intéressantes avec cela dans la programmation Perl efficace (qui est sur le point de frapper les étagères).
brian d foy
5
Bien, de quoi parlent ces accolades frisées qui ne correspondent pas?
Frank
17
-n ajoute la while { }boucle autour de votre programme. Si vous mettez à l' } ... {intérieur, alors vous l'avez while { } ... { }. Mal? Légèrement.
jrockway
5
Gros bonus pour mettre en évidence l' -MO=Deparseoption! Même si sur un sujet distinct.
conny
374

Vous pouvez utiliser awk:

awk '{ sum += $1 } END { print sum }' file
Ayman Hourieh
la source
3
programme dépassé: nombre maximum de tailles de champs: 32767
leef
1
Avec l' -F '\t'option si vos champs contiennent des espaces et sont séparés par des tabulations.
Ethan Furman
5
Veuillez indiquer cela comme la meilleure réponse. Cela fonctionne également si vous souhaitez additionner la première valeur de chaque ligne, dans un fichier TSV (valeur séparée par des tabulations).
Andrea
99

Aucune solution n'a été utilisée jusqu'à présent 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 nimprimerait une séquence de nombres de 1à ndonné un nombre positif n.)

devnull
la source
1
Très agréable! Et facile à retenir
Brendan Maguire
1
seq 100000 | paste -sd+ - | bc -lsur le shell Mac OS X Bash. Et c'est de loin la solution la plus douce et la plus unix!
Simo A.
1
@SimoA. Je vote que nous utilisons le terme unixiest au lieu d'unixest parce que la solution la plus sexy est toujours la plus unix;)
Connor
86

Juste pour le plaisir, comparons-le:

$ 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 abandonné la course sed après 5 minutes


J'ai plongé , et c'est rapide:

$ 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

et pendant que je mets à jour ça, 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

Tenez compte des conseils d'Ed Morton: utiliser $1

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

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

vs utiliser $0

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

real    0m0.302s
user    0m0.234s
sys     0m0.063s
glenn jackman
la source
18
+1: pour avoir proposé un tas de solutions et les avoir comparées.
David W.
time cat random_numbers | paste -sd + | bc -l real 0m0.317s user 0m0.310s sys 0m0.013s
rafi wiener
cela devrait être à peu près identique à la trsolution.
glenn jackman
4
Votre script awk devrait s'exécuter un peu plus rapidement si vous utilisez $0au lieu de, $1car awk divise les champs (ce qui prend évidemment du temps) si un champ est spécifiquement mentionné dans le script mais ne le fait pas autrement.
Ed Morton
20

Une autre option consiste à utiliser jq:

$ seq 10|jq -s add
55

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

nisetama
la source
1
C'est un outil génial pour des tâches rapides comme ça, presque oublié. merci
John
9

C'est tout simplement Bash:

sum=0
while read -r line
do
    (( sum += line ))
done < file
echo $sum
En pause jusqu'à nouvel ordre.
la source
2
Et c'est probablement l'une des solutions les plus lentes et donc pas si appropriée pour de grandes quantités de nombres.
David
7

Voici un autre doublure

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

Cela suppose que les nombres sont des nombres entiers. Si vous avez besoin de décimales, essayez

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

Ajustez 2 au nombre de décimales nécessaires.

lhf
la source
6

Je préfère utiliser le datamash GNU pour de telles 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.

hertzsprung
la source
1
Cela ne semble pas être un composant standard car je ne le vois pas dans mon installation Ubuntu. J'aimerais cependant qu'il soit comparé.
Steven the Easily Amused
5
$ perl -MList::Util=sum -le 'print sum <>' nums.txt
Zaid
la source
5

Je préfère utiliser R pour cela:

$ R -e 'sum(scan("filename"))'
fedorn
la source
Je suis fan de R pour d'autres applications mais ce n'est pas bon pour les performances de cette façon. Les E / S sur fichiers sont un problème majeur. J'ai testé la transmission d'arguments à un script qui peut être accéléré à l'aide du package vroom. Je publierai plus de détails lorsque j'aurai testé d'autres scripts sur le même serveur.
Tom Kelly
4
cat nums | perl -ne '$sum += $_ } { print $sum'

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

edibleEnergy
la source
J'aime ça, mais pourriez-vous expliquer les accolades? C'est bizarre de voir} sans {et vice versa.
drumfire

1
@drumfire voir la réponse de @brian d foy ci-dessus avec perl -MO=Deparsepour voir comment perl analyse le programme. ou les documents pour perlrun: perldoc.perl.org/perlrun.html (recherchez -n). perl enveloppe votre code avec {} si vous utilisez -n pour qu'il devienne un programme complet.
edibleEnergy
4

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")))'
Vidul
la source
La conversion en float semble être environ deux fois plus rapide sur mon système (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
Brad Gilbert
la source
3

Juste pour le plaisir, faisons-le avec PDL , le moteur mathématique de Perl!

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

rcolslit les colonnes dans une matrice (1D dans ce cas) et sum(surprise) additionne tous les éléments de la matrice.

Joel Berger
la source
Comment résoudre le problème Impossible de localiser PDL.pm dans @INC (vous devrez peut-être installer le module PDL) (@INC contient: / etc / perl /usr/local/lib/x86_64-linux-gnu/perl/5.22.1? )) pour le plaisir bien sûr =)
Fortran
1
Vous devez d'abord installer PDL, ce n'est pas un module natif Perl.
Joel Berger
3

Voici une solution utilisant python avec une expression de générateur. Testé avec un million de numéros sur mon vieux portable cruddy.

time python -c "import sys; print sum((float(l) for l in sys.stdin))" < file

real    0m0.619s
user    0m0.512s
sys     0m0.028s
dwurf
la source
3
Une simple compréhension de liste avec une fonction nommée est un bon cas d'utilisation pour map():map(float, sys.stdin)
sevko
3

Je ne pouvais pas simplement passer ... Voici mon one-liner Haskell. C'est en fait assez lisible:

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

Malheureusement, il n'y a pas ghci -eà l'exécuter, il a donc besoin de la fonction principale, de l'impression et de la compilation.

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

Pour clarifier, nous lisons l'intégralité de input ( getContents), divisé par lines, readsous forme de nombres et sum. <$>est fmapopérateur - nous l'utilisons à la place de l'application de fonction habituelle car bien sûr, tout cela se produit dans IO. reada besoin d'un supplément fmap, car il est également dans la liste.

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

Voici une étrange mise à niveau pour le faire fonctionner avec des flotteurs:

main = ((0.0 + ) <$> sum <$> (read <$>) <$> lines <$> getContents) >>= print
$ ./sum 
1.3
2.1
4.2
^D
7.6000000000000005
Peter K
la source
2
sed ':a;N;s/\n/+/;ta' file|bc
ghostdog74
la source
2

Exécution de 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 package "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))

Analyse comparative

Mêmes données d'analyse comparative 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

Mettre à 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

note: les appels sed semblent fonctionner plus rapidement sur les systèmes avec plus de mémoire disponible (notez les petits ensembles de données utilisés pour comparer 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 de fichiers ont des performances différentes.

Tom Kelly
la source
2

C ++ "one-liner":

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

int main() {
    cout << accumulate(istream_iterator<int>(cin), istream_iterator<int>(), 0) << endl;
}
Peter K
la source
1

Un autre pour le plaisir

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

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

nickjb
la source
1

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

Timing 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
dwurf
la source
1
Meilleure réponse! Meilleure vitesse)
Fortran
1

Avec Ruby:

ruby -e "File.read('file.txt').split.inject(0){|mem, obj| mem += obj.to_f}"
des sites
la source
Une autre option (lorsque l'entrée provient de STDIN) est ruby -e'p readlines.map(&:to_f).reduce(:+)'.
nisetama
0

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;
Stefan Kendall
la source
1
Très lisible. Pour perl. Mais oui, ça va devoir être quelque chose comme ça ...
dmckee --- chaton ex-modérateur
$_est la variable par défaut. L'opérateur d'entrée de ligne, <>, met son résultat là - dedans par défaut lorsque vous utilisez <>dans while.
brian d foy
1
@Mark, $_est la variable de sujet - elle fonctionne comme le «il». Dans ce cas, <> lui assigne chaque ligne. Il est utilisé à plusieurs endroits pour réduire l'encombrement du code et aider à l'écriture de lignes simples. Le script dit "Réglez la somme à 0, lisez chaque ligne et ajoutez-la à la somme, puis imprimez la somme."
daotoad
1
@Stefan, avec les avertissements et les restrictions désactivés, vous pouvez ignorer la déclaration et l'initialisation $sum. Comme c'est si simple, vous pouvez même utiliser un modificateur d'instruction while:$sum += $_ while <>; print $sum;
daotoad
0

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

DVK
la source
2
Ça ne marche pas. bcémet une erreur de syntaxe à cause du "+" de fin et du manque de nouvelle ligne à la fin. Cela fonctionnera et éliminera une utilisation inutile de cat: { tr "\n" "+" | sed 's/+$/\n/'| bc; } < numbers2.txt ou <numbers2.txt tr "\n" "+" | sed 's/+$/\n/'| bc
Suspendue jusqu'à nouvel ordre.
tr "\n" "+" <file | sed 's/+$/\n/' | bc
ghostdog74
0

En voici une autre:

open(FIL, "a.txt");

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

close(FIL);

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

Vous pouvez le faire avec Alacon - utilitaire de ligne de commande pour Alasql base de données .

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

Il n'est pas plus facile de remplacer toutes les nouvelles lignes par +, d'ajouter un 0et de l'envoyer à l' Rubyinterpréteur?

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

Si vous n'en avez pas irb, vous pouvez l'envoyer à bc, mais vous devez supprimer toutes les nouvelles lignes sauf la dernière (de echo). Il est préférable de l'utiliser trpour cela, sauf si vous avez un doctorat sed.

(sed -e "s/$/+/" file|tr -d "\n"; echo 0)|bc
Daniel Porumbel
la source
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)
}
dwurf
la source
Qu'est-ce que "64"? "10" Je suppose que c'est la base?
Peter K
Oui, 10 est la base. 64 est le nombre de bits, si l'int résultant ne peut pas être représenté avec autant de bits, une erreur est renvoyée. Voir golang.org/pkg/strconv/#ParseInt
dwurf
0

Variante bash

raw=$(cat file)
echo $(( ${raw//$'\n'/+} ))

$ wc -l file
10000 file

$ time ./test
323390

real    0m3,096s
user    0m3,095s
sys     0m0,000s
Ivan
la source
0

En shell utilisant awk, j'ai utilisé le script ci-dessous pour le faire:

    #!/bin/bash


total=0;

for i in $( awk '{ print $1; }' <myfile> )
do
 total=$(echo $total+$i | bc )
 ((count++))
done
echo "scale=2; $total " | bc
Shiwangini
la source