2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[AWK] 巨大な行列の転置を行う

Last updated at Posted at 2020-06-04

問題設定

下記のように文字列を転置することを考えます。

cat input.txt
AAA
BBB
CCC
cat output.txt
ABC
ABC
ABC

小さい行列の場合

まず手始めに小さい行列を転置します。
A-Cの3行で、それぞれ3文字を格納した行列を作製します。

seq_len=3
awk -v seq_len="${seq_len}" \
'BEGIN {A=65; C=A+3; for (i=A; i<C; i++){
    alphabet=sprintf("%c", i)
    s=sprintf("%"seq_len"s","")
    gsub(/ /,alphabet,s)
    print s}
    }' \
> input1.txt
cat input1.txt
AAA
BBB
CCC

それでは転置を行います。
Stack OverflowにAWKのコードがありました。
連想配列を巧みに扱ったエレガントなコードです。
拝借します。

awk -F "" '
{for (i=1; i<=NF; i++)  { a[NR,i] = $i }}
NF>p { p = NF }
END { for(j=1; j<=p; j++) {
        str=a[1,j]
        for(i=2; i<=NR; i++){ str=str""a[i,j] }
        print str}}' input1.txt \
> output1.txt
cat output1.txt
ABC
ABC
ABC

なにも問題ありませんね:smiley:

大きい行列の場合:メモリ消費量が尋常ではない

それでは大きいサイズの行列を転置します。
A-Zの26行で、それぞれ1,000,000文字を格納した行列を作製します。

seq_len=1000000
awk -v seq_len="${seq_len}" \
'BEGIN {A=65; Z=A+26; for (i=A; i<Z; i++){
    alphabet=sprintf("%c", i)
    s=sprintf("%"seq_len"s","")
    gsub(/ /,alphabet,s)
    print s}
    }' \
> input2.txt

ファイルサイズは25Mです。

ls -lh input2.txt | cut -d " " -f 5
25M

それでは、これを上記のコードで計算します。

その前に、計算時間とメモリ消費量を記録するコマンドを作ります。

alias time_='/usr/bin/time -f "========\nreal %es\nuser %Us \nsys  %Ss \nmemory:%MKB \n========"'

では、実行します。

time_ awk -F "" '
{for (i=1; i<=NF; i++)  { a[NR,i] = $i }}
NF>p { p = NF }
END { for(j=1; j<=p; j++) {
        str=a[1,j]
        for(i=2; i<=NR; i++){ str=str""a[i,j] }
        print str}}' input2.txt \
> output2.txt
========
real 25.33s
user 23.87s 
sys  1.44s 
memory:7554988KB 
========

メモリをなんと7.6GBも消費しています!! :scream:

解決法

メモリ消費量が大きい理由は、連想配列に全データを格納しているからです。
なので、目的の計算が終わったら直ちにファイルに書き出すようにします。

nr=$(cat input2.txt | wc -l)

time_ awk -v nr="${nr}" -F "" \
'{for(i=1; i<=NF; i++){
        row[i]=row[i] $i
        if(NR==nr) print row[i]
    }
}' input2.txt \
> output3.txt
========
real 6.46s
user 6.36s 
sys  0.09s 
memory:240704KB 
========

メモリ消費量240MB!!

計算時間はおよそ1/4、メモリ消費量にいたっては約3/100になりました!:tada:

最後に、Stack Overflowのコードと今回のコードで出力結果が同じか確認します。

[ -z $(diff output2.txt output3.txt) ] &&
printf "同じ結果です\n"
同じ結果です

同じ結果が得られました。

学んだこと

大きなファイルを扱う場合、連想配列に格納したデータはENDまで保持せず、すぐに出力しましょう:fist:

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?