如何快速求和文件中的所有数字?


194

我有一个包含数千个数字的文件,每个数字都在自己的行上:

34
42
11
6
2
99
...

我正在寻找一个脚本,它将打印文件中所有数字的总和。我有一个解决方案,但是效率不是很高。(运行需要几分钟。)我正在寻找一种更有效的解决方案。有什么建议?


5
您的慢速解决方案是什么?也许我们可以帮助您找出问题的答案。:)
brian d foy 2010年

4
@brian d foy,我很不好意思张贴。我知道为什么这么慢。这是因为我调用“猫文件名|头-n 1”来获取最高编号,将其添加到运行总计中,然后调用“猫文件名|尾巴...”以删除下一次迭代的顶行...我有很多关于编程的知识!!!
马克·罗伯茨

6
那是...非常系统的。非常明确和直接,我喜欢它是可怕的可憎之处。我认为,这些工具是在您启动时就已经知道的工具中构建的,对吗?
dmckee ---前主持人小猫,2010年


@MarkRoberts必须花很长时间才能解决这个问题。这是一个非常切肉的问题解决技术,哦,这是错误的。看起来像是过度思考的经典案例。Glen Jackman的解决方案中有几个是shell脚本解决方案(另外两个是不使用诸如awk和的纯shell bc)。这些都在不到10秒的时间内完成了100万个数字的累加。看看这些,看看如何在纯外壳中完成它。
David W.

Answers:


113

对于Perl一线软件,它基本上与Ayman Hourieh的答案中awk解决方案相同:

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

如果您对Perl单一代码的用途感到好奇,则可以贬低它们:

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

结果是该程序的更详细的版本,其形式是没有人可以自己编写的:

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

仅出于傻笑,我尝试了使用包含1,000,000个数字(范围为0-9,999)的文件进行此操作。在我的Mac Pro上,它几乎立即返回。太糟糕了,因为我希望使用起来mmap会很快,但这是同时的:

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
哇,这表明您对什么代码-nle实际上包裹了您提供的字符串有了深刻的了解。我最初的想法是,当您陶醉时不应该发布信息,但是后来我注意到您是谁,还记得您的其他Perl答案:-)
paxdiablo 2010年

-n和-p只是将字符放在-e的参数周围,因此您可以根据需要使用这些字符。我们有很多单行代码,它们与“ 有效Perl编程”(即将上架)中的功能很有趣。
brian d foy 2010年

5
很好,这些不匹配的花括号是什么?
弗兰克(Frank)

17
-n while { }在您的程序周围添加循环。如果放进去} ... {,那你就有了while { } ... { }。邪恶?略。
jrockway

5
突出显示该-MO=Deparse选项的大奖金!即使在单独的主题上。
conny

374

您可以使用awk:

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

3
超出程序:字段最大数量:32767
leef 2012年

1
-F '\t'如果您的字段包含空格并且由制表符分隔,则可以使用该选项。
伊桑·弗曼

5
请将此标记为最佳答案。如果要对TSV(制表符分隔的值)文件中每一行的第一个值求和,它也可以使用。
安德烈(Andrea)

99

到目前为止,没有一种解决方案可以使用paste。这是一个:

paste -sd+ filename | bc

例如,计算Σn,其中1 <= n <= 100000:

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

(出于好奇,seq n可以打印从1n给定正数的数字序列n。)


1
非常好!且容易记住
Brendan Maguire

1
seq 100000 | paste -sd+ - | bc -l在Mac OS X Bash shell上。这是迄今为止最甜蜜,最统一的解决方案!
Simo A.

1
@SimoA。我投票赞成我们使用“最单一”代替“最统一”,因为对于最性感的解决方案而言,始终是最“单一”的;)
Connor

86

只是为了好玩,让我们对其进行基准测试:

$ 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分钟后我放弃了sed运行


我一直在潜水 ,而且速度很快:

$ 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

当我更新它时,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

注意埃德·莫顿的建议:使用 $1

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

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

与使用 $0

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

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

18
+1:提出一系列解决方案并进行基准测试。
David W.

时间猫random_numbers | paste -sd + | bc -l real 0m0.317s用户0m0.310s sys 0m0.013s
rafi wiener

那应该和tr解决方案差不多。
格琳·杰克曼

4
如果您使用awk脚本$0而不是使用awk脚本,则它的执行速度会更快一些,$1因为如果脚本中特别提到了任何字段,那么awk就会进行字段拆分(显然会花费时间)。
Ed Morton

20

另一种选择是使用jq

$ seq 10|jq -s add
55

-s--slurp)将输入行读入数组。


1
这是用于执行此类快速任务的强大工具,几乎忘了它。谢谢
约翰


7

这是另一条线

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

假设数字是整数。如果您需要小数,请尝试

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

将2调整为所需的小数位数。


6

我更喜欢将GNU datamash用于此类任务,因为它比perl或awk更简洁明了。例如

datamash sum 1 < myfile

其中1表示数据的第一列。


1
这似乎不是标准组件,因为我在Ubuntu安装中看不到它。不过,我们希望看到它进行基准测试。
轻松逗乐的史蒂文(Steven the Easily Amused)


5

我更喜欢使用R:

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

我是R的其他应用程序的粉丝,但是这样做不利于性能。文件I / O是主要问题。我已经测试了将args传递到可以使用vroom软件包加速的脚本。在同一台服务器上对其他一些脚本进行基准测试时,我将发布更多详细信息。
汤姆·凯利

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

(与brian d Foy的答案相同,但不带“ END”)


我喜欢这个,但是您能解释一下花括号吗?看到}不包含{,反之亦然,这很奇怪。
6

1
@drumfire参见上面的@brian d foy的答案,perl -MO=Deparse以了解perl如何解析程序。或perlrun的文档:perldoc.perl.org/perlrun.html(搜索-n)。如果您使用-n,则perl用{}包装您的代码,因此它成为一个完整的程序。
DeliciousEnergy

4

更简洁:

# 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似乎快两倍(320 vs 640 ms)。time python -c "print(sum([float(s) for s in open('random_numbers','r')]))"
user12719


3

只是为了好玩,让我们用Perl的数组数学引擎PDL来做吧!

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

rcols将列读入矩阵(在这种情况下为1D),并且sum(惊奇)将矩阵的所有元素求和。


如何修复无法在@INC中找到PDL.pm(您可能需要安装PDL模块)(@ INC包含:/ etc / perl /usr/local/lib/x86_64-linux-gnu/perl/5.22.1? ))当然很有趣=)
Fortran

1
您必须先安装PDL,它不是Perl本机模块。
乔尔·伯杰

3

这是使用带有生成器表达式的python的解决方案。在我的老旧笔记本电脑上测试了100万个数字。

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
一个简单的带有命名函数的列表理解是一个很好的用例map()map(float, sys.stdin)
sevko 2015年

3

我不能只是路过...这是我的Haskell一线客。它实际上很可读:

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

不幸的是,不能ghci -e只运行它,因此它需要主要功能,打印和编译。

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

为了清楚起见,我们读取了整个输入(getContents),将其除以lines,分别read为数字和sum<$>fmap操作员-我们使用它而不是通常的函数应用程序,因为确保所有这些操作都在IO中发生。read需要一个额外的fmap,因为它也在列表中。

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

这是一个奇怪的升级,使其可以与浮点数一起使用:

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


2

运行R脚本

我已经编写了一个R脚本来接受文件名的参数并对行进行求和。

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

可以使用“ data.table”或“ vroom”包来加速,如下所示:

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

标杆管理

@glenn jackman相同的基准数据。

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

与上面的R调用相比,将R 3.5.0作为脚本运行与其他方法(在同一Linux Debian服务器上)相当。

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

具有readLines的R脚本

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

带有data.table的R脚本

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

带vroom的R脚本

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

与其他语言的比较

作为参考,作为在相同硬件上建议的其他一些方法

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

红宝石(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版本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

使用其他语言进行更新

卢阿(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计时,与zsh不兼容

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

sed(4.4)必须以bash计时,与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

注意:sed调用在具有更多可用内存的系统上似乎工作更快(请注意,用于sed基准测试的数据集较小)

朱莉娅(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中一样,文件I / O方法具有不同的性能。


2

C ++“单线”:

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

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

1

另一个很有趣

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

或仅另一个bash

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

但是awk解决方案可能是最好的,因为它最紧凑。


1

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

1M数字的计时(与我的python答案相同的机器/输入):

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

1
最佳答案!最佳速度)
Fortran

1

使用Ruby:

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

另一个选项(当输入来自STDIN时)是ruby -e'p readlines.map(&:to_f).reduce(:+)'
nisetama '19

0

考虑到您需要通读整个文件,我不知道您是否可以做得更好。

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

1
可读性强。对于perl。但是,是的,肯定会是那样的……
dmckee ---前主持人小猫

$_是默认变量。<>当您<>在中使用时,默认情况下,行输入运算符会将其结果放入其中while
brian d foy 2010年

1
@Mark $_是主题变量,其作用类似于“ it”。在这种情况下,<> 为其分配每一行。它在许多地方得到了使用,以减少代码混乱并帮助编写单行代码。脚本说:“将总和设置为0,读取每一行并将其添加到总和中,然后打印总和。”
daotoad

1
@Stefan,如果没有警告和限制,则可以跳过声明和初始化$sum。因为这很简单,所以您甚至可以使用语句修饰符while$sum += $_ while <>; print $sum;
daotoad

0

我没有测试过,但它应该可以工作:

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

如果bc不处理EOF和EOL,则可能必须在bc之前的字符串中添加“ \ n”(例如通过echo)。


2
没用 bc由于末尾带有“ +”且末尾缺少换行符而发出语法错误。这将起作用,并且消除了对cat{ tr "\n" "+" | sed 's/+$/\n/'| bc; } < numbers2.txt <numbers2.txt tr "\n" "+" | sed 's/+$/\n/'| bc
暂停直到另行通知

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

0

这是另一个:

open(FIL, "a.txt");

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

close(FIL);

print "Sum = $sum\n";

0

您可以使用Alacon - Alasql数据库的命令行实用程序来完成此操作。

它与Node.js一起使用,因此您需要先安装Node.js,然后再安装Alasql软件包:

要从TXT文件计算总和,可以使用以下命令:

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

0

将所有新行替换为+,添加a 0并将其发送给Ruby解释器并不容易吗?

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

如果没有irb,则可以将其发送给bc,但是您必须删除除最后一行(echo)外的所有换行符。最好使用tr此方法,除非您拥有博士学位sed

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

0

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

什么是“ 64”?我想“ 10”是基础?
彼得·K

是的,10是基数。64是位数,如果结果int无法用那么多位数表示,则会返回错误。见golang.org/pkg/strconv/#ParseInt
dwurf

0

重击变种

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的shell中,我使用下面的脚本来这样做:

    #!/bin/bash


total=0;

for i in $( awk '{ print $1; }' <myfile> )
do
 total=$(echo $total+$i | bc )
 ((count++))
done
echo "scale=2; $total " | bc
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.