| Top Page | プログラミング | Perl 目次 | prev | next | 索引 |

12. ハッシュ:キーワードと値のペアをたくさんまとめて管理する

ハッシュとはなにか

いよいよハッシュです. ハッシュは便利です. ハッシュが使えるようになると,Perl でできることの幅がぐんと広がります. ハッシュを使いこなしてこその Perl です.

ハッシュはたくさんのデータをまとめて管理してくれるという点で配列に 似ていますが,何番目の要素かを指定するのではなくて, キーになる文字列(キーワード)を手がかりに要素を指定するデータ構造です. 配列は(スカラー)変数がたくさん順番に並んだデータ構造, ハッシュは,キーになる文字列と,対応する値とのペアが たくさん順不同で並んだデータ構造 です.

例を見るまえに,表記上のルールを説明します. まず,ハッシュ全体の名前は最初に % をつけます (配列のように @ を付けるのではなく). 個々の要素を指定するときは,キーになる文字列を {} で囲んでハッシュ名の うしろにつけます(配列のように [] で囲むのではなく). その場合,ハッシュ名のまえに % でなく $ を書くのは配列と同様 です(配列の場合は '@ ではなく $' でした).

では,実例を.

%sp_list = ();    # からっぽのハッシュ.(書く必要はないけど)

# まだ存在しないキーに対応する要素に値を代入すると,あらたに要素が作られる.
$sp_list{"Arabidopsis"} = 3;           #  "Arabidopsisa"という文字列がキー,3が値.
$sp_list{"E. coli"} = "not available"; #  "E. coli" がキー,"not available"が値

print "Arabidopsis:", "\t", $sp_list{"Arabidopsis"}, "\n"; # キーと値を表示.
print "E. coli:", "\t", $sp_list{"E. coli"}, "\n";

$species = "Homo sapiens";

if (!exists $sp_list{$species}) {       # このキーに対応する要素がないならば…
    print "No data for for $species\n"; # メッセージを表示.
}

まだ存在しないキーに対応する要素に値を代入すると,あらたに要素が作られます. あるキーに対応する要素がハッシュ中にあるかどうかは,exists 関数を使って 調べることができます(存在するなら真,しないなら偽を返す).

では,何度も使った名前,身長,体重が並んだデータファイルを読んで, 今度は配列ではなくてハッシュにしまってみます. 名前をキーにして,身長,体重それぞれを記憶するハッシュを作ります.

Atsushi 177.0   75.0
Hide    175.0   68.0
Yutaka  180.0   78.0
Koji    182.0   74.0
Shinji  175.0   74.0
Mitsuo  173.0   68.0

データファイルの名前はコマンドライン引数として指定することを想定して プログラムを書きます.

while ($line = <>) {
    chomp $line;
    ($name, $h, $w) = split /\s+/, $line;
    $height{$name} = $h;
    $weight{$name} = $w;
}

foreach $name (keys %height)  { #  keys はハッシュのキーのリストを返す関数.
    print "$name\t$height{$name}\t$weight{$name}\n";
}

今度は,最初に空のハッシュを作ることはしていません. $height{$name} や $weight{$name} という表現が最初にでてきたところで,%height や %weight という名前のハッシュが自動的に作られます.

keys 関数は,ハッシュの全要素のキーがならんだリストを返します. foreach と keys 関数を組み合わせれば,ハッシュの全データを ひととおり参照することができます.最後の3行をもっとくわしく(コードも コメントも)書くと,次のようになります.

# @array_of_keys にハッシュ %height のキーのリストを代入
# %heightというハッシュは,人名をキー,身長を値とするペアの集合なので,
# @array_of_keys の各要素には人名がしまわれる.

@array_of_keys = keys (%height); 

# 一人ずつの名前を,対応する身長データ,体重データとあわせて表示する.
# $name は,配列 @array_of_keys の各要素(人名)の別名になる.

foreach $name (@array_of_keys)  { # 
    print "$name\t$height{$name}\t$weight{$name}\n";
}

foreach の説明は配列のページにでてきました. 忘れていたら復習してください.最初のプログラム例では,keys が返す人名リスト を配列変数にしまわないでそのまま foreach 文で使ってますが,動作は同じです.

うえのプログラムを実行すると,出力はこんな感じになるでしょう.

Shinji  175.0   74.0
Mitsuo  173.0   68.0
Hide    175.0   68.0
Koji    182.0   74.0
Atsushi 177.0   75.0
Yutaka  180.0   78.0

データの順番がもとのファイルと違ってますね. じつは,ハッシュに含まれる要素(キーと値のペア)には決まった順序というものが ありません.ここは配列と大きく違うところです. 要素を作った順番と,keys が返すキーの順番とは無関係です.

ハッシュ内部のデータの管理方法を理解すれば要素に決まった順序がないことが 納得できるのですが,そこまで深入りはしません.


ハッシュの要素をソート(整列)する

順序がデタラメなのも気持ち悪いので,キーでソート(整列)してみましょう.

while ($line = <>) {
    chomp $line;
    ($name, $h, $w) = split /\s+/, $line;
    $height{$name} = $h;
    $weight{$name} = $w;
}

foreach $name (sort keys %height)  { # ハッシュのキーのリストをソート.
    print "$name\t$height{$name}\t$weight{$name}\n";
}

sort という関数が使われています.これは,引数として受けとったリストの 要素を,大小関係にもとづいて並べ替えた(ソートした)ものを返します (この例の場合,文字列のアルファベット順に並べる).出力はこうなります.

Atsushi 177.0   75.0
Hide    175.0   68.0
Koji    182.0   74.0
Mitsuo  173.0   68.0
Shinji  175.0   74.0
Yutaka  180.0   78.0

ちゃんと名前のアルファベット順になりました. では,身長順にするには?

ハッシュのすべての値のリストを返す values という関数があります. でも,身長だけのリストがあっても,どの身長がだれのものかという情報が なければ,6人の名前を身長順に並べることはできません. 身長のデータの整列ができるだけで,どれがだれだか分かりません.

キーの文字列そのものを比較しながら整列するのではなく, 身長のデータだけを整列するのでもなく, キー(すなわち名前)に対応する値(すなわち身長)が整列するように, キーの順番を決める必要があります. ←ここはじっくり読んで違いを理解してください.

この目的のためには,sort 関数に要素の大小比較の仕方を指示するという, ちょっと凝ったワザが必要になります.

while ($line = <>) {
    chomp $line;
    ($name, $h, $w) = split /\s+/, $line;
    $height{$name}= $h;
    $weight{$name}= $w;
}

# ハッシュのキーを値の大小順にソート(数値としての比較で)
foreach $name (sort {$height{$a} <=> $height{$b}} keys %height)  {
    print "$name\t$height{$name}\t$weight{$name}\n";
}

sort { $height{$a} <=> $height{$b} } keys %height というのが問題の ワザです.この表現の説明は少々ややこしいのですが,いちおう書いておきます. めんどうだったら飛ばしてしまってけっこうです.

sort に引数としてリストだけ与えると,リストの要素を文字列として比較して 整列します.ほかの基準で比較して整列させたかったら,その比較のしかたを 記述した式を {} で囲んだものを sort とリストのあいだに書きます (※サブルーチンというものの名前を書いてもいいんですが,ここでは省略).

{} で囲んだブロック中では,比較するふたつの要素を $a と $b という名前で呼びます. これは決まった名前で,勝手にほかの名前を使うことはできません. $a が(ある基準で)$bより小さかったら負,同じだったらゼロ, $a のほうが大きかったら正の値を返すような式をブロック中に書くと, リストは(その基準で)昇順,すなわち小さいものから大きいものへと 並び替えられます.

この式のなかでよく使われるのは <=> という数値比較演算子です. これは,演算子の左辺と右辺の大小を数値として比べて,左辺 < 右辺なら -1, 左辺 == 右辺なら 0,左辺 > 右辺なら 1 を返すものです (※なお, <=> に対応する文字列の比較演算子は cmp です).

sort { $height{$a} <=> $height{$b} } keys %height では, ハッシュ %height のキーそのものを文字列として比較しながら並べるのではなく, 各キーに対応する値 $height{キー} が数値として昇順に並ぶようにキーを並べろ, と指示したことになります.

なかなかややこしいですね(>上の理屈を読んだ方). 理屈はともかく,このプログラムを実行すると,下のように身長の昇順 (低いほうから高いほうへ)に並び替えられて表示されます.

Mitsuo  173.0   68.0
Shinji  175.0   74.0
Hide    175.0   68.0
Atsushi 177.0   75.0
Yutaka  180.0   78.0
Koji    182.0   74.0

上の理屈は読み飛ばしていても,ちょっと類推すれば応用のしかたは 分かるでしょう.たとえば,

foreach $name (sort {$weight{$a} <=> $weight{$b}} keys %weight)  {
    print "$name\t$height{$name}\t$weight{$name}\n";
}

のように height のかわりに weight と書けば体重の昇順で並びます.

また,$a と $b を逆にして

foreach $name (sort {$height{$b} <=> $height{$a}} keys %height)  {
    print "$name\t$height{$name}\t$weight{$name}\n";
}

と書けば,身長の降順(高いほうから低いほうへ)で並びます.

ここまでの知識があれば,ハッシュを使って柔軟なデータ処理が できるようになります.実例は次のページにて.


書き方のおさらい

次のページに進む前に,変数名の書き方を整理しておきます.前に, 配列のページの最後 のおさらいに,ハッシュも加えて確認します.

です.

スカラー変数,配列,ハッシュの名前は独立に管理されていて, $x と @x と %x はたがいに無関係です. したがって, $x と $x[0] と $x{"a"} もたがいに無関係です.

くどいですが繰り返して書くと,

%x というハッシュにせっせとキーと値のペアを記録しても,その値を参照するのに $x[...] と書いてしまうと,@x という配列の要素を参照してしまいます. (私が)ときどきやってしまうまちがいです.気をつけましょう.


| Top Page | プログラミング | Perl 目次 | prev | next |