連想配列

これまで、スカラー変数と配列の2つを紹介してきたが、3つ目の連想配列を取り上げよう。

普通、配列といえば、数値をインデックスとした連続した要素からなるデータのことだ。 言語によって様々だが、普通は0または1から始まる添字があって、個々の要素を区別するわけだ。 Perl の配列は @ で始まる名前を持つが、個々の要素には $ を付けてアクセスするということはすでに紹介した通りである。

一方、連想配列は任意の文字列をインデックスとして使える配列だ。 普通の配列と違って数値インデックスにこだわらないので、非常に柔軟なデータを扱うことができるようになる。 まずは簡単なサンプルから始めよう。

$p{'kevin'} = 4;
$p{'lol'} = 3;
$p{'graham'} = 1;
$p{'eric'} = 2;

連想配列は、通常の配列が添字を [] に入れて表現するのと違い、{} に入れて表現する。 その添字は文字列を使うことができる。 たとえば上記のように、個々の要素に値を代入することができるし、値を取り出すことももちろんできる。 また、次のように一度に代入することもできる。

%p = (
	'kevin',  4,
	'lol',    3,
	'graham', 1,
	'eric',   2
);

もし Perl ver 5 を使っているなら、こんな風に分かりやすく書くこともできる。

%p = (
	'kevin'  => 4,
	'lol'    => 3,
	'graham' => 1,
	'eric'   => 2
);

単に文字列を添字に使えるだけなら、大したことはない。 肝心なのはアクセス方法だ。 まず、連想配列のインデックス(添字)を取り出す方法を示そう。 そのためには keys を使う。 連想配列に対して keys を使うと、インデックスだけを取り出してリストにしてくれる。 これと foreach を組み合わせれば、連想配列の一覧を表示することができる。

%p = (
	'kevin'  => 4,
	'lol'    => 3,
	'graham' => 1,
	'eric'   => 2
);
foreach $i (keys %p) {
	print "$i -> $p{$i}\n";
}

さて、このプログラムをセーブして実行してみてほしい。 うちのマシンではこういう結果になった。

% perl assoc.pl
graham -> 1
eric -> 2
kevin -> 4
lol -> 3

リストの順序が、代入した順序と異なっているのが分かると思う。 もしかすると、読者の環境では違った順序で出てくるかもしれない。 理由を細かく説明すると面倒なので省略するが、とにかく「代入した順に取り出せるわけじゃない」ということは覚えておいてほしい。 一応、sort という「アルファベット順に並べ替える」機能はサポートされているので、次のようにすればソートだけはしてくれる。

foreach $i (sort keys %p) {
	print "$i -> $p{$i}\n";
}

さらに、並べた順序を逆にする reverse という機能もある。 reverse を使えば降順に並べ替えることができる。

foreach $i (reverse sort keys %p) {
	print "$i -> $p{$i}\n";
}

さて、連想配列を使った例をひとつ紹介しておこう。 それはファイルの中でどんな文字が何個使われているかをカウントするプログラムだ。 C 言語ではちょっと面倒な問題なのだが、Perl ならば連想配列ですぐに書ける。 読み込んだ行を1文字ずつに分解し、文字数を記録する連想配列のインデックスにしてしまえばよい。

while (<>) {
        @t = split('');
        foreach $i (@t) {
                $count{$i}++;
        }
}
foreach $i (sort keys %count) {
        print "'$i' = $count{$i}\n";
}

最初の while が、各行ごとに文字数をカウントするループである。 split にスペースなしの「''」を指定すると、結果としてすべての文字がバラバラに分解される。 これが配列 @t に代入される。 続いて foreach を使って、ひとつひとつの文字を取り出し、その文字をキーとして連想配列 %count を作る。 たとえば「alpha」という行を読み込んだとすれば、a, l, p, h, a の各文字に分解され、最初は「$count{'a'}++」、次に「$count{'l'}++」という具合いに実行される。

次の foreach は、生成された連想配列を表示するループだ。 まず %count のキーが取り出され、ソートされる。 そして各文字ごとに「'a' = 14」のような形式で表示する。

試しにプログラムをセーブして、実行してみてほしい。 こんな感じになるはずだ。

% perl count.pl count.pl
'       ' = 6
'
' = 9
' ' = 15
'"' = 2
'$' = 7
'%' = 1
''' = 4
'(' = 4
')' = 4
'+' = 2
';' = 3
'<' = 1
'=' = 2
'>' = 1
'@' = 2
'\' = 1
'a' = 2
'c' = 5
'e' = 4
   :
   :
'w' = 1
'y' = 1
'{' = 5
'}' = 5

最初の3行がおかしな表示になっているのが分かる。 これはタブと改行がそのまま表示されているのだ。 これじゃちょっと見づらいので、タブや改行のときは表示を変えるようにしよう。 if を使えば、簡単にできる。

if (条件式) {
	ブロック;
}

if は、条件式が真のときにブロックを実行する。 偽のときには実行しない。 if には、続けて else を追加することができる。

if (条件式) {
	ブロック1;
} else {
	ブロック2;
}

else を加えると、条件式が真のときはブロック1、偽のときはブロック2を実行してくれる。 これで条件によって異なる動作をさせることができるわけだ。

if では、さらに elsif というブロックも追加できる。

if (条件式1) {
	ブロック1;
} elsif (条件式2) {
	ブロック2;
} else {
	ブロック3;
}

これで、条件式1が真ならばブロック1、条件式2が真ならばブロック2、どちらも偽ならばブロック3が実行される。 これを使って、以下のように書き換えてみよう。

while (<>) {
        @t = split('');
        foreach $i (@t) {
                $count{$i}++;
        }
}
foreach $i (sort keys %count) {
        if ($i eq "\n") {
                print "'\\n' = $count{$i}\n";
        } elsif ($i eq "\t") {
                print "'\\t' = $count{$i}\n";
        } else {
                print "'$i' = $count{$i}\n";
        }
}

最初の条件式「$i eq "\n"」は、変数 $i が "\n" (つまり改行)と等しいかどうかをテストする。 「eq」は文字列を比較するための演算子で、数値を比較したいなら「==」を使う。 改行のとき、タブのとき、その他のときでパターンを変えれば OK だ。 出力結果は次のようになる。

'\t' = 15
'\n' = 15
' ' = 32
'"' = 10
'$' = 13
'%' = 1
''' = 8
'(' = 6
')' = 6
'+' = 2
';' = 5
'<' = 1
'=' = 4
'>' = 1
'@' = 2
'\' = 9
'a' = 2
'c' = 7
'e' = 9
   :
   :
'w' = 1
'y' = 1
'{' = 10
'}' = 10

[go to next chapter]
[back to index]