講座「文系のためのawkプログラミング入門」
今回のテーマは連想配列です。連想配列は、C言語やBasic言語にもない独特の配列です。C++ や perlなど比較的新しい言語には同様の実装がなされています。連想配列も名前からわかるように配列の一種なんですが、前回学んだ配列の添え字は数でした。連想配列は数ではなく文字列を、添え字にしたものです。使い方を覚えれば実にユニークなテキスト処理のツールに成ります。
実例を示しましょう。
# script 17-1 BEGIN{ meibutsu["大阪"] = "たこやき" meibutsu["名古屋"] = "きしめん" meibutsu["福岡"] = "辛子明太子" meibutsu["静岡"] = "お茶" print "大阪の名物は " meibutsu["大阪"] print "名古屋の名物は " meibutsu["名古屋"] print "福岡の名物は " meibutsu["福岡"] print "静岡の名物は " meibutsu["静岡"] }
Input欄 Null Output欄 Screen で実行してみてください。文字列を添え字として配列が作られているのがわかるでしょう。都市の名前をキーワードにして、それと結びつけて別の文字列を記憶しているわけで、ここから連想配列と呼ばれます。添え字は文字列ですが、連想配列に代入する値は、文字列でなくてもかまいません。数値でも可です。連想配列の添え字([ ]内の文字列です)をキーと呼びます。連想配列の名前は普通の配列の命名法と同じです。ただキーは文字列なので、常に引用符でかこまないといけません。
(注)awkにおいては実は普通の数字を添え字とする配列も、内部的には、連想配列として実装されているということです。awk自体のプログラムソースを検討する暇がないので詳細は不明ですが。
配列名[キー] = 値 # 値は文字列か数値
文字列をキーとする配列なので、その使い方は慣れるまで、少し戸惑うかもしれません。いくつか使い方で注意すべき点をのべておきましょう。まず連想配列も配列の一種です。配列というのは同一の名前をもつ変数を、添え字(キー)によって区別するものです。したがって、連想配列の一つ一つはそれぞれ変数として、数値と文字列をその値として持ちます。このことを思い出してください。そしてawkの変数はスクリプト中に記述した時点で存在します。初期値は0と空文字列 ""です。
連想配列への代入は普通の変数と同じように代入演算子 = を使います。
# script 17-2 BEGIN{ meibutsu["大阪"] = 5 print "大阪の名物は " meibutsu["大阪"] "個ある" meibutsu["大阪"] = meibutsu["大阪"] + 1 print "大阪の名物は " meibutsu["大阪"] "個になった" }
上のスクリプトを実行すると(Input欄 Null Output欄 Screen)
大阪の名物は 5個ある 大阪の名物は 6個になった
という結果がでます。
このスクリプトを普通の変数を使って書くと、下のようになります。全く同じ結果が出力されます。
# script 17-3 BEGIN{ x = 5 print "大阪の名物は " x "個ある" x = x + 1 print "大阪の名物は " x "個になった" }
したがって、普通の変数と同じように インクリメント演算子 ++ やデクリメント演算子 -- も 使えます。17-2 の スクリプト3行目
meibutsu["大阪"] = meibutsu["大阪"] + 1
は、下のようにも書けます。
meibutsu["大阪"]++
普通の配列の全ての要素を数え上げるには、for文や while文を使えました。いくつの配列を作ったかを覚えておき、その数を使って、for文やwhile文で回してやればいいんですね。たとえば5つの要素をもつ配列 arr[ ] の全ての要素を表示するには、
for(i = 1; i <= 5; i++) print arr[i]
とします。しかし、連想配列は文字列をキーとしていますから、このように、数を添え字としてループするという方法はとれません。連想配列の場合は次のようにします。
for( 仮引数 in 連想配列名) 処理
連想配列にいくつのキーを設定したのか、これをawkは覚えていますから、任せてしまえばいいのです。上の式で、仮引数は、変数と同じで、どんな名前でもかまいません。また連想配列名は、連想配列の [ ] を除いたものです。これを使って一番最初の名物のスクリプトを書き換えますと、以下のようになります。
# script 17-4 BEGIN{ meibutsu["大阪"] = "たこやき" meibutsu["名古屋"] = "きしめん" meibutsu["福岡"] = "辛子明太子" meibutsu["静岡"] = "お茶" for( key in meibutsu) # key を 仮引数として設定 { print key "の名物は " meibutsu[key] # 設定した仮引数 key を用いる } }
上のfor文では、awkは覚えているキー( ここでは四つあります )を、一つ一つ変数key に代入しながら、そのキーで指定される連想配列変数の内容を、取り出して、print してくれます。
では、もうひとつ例をあげましょう。
次のような英文があります。ここにでてくる単語全ての出現回数を数え上げるスクリプトです。使うのは、data17.txtで次のような簡単な英文です。連想配列の使い方を示すためのものなので、簡単なものにします。
This is a book It is mine That is a pen It is yours
上の英文では処理のプロセスを簡単にするため、文末のピリオドは除いてあります。
ではスクリプトです。
# script 17-5 { for(i = 1; i <= NF; i++) appear[$i]++ } END{ for( key in appear) { print key " : " appear[key] n++ } print "計 " n "語" }
まずこのスクリプトでは、本処理の部分で、入力行の各フィールドの単語をキーとする連想配列をつくります。
例えば、ファイルの第一行目を読んで、for文のループを実行しますが、 i が 1 のときは、下のようになります。
appear[$1]++
今、$1 は "this" であるので、これは実際には
appear["this"]++
です。この式は複雑なようですが、this をキーとする連想配列の変数がひとつ作られ、その変数の値が、1だけインクリメントされることを意味しています。
x++
と同じことです。appear["this"] が一つの変数だということをしっかり頭に入れておいてください。配列の x[1] や x[2] などがそれぞれ変数であったのと同じことです。
x, y, z, #変数 x[1], x[2], x[3] # 配列変数 x["this"], x["is"], x["a"] # 連想配列変数
そしてこの変数も、作られたときは 0 と "" に初期化されます。そしてインクリメントは数値を加える演算ですから、この変数は数値として扱われ、その値は 0 から 1 にかわります。appear["this"] は 1 という値をもつようになります。
従って、第一行目 ( This is a book ) を読んだときのスクリプトの実行状態を具体的にあらわせば、以下のようになります。
appear["This"]++; appear["is"]++; appear["a"]++; appear["book"]++;
このとき連想配列変数は四つ作られて、それぞれ1という値を持つようになります。次に2行目を読んだときは
appear["It"]++; appear["is"]++; appear["mine"]++;
こうなります。このとき、appear["is"]はすでに作られている変数なので、インクリメントされるだけで、値は2になります。その他は新しく作られて、値は0から1にインクリメントされます。このように、初めてでてくる単語については、それをキーとする連想配列変数が作られインクリメントされ、既出の単語については、値がインクリメントされるだけです。
3行目では、新たに二つ作られて、二つはインクリメントされるだけです。
appear["That"]++; appear["is"]++; appear["a"]++; appear["pen"]++;
4行目では、一つが新しく作られ、二つがインクリメントされるだけです。
appear["It"]++; appear["is"]++; appear["yours"]++;
全部でいくつ連想配列変数がつくられたか分かりますか。覚えてないですよね。それでもかまいません。人間は忘れてもawkは覚えてくれています。全部の連想配列変数について、その値を出力してもらいます。
END{ for( key in appear) { print key " : " appear[key] n++ } print "計 " n "語" }
ここで変数 n はひとつのキーを処理するたびに0からインクリメントしますから、キーの数を表すことになります。
多分awkの文法のなかで、この連想配列がもっともややこしいものかも知れません。しかし最も強力なツールであるといっても良いでしょう。上のスクリプトを参考に自分でも色々と実験してマスターしてください。