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

25. リファレンスの利用:無名ハッシュを使って「もの」を表現する

「もの」を表すいくつかのデータをひとまとめにする

配列は,同じような種類のデータがたくさんあるときにそれをまとめるのに便利でした. たくさんの木の高さのデータ,連続測定している温度のデータ,たくさんの生き物の 名前のリストなど,いくらも例を考えることができます.

では,たくさんの木の種名と高さと太さと位置( 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 の文法と活用の話は,ひとまずここまでといたしましょう.



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