Làm thế nào tôi có thể nhanh chóng tổng hợp tất cả các số trong một tập tin?


194

Tôi có một tệp chứa vài nghìn số, mỗi số trên một dòng riêng:

34
42
11
6
2
99
...

Tôi đang tìm cách viết một tập lệnh sẽ in tổng của tất cả các số trong tệp. Tôi đã có một giải pháp, nhưng nó không hiệu quả lắm. (Mất vài phút để chạy.) Tôi đang tìm kiếm một giải pháp hiệu quả hơn. Bất kỳ đề xuất?


5
Giải pháp chậm của bạn là gì? Có lẽ chúng tôi có thể giúp bạn tìm ra những gì chậm về nó. :)
brian d foy

4
@brian d foy, tôi quá xấu hổ để đăng nó. Tôi biết tại sao nó chậm. Đó là bởi vì tôi gọi "tên tệp mèo | head -n 1" để lấy số trên cùng, thêm nó vào tổng số đang chạy và gọi "tên tệp mèo | đuôi ..." để xóa dòng trên cùng cho lần lặp tiếp theo ... Tôi có nhiều thứ để học về lập trình !!!
Mark Roberts

6
Điều đó ... rất có hệ thống. Rất rõ ràng và thẳng thắn, và tôi yêu nó vì tất cả đó là một sự ghê tởm khủng khiếp. Được xây dựng, tôi giả sử, trong số các công cụ mà bạn biết khi bạn bắt đầu, phải không?
dmckee --- ex-moderator mèo con


@MarkRoberts Phải mất một thời gian dài để bạn giải quyết nó. Đó là một kỹ thuật giải quyết vấn đề rất cleaver, và thật sai lầm. Có vẻ như một trường hợp cổ điển của suy nghĩ quá mức. Một số giải pháp kịch bản shell của giải pháp Glen Jackman (và hai là vỏ thuần túy không sử dụng những thứ như awkbc). Tất cả đã hoàn thành việc thêm một triệu số trong chưa đầy 10 giây. Hãy nhìn vào những cái đó và xem làm thế nào nó có thể được thực hiện trong vỏ tinh khiết.
David W.

Câu trả lời:


113

Đối với một lớp lót Perl, về cơ bản nó giống như awkgiải pháp trong câu trả lời của Ayman Hourieh :

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

Nếu bạn tò mò không biết Perl one-liners làm gì, bạn có thể loại bỏ chúng:

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

Kết quả là một phiên bản dài hơn của chương trình, ở dạng mà không ai có thể tự viết:

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

Chỉ để cười khúc khích, tôi đã thử điều này với một tệp chứa 1.000.000 số (trong phạm vi 0 - 9,999). Trên Mac Pro của tôi, nó trả về gần như ngay lập tức. Điều đó thật tệ, bởi vì tôi đã hy vọng việc sử dụng mmapsẽ rất nhanh, nhưng nó chỉ là cùng một lúc:

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
Ồ, điều đó cho thấy một sự hiểu biết sâu sắc về những gì mã thực sự bao quanh chuỗi bạn đưa ra. Suy nghĩ ban đầu của tôi là bạn không nên đăng bài trong khi say nhưng sau đó tôi nhận thấy bạn là ai và nhớ một số câu trả lời Perl khác của bạn :-)
paxdiablo

-n và -p chỉ cần đặt các ký tự xung quanh đối số thành -e, vì vậy bạn có thể sử dụng các ký tự đó cho bất cứ điều gì bạn muốn. Chúng tôi có rất nhiều lớp lót làm được những điều thú vị với điều đó trong Lập trình Perl hiệu quả (sắp được lên kệ).
brian d foy

5
Đẹp, những cái niềng răng không phù hợp này là gì?
Frank

17
-n thêm while { }vòng lặp xung quanh chương trình của bạn. Nếu bạn đặt } ... {bên trong, sau đó bạn có while { } ... { }. Tà ác? Nhẹ nhàng.
jrockway

5
Phần thưởng lớn cho việc làm nổi bật -MO=Deparsetùy chọn! Mặc dù về một chủ đề riêng biệt.
Conny

374

Bạn có thể sử dụng awk:

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

3
vượt quá chương trình: số lượng kích thước trường tối đa: 32767
leef

1
Với -F '\t'tùy chọn nếu các trường của bạn chứa khoảng trắng và được phân tách bằng các tab.
Ethan Furman

5
Hãy đánh dấu đây là câu trả lời tốt nhất. Nó cũng hoạt động nếu bạn muốn tính tổng giá trị đầu tiên trong mỗi hàng, bên trong tệp TSV (giá trị được phân tách bằng tab).
Andrea

99

Không có giải pháp nào cho đến nay sử dụng paste. Đây là một:

paste -sd+ filename | bc

Ví dụ: tính n trong đó 1 <= n <= 100000:

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

(Đối với người tò mò, seq nsẽ in một chuỗi các số từ 1cho đến nmột số dương n.)


1
Rất đẹp! Và dễ nhớ
Brendan Maguire

1
seq 100000 | paste -sd+ - | bc -ltrên vỏ Mac OS X Bash. Và đây là giải pháp ngọt ngào nhất và không thể trộn lẫn!
Simo A.

1
@SimoA. Tôi bỏ phiếu rằng chúng tôi sử dụng thuật ngữ unixiest thay cho unixest bởi vì giải pháp gợi cảm nhất luôn là không nhất thời;)
Connor

86

Để giải trí, hãy để điểm chuẩn cho nó:

$ 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

Tôi đã hủy bỏ sed chạy sau 5 phút


Tôi đã lặn đến và nó rất nhanh

$ 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

và trong khi tôi đang cập nhật điều này, 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

Lời khuyên của Heed Ed Morton: sử dụng $1

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

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

sử dụng $0

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

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

18
+1: Để đưa ra một loạt các giải pháp và điểm chuẩn chúng.
David W.

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

đó chỉ là giống hệt với trgiải pháp.
glenn jackman

4
Tập lệnh awk của bạn sẽ thực thi nhanh hơn một chút nếu bạn sử dụng $0thay vì $1vì awk thực hiện phân tách trường (điều này rõ ràng sẽ mất thời gian) nếu bất kỳ trường nào được đề cập cụ thể trong tập lệnh nhưng không thì khác.
Ed Morton

20

Một lựa chọn khác là sử dụng jq:

$ seq 10|jq -s add
55

-s( --slurp) đọc các dòng đầu vào thành một mảng.


1
Đó là một công cụ tuyệt vời cho các nhiệm vụ nhanh như thế, gần như quên nó. cảm ơn
John


7

Đây là một lót khác

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

Điều này giả sử các số là số nguyên. Nếu bạn cần số thập phân, hãy thử

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

Điều chỉnh 2 đến số thập phân cần thiết.


6

Tôi thích sử dụng dữ liệu GNU cho các tác vụ như vậy bởi vì nó ngắn gọn và dễ đọc hơn perl hoặc awk. Ví dụ

datamash sum 1 < myfile

trong đó 1 biểu thị cột dữ liệu đầu tiên.


1
Đây dường như không phải là một thành phần tiêu chuẩn vì tôi không thấy nó trong cài đặt Ubuntu của mình. Tuy nhiên, muốn xem nó được điểm chuẩn.
Steven dễ dàng thích thú


5

Tôi thích sử dụng R cho việc này:

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

Tôi là một fan hâm mộ của R cho các ứng dụng khác nhưng nó không tốt cho hiệu suất theo cách này. Tệp I / O là một vấn đề lớn. Tôi đã thử chuyển các đối số sang một tập lệnh có thể được tăng tốc bằng cách sử dụng gói vroom. Tôi sẽ đăng thêm chi tiết khi tôi đã điểm chuẩn một số tập lệnh khác trên cùng một máy chủ.
Tom Kelly

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

(giống như câu trả lời của brian d foy, không có 'END')


Tôi thích điều này, nhưng bạn có thể giải thích dấu ngoặc nhọn? Thật kỳ lạ khi thấy} mà không có {và ngược lại.
trống

1
@drumfire xem câu trả lời của @brian d foy ở trên perl -MO=Deparseđể xem cách perl phân tích chương trình. hoặc các tài liệu cho perlrun: perldoc.perl.org/perlrun.html (tìm kiếm -n). perl kết thúc mã của bạn bằng {} nếu bạn sử dụng -n để nó trở thành một chương trình hoàn chỉnh.
ăn được

4

Cô đọng hơn:

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

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

Chuyển đổi sang float dường như nhanh gấp khoảng hai lần trên hệ thống của tôi (320 so với 640 ms). time python -c "print(sum([float(s) for s in open('random_numbers','r')]))"
12719

4

Perl 6

say sum lines
~$ perl6 -e '.say for 0..1000000' > test.in

~$ perl6 -e 'say sum lines' < test.in
500000500000

3

Để giải trí, hãy làm điều đó với PDL , công cụ toán học mảng của Perl!

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

rcolsđọc các cột thành một ma trận (1D trong trường hợp này) và sum(bất ngờ) tính tổng tất cả các phần tử của ma trận.


Cách khắc phục Không thể định vị PDL.pm trong @INC (bạn có thể cần cài đặt mô-đun PDL) (@INC chứa: / etc / perl /usr/local/lib/x86_64-linux-gnu/perl/5.22.1? )) cho vui tất nhiên =)
Fortran

1
Bạn phải cài đặt PDL trước, đây không phải là mô đun gốc Perl.
Joel Berger

3

Đây là một giải pháp sử dụng python với biểu thức trình tạo. Đã thử nghiệm với một triệu số trên máy tính xách tay cũ của tôi.

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
Một sự hiểu biết danh sách đơn giản với một chức năng được đặt tên là một trường hợp sử dụng tốt cho map():map(float, sys.stdin)
Sevko

3

Tôi không thể đi ngang qua ... Đây là chiếc áo lót Haskell của tôi. Nó thực sự khá dễ đọc:

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

Thật không may, không có ghci -echỉ để chạy nó, vì vậy nó cần chức năng chính, in và biên dịch.

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

Để làm rõ, chúng tôi đọc toàn bộ đầu vào ( getContents), chia nó theo lines, readdưới dạng số và sum. <$>fmaptoán tử - chúng tôi sử dụng nó thay vì ứng dụng chức năng thông thường vì chắc chắn tất cả điều này xảy ra trong IO. readcần một bổ sung fmap, bởi vì nó cũng nằm trong danh sách.

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

Đây là một bản nâng cấp kỳ lạ để làm cho nó hoạt động với phao:

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


2

Chạy tập lệnh R

Tôi đã viết một tập lệnh R để lấy các đối số của tên tệp và tính tổng các dòng.

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

Điều này có thể được tăng tốc với gói "data.table" hoặc "vroom" như sau:

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

Điểm chuẩn

Dữ liệu điểm chuẩn tương tự như jackgl @glenn .

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

So với lệnh gọi R ở trên, chạy R 3.5.0 dưới dạng tập lệnh có thể so sánh với các phương thức khác (trên cùng máy chủ Linux Debian).

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

Tập lệnh R với readLines

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

Tập lệnh R với data.table

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

Kịch bản R với vroom

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

So sánh với các ngôn ngữ khác

Để tham khảo ở đây như một số phương pháp khác được đề xuất trên cùng một phần cứng

Con trăn 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

Con trăn 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 (phiên bản clang 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

Cập nhật với các ngôn ngữ bổ sung

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) phải được định thời trong bash, không tương thích với zsh

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

sed (4.4) phải được định thời trong bash, không tương thích với 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

lưu ý: các cuộc gọi sed dường như hoạt động nhanh hơn trên các hệ thống có sẵn nhiều bộ nhớ hơn (lưu ý các bộ dữ liệu nhỏ hơn được sử dụng để đo điểm chuẩn 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

Lưu ý rằng như trong R, các phương thức I / O của tệp có hiệu suất khác nhau.


2

C ++ "một-lót":

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

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

1

Một cái khác cho vui

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

hoặc chỉ bash khác

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

Nhưng giải pháp awk có lẽ là tốt nhất vì nó nhỏ gọn nhất.


1

C luôn thắng vì tốc độ:

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

Thời gian cho các số 1M (cùng máy / đầu vào như câu trả lời python của tôi):

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

1
Câu trả lời tốt nhất! Tốc độ tốt nhất)
Fortran

1

Với Ruby:

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

Một tùy chọn khác (khi đầu vào là từ STDIN) là ruby -e'p readlines.map(&:to_f).reduce(:+)'.
nisetama

0

Tôi không biết nếu bạn có thể nhận được nhiều hơn thế này, xem xét bạn cần đọc qua toàn bộ tệp.

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

1
Rất dễ đọc. Đối với perl. Nhưng vâng, nó sẽ phải là một thứ như thế ...
dmckee --- ex-moderator mèo con

$_là biến mặc định. Nhà điều hành dòng đầu vào, <>, puts kết quả của nó trong đó theo mặc định khi bạn sử dụng <>trong while.
brian d foy

1
@Mark, $_là biến chủ đề - nó hoạt động giống như 'nó'. Trong trường hợp này <> gán từng dòng cho nó. Nó được sử dụng ở một số nơi để giảm sự lộn xộn mã và giúp viết một lớp lót. Kịch bản cho biết "Đặt tổng thành 0, đọc từng dòng và thêm vào tổng, sau đó in tổng."
daotoad

1
@Stefan, với các cảnh báo và hạn chế tắt, bạn có thể bỏ qua việc khai báo và khởi tạo $sum. Vì việc này rất đơn giản, bạn thậm chí có thể sử dụng công cụ sửa đổi câu lệnh while:$sum += $_ while <>; print $sum;
daotoad

0

Tôi chưa thử nghiệm cái này nhưng nó sẽ hoạt động:

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

Bạn có thể phải thêm "\ n" vào chuỗi trước bc (như qua echo) nếu bc không xử lý EOF và EOL ...


2
Nó không hoạt động. bcđưa ra lỗi cú pháp do dấu "+" và thiếu dòng mới ở cuối. Điều này sẽ hoạt động và nó giúp loại bỏ việc sử dụng vô ích cat: { tr "\n" "+" | sed 's/+$/\n/'| bc; } < numbers2.txt hoặc <numbers2.txt tr "\n" "+" | sed 's/+$/\n/'| bc
Tạm dừng cho đến khi có thông báo mới.

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

0

Đây là một:

open(FIL, "a.txt");

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

close(FIL);

print "Sum = $sum\n";

0

Bạn có thể làm điều đó với Alacon - tiện ích dòng lệnh cho cơ sở dữ liệu Alasql .

Nó hoạt động với Node.js, vì vậy bạn cần cài đặt Node.js và sau đó là gói Alasql :

Để tính tổng từ tệp TXT, bạn có thể sử dụng lệnh sau:

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

0

Không dễ dàng hơn để thay thế tất cả các dòng mới bằng cách +thêm một 0và gửi nó cho trình Rubythông dịch?

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

Nếu bạn không có irb, bạn có thể gửi nó đến bc, nhưng bạn phải xóa tất cả các dòng mới ngoại trừ dòng cuối cùng (của echo). Nó là tốt hơn để sử dụng trcho việc này, trừ khi bạn có bằng tiến sĩ sed.

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

0

Trong Đi:

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" là gì? "10" Tôi cho là cơ sở?
Peter K

Vâng, 10 là cơ sở. 64 là số bit, nếu kết quả int không thể được biểu diễn bằng nhiều bit đó thì sẽ xảy ra lỗi. Xem golang.org/pkg/strconv/#PudeInt
dwurf

0

Biến thể 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

Trong shell bằng awk, tôi đã sử dụng đoạn script bên dưới để làm như vậy:

    #!/bin/bash


total=0;

for i in $( awk '{ print $1; }' <myfile> )
do
 total=$(echo $total+$i | bc )
 ((count++))
done
echo "scale=2; $total " | bc
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.