| 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 の文法と活用の話は,ひとまずここまでといたしましょう.