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

シェルではこれを行わないでください。どんなに調整しても効率よく作れません。シェルループは遅いシェルを使用してテキストを解析するのは悪い習慣です。スクリプト全体をawk次の簡単な1行に置き換えることができます。これははるかに高速です。

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までの数字を含むファイルに対して上記で提供されたシェルソリューション、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

ご覧のとおり、awkPythonよりも高速で作成が簡単です。必要に応じてこれを「シェル」スクリプトにすることもできます。次のようになります。

#!/bin/awk -f

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

または、シェルスクリプトにコマンドを保存します。

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

これは基本的に何もせずに時間を無駄にする明確な方法です:)。これらの2行は直接削除できます。

関連情報