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