숫자로 가득 찬 파일(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"
기본적으로:
- 입력 파일의 각 항목을 확인하여 모든 호출에서 bc가 0보다 큰지 확인하세요.
- 항목이 0보다 큰 경우 해당 값의 자연 로그(l)를 취하여 각 호출 bc의 누적 합계에 추가합니다.
- 항목 <= 0이면 아무것도 하지 않습니다.
- 기하 평균 계산
이는 작은 데이터 세트에 매우 효과적입니다. 안타깝게도 대규모 데이터 세트(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
다음과 같은 간단한 한 줄로 대체할 수 있으며 이는 훨씬 더 빠릅니다.
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
보시다시피 awk
Python보다 빠르고 작성이 더 간단합니다. 원하는 경우 이를 "쉘" 스크립트로 만들 수도 있습니다. 다음과 같습니다.
#!/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"
이것은 기본적으로 아무것도 하지 않고 시간을 낭비하는 명확한 방법입니다 :). 이 두 줄은 직접 삭제할 수 있습니다.