PDSC0 : Perlデータ構造実用例集 拡張版0

Tom Christiansen著
< tchrist@perl.com >
=head1 タイトル

Perlにおける混成データ構造の操作

=head1 序文

5.0のリリース以前のPerlプログラミング言語にほとんど丸ごと欠落していた唯一の
特徴は混成データ構造でした。直接的に言語のサポートがなくとも、いくらかの
勇敢なプログラマー達はなんとかそれをエミュレートしてきましたが、それはきつい
仕事でしたし、気のない人向けではありませんでした。
ひとつに連結された文字列"$LoL$b"のように、実際には、キーはもっと多くても、
時には、awkから借用した$m{$LoL,$b} 表記法でうまくやりおおすことができました。
しかし、トラバーサルやソーティングは困難でした。さらに死物狂いのプログラマー
達はPerl内部のシンボルテーブルを直接切り刻む[hack]ことさえ行いました。
 a strategy that proved hard to develop and maintain--to put it mildly.

Perlの5.0リリースは混成データ構造を我々に与えてくれました。今では、以下の
ようなものを記述してもいいし、突然、3次元の配列を使っても構わないのです!

    for $x (1 .. 10) {
        for $y (1 .. 10) {
            for $z (1 .. 10) {
                $LoL[$x][$y][$z] = 
                    $x ** $y + $z;
            }
        }
    }

ああ、これがどんなに簡単に見えようと、見た目以上の手の込んだ構成が
その下にあるのです!

どうやってそれを出力しますか? なぜ、単に print @LoLと言うことができない
のでしょう? どうしたらそれを関数に渡すことができるのでしょう、また、どうしたら
関数からこれらの一部を取り戻すことができるのでしょう? それはオブジェクト
ですか? あとで読み出すために、ディスクにセーブできますか? どうやって
そのマトリクスの全部の列と行にアクセスするのでしょう? すべての値は数値で
なければならないのでしょうか?

ご覧の通り、混乱するのはいともたやすいのです。この責の一端はレファランスベースの
実装にありますが、本当は、初心者用にデザインされた事例を備えた既存文書の欠如
によるものが大部分です。

この文書は、開発者の要望があると思われる、いろいろな異なった種類のデータ構造を、
詳細にしかも分かり易く取り扱うことを意図しています。それは同時に事例集としても
機能すべきものです。あなたがこれら混成データ構造のうちのひとつを生成する必要が
できた時は、ここからドロップイン式の事例をぱくったり、頂戴したり、くすねたり
することができるわけです。

詳細にそれら各々の構成を見てみましょう。  
次のような別々の文書があります。 

=over 5

=item * 配列の配列

=item * 配列のハッシュ

=item * ハッシュの配列

=item * ハッシュのハッシュ

=item * さらに精緻な構成

=item * 再帰的、自己参照的データ構造

=back

しかし、ここでは、これら全てのタイプのデータ構造に共通している
いくつかの一般的な問題を見てみましょう。

=head1 参考

Perlにおいて--多次元配列を含めた--全てのデータ構造について理解しておくべ
き最も重要な点は、一見そうでないように現れていても、Perlの @ARRAY と 
%HASH はすべて内部的には1次元であるということです。それらはスカラ値
(文字列、数値、参照を意味します)を保持することができるだけです。
直接他の配列やハッシュを包含することはできませんが、代わりに他の配列や
ハッシュへの参照を包含することは可能です。

本当の配列やハッシュに対するのとまったく同じやり方で、配列やハッシュへの
参照を使うことはできません。 配列とそれへのポインタの使い分けをすることが
ないCやC++プログラマにとって、これは混乱の種となりえます。そうであれば、
構造体と構造体へのポインタとの間の違いとして、考えてみて下さい。

参照についてはperlref(1) manページでさらに多くのことを読むことができます
(し、そうすべきです)。手短かに言えば、参照はむしろ指し示すものを知って
いるポインタのようなものです。(オブジェクトも同様に一種の参照ですが、
すぐには--めったには--それを必要とはしないでしょう。) すなわち、一見2次元
あるいは2次元以上の配列および/またはハッシュへのアクセスのように見える
何かがあったとしても、実際問題、全てのケースで、その根底にある型は、
次のレベルへの参照を含んだ単なる1次元のエンティティにすぎないのです。
ただ、あなたはそれが2次元のものであるかのように使用することができます。
実際、これは、ほとんどすべてのCの多次元配列が動作しているのと同様の方法
なのです。

    $list[7][12]                        # 配列の配列
    $list[7]{string}                    # ハッシュの配列
    $hash{string}[7]                    # 配列のハッシュ
    $hash{string}{'another string'}     # ハッシュのハッシュ

さて、トップレベルは単に参照を含んでいるだけですから、簡単なprint() 関数
で配列の中身をプリントアウトしようとした場合、下記のように、あまり見栄えの
しないものになってしまうでしょう。

    @LoL = ( [2, 3], [4, 5, 7], [0] );
    print $LoL[1][2];
  7
    print @LoL;
  ARRAY(0x83c38)ARRAY(0x8b194)ARRAY(0x8b1d0)

これはPerlは暗黙のうちに変数をdereferenceしたりしないからです。
(これまでもそうでした。)
参照が参照しているものを得たい場合、${$blah}、@{$blah}、@{$blah[$i]}の
ようなプリフィックスタイピングインジケータ[prefix typing indicators]、
あるいは$a->[3]、$h->{fred}、または$ob->method()->[3]のような
ポストフィックスポインタアロー[postfix pointer arrows]を使って、自分自身で
それを行わなければなりません。

=head1 共通の誤り

配列の配列のようなものを構築する際に犯しやすい最大の誤りは2つあり、
それはふと要素数を数えてしまうこと、あるいは繰り返して同じメモリー
ロケーションへの参照を行ってしまうことです。ここでは、ネストした
配列の代わりにカウントを取った事例を示します。:

    for $i (1..10) {
        @list = somefunc($i);
        $LoL[$i] = @list;       # 間違い!
    } 

これは、スカラにリストを割当て、その要素数を得る単純な事例です。
もし、本当に心から望んでいるものであれば、それについては、
下記のように、もう少しばかり明示するよう配慮しておくべきでしょう。:

    for $i (1..10) {
        @list = somefunc($i);
        $counts[$i] = scalar @list;     
    } 

こちらは、何度も同じメモリーロケーションへの参照を繰り返す事例です。:

    for $i (1..10) {
        @list = somefunc($i);
        $LoL[$i] = \@list;      # 間違い!
    } 

では、ここでは何が最大の問題となっているのでしょうか? 正しいように
見えませんか? 結局、参照の配列が必要だと申し上げました通り、おっと、
あなたがやってくれましたか!

残念ながら、正解ではありますが、まだ細かく分解されます。@LoLにある
全ての参照は、「まさに同じ場所」を参照し、それ故@list中に続くものは
何でもみんな保持してしまうのです!

Unfortunately, while this is true, it's still broken.  All the references
in @LoL refer to the I, and they will therefore all hold
whatever was last in @list!  It's similar to the problem demonstrated in
the following C program:

    #include 
    main() {
        struct passwd *getpwnam(), *rp, *dp;
        rp = getpwnam("root");
        dp = getpwnam("daemon");

        printf("daemon name is %s\nroot name is %s\n", 
                dp->pw_name, rp->pw_name);
    }

Which will print

    daemon name is daemon
    root name is daemon 

The problem is that both C and C are pointers to the same location
in memory!  In C, you'd have to remember to malloc() yourself some new
memory.  In Perl, you'll want to use the array constructor C<[]> or the
hash constructor C<{}> instead.   Here's the right way to do the preceding
broken code fragments

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

The square brackets make a reference to a new array with a I
of what's in @list at the time of the assignment.  This is what
you want.  

Note that this will produce something similar, but it's
much harder to read:

    for $i (1..10) {
        @list = 0 .. $i;
        @{$LoL[$i]} = @list;
    } 

Is it the same?  Well, maybe so--and maybe not.  The subtle difference
is that when you assign something in square brackets, you know for sure
it's always a brand new reference with a new I of the data.
Something else could be going on in this new case with the C<@{$LoL[$i]}}>
dereference on the left-hand-side of the assignment.  It all depends on
whether C<$LoL[$i]> had been undefined to start with, or whether it
already contained a reference.  If you had already populated @LoL with
references, as in

    $LoL[3] = \@another_list;

Then the assignment with the indirection on the left-hand-side would
use the existing reference that was already there:

    @{$LoL[3]} = @list;

Of course, this I have the "interesting" effect of clobbering
@another_list.  (Have you ever noticed how when a programmer says
something is "interesting", that rather than meaning "intriguing",
they're disturbingly more apt to mean that it's "annoying",
"difficult", or both?  :-)

So just remember to always use the array or hash constructors with C<[]>
or C<{}>, and you'll be fine, although it's not always optimally
efficient.  

Surprisingly, the following dangerous-looking construct will
actually work out fine:

    for $i (1..10) {
        my @list = somefunc($i);
        $LoL[$i] = \@list;
    } 

That's because my() is more of a run-time statement than it is a
compile-time declaration I.  This means that the my() variable is
remade afresh each time through the loop.  So even though it I as
though you stored the same variable reference each time, you actually did
not!  This is a subtle distinction that can produce more efficient code at
the risk of misleading all but the most experienced of programmers.  So I
usually advise against teaching it to beginners.  In fact, except for
passing arguments to functions, I seldom like to see the gimme-a-reference
operator (backslash) used much at all in code.  Instead, I advise
beginners that they (and most of the rest of us) should try to use the
much more easily understood constructors C<[]> and C<{}> instead of
relying upon lexical (or dynamic) scoping and hidden reference-counting to
do the right thing behind the scenes.

In summary:

    $LoL[$i] = [ @list ];       # usually best
    $LoL[$i] = \@list;          # perilous; just how my() was that list?
    @{ $LoL[$i] } = @list;      # way too tricky for most programmers


=head1 CAVEAT ON PRECEDENCE 

Speaking of things like C<@{$LoL[$i]}>, the following are actually the
same thing:

    $listref->[2][2]
    $$listref[2][2]

That's because Perl's precedence rules on its five prefix dereferencers
(which look like someone swearing: C<$ @ * % &>) make them bind more
tightly than the postfix subscripting brackets or braces!  This will no
doubt come as a great shock to the C or C++ programmer, who is quite
accustomed to using C<*a[i]> to mean what's pointed to by the I
element of C.  That is, they first take the subscript, and only then
dereference the thing at that subscript.  That's fine in C, but this isn't C.

The seemingly equivalent construct in Perl, C<$$listref[$i]> first does
the deref of C<$listref>, making it take $listref as a reference to an
array, and then dereference that, and finally tell you the I value
of the array pointed to by $LoL. If you wanted the C notion, you'd have to
write C<${$LoL[$i]}> to force the C<$LoL[$i]> to get evaluated first
before the leading C<$> dereferencer.

=head1 WHY YOU SHOULD ALWAYS C

If this is starting to sound scarier than it's worth, relax.  Perl has
some features to help you out avoid its most common pitfalls.  The best
way to avoid getting confused is to start every program like this:

    #!/usr/bin/perl -w
    use strict;

This way, you'll be forced to declare all your variables with my() and
also disallow accidental "symbolic dereferencing".  Therefore if you'd done
this:

    my $listref = [
        [ "fred", "barney", "pebbles", "bambam", "dino", ],
        [ "homer", "bart", "marge", "maggie", ],
        [ "george", "jane", "alroy", "judy", ],
    ];

    print $listref[2][2];

The compiler would immediately flag that as an error I,
because you were accidentally accessing C<@listref>, an undeclared
variable, and it would thereby remind you to instead write:

    print $listref->[2][2]

=head1 デバッギング

5.001における標準版Perlデバッガは、混成データ構造体を出力したり
するのにあまり大した働きはしていません。
しかしながら、Ilya Zakharevich EFE
が書いたperl5dbは、混成データ構造体をダンプアウトするC コマンドと同様の
コマンドライン編集機能を含めた、いくつかの新しい特徴を備えています。
これは、下記でアクセスできます。

    ftp::/ftp.perl.com/pub/perl/ext/perl5db-kit-0.9.tar.gz

例えば、上記の $LoLを指定したとすると、デバッガの出力はこうなります。:

    DB<1> X $LoL
    $LoL = ARRAY(0x13b5a0)
       0  ARRAY(0x1f0a24)
          0  'fred'
          1  'barney'
          2  'pebbles'
          3  'bambam'
          4  'dino'
       1  ARRAY(0x13b558)
          0  'homer'
          1  'bart'
          2  'marge'
          3  'maggie'
       2  ARRAY(0x13b540)
          0  'george'
          1  'jane'
          2  'alroy'
          3  'judy'

ほとんど同じですが、小文字の B コマンドもあります。

=head1 参照

perlref(1), perldata(1)



最終更新日

Sat Oct 7 22:41:09 MDT 1995

ご意見、ご要望は、 電子メールまたは 投稿にお願い致します。

ホームページへ戻る。