Bagaimana saya bisa dengan cepat menjumlahkan semua angka dalam file?


194

Saya memiliki file yang berisi beberapa ribu angka, masing-masing pada barisnya sendiri:

34
42
11
6
2
99
...

Saya ingin menulis skrip yang akan mencetak jumlah semua angka dalam file. Saya punya solusi, tetapi tidak terlalu efisien. (Butuh beberapa menit untuk menjalankannya.) Saya mencari solusi yang lebih efisien. Ada saran?


5
Apa solusi lambat Anda? Mungkin kami dapat membantu Anda mencari tahu apa yang lambat tentangnya. :)
brian d foy

4
@ Brian d foy, saya terlalu malu untuk mempostingnya. Saya tahu mengapa ini lambat. Itu karena saya memanggil "cat filename | head -n 1" untuk mendapatkan angka teratas, menambahkannya ke total yang sedang berjalan, dan memanggil "cat filename | tail ..." untuk menghapus baris teratas untuk iterasi berikutnya ... Saya punya banyak belajar tentang pemrograman !!!
Mark Roberts

6
Itu ... sangat sistematis. Sangat jelas dan lurus ke depan, dan aku menyukainya untuk semua itu adalah kekejian yang mengerikan. Dibangun, saya berasumsi, dari alat yang Anda tahu ketika Anda mulai, kan?
dmckee --- ex-moderator kitten


@ MarkRoberts Pasti butuh waktu lama untuk menyelesaikannya. Ini adalah teknik pemecahan masalah yang sangat cerdas, dan oh sangat salah. Ini seperti kasus klasik dari pemikiran berlebihan. Beberapa solusi shell scripting solusi Glen Jackman (dan dua adalah shell murni yang tidak menggunakan hal-hal seperti awkdan bc). Ini semua selesai menambahkan satu juta angka dalam waktu kurang dari 10 detik. Lihatlah itu dan lihat bagaimana hal itu dapat dilakukan dalam cangkang murni.
David W.

Jawaban:


113

Untuk Perl one-liner, pada dasarnya hal yang sama dengan awksolusi dalam jawaban Ayman Hourieh :

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

Jika Anda penasaran dengan apa yang dilakukan Perl one-liners, Anda dapat menjelaskannya:

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

Hasilnya adalah versi yang lebih verbose dari program ini, dalam bentuk yang tidak seorang pun akan menulis sendiri:

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

Hanya untuk cekikikan, saya mencoba ini dengan file yang berisi 1.000.000 angka (dalam kisaran 0 - 9.999). Di Mac Pro saya, ia mengembalikan hampir secara instan. Itu terlalu buruk, karena saya berharap menggunakan mmapakan sangat cepat, tetapi pada saat yang sama:

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

map_file my $map, $ARGV[0];

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

say $sum;

4
Wow, itu menunjukkan pemahaman yang mendalam tentang apa kode -nle sebenarnya membungkus string yang Anda berikan. Pikiran awal saya adalah bahwa Anda tidak boleh memposting ketika mabuk, tetapi kemudian saya perhatikan siapa Anda dan ingat beberapa jawaban Perl Anda yang lain :-)
paxdiablo

-n dan -p hanya menempatkan karakter di sekitar argumen ke -e, sehingga Anda dapat menggunakan karakter tersebut untuk apa pun yang Anda inginkan. Kami memiliki banyak one-liner yang melakukan hal-hal menarik dengan yang ada di Efektif Perl Programming (yang hampir tiba di pasaran).
brian d foy

5
Bagus, apa ini kurung kurawal yang tidak cocok?
Frank

17
-n menambahkan while { }loop di sekitar program Anda. Jika Anda memasukkan ke } ... {dalam, maka Anda memilikinya while { } ... { }. Jahat? Sedikit.
jrockway

5
Bonus besar untuk menyoroti -MO=Deparseopsi! Padahal pada topik terpisah.
conny

374

Anda dapat menggunakan awk:

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

3
program terlampaui: jumlah maksimum ukuran bidang:
32767

1
Dengan -F '\t'opsi jika bidang Anda berisi spasi dan dipisahkan oleh tab.
Ethan Furman

5
Harap tandai ini sebagai jawaban terbaik. Ini juga berfungsi jika Anda ingin menjumlahkan nilai pertama di setiap baris, di dalam file TSV (nilai yang dipisahkan tab).
Andrea

99

Sejauh ini tidak ada solusi yang digunakan paste. Ini dia:

paste -sd+ filename | bc

Sebagai contoh, hitung Σn di mana 1 <= n <= 100000:

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

(Bagi yang penasaran, seq nakan mencetak urutan angka dari 1ke ndiberikan angka positif n.)


1
Sangat bagus! Dan mudah diingat
Brendan Maguire

1
seq 100000 | paste -sd+ - | bc -lpada Mac OS X Bash shell. Dan sejauh ini ini adalah solusi termanis dan tidak tersentuh!
Simo A.

1
@SimoA. Saya memilih agar kita menggunakan istilah unixiest di tempat unixest karena solusi terseksi selalu yang unixiest;)
Connor

86

Hanya untuk bersenang-senang, mari patok:

$ 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

Saya membatalkan menjalankan sed setelah 5 menit


Saya sudah menyelam ke , dan cepat:

$ 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

dan sementara saya memperbarui ini, 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

Saran Heed Ed Morton: menggunakan $1

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

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

vs menggunakan $0

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

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

18
+1: Untuk datang dengan banyak solusi, dan membandingkannya.
David W.

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

yang seharusnya identik dengan trsolusinya.
glenn jackman

4
Script awk Anda harus menjalankan sedikit lebih cepat jika Anda menggunakan $0alih-alih $1karena awk melakukan pemisahan bidang (yang jelas membutuhkan waktu) jika ada bidang yang disebutkan secara khusus dalam skrip tetapi tidak sebaliknya.
Ed Morton

20

Pilihan lain adalah menggunakan jq:

$ seq 10|jq -s add
55

-s( --slurp) membaca baris input ke dalam array.


1
Ini alat yang luar biasa untuk tugas cepat seperti itu, hampir melupakannya. terima kasih
John


7

Ini satu kalimat lagi

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

Ini mengasumsikan angkanya bilangan bulat. Jika Anda membutuhkan desimal, cobalah

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

Sesuaikan 2 dengan jumlah desimal yang dibutuhkan.


6

Saya lebih suka menggunakan GNU datamash untuk tugas-tugas seperti itu karena lebih ringkas dan dapat dibaca daripada perl atau awk. Sebagai contoh

datamash sum 1 < myfile

di mana 1 menunjukkan kolom data pertama.


1
Ini tampaknya bukan komponen standar karena saya tidak melihatnya di instalasi Ubuntu saya. Akan tetapi, ingin melihatnya diperbandingkan.
Steven yang Mudah Dihibur

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

5

Saya lebih suka menggunakan R untuk ini:

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

Saya penggemar R untuk aplikasi lain tetapi tidak bagus untuk kinerja dengan cara ini. File I / O adalah masalah utama. Saya sudah menguji passing args ke skrip yang dapat dipercepat menggunakan paket vroom. Saya akan memposting rincian lebih lanjut ketika saya telah membuat benchmark beberapa skrip lain di server yang sama.
Tom Kelly

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

(sama dengan jawaban brian d foy, tanpa 'END')


Saya suka ini, tetapi bisakah Anda menjelaskan kurung keriting? Sangat aneh untuk melihat} tanpa {dan sebaliknya.
drumfire

1
@rumrum lihat jawaban @brian d foy di atas dengan perl -MO=Deparseuntuk melihat bagaimana perl mem-parsing program. atau dokumen untuk perlrun: perldoc.perl.org/perlrun.html (cari -n). perl membungkus kode Anda dengan {} jika Anda menggunakan -n sehingga menjadi program yang lengkap.
edibleEnergy

4

Lebih ringkas:

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

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

Konversi ke float tampaknya sekitar dua kali lebih cepat pada sistem saya (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

3

Hanya untuk bersenang-senang, mari kita lakukan dengan PDL , mesin matematika array Perl!

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

rcolsmembaca kolom menjadi matriks (1D dalam kasus ini) dan sum(kejutan) menjumlahkan semua elemen matriks.


Bagaimana cara memperbaiki Tidak dapat menemukan PDL.pm di @INC (Anda mungkin perlu menginstal modul PDL) (@INC berisi: / etc / perl /usr/local/lib/x86_64-linux-gnu/perl/5.22.1? )) untuk bersenang-senang tentu saja =)
Fortran

1
Anda harus menginstal PDL terlebih dahulu, itu bukan modul asli Perl.
Joel Berger

3

Berikut adalah solusi menggunakan python dengan ekspresi generator. Diuji dengan sejuta angka pada laptop saya yang kasar.

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
Pemahaman daftar sederhana dengan fungsi bernama adalah use-case yang bagus untuk map():map(float, sys.stdin)
sevko

3

Saya tidak bisa hanya lewat ... Ini Haskell one-liner saya. Ini sebenarnya cukup mudah dibaca:

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

Sayangnya tidak ada ghci -euntuk hanya menjalankannya, sehingga perlu fungsi utama, cetak dan kompilasi.

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

Untuk memperjelas, kami membaca seluruh input ( getContents), membaginya dengan lines, readsebagai angka dan sum. <$>adalah fmapoperator - kami menggunakannya bukan aplikasi fungsi biasa karena yakin ini semua terjadi di IO. readperlu tambahan fmap, karena juga ada dalam daftar.

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

Berikut ini adalah upgrade yang aneh untuk membuatnya bekerja dengan float:

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


2

Menjalankan skrip R

Saya telah menulis skrip R untuk mengambil argumen nama file dan menjumlahkan baris.

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

Ini dapat dipercepat dengan paket "data.table" atau "vroom" sebagai berikut:

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

Data pembandingan yang sama dengan @glenn jackman .

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

Dibandingkan dengan panggilan R di atas, menjalankan R 3.5.0 sebagai skrip dapat dibandingkan dengan metode lain (pada server Linux Debian yang sama).

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

R script dengan readLines

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

R script dengan data.table

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

R script dengan vroom

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

Perbandingan dengan bahasa lain

Untuk referensi di sini karena beberapa metode lain disarankan pada perangkat keras yang sama

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

Ruby (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 (dentang versi 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

Perbarui dengan bahasa tambahan

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) harus diberi batas waktu dalam bash, tidak kompatibel dengan zsh

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

sed (4.4) harus diberi batas waktu dalam bash, tidak kompatibel dengan 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

catatan: panggilan sed tampaknya bekerja lebih cepat pada sistem dengan lebih banyak memori yang tersedia (perhatikan kumpulan data yang lebih kecil digunakan untuk benchmarking 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

Perhatikan bahwa seperti dalam R, metode file I / O memiliki kinerja yang berbeda.


2

C ++ "satu garis":

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

int main() {
    cout << accumulate(istream_iterator<int>(cin), istream_iterator<int>(), 0) << endl;
}

1

Lain untuk bersenang-senang

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

atau bash lain saja

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

Tetapi solusi awk mungkin yang terbaik karena paling kompak.


1

C selalu menang untuk kecepatan:

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

Waktu untuk angka 1M (mesin / input yang sama dengan jawaban python saya):

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

1
Jawaban Terbaik! Kecepatan terbaik)
Fortran

1

Dengan Ruby:

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

Pilihan lain (ketika input dari STDIN) adalah ruby -e'p readlines.map(&:to_f).reduce(:+)'.
nisetama

0

Saya tidak tahu apakah Anda bisa mendapatkan jauh lebih baik dari ini, mengingat Anda perlu membaca seluruh file.

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

1
Sangat mudah dibaca Untuk perl. Tapi ya, itu akan menjadi sesuatu seperti itu ...
dmckee --- ex-moderator kitten

$_adalah variabel default. Operator baris masukan, <>, menempatkan hasil itu di sana secara default ketika Anda menggunakan <>di while.
brian d foy

1
@ Mark, $_adalah variabel topik - ia bekerja seperti 'itu'. Dalam hal ini, <> berikan setiap baris untuk itu. Itu akan digunakan di sejumlah tempat untuk mengurangi kekacauan kode dan membantu dengan menulis satu baris. Script mengatakan "Tetapkan jumlah ke 0, baca setiap baris dan tambahkan ke jumlah, lalu cetak jumlahnya."
daotoad

1
@Stefan, dengan peringatan dan penyempitan mati, Anda dapat melewati deklarasi dan inisialisasi $sum. Karena ini sangat sederhana, Anda bahkan dapat menggunakan pengubah pernyataan while:$sum += $_ while <>; print $sum;
daotoad

0

Saya belum menguji ini tetapi seharusnya berhasil:

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

Anda mungkin harus menambahkan "\ n" ke string sebelum bc (seperti via echo) jika bc tidak memperlakukan EOF dan EOL ...


2
Itu tidak bekerja. bcmengeluarkan kesalahan sintaksis karena trailing "+" dan kurangnya baris baru di akhir. Ini akan berhasil dan menghilangkan penggunaan yang tidak berguna dari cat: { tr "\n" "+" | sed 's/+$/\n/'| bc; } < numbers2.txt atau <numbers2.txt tr "\n" "+" | sed 's/+$/\n/'| bc
Dijeda sampai pemberitahuan lebih lanjut.

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

0

Ini yang lain:

open(FIL, "a.txt");

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

close(FIL);

print "Sum = $sum\n";

0

Anda dapat melakukannya dengan Alacon - utilitas baris perintah untuk basis data Alasql .

Ini bekerja dengan Node.js, jadi Anda perlu menginstal Node.js dan kemudian paket Alasql :

Untuk menghitung jumlah dari file TXT Anda dapat menggunakan perintah berikut:

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

0

Hal ini tidak mudah untuk mengganti semua lini baru +, menambah 0dan mengirimkannya ke Rubyjuru?

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

Jika tidak punya irb, Anda dapat mengirimnya ke bc, tetapi Anda harus menghapus semua baris baru kecuali yang terakhir echo. Lebih baik digunakan truntuk ini, kecuali jika Anda memiliki gelar PhD di sed.

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

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

Apa itu "64"? "10" Saya kira basis?
Peter K

Ya, 10 adalah dasarnya. 64 adalah jumlah bit, jika int yang dihasilkan tidak dapat diwakili dengan banyak bit maka kesalahan dikembalikan. Lihat golang.org/pkg/strconv/#ParseInt
dwurf

0

Varian 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

Dalam shell menggunakan awk, saya telah menggunakan skrip di bawah ini untuk melakukannya:

    #!/bin/bash


total=0;

for i in $( awk '{ print $1; }' <myfile> )
do
 total=$(echo $total+$i | bc )
 ((count++))
done
echo "scale=2; $total " | bc
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.