Bir dosyadaki tüm sayıları nasıl hızlı bir şekilde toplayabilirim?


203

Her biri kendi satırında olan birkaç bin sayı içeren bir dosyam var:

34
42
11
6
2
99
...

Dosyadaki tüm sayıların toplamını yazdıracak bir komut dosyası yazmak istiyorum. Bir çözümüm var ama çok verimli değil. (Çalıştırması birkaç dakika sürer.) Daha verimli bir çözüm arıyorum. Herhangi bir öneri?


5
Yavaş çözümünüz neydi? Belki neyin yavaş olduğunu anlamanıza yardımcı olabiliriz. :)
brian d foy

4
@brian d foy, bunu yayınlamaktan çok utandım. Neden yavaş olduğunu biliyorum. Bunun nedeni, en üstteki sayıyı almak için "kedi dosya adı | head -n 1", bir sonraki yinelemenin üst satırını kaldırmak için de "kedi dosya adı | kuyruk ..." diye çağırmam ... I programlama hakkında öğrenecek çok şey var !!!
Mark Roberts

6
Bu ... çok sistematik. Çok açık ve yalındır ve korkunç bir iğrençlik olduğu için onu seviyorum. Sanırım, başladığında bildiğin araçların dışında, değil mi?
dmckee --- eski moderatör kedicik


@MarkRoberts Bunu halletmek uzun zaman almış olmalı. Bu çok zekice bir problem çözme tekniği ve çok yanlış. Klasik bir aşırı düşünme durumu gibi görünüyor. Glen Jackman'ın birkaç çözümü kabuk betikleme çözümleri (ve ikisi, awkve gibi şeyler kullanmayan saf kabuktur bc). Bunların hepsi 10 saniyeden kısa bir sürede bir milyon sayı eklemeyi tamamladı. Bunlara bir bakın ve saf kabukta nasıl yapılabileceğini görün.
David W.

Yanıtlar:


113

Bir Perl tek astarlı için, temelde Ayman Hourieh'in cevabındakiawk çözümle aynı şey :

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

Perl tekli gömleklerin ne yaptığını merak ediyorsanız, onları ayırabilirsiniz:

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

Sonuç, programın hiç kimsenin kendi başına yazamayacağı bir biçimde daha ayrıntılı bir versiyonudur:

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

Sadece kıkırdamalar için, bunu 1.000.000 sayı içeren bir dosyayla denedim (0 - 9.999 aralığında). Mac Pro'mda neredeyse anında geri dönüyor. Bu çok kötü, çünkü kullanmanın mmapgerçekten hızlı olacağını umuyordum , ama aynı zamanda:

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
Vay canına, bu, -nle kodunun aslında verdiğiniz dizenin etrafına sarıldığı konusunda derin bir anlayış gösteriyor . İlk düşüncem sarhoşken gönderi paylaşmamalısın ama sonra kim olduğunu fark ettim ve diğer Perl yanıtlarından bazılarını hatırladım :-)
paxdiablo

-n ve -p sadece argümanın etrafına -e karakterleri koyar, böylece bu karakterleri istediğiniz gibi kullanabilirsiniz. Etkili Perl Programlamada (raflara çarpmak üzere olan) bununla ilginç şeyler yapan birçok tek satırlık programımız var .
brian d foy

5
Güzel, bu eşleşmeyen küme parantezleri ne hakkında?
Frank

17
-n while { }, programınızın etrafına döngü ekler . } ... {İçeri koyarsan , o zaman alırsın while { } ... { }. Kötü mü? Biraz.
jrockway

5
-MO=DeparseSeçeneği vurgulamak için büyük bonus ! Ayrı bir konu olsa bile.
conny

384

Awk kullanabilirsiniz:

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

4
program aşıldı: maksimum alan boyutu sayısı: 32767
leef

1
-F '\t'Alanlarınız boşluk içeriyorsa ve sekmelerle ayrılmışsa seçeneği ile .
Ethan Furman

5
Lütfen bunu en iyi cevap olarak işaretleyin. Ayrıca, her satırdaki ilk değeri bir TSV (sekmeyle ayrılmış değer) dosyası içinde toplamak istiyorsanız da çalışır.
Andrea

104

Şu ana kadar hiçbir çözüm kullanılmadı paste. Işte bir tane:

paste -sd+ filename | bc

Örnek olarak, 1 <= n <= 100000 olduğunda Σn değerini hesaplayın:

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

(Merak için, seq ngelen bir sayı dizisi yazdırmak 1için npozitif bir sayı verilir n.)


1
Çok hoş! Ve hatırlaması kolay
Brendan Maguire

1
seq 100000 | paste -sd+ - | bc -lMac OS X Bash kabuğunda. Ve bu açık ara en tatlı ve en kararsız çözüm!
Simo A.

2
@SimoA. Unixest yerine unixiest terimini kullandığımıza oy veriyorum çünkü en seksi çözüm her zaman en unix'tir;)
Connor

88

Sırf eğlence için kıyaslayalım:

$ 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

5 dakika sonra sed çalışmasını iptal ettim


Dalıyordum ve hızlıdır:

$ 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

ve bunu güncellerken 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

Ed Morton'un tavsiyesini dikkate alın: $1

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

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

ile karşılaştırıldığında $0

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

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

18
+1: Bir dizi çözüm bulmak ve bunları karşılaştırmak için.
David W.

zaman cat random_numbers | yapıştır -sd + | bc -l gerçek 0m0.317s kullanıcı 0m0.310s sys 0m0.013s
rafi wiener

bu, trçözümle hemen hemen aynı olmalıdır .
glenn jackman

4
Eğer komutta herhangi bir alandan özel olarak bahsediliyorsa, ancak aksi belirtilmiyorsa awk alan bölme yaptığı için (ki bu da zaman alır) $0yerine kullanırsanız, awk betiğiniz biraz daha hızlı çalışmalıdır $1.
Ed Morton

22

Başka bir seçenek de kullanmaktır jq:

$ seq 10|jq -s add
55

-s( --slurp) girdi satırlarını bir diziye okur.


1
Neredeyse unutulmuş gibi hızlı görevler için harika bir araçtır. teşekkürler
John


7

İşte başka bir tek satırlık

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

Bu, sayıların tam sayı olduğunu varsayar. Ondalık sayıya ihtiyacınız varsa, deneyin

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

2'yi gereken ondalık sayıya ayarlayın.


6

Bu tür görevler için GNU veri kodunu kullanmayı tercih ediyorum çünkü perl veya awk'tan daha kısa ve okunaklı. Örneğin

datamash sum 1 < myfile

burada 1, verilerin ilk sütununu gösterir.


1
Ubuntu kurulumumda görmediğim için bu standart bir bileşen gibi görünmüyor. Yine de karşılaştırmalı olarak görmek isterim.
Steven Kolayca Eğlendirildi

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

5

Bunun için R kullanmayı tercih ederim:

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

Diğer uygulamalar için R hayranıyım ancak bu şekilde performans için iyi değil. Dosya G / Ç önemli bir sorundur. Vroom paketi kullanılarak hızlandırılabilen bir betiğe argümanlar geçirmeyi test ettim. Aynı sunucuda başka komut dosyalarını karşılaştırdığımda daha fazla ayrıntı yayınlayacağım.
Tom Kelly

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

(Brian d foy'un cevabıyla aynı, "SON" olmadan)


Bunu beğendim ama küme parantezlerini açıklayabilir misin? } Olmadan {ve tam tersini görmek garip.
drumfire

1
@drumfire perl -MO=Deparse, programı nasıl ayrıştırdığını görmek için yukarıdaki @brian d foy'un cevabına bakın . veya perlrun için dokümanlar: perldoc.perl.org/perlrun.html (-n araması yapın). perl, kodunuzun tam bir program olması için -n kullanırsanız kodunuzu {} ile sarmalar.
yenilebilirEnergy

4

Daha kısa ve öz:

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

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

Float'a dönüştürme, sistemimde yaklaşık iki kat daha hızlı görünüyor (320'ye karşı 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

Sırf eğlence için, bunu Perl'in dizi matematik motoru PDL ile yapalım !

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

rcolssütunları bir matrise okur (bu durumda 1D) ve sum(sürpriz) matrisin tüm elemanlarını toplar.


@INC'de PDL.pm'yi bulamıyorum (PDL modülünü yüklemeniz gerekebilir) (@INC şunları içerir: / etc / perl /usr/local/lib/x86_64-linux-gnu/perl/5.22.1? )) tabii ki eğlence için =)
Fortran

1
Öncelikle PDL'yi kurmanız gerekir, bu bir Perl yerel modülü değildir.
Joel Berger

3

İşte bir üretici ifadesi ile python kullanan bir çözüm. Eski kirli dizüstü bilgisayarımda bir milyon numara ile test edildi.

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
Adlandırılmış bir işleve sahip basit bir liste anlama, map()map(float, sys.stdin)
şunlar

3

Öyle geçemezdim ... İşte tek hatlı Haskell'im. Aslında oldukça okunabilir:

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

Ne yazık ki ghci -esadece çalıştıracak bir şey yok, bu yüzden ana işleve, yazdırmaya ve derlemeye ihtiyacı var.

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

Açıklığa kavuşturmak için, tüm girdiyi ( getContents) okuruz lines, readsayılara böleriz ve sum. <$>is fmapoperator - olağan fonksiyon uygulaması yerine kullanıyoruz çünkü tüm bunların IO'da olduğundan emin olun. Listede olduğu readiçin ek ihtiyacı var fmap.

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

İşte şamandıralarla çalışmasını sağlamak için garip bir yükseltme:

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


2

R betiklerini çalıştırma

Bir dosya adının argümanlarını almak ve satırları toplamak için bir R betiği yazdım.

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

Bu, "data.table" veya "vroom" paketi ile aşağıdaki şekilde hızlandırılabilir:

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

Kıyaslama

@Glenn jackman ile aynı karşılaştırma verileri .

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

Yukarıdaki R çağrısıyla karşılaştırıldığında, R 3.5.0'ı komut dosyası olarak çalıştırmak diğer yöntemlerle (aynı Linux Debian sunucusunda) karşılaştırılabilir.

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

ReadLines ile R betiği

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

Data.table içeren R betiği

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

Vroom ile R betiği

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

Diğer dillerle karşılaştırma

Aynı donanımda önerilen diğer bazı yöntemlerle burada referans olması için

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

Yakut (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 sürüm 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

Ek dillerle güncelleyin

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) , bash ile zamanlanmalıdır, zsh ile uyumlu değildir

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

sed (4.4) , bash ile zamanlanmalıdır, zsh ile uyumlu değildir

$  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

not: sed çağrıları, daha fazla belleğe sahip sistemlerde daha hızlı çalışıyor gibi görünüyor (sed karşılaştırması için kullanılan daha küçük veri kümelerine dikkat edin)

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

R'de olduğu gibi, dosya G / Ç yöntemlerinin farklı performansa sahip olduğuna dikkat edin.


2

C ++ "tek satırlık":

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

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

1

Eğlence için başka

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

veya sadece başka bir bash

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

Ancak awk çözümü, en kompakt olduğu için muhtemelen en iyisidir.


1

C her zaman hız için kazanır:

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

1 milyon sayı için zamanlama (python cevabımla aynı makine / girdi):

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

1
En iyi cevap! En iyi hız)
Fortran

1

Ruby ile:

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

Diğer bir seçenek (giriş STDIN'den olduğunda) ruby -e'p readlines.map(&:to_f).reduce(:+)'.
nisetama

0

Dosyanın tamamını okumanız gerektiğini düşünürsek bundan çok daha iyisini bulabilir misiniz bilmiyorum.

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

1
Çok okunabilir. Perl için. Ama evet, böyle bir şey olmalı ...
dmckee --- eski moderatör kedi

$_varsayılan değişkendir. Hat girişi operatörü, <>varsayılan olarak içeride koyar 's sonucunu kullandığınızda <>içinde while.
brian d foy

1
@Mark, $_konu değişkenidir - 'o' gibi çalışır. Bu durumda <> her satırı ona atar. Kod dağınıklığını azaltmak ve tek satırlık yazımlara yardımcı olmak için birçok yerde kullanılır. Komut dosyası "Toplamı 0 olarak ayarlayın, her satırı okuyun ve toplama ekleyin, ardından toplamı yazdırın" der.
daotoad

1
@Stefan, uyarılar ve kısıtlamalar kapalıyken bildirmeyi ve başlatmayı atlayabilirsiniz $sum. Bu çok basit olduğu için, bir ifade değiştirici bile kullanabilirsiniz while:$sum += $_ while <>; print $sum;
daotoad

0

Bunu test etmedim ama çalışmalı:

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

Bc, EOF ve EOL'yi işlemezse, bc'den önce (echo aracılığıyla olduğu gibi) dizeye "\ n" eklemeniz gerekebilir ...


2
Çalışmıyor. bcsondaki "+" ve sonunda satırsonu olmaması nedeniyle bir sözdizimi hatası verir. Bu işe yarayacak ve aşağıdakilerin gereksiz kullanımını ortadan kaldıracaktır cat: { tr "\n" "+" | sed 's/+$/\n/'| bc; } < numbers2.txt veya <numbers2.txt tr "\n" "+" | sed 's/+$/\n/'| bc
sonraki duyuruya kadar Duraklatıldı.

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

0

İşte bir tane daha:

open(FIL, "a.txt");

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

close(FIL);

print "Sum = $sum\n";

0

Alacon - Alasql veritabanı için komut satırı yardımcı programı ile yapabilirsiniz .

Node.js ile çalışır, bu nedenle Node.js ve ardından Alasql yüklemeniz gerekir. paketini :

TXT dosyasından toplamı hesaplamak için aşağıdaki komutu kullanabilirsiniz:

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

0

Tüm yeni satırları ile değiştirmek +, a eklemek 0ve Rubytercümana göndermek daha kolay değil mi?

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

Eğer yoksa irb, adresine gönderebilirsiniz bc, ancak son satır (/ echo) dışındaki tüm satır başlarını kaldırmanız gerekir . trDoktora programınız yoksa bunun için kullanmak daha iyidir sed.

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

0

Hareket halinde:

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

"64" nedir? Sanırım "10" temel mi?
Peter K

Evet, 10 temeldir. 64 bit sayısıdır, sonuçta ortaya çıkan int bu kadar bit ile gösterilemezse bir hata döndürülür. Golang.org/pkg/strconv/#ParseInt
dwurf

0

Bash varyantı

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

$ wc -l file
10000 file

$ time ./test
323390

real    0m3,096s
user    0m3,095s
sys     0m0,000s

0

Awk kullanan kabukta, bunu yapmak için aşağıdaki betiği kullandım:

    #!/bin/bash


total=0;

for i in $( awk '{ print $1; }' <myfile> )
do
 total=$(echo $total+$i | bc )
 ((count++))
done
echo "scale=2; $total " | bc
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.