5.6.1

名前

perllol - Manipulating Arrays of Arrays in Perl

perllol - Perl で配列の配列を操作する

説明

配列の配列の定義とアクセス

組み立てるのが最も単純なことは、配列の配列(不正確にリストのリストとも 呼ばれることがあります)です。これは理解しやすく、そしてより複雑な データ構造に対しても適用することのできるものです。

配列の配列は、あなたが望めば通常の古い配列 @AoA のようなものです。 これは$LoL[3][2]のように、二つの添え字で要素を取得することができます。 配列の宣言の例を挙げましょう。

    # 配列に配列への参照の配列を代入する
    @AoA = (
           [ "fred", "barney" ],
           [ "george", "jane", "elroy" ],
           [ "homer", "marge", "bart" ],
    );

    print $AoA[2][2];
  bart

このとき、外側の括弧が丸括弧であったとこに注意すべきです。 これは、上の例では@配列に代入するので丸括弧を使う必要があったためなのです。 もし@AoAではなくて、単にリファレンスを代入したかったというのであれば、 次のように書くことができます。

    # 配列への参照の配列に対する参照を代入
    $ref_to_AoA = [
        [ "fred", "barney", "pebbles", "bambam", "dino", ],
        [ "homer", "bart", "marge", "maggie", ],
        [ "george", "jane", "elroy", "judy", ],
    ];

    print $ref_to_AoA->[2][2];

外側の括弧が変わったことと、アクセスの構文が変わっているということに 注目してください。これはCとは違って、Perlでは配列と参照とを 自由に交換できないからです。$ref_to_AoAは配列への参照です。 その配列は@AoAで、これがまた配列です。 同様に、$AoA[2]は配列ではなく配列への参照です。ですから、

    $AoA[2][2]
    $ref_to_AoA->[2][2]

これは、以下のような書き方でも同じことになります。

    $AoA[2]->[2]
    $ref_to_AoA->[2]->[2]

この規則は隣り合った括弧(それがブラケットだろうがカーリーブレースだろうが) だけのものなので、参照外しをするarrowを自由に省略できます。 けれども一番最初にあるarrowだけは、それがリファレンスを保持する スカラーであるために省略することはできません。 これは$ref_to_AoAが常に必要とするものです。

大きくする

固定的なデータ構造の宣言は良いのですが、その場で新しい要素を 追加したいとき、あるいは完全に0から作り上げたいときにはどうするのでしょう?

まず最初に、ファイルから読み込むことを見てみましょう。 これは一度に一つの行を追加していくようなものです。私たちはここで、 読み込んでいるファイルが、一行(line)が一つの行(row)に対応し、 各単語が要素に対応しているようなフラットなファイルであると仮定しています。 もし配列@AoAにそういった物を設定しようとするのであれば、 それは以下のようなやり方になります。

    while (<>) {
        @tmp = split;
        push @AoA, [ @tmp ];
    }

関数を使って読み込むこともできます。

    for $i ( 1 .. 10 ) {
        $AoA[$i] = [ somefunc($i) ];
    }

あるいは、配列に設定するために使う一時変数を使うこともできます。

    for $i ( 1 .. 10 ) {
        @tmp = somefunc($i);
        $AoA[$i] = [ @tmp ];
    }

配列への参照のコンストラクターである[]を使うことが非常に重要です。 次のように書いてしまうのはとてもまずいやりかたです。

    $AoA[$i] = @tmp;

このようなスカラーに対する名前付き配列の代入では、 @tmpにある要素の数を数えてその数を代入します。 そしてこれはおそらくはあなたの望んだことではないでしょう。

use strictの元で実行するのであれば、以下の様にちょっと宣言を 付け加えるとよいでしょう。

    use strict;
    my(@AoA, @tmp);
    while (<>) {
        @tmp = split;
        push @AoA, [ @tmp ];
    }

もちろん、一時的な配列もなければならないというものではありません。

    while (<>) {
        push @AoA, [ split ];
    }

また、push()を使わなくてもできます。 どこに押し込めたいかと言うことがわかっているのなら、 直接代入させることもできます。

    my (@AoA, $i, $line);
    for $i ( 0 .. 10 ) {
        $line = <>;
        $AoA[$i] = [ split ' ', $line ];
    }

あるいはこういう風にもできます。

    my (@AoA, $i);
    for $i ( 0 .. 10 ) {
        $AoA[$i] = [ split ' ', <> ];
    }

本当にそうしたいときを除き、スカラーコンテキストでリストを返すかもしれない 関数を使ってしまう可能性に気をつけるべきです。 これは普通の読み手には明らかでしょう。

    my (@AoA, $i);
    for $i ( 0 .. 10 ) {
        $AoA[$i] = [ split ' ', scalar(<>) ];
    }

配列へのリファレンスとして変数 $ref_to_AoAを使いたいというのであれば、 以下の様にする必要があるでしょう。

    while (<>) {
        push @$ref_to_AoA, [ split ];
    }

これで新しい行を追加することができます。新しいカラムを追加するのは? あなたがまさに行列を扱っているのなら、大概は単純な代入となります。

    for $x (1 .. 10) {
        for $y (1 .. 10) {
            $AoA[$x][$y] = func($x, $y);
        }
    }

    for $x ( 3, 7, 9 ) {
        $AoA[$x][20] += func2($x);
    }

これは対象となる要素が既に存在しているかどうかには影響されません。 (ない場合でも)喜んであなたのためにその要素を作り出し、必要に応じて 間にある要素にundefをセットします。

あなたは、単に行に追加したいだけという場合であっても、 ちょっと妙に見えることをしなければならないでしょう。

    # 新たなカラムを既にある行に追加する
    push @{ $AoA[0] }, "wilma", "betty";

次のようには書けないことに注意してください。

    push $AoA[0], "wilma", "betty";  # 間違い!

事実、これはコンパイルすらできません。なぜでしょうか? それはpush()の引数は 参照ではなく、実際の配列でなければならないからです。

アクセスと表示

こんどはこのデータ構造を出力する番です。あなたはどうやろうと考えてますか? そうですね、簡単に要素を一つだけ出力したいとするとこうなります。

    print $AoA[0][0];

配列の内容全部を出力したいとき、次のようには書けません。

    print @AoA;         # ダメ

なぜなら、これでは単にリストへのリファレンスが 取れるだけで、 perlはそれを自動的に参照外しするようなことはしないからです。 このため、あなたは自分自身でループしなければなりません。 これは外側の添え字に対するループでシェルスタイルのfor()を使って 構造全体を出力します。

    for $aref ( @AoA ) {
        print "\t [ @$aref ],\n";
    }

添え字を記録したいのなら、このようにできます。

    for $i ( 0 .. $#AoA ) {
        print "\t elt $i is [ @{$AoA[$i]} ],\n";
    }

あるいはこのようなやり方もあります。内側のループに注目してください。

    for $i ( 0 .. $#AoA ) {
        for $j ( 0 .. $#{$AoA[$i]} ) {
            print "elt $i $j is $AoA[$i][$j]\n";
        }
    }

見て判るようにこれは少々複雑ですが、途中で一時変数を使えば簡単にできます。

    for $i ( 0 .. $#AoA ) {
        $aref = $AoA[$i];
        for $j ( 0 .. $#{$aref} ) {
            print "elt $i $j is $AoA[$i][$j]\n";
        }
    }

うーんまだちょっと見にくいですね。これでどうでしょう:

    for $i ( 0 .. $#AoA ) {
        $aref = $AoA[$i];
        $n = @$aref - 1;
        for $j ( 0 .. $n ) {
            print "elt $i $j is $AoA[$i][$j]\n";
        }
    }

スライス

多次元配列のスライス(行部分)を取りたいのであれば、fancy subscripting(訳注: 風変わりな添え字付け?)をする必要があるでしょう。 これは参照外しのためのpointer arrowを使った単一の要素に対するものは あるのですが、それに対応するスライス用の便利なものはないのです。 (もちろん、スライス操作をするためにループを書くことは常に 可能だと言うことを忘れないで下さい)。

以下は、ループを使った一つの操作をどのように行うかの例です。 変数@AoAが前のものと同じであると仮定しています。

    @part = ();
    $x = 4;
    for ($y = 7; $y < 13; $y++) {
        push @part, $AoA[$x][$y];
    }

このループをスライス演算に置き換えることができます。

    @part = @{ $AoA[4] } [ 7..12 ];

あなたも見て感じるかもしれませんが、これは読み手にはちょっと不親切です。

あー、でも、$xを4..8、$yを7から12とするような 二次元のスライスを 必要とするときには? うーん、単純なやり方はこうでしょう。

    @newAoA = ();
    for ($startx = $x = 4; $x <= 8; $x++) {
        for ($starty = $y = 7; $y <= 12; $y++) {
            $newAoA[$x - $startx][$y - $starty] = $AoA[$x][$y];
        }
    }

スライスを使ってループを簡単にできます。

    for ($x = 4; $x <= 8; $x++) {
        push @newAoA, [ @{ $AoA[$x] } [ 7..12 ] ];
    }

あなたがシュワルツ変換に興味を持っているのなら、 mapを使って次のようにすることを選ぶかもしれません。

    @newAoA = map { [ @{ $AoA[$_] } [ 7..12 ] ] } 4 .. 8;

あなたの上司が不可解なコードによるジョブセキュリティ(もしくは rapid insecurity)の追求を非難していたとしても、 説得するのは難しいでしょうね :-) もし私があなたの立場だったら、こういった操作は関数に押し込めるでしょう。

    @newAoA = splice_2D( \@AoA, 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;
    }

SEE ALSO

perldata(1), perlref(1), perldsc(1)

作者

Tom Christiansen <tchrist@perl.com>

Last update: Thu Jun 4 16:16:23 MDT 1998