最优雅的unix shell one-liner汇总任意精度的数字列表?

时间:2017-06-27 01:30:55

标签: shell awk scripting bc dc

关于添加单行的整数,存在几种提议的shell脚本解决方案;
然而,仔细观察所选择的每种解决方案,都存在固有的局限性:

  • awk会以任意精度和整数大小扼杀(它表现得像C一样,毕竟)
  • bc我们宁愿对任意长输入感到不满:(sed 's/$/+\\/g';echo 0)|bc

了解跨平台可能存在可移植性问题(参见[1] [2]),这是不受欢迎的, 是否有一个通用的解决方案,它在实用性简洁方面都是赢家?

提示:SunOS& MacOSX是可移植性问题的例子 网络连接。 can dc命令允许处理任意大的2 ^ n,整数或其他输入?

[1] awkhttps://stackoverflow.com/a/450821/1574494https://stackoverflow.com/a/25245025/1574494Printing long integers in awk

[2] bcBash command to sum a column of numbers

5 个答案:

答案 0 :(得分:3)

我通常使用的是paste -sd+|bc

$ time seq 1 20000000 | paste -sd+|bc
200000010000000

real    0m10.092s
user    0m10.854s
sys     0m0.481s

(对于严格的Posix兼容性,paste需要提供一个显式参数:paste -sd+ -|bc。显然,在OS X上默认安装BSD paste实现是必要的。)

但是,对于较大的输入,这将失败,因为bc在评估它之前在内存中缓冲整个表达式。在我的系统上,bc内存不足,试图增加1亿个数字,尽管它能够达到7000万个。但其他系统的容量可能较小。

由于bc具有变量,因此可以通过重复添加到变量而不是构造单个长表达式来避免长行。这是(据我所知)100%Posix兼容,但有3倍的时间惩罚:

$ time seq 1 20000000|sed -e's/^/s+=/;$a\' -es|bc
200000010000000

real    0m29.224s
user    0m44.119s
sys     0m0.820s

处理输入大小超过bc缓冲容量的情况的另一种方法是使用标准xargs工具在组中添加数字:

$ time seq 1 100000000 |
> IFS=+ xargs sh -c 'echo "$*"' _ | bc | paste -sd+ | bc
5000000050000000

real    1m0.289s
user    1m31.297s
sys     0m19.233s

每个xargs评估使用的输入行数量因系统而异,但通常为数百个,可能更多。显然,xargs | bc调用可以任意链接以增加容量。

xargs超出-s命令容量的系统上,可能需要使用ARG_MAX开关限制bc扩展的大小。除了执行实验以确定bc缓冲区限制之外,没有可移植的方法来确定该限制可能是什么,但它肯定应该不小于LINE_MAX,保证至少为2048。即使使用100位数的加数,也可以减少20倍,因此假设您准备等待一对,10个xargs|bc管道链将处理超过10个 13 加数完成的几个月。

作为构建大型固定长度管道的替代方法,您可以使用函数递归管道xargs|bc的输出,直到只生成一个值:

radd () { 
    if read a && read b; then
        { printf '%s\n%s\n' "$a" "$b"; cat; } |
          IFS=+ xargs -s $MAXLINE sh -c 'echo "$*"' _ |
          bc | radd
    else
        echo "$a"
    fi
}

如果对MAXLINE使用一个非常保守的值,上面的速度相当慢,但是如果值较大,则它比简单的paste|bc解决方案慢得多:

$ time seq 1 20000000 | MAXLINE=2048 radd
200000010000000

real    1m38.850s
user    0m46.465s
sys     1m34.503s

$ time seq 1 20000000 | MAXLINE=60000 radd 
200000010000000

real    0m12.097s
user    0m17.452s
sys     0m5.090s

$ time seq 1 100000000 | MAXLINE=60000 radd 
5000000050000000

real    1m3.972s
user    1m31.394s
sys     0m27.946s

除了bc解决方案外,我还有其他一些可能性。如上所示,输入2000万个数字,paste|bc需要10秒。这几乎与使用

添加2000万个数字所用的时间相同
gawk -M '{s+=$0} END{print s}'

pythonperl等编程语言证明更快:

# 9.2 seconds to sum 20,000,000 integers
python -c $'import sys\nprint(sum(int(x) for x in sys.stdin))'
# 5.1 seconds
perl -Mbignum -lne '$s+=$_; END{print $s}'

我无法在大输入上测试dc -f - -e '[+z1<r]srz1<rp',因为它的性能似乎是二次的(或更差);它在3秒内总计了25000个数字,但花了19秒来总计5万和90秒来做10万。

虽然bc不是最快的,并且内存限制需要笨拙的解决方法,但它具有在Posix兼容系统上开箱即用的优势,而无需安装任何标准实用程序的增强版本({{1 }}或Posix不需要的编程语言(awkperl)。

答案 1 :(得分:2)

dc(1)的最佳解决方案在读取输入时对其进行求和:

$ jot 1000000 | sed '2,$s/$/+/;$s/$/p/' | dc
500000500000

答案 2 :(得分:1)

您可以将$ seq 1 20000000 | gawk -M '{s+=$0} END{print s}' 200000010000000 -M标志一起使用:

Perl

或启用bignum$ seq 1 20000000 | perl -Mbignum -lne '$s+=$_; END{print $s}' 200000010000000

{{1}}

答案 3 :(得分:1)

$ seq 1000|(sum=0;while read num; do sum=`echo $sum+$num|bc -l`;done;echo $sum) 500500

此外,这个人不会赢得最高奖项,但它是:

  • oneliner,是的。
  • 便携式
  • 添加任意长度的列表
  • 添加任何精度的数字(每个数字的长度仅受MAXLINE限制)
  • 不依赖于外部工具,如python / perl / awk / R等
拉伸,你可以称之为优雅;-) 来吧,表明更好的方法!

答案 4 :(得分:0)

以下似乎可以解决问题:

$ seq 1000|dc -f - -e '[+z1<r]srz1<rp'
500500

但是,它是最佳解决方案吗?