ここにあるドキュメントは古いものです。 より新しいものが perldoc.jpにあります → perllol - Perl で配列の配列を操作する
perlLoL - Perlでリストのリストを扱う
組み立てるのが最も単純なことは、リストのリスト(配列の配列とも呼 ばれることがあります)です。これは理解しやすく、そしてより複雑な データ構造に対しても適用することのできるものです。
リストのリスト、もしくは配列の配列は、あなたが望めば通常の古い配
列@LoLのようなものです。これは$LoL[3][2]のように、二つの添え
字で要素を取得することができます。配列の宣言の例を挙げましょう。
# 配列にリストへの参照のリストを代入する
@LoL = (
[ "fred", "barney" ],
[ "george", "jane", "elroy" ],
[ "homer", "marge", "bart" ],
);
print $LoL[2][2]; bart
このとき、外側の括弧が丸括弧であったとこに注意すべきです。これは、 上の例では@listに代入するので丸括弧を使う必要があったためなので す。もし@LoLではなくて、単にリファレンスを代入したかったというの であれば、次のように書くことができます。
# リストへの参照のリストに対する参照を代入
$ref_to_LoL = [
[ "fred", "barney", "pebbles", "bambam", "dino", ],
[ "homer", "bart", "marge", "maggie", ],
[ "george", "jane", "elroy", "judy", ],
];
print $ref_to_LoL->[2][2];
外側の括弧が変わったことと、アクセスの構文が変わっているというこ
とに注目してください。これはCとは違って、Perlでは配列と参照とを
自由に交換できないからです。$ref_to_LoLは配列への参照です。その
配列は@LoLで、これがまた配列です。同様に、$LoL[2]は配列ではな く配列への参照です。ですから、
$LoL[2][2]
$ref_to_LoL->[2][2]
これは、以下のような書き方でも同じことになります。
$LoL[2]->[2]
$ref_to_LoL->[2]->[2]
この規則は隣り合った括弧(それがブラケットだろうがカーリーブレー スだろうが)だけのものなので、参照外しをするarrowを自由に省略でき ます。けれども一番最初にあるarrowだけは、それがリファレンスを保 持するスカラーであるために省略することはできません。これは $ref_to_LoLが常に必要とするものです。
固定的なデータ構造の宣言は良いのですが、その場で(on the fly)新し い要素を追加したいとき、あるいは完全に0から作り上げたい(entirely from scratch)ときにはどうするのでしょう?
まず最初に、ファイルから読み込むことを見てみましょう。これは一度 に一つの行を追加していくようなものです。私たちはここで、読み込ん でいるファイルが、一行(line)が一つの行(row)に対応し、各単語が要 素に対応しているようなフラットなファイルであると仮定しています。 もしリスト@LoLにそういった物を設定しようとするのであれば、それは 以下のようなやり方になります。
while (<>) {
@tmp = split;
push @LoL, [ @tmp ];
}
関数を使ってロードすることもできます。
for $i ( 1 .. 10 ) {
$LoL[$i] = [ somefunc($i) ];
}
あるいは、リストに設定するために使う一時変数を使うこともできます。
for $i ( 1 .. 10 ) {
@tmp = somefunc($i);
$LoL[$i] = [ @tmp ];
}
リストへの参照のコンストラクターであるC<[]>を使うことが非常に重 要です。次のように書いてしまうのはとてもまずいやりかたです。
$LoL[$i] = @tmp;
このようなスカラーに対する名前付きリストの代入では、@tmpにある要 素の数を数えてその数を代入します。そしてこれはおそらくはあなたの 望んだことではないでしょう。
use strictの元で実行するのであれば、以下の様にちょっと宣言を 付加えるとよいでしょう。
use strict;
my(@LoL, @tmp);
while (<>) {
@tmp = split;
push @LoL, [ @tmp ];
}
もちろん、一時的な配列もなければならないというものではありません。
while (<>) {
push @LoL, [ split ];
}
また、push()を使わなくてもできます。どこに押し込めたいかと言うこ とがわかっているのなら、直接代入させることもできます。
my (@LoL, $i, $line);
for $i ( 0 .. 10 ) {
$line = <>;
$LoL[$i] = [ split ' ', $line ];
}
あるいはこういう風にもできます。
my (@LoL, $i);
for $i ( 0 .. 10 ) {
$LoL[$i] = [ split ' ', <> ];
}
本当にそうしたいときを除き、スカラーコンテキストでリスト関数を使 ってしまう可能性に気をつけるべきです。これは普通の読み手には明 らかでしょう。
my (@LoL, $i);
for $i ( 0 .. 10 ) {
$LoL[$i] = [ split ' ', scalar(<>) ];
}
配列へのリファレンスとして変数 $ref_to_LoLを使いたいというのであ れば、以下の様にする必要があるでしょう。
while (<>) {
push @$ref_to_LoL, [ split ];
}
これで新しい行を追加することができます。新しいカラムを追加 するのは? あなたがまさに行列(matrices)を扱っているのなら、大概は 単純な代入となります。
for $x (1 .. 10) {
for $y (1 .. 10) {
$LoL[$x][$y] = func($x, $y);
}
}
for $x ( 3, 7, 9 ) {
$LoL[$x][20] += func2($x);
}
これは対象となる要素が既に存在しているかどうかには影響されません。
(ない場合でも)喜んであなたのためにその要素を作り出し、必要に応じ
て間にある要素にundefをセットします。
あなたは、単に行に追加したいだけという場合であっても、ちょっと妙 に見えることをしなければならないでしょう。
# 新たなカラムを既にある行に追加する
push @{ $LoL[0] }, "wilma", "betty";
次のようには書けないことに注意してください。
push $LoL[0], "wilma", "betty"; # 間違い!
事実、これはコンパイルすらできません。なぜでしょうか? それはpush() の引数は参照ではなく、実際の配列でなければならないからです。
こんどはこのデータ構造を出力する番です。あなたはどうやろうと考え てますか? そうですね、簡単に要素を一つだけ出力したいとするとこう なります。
print $LoL[0][0];
配列の内容全部を出力したいとき、次のようには書けません。
print @LoL; # ダメ
なぜなら、これでは単にリストへのリファレンスが 取れるだけで、 perlはそれを自動的に参照外しするようなことはしないからです。この ため、あなたは自分自身でループしなければなりません。これは外側の 添え字に対するループでシェルスタイルのfor()を使って構造全体を出 力します。
for $aref ( @LoL ) {
print "\t [ @$aref ],\n";
}
添え字を記録したいのなら、このようにできます。
for $i ( 0 .. $#LoL ) {
print "\t elt $i is [ @{$LoL[$i]} ],\n";
}
あるいはこのようなやり方もあります。内側のループに注目してくださ い。
for $i ( 0 .. $#LoL ) {
for $j ( 0 .. $#{$LoL[$i]} ) {
print "elt $i $j is $LoL[$i][$j]\n";
}
}
見て判るようにこれは少々複雑ですが、途中で一時変数を使えば簡単に できます。
for $i ( 0 .. $#LoL ) {
$aref = $LoL[$i];
for $j ( 0 .. $#{$aref} ) {
print "elt $i $j is $LoL[$i][$j]\n";
}
}
うーんまだちょっと見にくいですね。
for $i ( 0 .. $#LoL ) {
$aref = $LoL[$i];
$n = @$aref - 1;
for $j ( 0 .. $n ) {
print "elt $i $j is $LoL[$i][$j]\n";
}
}
多次元配列のスライス(行部分)を取りたいのであれば、fancy
subscripting(訳注: 風変わりな添え字付け?)をする必要があるでしょ
う。これは参照外しのためのpointer arrowを使った単一の要素に対す
るものはあるのですが、それに対応するスライス用の便利なものはない
のです。(もちろん、スライス操作をするためにループを書くことは常
に可能だと言うことを忘れないで下さい)。
以下は、ループを使った一つの操作をどのように行うかの例です。変数 @LoLが前のものと同じであると仮定しています。
@part = ();
$x = 4;
for ($y = 7; $y < 13; $y++) {
push @part, $LoL[$x][$y];
}
このループをスライス演算に置き換えることができます。
@part = @{ $LoL[4] } [ 7..12 ];
あなたも見て感じるかもしれませんが、これは読み手にはちょっと不親 切です。
あー、でも、$xを4..8、$yを7から12とするような 二次元のスライス を必要とするときには? うーん、単純なやり方はこうでしょう。
@newLoL = ();
for ($startx = $x = 4; $x <= 8; $x++) {
for ($starty = $y = 7; $y <= 12; $y++) {
$newLoL[$x - $startx][$y - $starty] = $LoL[$x][$y];
}
}
スライスを使ってループを簡単にできます。
for ($x = 4; $x <= 8; $x++) {
push @newLoL, [ @{ $LoL[$x] } [ 7..12 ] ];
}
あなたが Schwartzian Transforms に興味を持って いるのなら、mapを使って次のようにすることを選ぶかもしれません。
@newLoL = map { [ @{ $LoL[$_] } [ 7..12 ] ] } 4 .. 8;
あなたの上司が不可解なコードによるジョブセキュリティ(もしくは rapid insecurity)の追求を非難していたとしても、説得するのは難し いでしょうね :-) もし私があなたの立場だったら、こういった操作は 関数に押し込めるでしょう。
@newLoL = splice_2D( \@LoL, 4 => 8, 7 => 12 );
sub splice_2D {
my $lrr = shift; # リファレンスのリストのリストへのリファレンス!
my ($x_lo, $x_hi,
$y_lo, $y_hi) = @_;
return map {
[ @{ $lrr->[$_] } [ $y_lo .. $y_hi ] ]
} $x_lo .. $x_hi;
}
perldata(1), perlref(1), perldsc(1)
Tom Christiansen <tchrist@perl.com>
Last udpate: Sat Oct 7 19:35:26 MDT 1995