| Top Page | プログラミング | Perl 目次 | prev | next | 索引 |
これからしばらく,リファレンスの活用法をいくつか紹介します. まずはサブルーチンに渡す引数としての利用です 数百行程度以上のプログラムになると,サブルーチンを使うことが必須になってきます. 独立性のあるサブルーチンを書くには,グローバルな(プログラム中のどこからでも見える) 変数はあまり使いたくありません.なるべく引数として受け取ったものを処理させるように するのが望ましい設計です.
けれども, 「サブルーチンの引数はスカラーが並んだひとつのリスト」 という制約があるために,サブルーチンに2つの配列を渡したり,ハッシュを渡したり するのが困難だという話は前のページに書いたとおりです.
でも,リファレンスという特別なスカラーを使えばこの問題は解決です. 配列でもハッシュでも,サブルーチンにほんとに渡したいもののリファレンスを渡し, サブルーチンではこのリファレンスを介して配列やハッシュを扱えばよいのです.
まず,簡単な(少々わざとらしい)例です.数値の配列をふたつ渡して, それぞれの平均値のどちらが大きいかに応じて 1 (最初の配列のほうが大きい), 0(等しい),-1(2番めの配列のほうが大きい)を返すをサブルーチンを作ってみます. つまり,2つの配列それぞれの平均値の大小関係を数値比較演算子 <=> で比較した ときの値を返すサブルーチンです.
@x1 = (14, 11, 5, 12, 8, 15);
@x2 = (12, 10, 8, 9);
# サブルーチン compare_mean に2つの配列へのリファレンスを渡す.
$compare = &compare_mean (\@x1, \@x2);
if ($compare == 1) { # compare_mean の返す値によりメッセージを表示.
print 'mean of @x1 is larger.';
}
elsif ($compare == -1) {
print 'mean of @x2 is larger.';
}
else { # $compare == 0;
print 'Means of @x1 and @x2 is identical.';
}
#########################################
# 2つの配列を受け取り,それぞれの平均値を数値比較演算子 <=> で比較した結果を返す.
sub compare_mean
{
my ($ref_array1, $ref_array2) = @_; # ふたつの配列へのリファレンスを取得.
my $num1 = @$ref_array1; # それぞれの配列の要素数.
my $num2 = @$ref_array2;
if ($num1 == 0 || $num2 == 0) { # どちらかの配列の要素数が0だったら
print "No element in the array"; # メッセージを表示.
return;
}
my ($sum1, $sum2) = (0, 0); # 積算用変数の初期化
foreach my $data (@$ref_array1) { # 最初の配列の積算
$sum1 += $data;
}
foreach my $data (@$ref_array2) { # 2番めの配列の積算
$sum2 += $data;
}
# <=> は,左辺が大きかったら 1,右辺が大きかったら -1,等しかったら 0 を返す.
return ($sum1 / $num1) <=> ($sum2 / $num2);
}
リファレンスの意味と書き方が分かっていれば,とくに難しいところはありません. my $num1 = @$ref_array1; では,$ref_array1 に @ をつけて配列にし,これを スカラー変数への代入式の右辺においています.スカラーが期待されるところに配列を書く (つまりスカラーコンテキストで評価する)と,要素数が得られるのでしたね. これは,リファレンスを介して配列を表現してもまったく同じことです.
サブルーチン中だけで使う変数はみな my で外(サブルーチン以外のところ)からは 見えないようにしています.これで,サブルーチン外で使ってる変数との衝突を気に しなくてよくなるのでした.
なお,この例ではリファレンスの変数名は ref_ で始めています.これは別段 文法上のルールではないし広く行われている慣例でもないのですが,分かりやすい ようにと思ってこうしています.以下の例でも同様です.
こんどは, 配列の活用のページ に出てきた,ふたつの種名リストを比較して共通種とどちらか一方にしか含まれない種を 探すという プログラムをサブルーチンを使うように 書き直してみます.
まず,種名リストの配列2つをサブルーチンに渡すためにリファレンスを使っています. また,このサブルーチンは3種類の配列(共通種の配列,最初のリストだけに 含まれる種の配列,2番めのリストだけに含まれる種の配列)を返したいので, それぞれの配列のリファレンスを並べてリストにして返しています.
@species1 = (); # 最初の種群をしまう配列を明示的に用意(必須ではないけど).
@species2 = (); # 第二種群を…(以下同文)
# 以下,ファイルから @species1 と@species2 にデータを読み込むところは
# 前と同じなので省略.
# サブルーチンにふたつの種名リストを渡し,共通種と非共通種を調べる.
($ref_common, $ref_only_in1, $ref_only_in2) = &find_common(\@species1, \@species2);
print "common species\n";
foreach $sp (@$ref_common) { # 共通種を表示.
print $sp, "\n";
}
print "\n";
print "only in the first group\n";
foreach $sp (@$ref_only_in1) { # 第一種群だけに含まれる種を表示.
print $sp, "\n";
}
print "\n";
print "only in the second group\n";
foreach $sp (@$ref_only_in2) { # 第二種群だけに含まれる種を表示.
print $sp, "\n";
}
#################################################3
# 文字列の配列2つへのリファレンスを受け取り,
# 共通要素,最初の配列だけにある要素,2番めの配列だけにある要素
# それぞれの配列を作り,それら3つの配列へのリファレンスを返す.
sub find_common
{
my ($ref1, $ref2) = @_; # 二つの配列へのリファレンスを受け取る.
my @common = (); # 共通種をしまう配列を明示的に用意(必須ではないけど)
my @only_in1 = (); # 第一種群にのみ含まれる種をしまう配列.
my @only_in2 = (); # 第二種群にのみ含まれる種をしまう配列.
my $found;
foreach my $sp1 (@$ref1) { # 第一種群の各種について…,
foreach my $sp2 (@$ref2) { # 第二種群の各種について…,
$found = 0; # まず,発見マーカ−をゼロにしておく.
if ($sp1 eq $sp2) { # sp1 と sp2 が同じだったら,
$found = 1; # 発見マーカーを1にして,
last; # 第二種群のチェックはもうやめてよい.
}
}
if ($found == 1) { # 発見マーカーが1だったら,
push @common, $sp1; # $sp1 は共通種なので,共通種配列に追加
}
else { # そうでなかったら,
push @only_in1, $sp1; # 第一種群にだけ含まれるので,そっちの配列に追加
}
}
foreach my $sp2 (@$ref2) { # 第二種群の各種について…,
foreach my $sp_common (@common) { # 共通種のそれぞれについて…
$found = 0; # まず,発見マーカ−をゼロにしておく.
if ($sp2 eq $sp_common) { # $sp2 と $sp_common が同じだったら,
$found = 1; # 発見マーカーを1にして,
last; # 共通種のチェックはもうやめてよい.
}
}
if ($found == 0) { # 発見マーカーがゼロのままだったら,
push @only_in2, $sp2; # $sp2 は第二種群だけに含まれる
}
}
return (\@common, \@only_in1, \@only_in2); # 各配列のリファレンスを返す
}
もとのプログラムとよく見比べてください.
基本的なアルゴリズムはまったく同じです.
サブルーチンとの配列の受け渡しをリファレンスでやっているのが新しい点です.
サブルーチン中だけで使う変数を,みな my で外(サブルーチン以外のところ)からは
見えないようにしているのは前と同様です.
なお,my のために外から見えないのは変数名です. サブルーチンから抜けたあとも変数そのもの(メモリの領域と, そこに記録された値)は存続しています.そうでなかったら,サブルーチンがそれらへの リファレンスを返しても意味がありませんね.
※余談ですが,一度作られた変数がいつまで存続しているかというのは,言語の メモリ管理の仕組みと密接にかかわっています.Perl の場合,ガベッジコレクション という機能があって,「変数は使われてる限りは存続させる.使われなくなった変数が 占有してたメモリは開放する」という仕事をしてくれています.言語によっては, これをいちいち人間がやらなくてはいけないものもあります.
サブルーチンにハッシュを渡す例をひとつ見てみましょう.
%tree_abundance = ('コナラ' => 18, # 種名がキー,個体数が値のハッシュ
'クヌギ' => 25,
'クリ' => 10,
'ケヤキ' => 1,
'エノキ' => 12,
'ヒサカキ' => 32);
%sp_to_family = ( 'コナラ' => 'ブナ科', # 種名がキー,科が値のハッシュ
'クヌギ' => 'ブナ科',
'クリ' => 'ブナ科',
'ケヤキ' => 'ニレ科',
'エノキ' => 'ニレ科',
'ヒサカキ' => 'ツバキ科');
# 二つのハッシュのリファレンスをサブルーチンに渡し,科名がキー,個体数が値
# のハッシュへの_リファレンス_を受けとる.
$ref_family_abundance = &make_family_abundance_hash(\%tree_abundance, \%sp_to_family);
# 受けとったハッシュの内容を表示.
foreach $family (keys %$ref_family_abundance) {
print $family, "\t", $$ref_family_abundance{$family}, "\n";
}
#########################################
# 種名 => 個体数 のハッシュと,種名 => 科 のハッシュへのリファレンスを
# 受け取り,科 => 個体数 のハッシュへのリファレンスを返すサブルーチン
sub make_family_abundance_hash
{
my ($ref_tree_abundance, $ref_sp_to_family) = @_;
my %family_abundance;
foreach my $species (keys %$ref_tree_abundance) {
my $family = $$ref_sp_to_family{$species};
$family_abundance{$family} += $$ref_tree_abundance{$species};
}
return \%family_abundance; # ハッシュ名のまえに \ をつけてリファレンスに
}
もう,読めば分かりますね(ぜひ一字一句読んで理解を確かめてください).
$$ref_tree_abundance{$species} とか $$ref_array[1] のように, $ をふたつ続けて $$ のように書くのはなんとなく読みにくい感じがします. もう少し見た目をよくするための syntactic sugar が用意されています. あたまに $ を付けるかわりに,うしろに -> を付ける表記法です.
@array = (1, 2, 3);
$ref_array = \@array;
for ($i = 0; $i < 3; ++$i) {
print $ref_array->[$i], "\n"; # $ref_array->[$i] は
} # $$ref_array[$i]と同じ
%hash = ('山' => '川', '風' => '雨', '空' => '海');
$ref_hash = \%hash;
for $word (keys %$ref_hash) {
print $word, " : ", $ref_hash->{$word}, "\n"; # $ref_hash->{$word} は
} # $$ref_hash{$word} と同じ
読みやすいと感じるほうを使ってください.