| Top Page | プログラミング | Perl 目次 | prev | next | 索引 |
配列は,同じような種類のデータがたくさんあるときにそれをまとめるのに便利でした. たくさんの木の高さのデータ,連続測定している温度のデータ,たくさんの生き物の 名前のリストなど,いくらも例を考えることができます.
では,たくさんの木の種名と高さと太さと位置( x, y 座標)のデータがあったら, どんなデータ構造で表現したらよいでしょうか.
あるいは,ニホンザルの群れの一頭一頭の性別,年齢,(人間がつけた)名前, 母親の名前,父親の名前,配偶者の名前,体重,群れの中での地位,子供の数の データがあったら,どう表現しましょう.
はたまた,学会の大会参加申し込みデータ1140人分,それぞれ 参加者の受付け番号,名前,よみ,所属,住所,メールアドレス,懇親会参加, 発表の演題のデータがあるとしたら?
こんなのとき,種名の配列と高さの配列と太さの配列と…とか, 性別の配列の年齢の配列と名前の配列と…とか, 名前の配列とふりがなの配列と所属の配列と…というように, 同種のデータをそれぞれ配列でまとめるのはひとつの方法です.
でも,「一本の木を表現するひとかたまりのデータ」の集合とか, 「一個体のサルの諸データ」の集合とか,「一人の申し込みデータ」の集合 というかたちで表現したほうが便利なこともあります.
データを種類(高さとか,年齢とか,名前とか)ごとに束ねるのでなく, データが属する「もの」ごと(一本の木ごととか,一頭のサルごととか, 一件の学会申し込みごととか)にデータを束ねて塊にするには, ハッシュを利用します.データの種類をキー,対応するデ−タを値にするのです.
さっそく例を見てみましょう. 一本の木の高さの値,太さの値,x座標の値…をハッシュにまとめてみます.
%tree = ('species' => 'イタヤカエデ',
'x' => 32.5,
'y' => 0.4,
'height' => 12.3,
'diameter' => 21.3);
print "species\t", $tree{species}; # {} 内にハッシュのキーを書くときは,
print "\n"; # 引用符は省略してもよい.
print "height\t", $tree{height};
ハッシュの初期化は, こまごまとした(有用な)補足 のところで説明した方法を使っています. こんなハッシュをたくさん作ってまとめて管理できれば便利です. 作ったハッシュのリファレンスなら,配列にたくさんしまっておけます. でも,いくつあるかわからないたくさんのハッシュそれぞれに %tree01, %tree02,... と名前をつけるわけにいかないのが問題です.
…と,なにやら前のぺージでの無名配列の導入のところと良く似た展開です. この流れからするとここで無名ハッシュが導入されるかと思うと, そう,その通りです.
前に使ったデータファイル をまた使って説明します.こんなファイルでした.
species X Y height dbh エゴノキ 0.28 0.25 10.7 15.5 エゴノキ 0.59 1.63 12.8 19.4 アカシデ 0.50 2.31 2.5 2.6 シラカシ 0.02 3.71 14.0 21.7 ....
$file_name = shift; # コマンドラインからファイル名を取得
unless (-e $file_name) {
die "!file $file_name not found";
}
open FILE, $file_name;
$line = <FILE>; # ヘッダ行を読み捨て
@trees = (); # 空の配列を用意.
while ($line = <FILE>) {
chomp $line;
@data = split /\s+/, $line;
$ref_hash = {'species' => $data[0], # 無名ハッシュを生成し,
'x' => $data[1], # $ref_hash にそのリファレンスを代入
'y' => $data[2],
'height' => $data[3],
'diameter' => $data[4]
};
push @trees, $ref_hash; # 配列 @trees に無名ハッシュへのリファレンスを追加
}
foreach $ref (@trees) { # 各無名ハッシュ(一本の木のデータ)ごとに,
print "$$ref{species}", "\t"; # 種名,高さ,直径を表示.
print "$$ref{height}", "\t";
print "$$ref{diameter}", "\n";
}
最初の例では,文字列 => 値 を ( ) で囲ったものでハッシュの初期設定を していましたが,同じものを { } で囲むと無名ハッシュが作られて, それへのリファレンスを得ることができます.
無名ハッシュのリファレンスから,キーを指定して対応する値を取り出すには, ふつうの(名のある)ハッシュのリファレンスの場合と同様です. 上のプログラムのように $$ref{キー} という書き方をしてもいいし, $ref->{キー} という書き方もできます.
もうちょっと進めて,こんどは読み込んだたくさんの木のデータを高さ順に 並べ替えてから表示してみましょう.無名ハッシュへのリファレンスの配列 @trees の要素を,各ハッシュの height というキーに対応する値に応じて 整列します.そのためには,大小関係のチェック方法を指定して sort 関数を 呼ぶ,という前にも出てきたワザを使います.
$file_name = shift; # コマンドラインからファイル名を取得
unless (-e $file_name) {
die "!file $file_name not found";
}
open FILE, $file_name;
$line = <FILE>; # ヘッダ行を読み捨て
@trees = (); # 空の配列を用意.
while ($line = <FILE>) {
chomp $line;
@data = split /\s+/, $line;
$ref_hash = {'species' => $data[0], # 無名ハッシュを生成し,
'x' => $data[1], # $ref_hash にそのリファレンスを代入
'y' => $data[2],
'height' => $data[3],
'diameter' => $data[4]
};
push @trees, $ref_hash; # 配列 @trees に無名ハッシュへのリファレンスを追加
}
# すべての木を高さで整列する.
@sorted_trees = sort { $a->{height} <=> $b->{height} } @trees;
foreach $ref (@sorted_trees) { # 一本の木のデータごとに出力
print "$ref->{species}", "\t";
print "$ref->{height}", "\t";
print "$ref->{diameter}", "\n";
}
sort { $a->{height} <=> $b->{height} } @trees がポイントです. sort 関数は,配列内のふたつの要素の大小評価のしかたを{} で囲んで 指定することができる,という話は何度か出てきましたね. { } 内では,比較するふたつの要素を仮に $a と $b という名前で呼ぶのでした.
このプログラム例では,配列の要素は(無名)ハッシュへのリファレンスでした. { } のなかでは,リファレンスが指すハッシュの, キー height に対応する値の大小を比較演算子 <=> で評価しています.
こんどは, $$ref{キー} という表記のかわりに $ref->{キー} という表記方法を 使ってますね.
ハッシュで表現した2本の木へのリファレンスを受けとって, それらの間の距離を計算して返すサブルーチンを書いてみましょう.
sub calc_distance
{
my ($tree0, $tree1) = @_; # 2つのハッシュへのリファレンス
my ($x0, $y0) = ($tree0->{x}, $tree0->{y}); # x, y 座標を取り出す.
my ($x1, $y1) = ($tree1->{x}, $tree1->{y});
return sqrt( (x0 - x1) ** 2 + (y0 - y1) ** 2 ); # 距離を計算して返す.
}
sqrt は,名前から想像できるように平方根を返す関数です.
とっても久しぶりに,練習問題を書いておきます.
ぜひ実際に書いて動かしてみて,無名ハッシュのリファレンスの扱い方に 慣れてください.とても柔軟なデータ処理ができるようになるし, 「もの」の振る舞いを扱うシミュレーション計算にも活用できます.
ここまでの例では,無名ハッシュへのリファレンスを配列にしまっていました. 当然,ハッシュにしまうこともできます. そうすれば,ハッシュのキーを手掛かりにして, 無名ハッシュで表現される「もの」の集合を管理できるようになります.
たとえば,学会の参加登録情報を一件ずつ無名ハッシュにしまって, それらを登録番号をキーにしたハッシュに貯めこんでおけば, 指定した登録番号に対応する情報がいつでも取り出せる, 一種のデータベースがつくれます.
また,生き物の個体ごとに識別 ID をつけて,各個体に親や子供や配偶者の ID も データとして持たせれば,その ID をキーにして別個体を呼び出すことができますね.
たとえばある動物の群れのデ−タがあるとします. 個体ごとに名前,性別,年齢,体重,配偶者の名前のデ−タです. 配偶者名に x と書かれているのは独り者です.
name sex age size spouse ハナ female 15 12.5 サスケ サスケ male 16 16.2 ハナ トラ female 11 10.2 ヤキチ カネ female 12 8.8 x ヤキチ male 13 11.2 トラ タロー male 8 9.2 ハナコ ハナコ female 10 11.2 タロー アイ female 5 8.8 タクヤ タクヤ male 6 10.0 アイ ユウジ male 7 7.8 x
このデータを読んで,個体ごとに無名ハッシュにしまい, 個体の名前をキーとして,群れ全体のデータを管理するハッシュにしまいます. そのあと,ペアの名前を年齢付きで表示させてみます.
#データファイル名はコマンドラインで指定
$line = <>; # ヘッダ行を読み捨てる.
%population = (); # 空のハッシュを用意.
while ($line = <>) {
chomp $line;
($name, $sex, $age, $size, $spouse_name) = split /\s+/, $line;
$ref = {'name' => $name, # 無名ハッシュを作り,
'sex' => $sex, # リファレンスを $ref に代入する.
'age' => $age,
'size' => $size,
'spouse' => $spouse_name};
$population{$name} = $ref; # 名前をキーにして,リファレンスをしまう.
}
foreach $name (keys %population) {
$one = $population{$name}; # 一個体へのリファレンス.
next if ($one->{sex} eq 'male'); # オスはスキップ(二重表示しないため)
$spouse_name = $one->{spouse}; # 配偶者の名前を取り出す.
next if ($spouse_name eq 'x'); # 'x' だったら独り者 -> スキップ
$spouse = $population{$spouse_name}; # 配偶者へのリファレンス.
next unless ($spouse); # 該当する個体がなし -> スキップ
# カップルの名前と年齢を表示.
print $one->{name}, " (", $one->{age}, ")\t";
print $spouse->{name}, " (", $spouse->{age}, ")\n";
}
このプログラムで上のデータを処理すると,こんな出力が得られます.
ハナ (15) サスケ (16) ハナコ (10) タロー (8) トラ (11) ヤキチ (13) アイ (5) タクヤ (6)
このページで解説した「もの」に,その「もの」らしい振る舞い方まで 教えてやることができれば,それはオブジェクト指向プログラミングの世界です (参考 > e-Word, オブジェクト指向 ) . Perl では,モジュールというファイルを作って,その中でその「もの」らしい 振る舞い方をサブルーチンとして書いて,それを「もの」のデータ部分である ハッシュと結びつけてオブジェクトを作ります. これはとても強力なワザですが,「基礎の基礎」からは大きくはみ出しています. Perl の文法と活用の話は,ひとまずここまでといたしましょう.