[awk] awkでカラム方向の計算

2017年7月10日月曜日

awk bash シェルスクリプト

tr -s ' ' '\n'

前回は以下のようなコマンドでcalコマンドの結果を行方向に変換した。
$ cal | tr -s ' ' '\n' | 
awk 'NR==2 { mm = $1 } NR==3 { yy = $1 } NR>10 { print mm,$1,yy }'

これはawkの中で行毎にカラム数分ループさせるのがかっこ悪いなあと思ったので、事前に tr でデータの並びを行方向に展開する方法を取っていた。

でも、これって本当に効率がいいんだろうか。
ひょっとして、awkの中でループした方が速いんじゃないだろうか。

カラム方向にループ

同じことをawkの中でループするようにすると。
$ time LC_TIME=POSIX cal | tr -s ' ' '\n' | 
awk 'NR==2 { mm=$1 } NR==3 { yy=$1 } NR>10 { print mm,$1,yy }' | 
date -f- '+%Y/%m/%d(%a)' | grep -e '[土日]'
2017/06/03(土)
2017/06/04(日)
2017/06/10(土)
2017/06/11(日)
2017/06/17(土)
2017/06/18(日)
2017/06/24(土)
2017/06/25(日)

real    0m0.003s
user    0m0.006s
sys     0m0.002s
$ time LC_TIME=POSIX cal | 
awk 'NR==1 { mm=$1; yy=$2 } NR>2 { for (i=1; i<=NF; i++) print mm,$i,yy }' | 
date -f- '+%Y/%m/%d(%a)' | grep -e '[土日]'
2017/06/03(土)
2017/06/04(日)
2017/06/10(土)
2017/06/11(日)
2017/06/17(土)
2017/06/18(日)
2017/06/24(土)
2017/06/25(日)

real    0m0.002s
user    0m0.004s
sys     0m0.002s

うーん、全然変わらない。というか、誤差範囲か。この程度では。

データ量が多いと...

もうちょっと、データ量が多い場合をやってみよう。
500列 * 10000行のデータで毎行平均値を算出するようにしてみよう。

まずはデータを作る。
$ seq 5000000 | 
awk "BEGIN { srand($RANDOM); } { print rand() }" | 
xargs -n500 >rand.dat

これを元に毎行の平均値を算出すると。
$ awk --version
GNU Awk 4.1.3, API: 1.1
Copyright (C) 1989, 1991-2015 Free Software Foundation.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/.

$ time cat rand.dat | tr -s ' ' '\n' | 
awk 'NR%500==1{ s=0 } { s+=$1 } NR%500==0 { print s/500 }' >/dev/null

real    0m3.079s
user    0m3.393s
sys     0m0.076s
$ time cat rand.dat | 
awk '{ s=0; for ( i=1; i<=NF; i++) s+=$i; print s/NF }' >/dev/null

real    0m1.875s
user    0m1.855s
sys     0m0.106s

awkの中でカラム方向にループするほうが速い結果になりました。
毎行、剰余を計算して条件分岐しているので、そりゃそうだという気もしますが...

mawkを使ってみよう

[awk]gawkとmawkで紹介したmawkで、awk内ループバージョンを実行すると。
$ mawk -W version
internal regex
compiled limits:
max NF             32767
sprintf buffer      2040

$ time cat rand.dat | 
mawk '{ s=0; for ( i=1; i<=NF; i++) s+=$i; print s/NF }' >/dev/null

real    0m1.204s
user    0m1.188s
sys     0m0.062s

mawkのほうが3分の2程度の実行時間で完了した。
やっぱり、数値計算が多い場合はmawkを使ったほうが速いのかな。