在 bash 中进行计算的有效方法

在 bash 中进行计算的有效方法

我正在尝试计算充满数字的文件(1 列)的几何平均值。

几何平均值的基本公式是所有值的自然对数(或对数)的平均值,然后将 e(或以 10 为底)提高到该值。

我当前仅使用 bash 的脚本如下所示:

# Geometric Mean
count=0;
total=0; 

for i in $( awk '{ print $1; }' input.txt )
  do
    if (( $(echo " "$i" > "0" " | bc -l) )); then
        total="$(echo " "$total" + l("$i") " | bc -l )"
        ((count++))
    else
      total="$total"
    fi
  done

Geometric_Mean="$( printf "%.2f" "$(echo "scale=3; e( "$total" / "$count" )" | bc -l )" )"
echo "$Geometric_Mean"

本质上:

  1. 检查输入文件中的每个条目,确保每次调用 bc 都大于 0
  2. 如果条目 > 0,我会取该值的自然对数 (l),并将其添加到每次调用 bc 的累计总数中
  3. 如果条目 <=0,我什么都不做
  4. 计算几何平均值

这对于小数据集来说效果非常好。不幸的是,我试图在大型数据集上使用它(input.txt 有 250,000 个值)。虽然我相信这最终会奏效,但速度非常慢。我从来没有足够的耐心让它完成(45 分钟以上)。

我需要一种更有效地处理该文件的方法。

还有其他方法,例如使用 Python

# Import the library you need for math
import numpy as np

# Open the file
# Load the lines into a list of float objects
# Close the file
infile = open('time_trial.txt', 'r')
x = [float(line) for line in infile.readlines()]
infile.close()

# Define a function called geo_mean
# Use numpy create a variable "a" with the ln of all the values
# Use numpy to EXP() the sum of all of a and divide it by the count of a
# Note ... this will break if you have values <=0
def geo_mean(x):
    a = np.log(x)
    return np.exp(a.sum()/len(a))

print("The Geometric Mean is: ", geo_mean(x))

我想避免使用 Python、Ruby、Perl ...等。

关于如何更有效地编写 bash 脚本有什么建议吗?

答案1

请不要在 shell 中执行此操作。无论进行多少调整都无法使其变得高效。 Shell 循环是慢的使用 shell 来解析文本只是一种不好的做法。您的整个脚本可以用这个简单的awk单行代码替换,这将快几个数量级:

awk 'BEGIN{E = exp(1);} $1>0{tot+=log($1); c++} END{m=tot/c; printf "%.2f\n", E^m}' file

例如,如果我在包含 1 到 100 之间的数字的文件上运行该命令,我会得到:

$ seq 100 > file
$ awk 'BEGIN{E = exp(1);} $1>0{tot+=log($1); c++} END{m=tot/c; printf "%.2f\n", E^m}' file
37.99

在速度方面,我在包含 1 到 10000 数字的文件上测试了您的 shell 解决方案、您的 python 解决方案以及我上面给出的 awk:

## Shell
$ time foo.sh
3677.54

real    1m0.720s
user    0m48.720s
sys     0m24.733s

### Python
$ time foo.py
The Geometric Mean is:  3680.827182220091

real    0m0.149s
user    0m0.121s
sys     0m0.027s


### Awk
$ time awk 'BEGIN{E = exp(1);} $1>0{tot+=log($1); c++} END{m=tot/c; printf "%.2f\n", E^m}' input.txt
3680.83

real    0m0.011s
user    0m0.010s
sys     0m0.001s

正如您所看到的,它awk比 python 更快,而且编写起来更简单。如果您愿意,您还可以将其制作成“shell”脚本。要么像这样:

#!/bin/awk -f

BEGIN{
    E = exp(1);
} 
$1>0{
    tot+=log($1);
    c++;
}
 
END{
    m=tot/c; printf "%.2f\n", E^m
}

或者将命令保存在 shell 脚本中:

#!/bin/sh
awk 'BEGIN{E = exp(1);} $1>0{tot+=log($1); c++;} END{m=tot/c; printf "%.2f\n", E^m}' "$1"

答案2

以下是一些建议。如果不确切知道您的文件中的内容,我无法测试它们,但我希望这会有所帮助。总是有不同的、更好的方法来做事,所以这并不是详尽无遗的。


改变if条件

if (( $(echo " "$i" > "0" " | bc -l) )); then

将其更改为:

if [[ "$i" -gt 0 ]]; then

第一行创建了多个进程,即使它只是做简单的数学运算。解决方案是使用[[shell 关键字。


删除不需要的代码

else
  total="$total"

这基本上是一种明确浪费时间无所事事的方法:)。这两行可以直接删除。

相关内容