¿Cómo puedo sumar rápidamente todos los números en un archivo?


194

Tengo un archivo que contiene varios miles de números, cada uno en su propia línea:

34
42
11
6
2
99
...

Estoy buscando escribir un script que imprima la suma de todos los números en el archivo. Tengo una solución, pero no es muy eficiente. (Tarda varios minutos en ejecutarse). Estoy buscando una solución más eficiente. ¿Alguna sugerencia?


55
¿Cuál fue tu solución lenta? Tal vez podamos ayudarlo a descubrir qué fue lento al respecto. :)
brian d foy

44
@brian d foy, me da mucha vergüenza publicarlo. Sé por qué es lento. Es porque llamo "cat filename | head -n 1" para obtener el número superior, lo agrego a un total acumulado y llamo "cat filename | tail ..." para eliminar la línea superior para la próxima iteración ... I tiene mucho que aprender sobre programación !!!
Mark Roberts

66
Eso es ... muy sistemático. Muy claro y directo, y me encanta por todo lo que es una horrible abominación. Construido, supongo, a partir de las herramientas que conocías cuando comenzaste, ¿verdad?
dmckee --- gatito ex moderador


@ MarkRoberts Debió tomarte mucho tiempo resolverlo. Es una técnica de resolución de problemas muy ingeniosa, y está muy mal. Parece un caso clásico de exceso de pensamiento. Varias de las soluciones de scripting de shell de Glen Jackman (y dos son shell puro que no usan cosas como awky bc). Todo esto terminó de sumar un millón de números en menos de 10 segundos. Eche un vistazo a esos y vea cómo se puede hacer en shell puro.
David W.

Respuestas:


113

Para una línea de Perl, es básicamente lo mismo que la awksolución en la respuesta de Ayman Hourieh :

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

Si tienes curiosidad por saber qué hacen las frases de Perl, puedes eliminarlas:

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

El resultado es una versión más detallada del programa, en una forma que nadie escribiría por sí solo:

BEGIN { $/ = "\n"; $\ = "\n"; }
LINE: while (defined($_ = <ARGV>)) {
    chomp $_;
    $sum += $_;
}
sub END {
    print $sum;
}
-e syntax OK

Solo por risas, probé esto con un archivo que contiene 1,000,000 de números (en el rango de 0 a 9,999). En mi Mac Pro, regresa casi instantáneamente. Eso es una lástima, porque esperaba que usarlo mmapfuera realmente rápido, pero es al mismo tiempo:

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

map_file my $map, $ARGV[0];

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

say $sum;

44
Wow, eso muestra una comprensión profunda sobre qué código -nle realmente envuelve la cadena que le das. Mi pensamiento inicial fue que no debería publicar mientras estaba intoxicado, pero luego me di cuenta de quién era y recordé algunas de sus otras respuestas de Perl :-)
paxdiablo

-n y -p simplemente colocan caracteres alrededor del argumento a -e, para que pueda usar esos caracteres para lo que quiera. Tenemos muchas frases que hacen cosas interesantes con eso en la Programación efectiva de Perl (que está a punto de llegar a los estantes).
brian d foy

55
Bien, ¿de qué se tratan estas llaves rizadas que no coinciden?
Frank

17
-n agrega el while { }ciclo alrededor de su programa. Si pones } ... {dentro, entonces tienes while { } ... { }. ¿Mal? Ligeramente.
jrockway

55
Gran bonificación por resaltar la -MO=Deparseopción! Aunque sobre un tema aparte.
conny

374

Puedes usar awk:

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

3
programa excedido: número máximo de tamaños de campo: 32767
leef

1
Con la -F '\t'opción si sus campos contienen espacios y están separados por pestañas.
Ethan Furman

55
Por favor marque esto como la mejor respuesta. También funciona si desea sumar el primer valor en cada fila, dentro de un archivo TSV (valor separado por tabulaciones).
Andrea

99

Ninguna de las soluciones hasta ahora se utiliza paste. Aquí hay uno:

paste -sd+ filename | bc

Como ejemplo, calcule Σn donde 1 <= n <= 100000:

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

(Para los curiosos, seq nimprimiría una secuencia de números de 1a ndado un número positivo n).


1
¡Muy agradable! Y fácil de recordar
Brendan Maguire

1
seq 100000 | paste -sd+ - | bc -len el shell de Mac OS X Bash. ¡Y esta es, con mucho, la solución más dulce y única!
Simo A.

1
@SimoA. Voto que usemos el término unixiest en lugar de unixest porque la solución más sexy es siempre la más unixiest;)
Connor

86

Solo por diversión, vamos a compararlo:

$ 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

Aborté la carrera sed después de 5 minutos


He estado buceando a , y es rápido:

$ 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

y mientras estoy actualizando esto, 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

Preste atención al consejo de Ed Morton: usar $1

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

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

vs usar $0

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

real    0m0.302s
user    0m0.234s
sys     0m0.063s

18
+1: para encontrar un montón de soluciones y compararlas.
David W.

time cat random_numbers | paste -sd + | bc -l real 0m0.317s usuario 0m0.310s sys 0m0.013s
rafi wiener

eso debería ser casi idéntico a la trsolución.
Glenn Jackman

44
Su script awk debería ejecutarse un poco más rápido si lo usa en $0lugar de hacerlo, $1ya que awk divide los campos (lo que obviamente lleva tiempo) si algún campo se menciona específicamente en el script pero no lo hace de otra manera.
Ed Morton

20

Otra opción es usar jq:

$ seq 10|jq -s add
55

-s( --slurp) lee las líneas de entrada en una matriz.


1
Es una herramienta increíble para tareas rápidas como esa, casi la olvido. gracias
John

9

Esto es directamente Bash:

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

2
Y es probablemente una de las soluciones más lentas y, por lo tanto, no tan adecuada para grandes cantidades de números.
David

7

Aquí hay otra línea

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

Esto supone que los números son enteros. Si necesita decimales, intente

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

Ajuste 2 a la cantidad de decimales necesarios.


6

Prefiero usar GNU Datamash para tales tareas porque es más sucinto y legible que perl o awk. Por ejemplo

datamash sum 1 < myfile

donde 1 denota la primera columna de datos.


1
Esto no parece ser un componente estándar, ya que no lo veo en mi instalación de Ubuntu. Sin embargo, me gustaría verlo como referencia.
Steven el fácilmente divertido

5
$ perl -MList::Util=sum -le 'print sum <>' nums.txt

5

Prefiero usar R para esto:

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

Soy fanático de R para otras aplicaciones, pero no es bueno para el rendimiento de esta manera. La E / S de archivo es un problema importante. He probado pasar argumentos a un script que se puede acelerar usando el paquete vroom. Publicaré más detalles cuando haya comparado algunos otros scripts en el mismo servidor.
Tom Kelly

4
cat nums | perl -ne '$sum += $_ } { print $sum'

(igual que la respuesta de brian d foy, sin 'FIN')


Me gusta esto, pero ¿podrías explicar las llaves? Es raro ver} sin {y viceversa.
drumfire

1
@drumfire vea la respuesta de @brian d foy arriba perl -MO=Deparsepara ver cómo Perl analiza el programa. o los documentos para perlrun: perldoc.perl.org/perlrun.html (busque -n). perl envuelve su código con {} si usa -n para que se convierta en un programa completo.
edibleEnergy

4

Más sucinto:

# Ruby
ruby -e 'puts open("random_numbers").map(&:to_i).reduce(:+)'

# Python
python -c 'print(sum(int(l) for l in open("random_numbers")))'

La conversión a flotante parece ser aproximadamente el doble de rápida en mi sistema (320 frente a 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

3

¡Solo por diversión, hagámoslo con PDL , el motor matemático de matriz de Perl!

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

rcolslee columnas en una matriz (1D en este caso) y sum(sorpresa) suma todo el elemento de la matriz.


¿Cómo solucionarlo? )) por diversión, por supuesto =)
Fortran

1
Primero debe instalar PDL, no es un módulo nativo de Perl.
Joel Berger

3

Aquí hay una solución usando python con una expresión generadora. Probado con un millón de números en mi vieja computadora portátil.

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
Una simple comprensión de la lista con una función con nombre es un buen caso de uso para map():map(float, sys.stdin)
sevko

3

No podría simplemente pasar ... Aquí está mi frase de Haskell. En realidad es bastante legible:

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

Desafortunadamente, no hay ghci -eque ejecutarlo, por lo que necesita la función principal, impresión y compilación.

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

Para aclarar, leemos la entrada completa ( getContents), la dividimos por lines, readcomo números y sum. <$>es fmapoperador: lo usamos en lugar de la aplicación de función habitual porque seguro que todo esto sucede en IO. readnecesita un adicional fmap, porque también está en la lista.

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

Aquí hay una actualización extraña para que funcione con flotadores:

main = ((0.0 + ) <$> sum <$> (read <$>) <$> lines <$> getContents) >>= print
$ ./sum 
1.3
2.1
4.2
^D
7.6000000000000005


2

Ejecutar scripts R

He escrito un script R para tomar argumentos de un nombre de archivo y sumar las líneas.

#! /usr/local/bin/R
file=commandArgs(trailingOnly=TRUE)[1]
sum(as.numeric(readLines(file)))

Esto se puede acelerar con el paquete "data.table" o "vroom" de la siguiente manera:

#! /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

Los mismos datos de evaluación comparativa que @glenn jackman .

for ((i=0; i<1000000; i++)) ; do echo $RANDOM; done > random_numbers

En comparación con la llamada R anterior, ejecutar R 3.5.0 como script es comparable a otros métodos (en el mismo servidor Linux Debian).

$ time R -e 'sum(scan("random_numbers"))'  
 0.37s user
 0.04s system
 86% cpu
 0.478 total

R script con readLines

$ time Rscript sum.R random_numbers
  0.53s user
  0.04s system
  84% cpu
  0.679 total

R script con data.table

$ time Rscript sum.R random_numbers     
 0.30s user
 0.05s system
 77% cpu
 0.453 total

R script con vroom

$ time Rscript sum.R random_numbers     
  0.54s user 
  0.11s system
  93% cpu
  0.696 total

Comparación con otros idiomas.

Para referencia aquí como algunos otros métodos sugeridos en el mismo hardware

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

Rubí (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 versión 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

Actualización con idiomas adicionales

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) debe cronometrarse en bash, no es compatible con zsh

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

sed (4.4) debe cronometrarse en bash, no es compatible con 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

nota: las llamadas sed parecen funcionar más rápido en sistemas con más memoria disponible (tenga en cuenta los conjuntos de datos más pequeños utilizados para la evaluación comparativa de 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

Observe que, como en R, los métodos de E / S de archivo tienen un rendimiento diferente.


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

1

Otro para divertirse

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

u otra fiesta solamente

s=0;while read l; do s=$((s+$l));done<file;echo $s

Pero la solución awk es probablemente la mejor, ya que es más compacta.


1

C siempre gana por velocidad:

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

Tiempo para números 1M (la misma máquina / entrada que mi respuesta de Python):

$ gcc sum.c -o sum && time ./sum < numbers 
5003371677.000000
real    0m0.188s
user    0m0.180s
sys     0m0.000s

1
¡La mejor respuesta! Mejor velocidad)
Fortran

1

Con Ruby:

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

Otra opción (cuando la entrada es de STDIN) es ruby -e'p readlines.map(&:to_f).reduce(:+)'.
nisetama

0

No sé si puede ser mucho mejor que esto, considerando que necesita leer todo el archivo.

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

1
Muy legible Por perl. Pero sí, tendrá que ser algo así ...
dmckee --- ex-gatito moderador

$_es la variable por defecto. El operador de entrada de línea, <>, pone de resultado en que hay por defecto cuando se utilizan <>en while.
brian d foy

1
@Mark, $_es la variable de tema: funciona como el 'it'. En este caso, le <> asigna cada línea. Se utiliza en varios lugares para reducir el desorden de código y ayudar a escribir frases ingeniosas. El script dice "Establezca la suma en 0, lea cada línea y agréguela a la suma, luego imprima la suma".
Daotoad

1
@Stefan, con advertencias y restricciones desactivadas, puede omitir la declaración y la inicialización $sum. Como esto es tan simple, incluso puede usar un modificador de declaración while:$sum += $_ while <>; print $sum;
daotoad

0

No he probado esto pero debería funcionar:

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

Puede que tenga que agregar "\ n" a la cadena antes de bc (como a través de echo) si bc no trata EOF y EOL ...


2
No funciona bcemite un error de sintaxis debido al "+" final y la falta de nueva línea al final. Esto funcionará y elimina el uso inútil de cat: { tr "\n" "+" | sed 's/+$/\n/'| bc; } < numbers2.txt o <numbers2.txt tr "\n" "+" | sed 's/+$/\n/'| bc
pausa hasta nuevo aviso.

tr "\n" "+" <file | sed 's/+$/\n/' | bc
ghostdog74

0

Aquí está otro:

open(FIL, "a.txt");

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

close(FIL);

print "Sum = $sum\n";

0

Puede hacerlo con Alacon - utilidad de línea de comandos para la base de datos Alasql .

Funciona con Node.js, por lo que debe instalar Node.js y luego el paquete Alasql :

Para calcular la suma del archivo TXT puede usar el siguiente comando:

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

0

¿No es más fácil reemplazar todas las líneas nuevas +, agregar una 0y enviarla al Rubyintérprete?

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

Si no lo tiene irb, puede enviarlo a bc, pero debe eliminar todas las líneas nuevas excepto la última (de echo). Es mejor usarlo trpara esto, a menos que tenga un doctorado sed.

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

0

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

¿Qué es "64"? "10" supongo que es la base?
Peter K

Sí, 10 es la base. 64 es el número de bits, si el int resultante no se puede representar con tantos bits, se devuelve un error. Ver 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

0

En shell usando awk, he usado el siguiente script para hacerlo:

    #!/bin/bash


total=0;

for i in $( awk '{ print $1; }' <myfile> )
do
 total=$(echo $total+$i | bc )
 ((count++))
done
echo "scale=2; $total " | bc
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.