Top > MyPage
 

変数の種類から、シュワルツ変換まで

変数

Perlでは、おおざっぱに分けると、変数(値を入れておく入れ物)にはスカラー、配列、ハッシュの3種類があります。これについて、簡単に見ておきましょう。

スカラー

スカラーとは、基本的には、数字や文字列を入れるものです。Perlの場合CやJavaのような、変数に型というものがないので、同じ変数に型宣言することなしに、文字列でも数字でも何でも入れることができます。

例えば、

my $var = "sakai";
$var = 0;
print $var,"\n";

のように、文字列でも数字でも同じ変数に入れることができます。この場合0が表示されます。

my $var;
$var++;
print $var,"\n";

のように、何も変数に代入せずに突然インクリメントしたら、1に変わります。これは一見便利なようで、思わぬ副作用が起こりえます。

my $var;
$var++;
print $var,"\n";
$var = "A";
$var++;
print $var,"\n";

では、最初は先ほどと同じに1が表示されますが、次はBが表示されます。

さて、通常はスカラーとして代入するのは、今までのように数字や文字列を入れることが多いのですが、それ以外にリファレンスというものを入れる場合があります。リファレンスに関しては、後述します。

余談ですが、文字列を表すときにダブルクォーテーションを使う場合と、シングルクォーテーションを使う場合で、その中に含まれる変数を展開するかどうかの違いがあります。ベストプラクティスという本では、変数展開(\nなどの制御文字なども)を積極的にしたい場合は除いて、シングルクォーテーションを使うことを推奨しています。

$name = "sakai";
print "$name\n";
print '$name\n';

では

sakai
$name\n

とそれぞれ出力されます。

ただ、

$name = "sakai";
print "$name's brother is dankai-sedai!\n";

だと、最初の変数がどこまで変数の名前なのかわからないので('も変数名に使えるので)、

$name = "sakai";
print "${name}'s brother is dankai-sedai!\n";

のように中括弧でわかるようにしてあげる必要があります。

さらに、文字列の中に「ダブルクォーテーションやシングルクォーテーション」が含まれる場合は、それらを\を付けることによって、エスケープする方法もありますが、

print "$name said \"hello\".";

文字列のデリミタのダブルクォーテーションにあたるqqを使った方が読みやすいでしょう。

print qq{$name said "hello".\n};

文字列のデリミタのシングルクォーテーションにあたるqもあります。これら2つのデリミターは、q{ .. }のように中括弧でも、q[ .. ]のように大括弧でも使えます。

配列

配列とは他の言語と同様、添え字が0から始まる、複数の値を保持することのできる変数のことです。例えば、

$array[0] = 1;
$array[1] = 100;
$array[2] = "chikkun";

とやれば、@arrayという配列に3種類の値(1と100とchikkun)を保持したことになります。

注意すべきこととしては、配列自体は@arrayですが、その中の値1つ1つはスカラーなので、1番目の(添え字が0の)値を取り出したかったら、上記のように$array[0]となります。つまり@から$に変わります。最初の頃よくこれを間違えるので注意が必要です。また、代入の仕方としては、上記のような1つ1つを添え字を付けて代入することも可能ですが、

@array = (1, 2, 3);

とか

@list = qw(1 2 3);

と一度にに代入することが可能です。qwをつけた場合は,がいらないし、

@list = qw(This is a pen);

というように、文字列にクォーテーションもいりませんので、便利です。

さらに、pushという関数で保存し、shiftで取り出すことが出来ます。

push @array, 'sakai';
push @array, 'tomoko';
print shift @array , "\n";
print @array , "\n";

上記の例では、最初のpushで$array[0]にsakaiが、$array[1]にtomokoが保存され、次にshiftで$array[0]が取り出されます。ただし、その上$array[0]はなくなり、$array[1]が$array[0]に繰り上がります。結果的に

print shift @array , "\n";

の時点で、@arrayには1つだけ$array[0] = 'tomoko'が入っていることになります。ついでですが、printという関数は引数に配列を取るので、最後のprint文で、tomokoが表示されているわけです。

このような、データの出し入れを「FIFO」First IN First OUT(最初に入れたものが、最初に出てくる)と言います。これに対し、「LIFO」Last IN First OUTはpushpopで実現できます。

push @array, 'sakai';
push @array, 'tomoko';
print pop @array , "\n";
print pop @array , "\n";

だと、最初のprintでtomokoが、次のprintでsakaiが出力されます。

このように配列はある何らかのカテゴリーでくくられるような、まとまりのある値を保持しておくには便利な変数です。したがって、これらを一挙に処理したいという要求が出てきます。Perlに配列を扱う関数が多数あります。いくつか紹介すると、

  • for文

これはCなどのfor文と全く同じで、

@array = qw/chiku toda kurosawa/;
for ( $i = 0 ; $i < @array ; $i++ ) {
    print $array[$i] . "\n";
}

などのように使います。

  • foreach文

これは、上記のfor文をもう少し簡単に使えるようなもので、配列の個数の指定などが必要なく、全部に対して行う場合には便利です。

@array = qw/chiku toda kurosawa/;
foreach $ar (@array){
    print $ar . "\n";
}

ですし、

@array = qw/chiku toda kurosawa/;
foreach (@array){
    print $_ . "\n";
}

のように、1つ前の$arのように配列の値を取らなくても、Perlは自動で$_にセットしてくれます。

  • grep関数

これは`grep(EXPR,LIST)'という形を取り、マニュアルによれば「LIST の全要素について EXPR を評価し (各要素を $_ にセットしながら)、 expression が true であると評価された要素からなる配列を返す。スカラーのコンテキストでは、expression が true となった回数を返す。」ということですが、具体的じゃないとちょっと意味がわかりづらい(Linuxのgrepでいえば、配列の中をgrepするという感じ)。

まずは

push @array, 'sakai';
push @array, 'tomoko';
push @array, 'iwane';
push @array, 'shibata';
push @array, 'kurosawa';
push @array, 'sakamizu';
push @array, 'sasaki';
push @array, 'toda';

@result = grep($_ =~ /t/,@array);

foreach $n (@result){
    print $n,"\n";
}

これの

@result = grep($_ =~ /t/,@array);

では$_ =~ /t/が条件式になり、$_には配列それぞれの値が入ります。したがって、この場合は配列の値にtが含まれているとこの条件式が真になります。そのような真になる配列を新たに作成して、その配列を@resultに代入しているわけです。

仮に

$result = grep($_ =~ /t/,@array);

ですと、$resultには3が入ります(これがマニュアルの「スカラーのコンテキストでは、expression が true となった回数を返す」の意味です。スカラーで値を取得しようとしているので)。

  • map

これはmap BLOCK LISTmap EXPR,LISTの形を取り、マニュアルによれば、「LIST の個々の要素に対して、BLOCK か EXPR を評価し ($_ は、ローカルに個々の要素が設定されます) 、それぞ れの評価結果からなるリスト値が返されます。BLOCK や EXPR をリストコンテキストで評価しますので、LIST の 個々の要素によって作られる、返却値であるリストの要素 数は、0 個の場合もあれば、複数の場合もあります。」ということになります。

ちょっとgrepと区別が付きづらいですね。具体的な例を挙げると、

@array = ('tomoko', 'chiku' , 'iwane' , 'sakamizu','shibata');

@after = map {length($_)} @array;

print @after , "\n";

を実行すると、

65587

が出力されます。つまり

$after[0] = 6;
$after[1] = 5;
$after[2] = 5;
$after[3] = 8;
$after[4] = 7;

が、一気に行われたわけです。もう少し具体的に言えば、

$after[0] = length('tomoko');
$after[1] = length('chiku');
$after[2] = length('iwane');
$after[3] = length('sakamizu');
$after[4] = length('shibata');

ですね。

これは、時間を追って考えると、後ろから読んでいくとわかりやすいと思います。つまり、@arrayの配列に入っている値を1つずつ取り出して(それが$_に自動で代入される)、その値を利用しながら(利用しなくても良いけれど、その場合はmapを使う必要がない?)ブロックの中を実行し、その返値、ここではlength($_)を配列の1つの要素としていき、@arrayの要素分だけ繰り返されあとで、できた配列を@afterに代入しているわけです。

これを別の書き方をしていけば

foreach(@array){
    push @after2,length($_);
}

ということになります。

今ひとつこのmapのありがたみがわからない人は、最後に シュワルツ変換によるソートの例の中にもmapの利用方法が書いてあるので、そちらを参照してください。他の方法で実装可能でも、このmapを使うとプログラムがすっきりすることが多いようです。

  • join

このjoinは、join EXPR,LISTという形を取り、「LISTの個別の文字列を、EXPRの値で区切って1 つの文字列につなげ、その文字列を返します」ということになります。具体的には

@array = ('tomoko', 'chiku', 'iwane', 'sakamizu', 'shibata');

$str = join("//", @array);

print $str, "\n";

を実行すると、@arrayの中身を「//」でつなげます。つまり、

tomoko//chiku//iwane//sakamizu//shibata

が出力されます。配列を、スカラーに変換する際にちょっと細工したい場合には便利です。

ちなみに、

@array = ('tomoko', 'chiku', 'iwane', 'sakamizu', 'shibata');

$str2 = join(&ss,@array);

print $str2, "\n";

sub ss{
    return "/";
}

を実行すると、

tomoko/chiku/iwane/sakamizu/shibata

が出力されます。EXPRには関数も指定できるようです。ただ、配列の値などをその関数で使えるわけじゃないので、ちょっとこったことをしようとするとjoinでは無理です。あまり良い例じゃありませんが、

@array = ('tomoko', 'chiku', 'iwane', 'sakamizu', 'shibata');

$str3 = join("\n",map {"($_)"} grep {$_ =~ 't'} @array);

print $str3, "\n";

などとやると、いろいろな配列の加工が出来るでしょう。

  • reverse

配列の順番を逆にします。

@array = ('tomoko', 'chiku', 'iwane', 'sakamizu', 'shibata');

@after = reverse(@array);

print join(',',@after),"\n";

とすると、並びが逆の

shibata,sakamizu,iwane,chiku,tomoko

が出力されます。

  • splice

splice array,offset,length,listという形を取り、「array から offset、length で指定される要素を取り除き、 list があれば、それを代わりに挿入します。配列から取り除かれた要素を返します。配列は、必要に応じて、大きくなったり、小さくなったりします。 length が省略されると、offset 以降のすべての要素を取り除きます。以下は、($[ == 0 と仮定すると) それぞれ、等価です」ということです。例を見ると

@array = ('tomoko', 'chiku', 'iwane', 'sakamizu', 'shibata');

@replace = ('ono', 'toda');

@r = splice(@array, 1, 3, @replace);

print join(',', @array), "\n";
print join(',', @r),     "\n";

を実行すると、

tomoko,ono,toda,shibata
chiku,iwane,sakamizu

が出力されます。

@r = splice(@array, 1, 3, @replace);

では、

@array = ('tomoko', 'chiku', 'iwane', 'sakamizu', 'shibata');

配列の添え字が1(2番目のchiku)から、3つ分の配列を削除し、@replaceと置き換えています。削除したものが3つ、挿入したものが2つなので、当然ながら配列の個数は変わってしまいます。

また、@arrayという配列自身を書き換えてしまうことと、返値は削除された配列が返ってきます。

また、この関数の引数における最後のlistがなければ、挿入がなく、lengthがないと、offset以降の配列が全て対象になります。

  • scalar

これは配列を配列コンテキストにもかかわらず、スカラーコンテキストと解釈させるために利用されます。紙面の関係で(HTMLだから、嘘ですね。もとい、時間の関係で)、詳しくは触れませんが、Perlを理解する上ではこのコンテキストというのは重要です。

簡単に言うと、

print @array;

というのは配列コンテキストです。なぜならば、printという関数はLISTや配列を期待しているからです。また、

$num = @array;

というのはスカラーコンテキストです。$numに代入しようとしているわけですが、この$numはスカラー値を代入する変数だからです。この場合は配列の個数が$numに代入されます。

さて、どういう場面でこのscalarを利用するかというと

print "配列の個数は", scalar @array ,"です";

などで、この場合scalarを付けないと、配列の値がなんの区切り文字もなく連続して出力されてしまいます。

配列の個数はtomokochikuiwanesakamizushibataです;

という感じになってしまうわけです。

よくある失敗に、

print @array . "\n";
print @array , "\n";

の違いを理解していない場合です。上は「.」という文字列を連結する演算子が使われていますが、つまりスカラーとスカラーをつなげるわけなので、スカラーコンテキストです。しかし、下はリストを作る,なので、配列コンテキストということになります。

  • CPANの配列を扱うモジュールの例

Perlには豊富な配列を扱う関数がそろっていますが、完璧じゃありません。その点、CPANにはそれらを補うためのモジュールが色々とあります。その中で1つだけ例を見てみましょう。<URL:http://search.cpan.org/~jkeenan/List-Compare-0.33/lib/List/Compare.pm>です。

use List::Compare;

@larray = ('tomoko', 'chiku', 'iwane', 'sakamizu', 'shibata');
@rarray = ('tomoko', 'ono',   'iwane', 'toda',     'ogasawara');

$lc = List::Compare->new(\@larray, \@rarray);

@intersection = $lc->get_intersection;
@union        = $lc->get_union;
@Lonly        = $lc->get_Lonly;
@Ronly        = $lc->get_Ronly;

print join(",", @intersection), "\n";
print join(",", @union),        "\n";
print join(",", @Lonly),        "\n";
print join(",", @Ronly),        "\n";

を実行すると(List::Compareがないと実行できないので、CPANからインストールしておいてください。)、

iwane,tomoko
chiku,iwane,ogasawara,ono,sakamizu,shibata,toda,tomoko
chiku,sakamizu,shibata
ogasawara,ono,toda

が出力されます。これは結果を見るとわかりますが、2つの配列

@larray = ('tomoko', 'chiku', 'iwane', 'sakamizu', 'shibata');
@rarray = ('tomoko', 'ono',   'iwane', 'toda',     'ogasawara');

の積集合(今は懐かし「集合」の2つの丸の重なった部分)が

@intersection = $lc->get_intersection;

であるし、和集合(「集合」の2つの丸の全ての部分)が

@union        = $lc->get_union;

で、引数の左だけの配列にあるもの、右だけの配列にあるものがそれぞれ

print join(",", @Lonly),        "\n";
print join(",", @Ronly),        "\n";

です。これを自分で実装するとなると結構面倒ですが、これを使うと簡単にできます。もちろん、上記の例以外にも色々便利な機能があるので、使わない手はないでしょう。

ハッシュ

いよいよ、基本の最後を飾るハッシュは以前「連想配列」などと呼ばれていましたが、もう古い呼び方のようです(僕には慣れ親しんだ呼び名なんですが)。今回もハッシュで統一しておきます。

ハッシュとは、配列の添え字に当たるものが「文字列(実際には文字列である必要がないけれど、一般的には)」になっているものです。ハッシュという名前は、配列と違い、添え字が(ハッシュでは通常キーと呼ぶ)文字列なので、探し出すのに結構時間的コストがかかってしまうので、それをハッシュテーブルというものを使って効率よく探索する方法に「ハッシュテーブル」を使うものがあり、Perlではその方法を利用しているからしい。

難しいことはさておき、実際に見ていきましょう。ハッシュは配列が@arrayで表されるのに対して、%hashというように%を使って表します。ただし配列と同様、

その値自体は$hash{'name'}というように$をつけてアクセスします。

%hash = ('name', 'sakai', 'tel', '042-799-0000');

print $hash{'name'}, "\n";

print $hash{'tel'}, "\n";

とやれば、

sakai
042-799-0000

が出力されます。つまり、

%hash = ('name', 'sakai', 'tel', '042-799-0000');

で、代入が出来るわけです。奇数番目の値がハッシュのキーになり、偶数番目が1つ前のキーに対する値になります。また、

print $hash{'name'}, "\n";

print $hash{'tel'}, "\n";

で、それぞれname、telに対応する値を取り出してprintしています。キーと値の関係をもっとわかりやすく書く方法も用意されています。

%hash = ('name' => 'sakai', 'tel' => '042-799-0000');

print $hash{'name'}, "\n";

print $hash{'tel'}, "\n";

%hash = ('name' => 'sakai', 'tel' => '042-799-0000');

です。これだと、何のキーに対して、何の値を代入しているかがよくわかりますね。もちろん、個別に代入することも出来ます。

$hash{'name'} = 'sakai';
$hash{'tel'}  = '042-799-0000';

という感じです。ここで注意点として「ダブル・シングルクォーテーション」を

$hash{'name'} = 'sakai';
%hash = ('name' => 'sakai', 'tel' => '042-799-0000');

は省略できますが、

%hash = ('name', 'sakai', 'tel', '042-799-0000');

は出来ません。つまり、

$hash{name} = 'sakai';
%hash = (name => 'sakai', tel => '042-799-0000');

はOKです。

さて、Perlのハッシュに関する組み込み関数は配列とだいぶかぶるので、その中でハッシュ特有なものの例を見ておきましょう。

  • each

このeacheach ASSOC_ARRAYという形をとり、「連想配列の次の value に対する、key と value からなる 2 要素の配列を返しますので、連想配列上での繰り返しを 行なうことができます。エントリは見かけ上、ランダム な順序で返されます。配列をすべて読み込んでしまうと、 空配列が返されます (これは代入されると、偽 (0) とな ります)。」という意味になります。

具体的には、

%hash = (name => 'sakai', tel => '042-799-0000', age => 47, sex => 'male');

while (($key , $val) = each %hash) {
    print "${key}は${val}です。\n";
}

を実行すると、

telは042-799-0000です。
nameはsakaiです。
sexはmaleです。
ageは47です。

が出力されます。つまりeachでキーと値のペアの配列を返してくるので、$key , $valで受け取ることが可能です。ハッシュの値が最後まで行ったら、空の配列を返すので、その代入し式自体の評価が偽となるので、while文を抜けます。これはイディオムとして覚えておくと良いでしょう。

注意すべき点は、代入した順番には取り出せないと言うことです(上記の例でも順番通りには出力されていないことがわかると思います)。これはハッシュの特性なので、やむを得ません。

  • keys

このkeysはハッシュのキーの配列を返します。マニュアルには「指定した連想配列のすべての key からなる、通常配列を 返します。(スカラコンテキストでは、key の数を返し ます。) 返される key の順序は、見た目にばらばらなも のですが (連想配列に変更がなければ)、values() 関数や each() 関数で返されるものと同じ順序です。」とあります。

%hash = (name => 'sakai', tel => '042-799-0000', age => 47, sex => 'male');

foreach $key (keys %hash) {
  print "${key}は$hash{$key}です。\n";
}

で、eachと同じようなことが出来ます。eachを使った場合の方がメモリー的には有利のようなので、多量のハッシュを使う場合などは、eachの方が良いかもしれません。ただ、上記の例でもeachと同様、代入した順番と取り出した順番が同じにはなりません。ただ次のsort関数を使うと、代入した順番にはなりませんが、sortで可能な順番にすることは可能になります。ただeachでは使えません。sortの具体的な例はシュワルツ変換によるソートを参照下さい。

%hash = (name => 'sakai', tel => '042-799-0000', age => 47, sex => 'male');

foreach $key (sort keys %hash) {
  print "${key}は$hash{$key}です。\n";
}

を実行すると、

ageは47です。
nameはsakaiです。
sexはmaleです。
telは042-799-0000です。

というように、キーでソートされた順番に出力されます。

  • values

valuesは上記のkeysの値版です。ハッシュの値の配列を返します。

  • exists

existはハッシュの中にそのキーが存在すれば真を返し、そうでなければ偽を返します。値の有無などには関係ないので、注意が必要です。

%hash = (
         name     => 'sakai',
         tel      => '042-799-0000',
         age      => 47,
         sex      => 'male',
         child    => 0,
         helpmate => undef
);

print "子供はいません。\n" if !$hash{child};

print "子供があるかどうかの答えはしています。\n" if exists $hash{child};

print "配偶者がいるかどうかの答えはしていません。\n" if !defined($hash{helpmate});

print "そもそも収入に関する質問していません。\n" if !exists $hash{salary};

を実行すると、全てprint文は実行されます。

  • delete

これはハッシュ全体ではなく、ハッシュの値を消す場合に使います。

%hash = (
         name     => 'sakai',
         tel      => '042-799-0000',
         age      => 47,
         sex      => 'male',
         child    => 0,
         helpmate => undef
);


delete $hash{age};

$hash{tel} = undef;

foreach (keys %hash) {
    print "$_ = $hash{$_}\n";
}

を実行すると

tel = 
helpmate = 
name = sakai
child = 0
sex = male

となり、ageというキーと値がなくなっています。

$hash{tel} = undef;

で、値を消してもキーがなくなるわけではないので、注意を要します。また、ハッシュや配列全体を削除したかったらundefを使った方が高速だと言うことがマニュアルに出ていました。

undef(%hash);

で、削除されるわけです。

CPANの便利なものTie::Hash::Indexed

先ほど、ハッシュは順序が保証されない、という話をしたと思うのですが、これはもともと高速にアクセスするためにそういうふうになっているので、どうしようもありません。だけどCPANには、順序を保証してくれるようなハッシュの実装するモジュールがあります(Javaにはあるので、探したらありました)。Tie::Hash::Indexedをインストールしてみてください。

use Tie::Hash::Indexed;
tie my %hash, 'Tie::Hash::Indexed';

%hash = ( name => 'sakai', sex => 'male', age => 42, nationality => 'Japan' \
    );
$hash{ability} = 'low';

foreach $key ( keys %hash ) {
    print "$keyは$hash{$key}です。\n";
}

%hash2 = ( name => 'sakai', sex => 'male', age => 42, nationality => \
    'Japan' );
$hash2{ability} = 'low';

foreach $key ( keys %hash2 ) {
    print "$keyは$hash2{$key}です。\n";
}

上記の%hashが、順序を保持しているもので、次の%hash2が通常のハッシュです。それぞれ結果は

nameはsakaiです。
sexはmaleです。
ageは42です。
nationalityはJapanです。
abilityはlowです。


nameはsakaiです。
abilityはlowです。
nationalityはJapanです。
ageは42です。
sexはmaleです。

となり、しっかり順序が保持されていますね。CPANは偉大だ!

リファレンス

主にデータとしての利用方法

スカラーのところで、予告しておきました「リファレンス」についてです。古くからのPerlユーザーの中に、このリファレンスを理解していない人も多いようですが、今日このリファレンスを知らないで通すわけにはいきません。

スカラーで触れたように、$scalarという変数には文字列も数値でも何でも入れることが出来ます。そしてリファレンスも入れることが出来ます。ではそのリファレンスとは何でしょう。

Cのポインターなどと同様、変数そのものじゃなく、変数を指しているもの(つまりpointer)であるし、リファレンスを通じて変数などを参照している(reference)そのものです。う〜ん、これだとわかりづらいですね。

$a = 'ono';

イメージ($aという名前の箱にonoが入っている)
   ┌──┐
$a │ono │
   └──┘

参照の場合は値ではなく、値のある場所を示すものが入ります。

$b = \$a;

イメージ($bは$aを参照している)
   ┌──┐
$a │ono │
   └──┘
     ↑
$b ─┘

上記の例にあるように、$aなどにonoなどの文字列を入れた場合、aという名前の変数(箱)に直接onoという文字列が入っている一方で、リファレンスというのは$aが格納している箱の場所(メモリーアドレスのようなもの)を格納します。上記の

$a = 'ono';
$b = \$a;

\$aは、$aという変数のリファレンスを意味し(つまり\をつけるとリファレンスを返す)、それを$bに代入しているわけです。じゃあ、その$bを使ってonoという文字列を取り出したいときには$$bというような方法で(デリファレンスという)参照します。つまり

print $$b,"\n";

で、onoが出力されます。

このリファレンスですが、どんな場面で便利かというと、もちろん、色々あるわけですが、例えば、ちょっと複雑なデータを作りたいようなときに使います。

配列は、

@array = ('totoro', 'mononoke', 'kokakukidotai,'patlabor');

となりますが、これをもう少し複雑にして、

@miyazaki = ('totoro',        'mononoke');
@osii    = ('kokakukidotai', 'patlabor');
%hash = (miyazaki => @miyazaki, osii => @osii);

while (($key, $val) = each %hash) {
  print "$key=$val\n";
}

というようなハッシュ(ここでは映画監督名というキーに対して、映画の名前の配列を代入するイメージです)を作りたいのですが、上記ではうまく行きません。出力結果は

mononoke=osii
kokakukidotai=patlabor
miyazaki=totoro

という予想外の結果です。これは

%hash = (miyazaki => @miyazaki, osii => @osii);

=>じたいは,のエイリアスなので、

%hash = (miyazaki ,@miyazaki, osii , @osii);

となり、この中の配列を展開して、

%hash2 = ('miyazaki', 'totoro', 'mononoke', 'osii', 'kokakukidotai', \
    'patlabor');

であり、これをハッシュとしてわかりやすいように書くと

%hash2 = ('miyazaki' => 'totoro', 'mononoke' => 'osii', 'kokakukidotai' => \
    'patlabor');
while (($key, $val) = each %hash2) {
  print "$key=$val\n";
}

となったわけですね。

いずれにしろ、これは意図するものとは違います。正しくは

@miyazaki = ('totoro',        'mononoke');
@osii     = ('kokakukidotai', 'patlabor');
%hash = (miyazaki => \@miyazaki, osii => \@osii);

while (($key, $val) = each %hash) {
  print "$key=", join("//", @$val), "\n";
}

で、出力は

osii=kokakukidotai//patlabor
miyazaki=totoro//mononoke

となります。

ちょっとまとめておくと、

1)リファレンスを取得するには、その変数(スカラー・配列・ハッシュなど何でも)の前に\を付ける。 2)リファレンスの実際の値を取得するにはリファレンスの入ったスカラー変数の前にもとの$や@や%を付ける

$s = \$scalar;
$a = \@array;
$h = \%hash;

だし、

$scalar = $$s;
@array  = @$a;
%hash   = %$h;

となります。ただし、本来は${$s}@{$a}%{$h}のように中括弧を付けるのが正しく、省略できる場合があるという感じなので、自信がない場合は中括弧を付ける方が良いかもしれません。

配列の中に配列が入っているような多元配列も同様に作成します。

@array1 = qw(sakai kazuro 47 male);
@array2 = qw(chiku tomoko 47 female);
@array3 = qw(ono masami 42 male);

@bad = (@array1, @array2, @array3);

print join(',', @bad), "//という1つの配列が出来てしまう!\n";

は、先ほど同様に上記の出力結果

sakai,kazuro,47,male,chiku,tomoko,47,female,ono,masami,42,male//という1つの配列が出来てしまう!

となります。なので、リファレンスを利用し、

@array1 = qw(sakai kazuro 47 male);
@array2 = qw(chiku tomoko 47 female);
@array3 = qw(ono masami 42 male);

@good = (\@array1, \@array2, \@array3);

print "名前は${$good[0]}[0] ${$good[0]}[1]、
           年齢は${$good[0]}[2]、性は${$good[0]}[3]です\n";
print "名前は${$good[1]}[0] ${$good[1]}[1]、
           年齢は${$good[1]}[2]、性は${$good[1]}[3]です\n";
print "名前は${$good[2]}[0] ${$good[2]}[1]、 
           年齢は${$good[2]}[2]、性は${$good[2]}[3]です\n";

この出力結果は

名前はsakai kazuro、 年齢は47、性はmaleです
名前はchiku tomoko、 年齢は47、性はfemaleです
名前はono masami、 年齢は42、性はmaleです

です。参照している${$good[2]}[0]の書き方に注意してください。$good[2]@goodの3つめの要素にアクセスし、そこには配列のリファレンスが入っているので(配列のデリファレンスだったら{@{$good[2]}でしょうが、実際にはスカラーへの参照なので)、${$good[2]}[0]でono masamiが取得できます。

さらに、複雑にすると、

@array1 = qw(sakai kazuro 47 male);
@array2 = qw(chiku tomoko 47 female);
@array3 = qw(ono masami 42 male);

@kikon = (\@array1, \@array2, \@array3);

@array4 = qw(toda kazuhiko 43 male);
@array5 = qw(dennpoya chiyoaru 47 male);

@mikon = (\@array4, \@array5);

%hash = (kikon => \@kikon, mikon => \@mikon);

$hashref = \%hash;

print "${${${$hashref}{kikon}}[0]}[0]は既婚です。\n";

print "$hashref->{kikon}->[0]->[0]は既婚です。\n";

さて、最後から2行目の${${${$hashref}{kikon}}[0]}[0]が理解できれば十分だと思います。じっくり見てみてください。${${${$hashref}{kikon}}[0]}[0]の値は何でしょうか?

ここで重要な新しい表記法があります。

上記のデリファレンスは、じっくり読めばわかるとは言え、ちょっとわかりづらいので、perlでは別の参照の方法が使えます。

配列の場合は$配列の参照->[0]、ハッシュの場合はハッシュのリファレンス->{キー}のように、直接リファレンスから参照する方法です。これを使ったのが

print "$hashref->{kikon}->[0]->[0]は既婚です。\n";

で、上を言葉にすれば「$hashrefというハッシュのリファレンスが指すハッシュのキー:kikonに入っている、配列の参照先の配列の0個目に入っている、配列の参照先の配列の0個目の値ということになります。さらに言い換えれば、「$hashrefが指すハッシュのキーがkikonの値である、\@kikonの指す@kikonの0個目の値、\@array1の指す@array1の0個目の値」ということです(言葉にすると、色々変えてもわかりやすくなりませんなあ〜)。

  • 配列のリファレンスが指す配列の値にアクセスするには$arrayref->[2]というように->の先に[]大括弧を使う
  • ハッシュのリファレンスが指すハッシュの値にアクセスするには$hashref->{name}というように->の先に{}中括弧を使う

を覚えておきましょう。

サブルーチンの引数としての利用

まず、その話をする前に、ちょっとサブルーチンへ引数を渡すPerlのお決まりに触れておきましょう。Perlでサブルーチンを定義するには、

sub method {
   ....
}

とうような形で定義します。それで、実行するには

&method();

sub method {
   ....
}

のように、&method()のように書きます。この&は省略可能ですし、最後の()という括弧もいりませんが、ベストプラクティスという本では自分で定義したサブルーチンには&を付けよう(組み込み関数には付けないようにしよう---これは僕にはちょっと納得できないのですが、そうすれば組み込みと自作とを区別できるメリットは確かにありますね)としています。僕は()があると、すぐにサブルーチンだとわかるので、読みやすさの上で、大抵付けています(忘れているときもありそうですが)。

さて、通常そのようなサブルーチンに対して、何らかの値を渡し、その結果を受け取るようなことをするのが普通です。その場合は

$name = "Sakai";
$name = &addStr($name);

print $name;

sub addStr {
    ($n) = @_;
    $n .= " is not a foolish man!\n";
}

のように、

$name = &addStr($name);

で、サブルーチンに値を渡し、

($n) = @_;

で、サブルーチン側では値を受け取っています。では引数が2つ以上あるような場合はどうなるかというと、

$lname = "Sakai";
$fname = "Kazuro";

$name = &addStr($lname, $fname);

print $name;

sub addStr {
    ($l, $f) = @_;
    $name = "$l $f is not a foolish man!\n";
}

という感じになります。ここでポイントとなるのは、Javaなどでは引数の個数や型などを最初から定義しておかなければなりませんが、Perlでは引数は全て@_という配列に入れて、サブルーチンに渡されます。

($l, $f) = @_;

ですね。これは配列の要素を($l, $f)という形で分けて受け取ることが出来るわけです。もし、このとき引数が1つの場合は、2つめの$fにはundef(未定義)が入り、仮に引数が3つある場合は、その3つめの値は捨てられます。さらに、このような形で受け取るようなことも良くあります。

$lname = "Sakai";
$fname = "Kazuro";

$name = &addStr( $lname, $fname );

print $name;

sub addStr {
    $l    = shift;
    $f    = shift;
    $name = "$l $f is not a foolish man!\n";
}

というように、shift@_からデータを1つずつ取り出す方法です。配列でも触れたように、これは最初に入っている(つまり$_[0])取り出すわけです。ただ、ちょっと次の話を先走って言えば、次のような場合に困ってしまうことがあります(解決は可能ですが、少々面倒です)。

@array = qw(shibata toda sakamizu eriko);
$lname = "good";
$fname = "people";

$name = &addStr( @array, $lname, $fname );

print $name;

sub addStr {
    $name1 = shift;
    $name2 = shift;
    $name3 = shift;
    $name4 = shift;
    $l     = shift;
    $f     = shift;
    $name  = "$name1 and $name2 and $name3 and $name4 are $l $f!\n";
}

というように、サブルーチンに配列を渡すような場合です。ここではその配列@arrayの個数を固定しているので、上記のような受け取り方が出来ますが、これが動的に変わる場合はこの方法が使えません。

@array = qw(shibata toda sakamizu eriko);
$lname = "good";
$fname = "people";

$name = &addStr( $lname, $fname, @array );

print $name;

sub addStr {
    $l   = shift;
    $f   = shift;
    @arr = @_;
    $num = 0;
    foreach (@arr) {
        $num++;
        if ( $num == 1 ) {
            $str .= "$_ ";
        }
        else {
            $str .= "and $_ ";
        }
    }
    $name = "${str}are $l $f!\n";
}

のように、配列を最後に置くという方法で回避することは出来ます。今回の例では、仮に@arrayの数が変わっても大丈夫です。しかし、配列が2つになったらお手上げです。

その場合は、次の話の参照渡しを利用できます。

@array = qw(shibata toda sakamizu eriko);
$lname = "good";
$fname = "people";

$name = &addStr( $lname, $fname, \@array );

print $name;

sub addStr {
    $l   = shift;
    $f   = shift;
    $a   = shift;
    $num = 0;
    foreach (@$a) {
        $num++;
        if ( $num == 1 ) {
            $str .= "$_ ";
        }
        else {
            $str .= "and $_ ";
        }
    }
    $name = "${str}are $l $f!\n";
}

リファレンスを渡せば、それはスカラー値なので、その他のスカラーと同様に扱えます(詳しくは、この後をお読み下さい)。

また、もう1つ触れておかなければならないのは

$name = "Sakai";
$name = &addStr($name);

print $name;

sub addStr {
    ($n) = @_;
    $n .= " is not a foolish man!\n";
}

の、

$name = &addStr($name);

で、どうしてサブルーチンaddStrで作成した$nの値が戻ってきて、$nameに代入できるのでしょうか。これもPerl特有の話ですが、Perlではサブルーチンの最後(サブルーチンとは限らないのですが)の値を評価したものが(そしてその評価の結果ではなく)、変数の値が自動で返されます。つまり

$n .= " is not a foolish man!\n";

が最後のなので、$nが返されるからです。ただ通常は他の言語との互換性やコードの読みやすさから、しっかりreturn文を書いた方が良いと思います。つまり

$name = "Sakai";
$name = &addStr($name);

print $name;

sub addStr {
    ($n) = @_;
    $n .= " is not a foolish man!\n";
    return $n;
    ##########
}

という感じです。

さらに脱線すれば、これを利用すると、式自体が条件式になり(ちょっと視点は違いますが)、こんなこともあり得ます。

$temp = 1 or print "1: fail\n";
$temp = 0 or print "0: fail\n";
$temp or print "fail\n";

では、2行目と3行目が出力されます。なぜならば、$temp = 1を評価し(つまり代入し)、それで代入された1が返されます。それで、orand&&||は論理演算子と呼ばれていますが、別名「短絡演算子」とも呼ばれ、if文などなしに、この場合だと返された値が真(1)だったら出力しないし、偽(undefや0、空文字)だったら出力するというような書き方が出来ます。

注意すべき点として、偽になるのはundefや0、空文字なので、仮に0が入力されてもOKの場合、なかなかみつからないバグになったりします。また、

or||and&&は意味が同じですが、演算子の優先順位が相当違い、以下のコードは意味が違います。

$temp = $one or $two or $three;

print $temp,"\n";

$temp = $one || $two || $three;

print $temp,"\n";

この場合、たぶん、このコーディングをした人の意図は

$temp = $one || $two || $three;

であって、

$temp = $one or $two or $three;

ではないと思われます。このorは代入のための=よりもさらに優先順位が低いので(||=よりも高い)ので、先に代入されてしまい、

$temp = $one or $two or $three;

では先に$temp = $oneが実行されてしまい、残りが無駄になってしまいます。といっても、$temp = $oneが評価され、0が返ってくるので、次のorには行きますが、それが0なので、さらに次に行き、$threeは1なので真になりますが、代入はすでに終わっているので、$tempは0のままです。もし

$x = ($temp = $one or $two or $three);

を実行すると、$tempには先ほどと同様0が入っていますが、$xには最後の$threeの値が入ります(なんだか頭がウニ〜になってきますね)。

$temp = $one || $two || $three;

ですと、代入が行われる前に、$oneが0で次に進み、$twoも0なのでさらに進み、$threeが1なのでこれが最後に代入されるというわけです。

さてさて、話を本題に戻しましょう。よく値渡しとか参照渡しなどと昔は言われましたが、Javaなどでは参照渡しが普通になっており、最近ではあまり言われなくなったのかもしれませんが、PHPやPerlでは、まだまだこの言葉は現役です(と思います)。

どんなことを言うかというと、下の例を見てください。

$name = "Sakai";

$henkogo = &changeString($name);
print $name , "\n";
print $henkogo , "\n";

sub changeString {
  $n = shift;
  $n .= " is good guy!";
  return $n;
}

は、値渡しの例です。値渡しとは、Perlで言えばリファレンスじゃない通常のスカラーや配列を渡すことであり、サブルーチンで加工されたものを必要であれば、サブルーチン側で

return $n;

返してもらって、

$henkogo = &changeString($name);

それを受け取るように行います。しかし参照渡しでは、渡した値自体が変わってしまいます。例えば、似たような例では

$name = "Sakai";

$henkogo = &changeString(\$name);
print $name , "\n";
print $$henkogo , "\n";

sub changeString {
  $n = shift;
  $$n .= " is good guy!";
  return $n;
}

サブルーチンに渡す値をリファレンスにしています。

$henkogo = &changeString(\$name);

サブルーチンの最後にリファレンスを返していますが、これは実質必要ありません。

sub changeString {
  $n = shift;
  $$n .= " is good guy!";
  return $n;
}

ポイントは、

print $name , "\n";

でも、

Sakai is good guy!

と出力されることです。これは便利である一方で、リファレンスのことを知らないと下手をすると「わからないバグ」になる可能性があります。また、

$newName = $n;

などとやって、コピーしたつもりになって、

$name = "Sakai";
$new = &changeString(\$name);

print $name , "\n";
print $$new , "\n";

sub changeString {
  $n       = shift;
  $newName = $n;
  $$n .= " is good guy!";
  return $newName;
}

どちらも、変更されてしまっているということが起こったります(つまり$newName = $n;とやっても、$nameが指す場所をコピーしているので、結局$nameを見ているという意味では同じものなわけです)。

無名配列、無名ハッシュ

@array%hashはそれぞれarrayやhashという名前の配列、ハッシュです。つまり名前があるわけです。しかし、リファレンスを使っているとこの名前は不要になります。

@sosu = qw(1 2 3 5 7);
@gusu = qw(2 4 6 8 10);
@kisu = qw(1 3 5 7 9);

%kazu = (sosu => \@sosu, gusu => \@gusu, kisu => \@kisu);

print "10以下の素数は", join(",",@{$kazu{sosu}}), "\n";

で、素数という配列にアクセする場合、もともとの@sosuのsosuという名前は利用していません。もちろん、$kazu{sosu}のsosuはハッシュのキーです。また、こうしてしまうと、

@sosu = qw(1 2 3 5 7);
@gusu = qw(2 4 6 8 10);
@kisu = qw(1 3 5 7 9);

%kazu = (sosu => \@sosu, gusu => \@gusu, kisu => \@kisu);

$number = \%kazu;

print "10以下の素数で最後のは$number->{sosu}->[4]です\n";

などの場合は、最後のprintで参照する場合には、唯一ハッシュのキーであるsosuだけが必要なわけで、冒頭で話した、変数に付いた名前など使わなくなってしまいます。そこで、Perlでは無名配列・無名ハッシュを作成する方法が用意されています。それを使って、上記のコードを書き直すと、

$arrayrefS = [ 1, 2, 3, 5, 7 ];
$arrayrefG = [ 2, 4, 6, 8, 10 ];
$arrayrefK = [ 1, 3, 5, 7, 9 ];
$hashref = { sosu => $arrayrefS, gusu => $arrayrefG, kisu => $arrayrefK };

print "10以下の素数で最後のは$hashref->{sosu}->[4]です\n";

となります。これは決して、$arrayrefSという名前の配列ではありません。あくまでも、配列のリファレンスであり、敢えて言えばそのリファレンス(スカラー値)の名前です。したがって、配列には名前がありません(なので無名配列という)。

最初の頃には必ず混乱するのですが、普通の配列、ハッシュの作成方法は

@array = (1, 3, 5);

%hash = (name => 'sakai', age => '18');

というように両方とも、( .. )というように小括弧です。無名配列、無名ハッシュの場合はそれぞれ

$arrayref = [1, 3, 5];

[ .. ]のように大括弧で、

$hashref = {name => 'sakai', age => '18'};

{ .. }のように中括弧です。混乱してしまいますが、これはしっかり身につけておきましょう。

サブルーチンの参照

リファレンスには、今回は触れないblessされた値のリファレンス(クラスで使用)、リファレンスのリファレンスなどがありますが、あとはのサブルーチンのリファレンスに触れます。

これは主にコールバックというような手法で使われたりします。コールバックとは、サブルーチンの処理がアルゴリズムの特定の場所に到達したら実行される処理のことです。

以下の例では、名前の入った配列を渡し、その1つ1つの要素を改行コードでつないでいくjoinArrayというサブルーチンに、第1引数にサブルーチンのリファレンスを渡し(undefを渡すと何もしない)、名前の小文字のsを大文字のSに変更する使い方の例です。

@names = qw(sakai sasaki shibata suzuki toda);

$string = &joinArray(\&upperS, @names);

print $string, "\n";

$string2 = &joinArray(undef, @names);

print $string2, "\n";

sub joinArray {
  $func   = shift;
  @array  = @_;
  $string = "";
  foreach $i (0 .. $#array) {
    if (ref($func) eq 'CODE') {
      $string .= &$func($array[$i]) . "\n";
    } else {
      $string .= $array[$i] . "\n";
    }
  }
  return $string;
}

sub upperS {
  $arg = shift;
  $arg =~ s/(s)/S/g;
  return $arg;
}


$string = &joinArray(\&upperS, @names);

\&upperSが、サブルーチンのリファレンスであり、joinArrayの中の

$string .= &$func($array[$i]) . "\n";

で、デリファレンスして、その関数を使っています。ちなみに

if (ref($func) eq 'CODE') {

refという組み込み関数

ref EXPR
    EXPR がリファレンスであれば、真を返し、さもなくば、 偽を返します。
    返される値は、リファレンスが参照する ものの型に依存します。
    組み込みの型には、

                     REF
                     SCALAR
                     ARRAY
                     HASH
                     CODE
                     GLOB

   がり、それらの文字列が返ってきます。

を使って、サブルーチンのリファレンスの場合CODEを返すので、その場合は利用し、そうじゃない場合は利用しないという方法をとっています。

このサブルーチンのリファレンスには、配列・ハッシュと同様、無名サブルーチンも定義できます。

@names = qw(sakai sasaki shibata suzuki toda);

$string = &joinArray(
  sub {
    $arg = shift;
    $arg =~ s/(s)/S/g;
    return $arg;
  },
  @names
);

print $string, "\n";

$string2 = &joinArray(undef, @names);

print $string2, "\n";

sub joinArray {
  $func   = shift;
  @array  = @_;
  $string = "";
  foreach $i (0 .. $#array) {
    if (ref($func) eq 'CODE') {
      $string .= &$func($array[$i]) . "\n";
    } else {
      $string .= $array[$i] . "\n";
    }
  }
  return $string;
}

$func = sub {
  $arg = shift;
  $arg =~ s/(s)/S/g;
  return $arg;
  }

のように、無名配列を定義することも出来ます。

シュワルツ変換によるソート

これまでの知識を活かしながら、課題として、ちょっと難しい例を見ていきましょう。

「続・はじめてのPerl」の例を多少アレンジして

use Benchmark;
use List::Compare;
###とりあえず、データの作成

##アルファベットを全て用意
@records = ("a" .. "z", "A" .. "Z");

#別にいつも違う必要がないので
srand("sakai");

#10000個のデータ(hash)を作成
foreach (0 .. 100000) {
  $val = '';
  foreach (0 .. 10) {

    #適当なアルファベットの列(11桁)を取得
    $val .= $records[ int(rand(51)) ];
  }

#インクリメントされている0〜100000の数をキーとし、値を上で作った$valのハッシュを作成していく
  $hash{$_} = $val;
}

###データ作成終了

@array = (0 .. 100000);

#@arrayの値1つ1つがキーとなる%hashの値を、その値でソートした(つまり上記の$valでソート)した配列を作る

##普通のソート
$t0    = new Benchmark;
@after = sort { getVal($a) cmp getVal($b) } @array;
$t1    = new Benchmark;
$td    = timediff($t1, $t0);
print "the normal sort took:", timestr($td), "\n";

##シュワルツ変換を用いたソート
$t0 = new Benchmark;

########################################
@after2 =
  map $_->[0],
  sort { $a->[1] cmp $b->[1] }
  map [ $_, getVal($_) ], @array;
########################################

$t1 = new Benchmark;
$td = timediff($t1, $t0);
print "the Schwartzian sort took:", timestr($td), "\n";

##念のため、作成したものが同じかどうかをチェック

$lc = List::Compare->new(\@Llist, \@Rlist);

@Lonly = $lc->get_Lonly;
@Ronly = $lc->get_Ronly;

print @Lonly, "/Lonly\n";
print @Ronly, "/Ronly\n";

###本当はデータベースにアクセするようなものだけど、今回は安易にしている
sub getVal {
  $key = shift;
  return $hash{$key};
}

ちょっと長いので、簡単に説明すると、

1)0〜10000までの100001個の数字をキーとするハッシュを作成。その値にはa-zA-Zのランダムな文字が11文字入っています。 2)サブルーチンgetValは、引数を1)のデータのキーから、値を返すものです(こんなことは必要ないのですが、実際にはここではDBにアクセスするなどロジックが入ることを想定していますが、それだと確認が取りづらいので、とりあえず)。 3)そして、0から100000までの100001回、それぞれの値を2のサブルーチンに問い合わせます。 4)3)で返ってきた(実際には%hashのキーに対する値)をもとにソートした配列を取得する方法として、2つ行っています。

もともとsortメソッドは一般的にはsort BLOCK LISTというような形を取り、

@articles = sort {$a cmp $b} @files;

のように使います。{}のブロックでは、$a(@filesの$bより前の値)と$a(@filesの$aよりも後の値)が予約されている変数として使え、上記のケースでは

$a cmp $b

で、比較を行うブロックを渡していることになります。大きいと1を返し、同じだと0、小さいと-1を返すものであれば、自前のメソッドでもOKです(cmpは組み込み関数で、文字列の比較を行い、前述した値を返すものです)。

さて、じゃあ、

@after = sort { getVal($a) cmp getVal($b) } @array;

では配列に入った(例えば、$a=100と$b=101)値をブロックの中で、getValに渡して、返ってきた文字列を比較しています。

この方法は難点があります。このgetValの呼び出しが複数行われるからです。

@after = sort { getVal($a) cmp getVal($b) } @array;

これだけ見るとまるで、getVal(100)は1回しか呼び出されないように思えるかもしれませんが、実際にはそうではありません。ソートの方法にもよりますが(5.6以前はクイックソート、それ以降はマージソートも使えるらしい)、マージソートの例で言えば(Wikipedia)、

初期データ: 8 4 3 7 6 5 2 1

1. データ列を二分割する。

       8 4 3 7 | 6 5 2 1

2. もう一度、二分割する。

       8 4 | 3 7 | 6 5 | 2 1

3. 各データ列にデータ数が2以下になったところで、各データ列内のデータをソートする。

       4 8 | 3 7 | 5 6 | 1 2

4. この例の場合は、右2つのデータ列、左2つのデータ列をそれぞれマージとソートし、2つのデータ列にする。

       3 4 7 8 | 1 2 5 6

5. 2つのデータ列をマージとソートする。

       1 2 3 4 5 6 7 8

のようにソートするわけですが、1の位置が変わる度に1と他の数が比較されているわけで、1と比較するものが複数出てくるわけです。今回の例で言えば、getVal(1)というメソッドが複数実行されると言うことになります(マージソートはここがわかりやすい!)。

「そんなの無駄じゃん」と思った人がいるわけで(続・初めてのPerlの作者は「そんなことやっていたらサルが怒り出す」と言っている!)、それを避ける方法が、

########################################
@after2 =
  map $_->[0], 
  sort { $a->[1] cmp $b->[1] }
  map [ $_, getVal($_) ], @array;
########################################

です。要するにさっきの例で言えば、getVal(1)などが何度も呼ばれないようにすればよいので、事前にgetVal(0) .. getVal(100000)を呼んでおき、それをデータに保存しておけばよいじゃん、ということになります。つまり、上記のコードも下(後ろ)から読むとわかりやすい。

map [ $_, getVal($_) ], @array;

で、処理する@arrayの配列全ての必要な配列を事前に作成します。実際には(key, value)というような無名配列のリファレンスを入れた配列を作成し、

sort { $a->[1] cmp $b->[1] }

で、その無名配列の2番目の値、つまりgetVal($_)したものを比較し、ソートしたものをさらに

map $_->[0], 

で、[ $_, getVal($_) ]の最初のもの、つまり、@arrayの値をだけを返しているわけです。