use strict
を使うべきなのか
perldsc - Perl のデータ構造クックブック
Perl というプログラミング言語の、5.0 以前のもので最も欠けていた一つの機能とは
複雑なデータ構造でした。
言語による直接のサポートがなくても、一部の英雄的なプログラマー達は複雑な
データ構造をエミュレートしていました。
しかし、それはとても大変な作業でした。
あなたは(多次元配列をエミュレートするために) awk から借りてきた
$m{$AoA,$b}
という記法を使うことができましたが、これは実際には
そのキーを "$AoA$b"
のような連結された文字列にしているので、
分解やソートが困難でした。
もっと命知らずのプログラマーたちはデータ構造を優しく put するために、Perl の
内部シンボルテーブルを直接ハックすることさえ行って、開発や保守を困難にする
戦略をとったのです。
Perl のリリース 5.0 は、私たちに複雑なデータ構造をもたらしました。 あなたは今では三次元の配列のようなものだって記述できるんです!
for $x (1 .. 10) { for $y (1 .. 10) { for $z (1 .. 10) { $AoA[$x][$y][$z] = $x ** $y + $z; } } }
ああ、これは単純に見えるけど見えないところに複雑なものが隠れてるんです!
これをどのように出力しますか? なぜ単に print @AoA
としては
いけないのでしょう?
ソートはどうやるのですか? どうすれば関数に引数として渡したり、関数の
戻り値として受け取ることができるでしょうか?
それはオブジェクトですか? 後で読み返すためにディスクにセーブすることが
できますか?
配列の行全体や列全体にどうアクセスしますか?
すべての値は数値でなければいけないのでしょうか?
見ての通り、これは簡単に混乱してしまいます。 これに対する portion of blame の一部はリファレンスベースの実装に 帰することができるとしても、初心者向けにデザインされた例を持った ドキュメントが欠けているということが大きいでしょう。
本ドキュメントは詳しく説明することが目的ですが、あなたが開発しようと 考えるかもしれないデータ構造の多くの相違点の扱いは理解可能なものです。 詳細な説明はサンプルのレシピ集に取っておくべきものです。 あなたがこれら複雑なデータ構造の一つを作成する必要がある場合、あなたは ここにある例からひょいと持っていくことができます。
これら可能な構造をそれぞれ詳しく見て行きましょう。 以下の様に、それぞれセクションとして独立しています。
しかし今のところは、これら全てのデータ構造全てに共通の一般的な問題を 見ていきましょう。
多次元配列も含めた Perl におけるすべてのデータ構造を理解するに当たって
最も重要な事は、Perl の @ARRAY
や %HASH
は外見はそうは見えなくても、
内部的には全て一次元であるということです。
これらのものはスカラー値(文字列や数値、リファレンス)だけを保持することが
できます。
(配列やハッシュは)直接他の配列やハッシュを保持することはできませんが、
他の配列、ハッシュに対する リファレンス を保持することができます。
配列へのリファレンスやハッシュのリファレンスを、本当の配列やハッシュと 同じやり方で使うことはできません。 C や C++ プログラマーは、ある配列と(配列要素の型への)ポインターとの間の 区別していませんから混乱してしまうかもしれません。 もし本当に混乱してしまったら、構造体と構造体へのポインタの違いのような ものであると考えてください。
perlref(1)
マニュアルページを読めばリファレンスに関してより多くのことが
ありますし、そうすべきです。
一言で言えば、リファレンスは自分が何を指しているかを知っているポインターの
ようなものです(オブジェクトもまたリファレンスのようなものですが、
いますぐそうする必要はないでしょう)。
これはつまり、あなたが二次元、あるいはそれ以上の
次元を持った配列やハッシュにアクセスしようとしたとき、実際に
はそれが次のレベルへのリファレンスを保持している一次元の実体
(entity)であるということなのです。
あなたはこれを二次元配列であるかのように使うことができます。
これはほとんどすべての C の多次元配列が動作しているのと同じ方法です。
$array[7][12] # 配列の配列 $array[7]{string} # ハッシュの配列 $hash{string}[7] # 配列のハッシュ $hash{string}{'another string'} # ハッシュのハッシュ
トップレベルはリファレンスのみで構成されるので、これらの配列を単純に
print()
関数を使って出力しようとすれば、次のような良くわから
ない結果となるでしょう。
@AoA = ( [2, 3], [4, 5, 7], [0] ); print $AoA[1][2]; 7 print @AoA; ARRAY(0x83c38)ARRAY(0x8b194)ARRAY(0x8b1d0)
これはPerlがあなたの使う変数のデリファレンス(dereference)をこっそりやらないからです。
リファレンスが参照しているものを取り出したいのなら、${$blah}
, @{$blah}
,
@{$blah[$i]}
のような型指定子を前置したり、あるいは$a->[3]
,
$h->{fred}
, $ob->method()->[3]
のようにpointer arrowを
後置して自分自身でデリファレンスをしなければなりません。
配列の配列を構築するようなとき犯しやすい間違いとして、誤って要素の 数を数えてしまうことと、同じメモリ位置にあるものリファレンスを 繰り返しとってしまうという二つがあります。 以下の例は、ネストした配列の代わりにその数を数えてしまうという例です。
for $i (1..10) { @array = somefunc($i); $AoA[$i] = @array; # WRONG! }
これは配列をスカラーに代入し、その要素数を取得するという単純な サンプルです。 もし本当にそれがあなたのやりたいことであるというのであれば、 それを明確にするために以下の様にすることが良いかもしれません:
for $i (1..10) { @array = somefunc($i); $counts[$i] = scalar @array; }
次の例は、同じメモリ位置にあるリファレンスを何度もくり返し取って しまうというものです。
for $i (1..10) { @array = somefunc($i); $AoA[$i] = \@array; # WRONG! }
さて、何が問題なんでしょう? 正しいように見えますが、違うのでしょうか? リファレンスの配列が必要だといいましたよね。 あれ、あなたもう作ってるじゃないですか!
残念なことに、これは正しいのですがまだおかしいのです。 @AoA にあるすべてのリファレンスは全く同じ場所を参照していて、 そのためそういったリファレンスはすべて最後に @array にあったものを 保持してるんです! つまり、以下の C プログラムにある問題と同じです。
#include <pwd.h> 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); }
これの出力はこうなります:
daemon name is daemon root name is daemon
この問題は、rp
と dp
の両方が同じメモリ位置を指している
ポインターであるということです!
C では、新たなメモリを確保するために自分でmalloc()することを
忘れてはいけません。
Perlでは、代わりに配列コンストラクタ []
や
ハッシュコンストラクタ {}
を使います。
以下の例は、先に挙げた間違ったコード片の正しいやり方です:
for $i (1..10) { @array = somefunc($i); $AoA[$i] = [ @array ]; }
ブラケットは新たな配列への参照を作り出し、代入のときに @array の内容を コピー します。 これがあなたの望むことです。
以下のようなやり方でも同様の結果となりますが、読みやすさという点では 劣るということに注意してください。
for $i (1..10) { @array = 0 .. $i; @{$AoA[$i]} = @array; }
同じことなんでしょうか? そう、同じでもあるでしょうし、
違うとも言えるでしょう。
そこにある微妙な違いとは、大かっこの中にある何かを代入しようとしたときは、
それは常にデータの新たな コピー による
新たなリファレンスであるということです。
そうでない場合には、代入の左辺で @{$AoA[$i]}
のデリファレンスを
しようとするかもしれません。
これは $AoA[$i]
が未定義の状態であるかあるいはすでにリファレンスが
入っているかということに依存しています。
すでに次のようにして @AoA をリファレンスのために使っていた場合:
$AoA[3] = \@another_array;
そして左辺の間接代入では、既に存在するリファレンスを 使うことになります:
@{$AoA[3]} = @array;
もちろんこれは、@another_array を壊すという「興味深い」効果を もたらすでしょう。 (プログラマーが何かを「興味深い」といったときに、 それは「好奇心をそそるもの」というよりはむしろ「困ったもの」、 「困難なもの」という意味で使われるということに 気がついたことがありますか? :-)
[]
や{}
による配列やハッシュのコンストラクターを
常に使うのだと言うことを忘れないでください。
そうすれば、たとえそれが効率的に最良でない場合があるにせよ、
あなたは気持ちよくいられるでしょう。
驚くべきことに、以下の例は危険なもののように見えるにも関らず、 実際にはきちんと動作します。
for $i (1..10) { my @array = somefunc($i); $AoA[$i] = \@array; }
なぜなら、my() はコンパイル時に処理される宣言文ではなく、
実行時に処理される文であるからです。
これはつまり、my で宣言された変数は、ループを通過する度に新たに
再生成されるということです。
このため、このコードが毎回同じ変数のリファレンスを格納しているように
見えるにも関らず、実際にはそうではないのです!
これは、熟練したプログラマー以外の人を誤解させる危険性をはらんだ上で、
より効率の良いコードを作ることのできるような微妙な違いです。
ですから、私は普段は初心者に対してこれを使わないように教えるのです。
事実、関数に対してパラメーターを渡すときを除いて、私はプログラム中に
参照演算子がうじゃうじゃでて来ることを好みません。
その代わり、私は初心者に(初心者と、我々の大部分は)レキシカル
(または動的)スコープや隠れた参照カウントに影響されるよりは、
より理解しやすい []
, {}
をもっと使うようにすべきであると
アドバイスしています。
まとめるとこうなります:
$AoA[$i] = [ @array ]; # 普通はこれが最善 $AoA[$i] = \@array; # 危険; just how my() was that array? @{ $AoA[$i] } = @array; # ほとんどのプログラマには技巧的過ぎ
@{$AoA[$i]}
のときと同様、以下の二つは同じ動作をします。
$aref->[2][2] # 明快 $$aref[2][2] # まぎらわしい
これは、Perlの優先順位規則では 5つの前置形式のデリファレンス演算子
($ @ * % &
)は、後置形式の添え字付け演算子のブラケットや
カーリーブレースよりも強く結び付くからなのです!
これは *a[i]
を、a
のi番目が指しているものであるとみなすことに
慣れきった C/C++ プログラマーにとっては大きな衝撃であることは
疑いないでしょう。
つまり、まず最初に添え字を取って、それから添え字付けされたものの
デリファレンスを行うということです。
それは C では正しいことですが、これは C じゃないのです。
Perlにおける等価な構造の $$aref[$i]
では、最初に $aref の
デリファレンスをして、$aref から配列への参照にします。
そしてそのデリファレンスをして、最後に $AoA によって指されている配列の
i番目 の値を取り出します。
もし C の記法を望むのなら、先頭のデリファレンス演算子 $
より前に
$AoA[$i]
を評価することを強制するために、${$AoA[$i]}
と
記述しなければなりません
use strict
を使うべきなのかもしこのことが、価値あるものというより恐ろしいものに感じられても リラックスしてください。 Perlはありがちな落とし穴を避けるための幾つかの機能を備えています。 混乱を避けるための最善の方法は、すべてのプログラムを以下のような 行で始めることです:
#!/usr/bin/perl -w use strict;
この場合、あなたはすべての変数を my()
を使って宣言することが強制され、
間違った「シンボリックデリファレンス」が禁止されます。
したがって、以下のようにした場合:
my $aref = [ [ "fred", "barney", "pebbles", "bambam", "dino", ], [ "homer", "bart", "marge", "maggie", ], [ "george", "jane", "elroy", "judy", ], ];
print $aref[2][2];
宣言されていない変数 @aref
に間違ってアクセスしたために、
コンパイラは コンパイル時 にフラグをエラーに設定し、次のように
書くということあなたに思い出させるでしょう。
print $aref->[2][2]
5.002 より前の、標準の Perl デバッガは複雑なデータ構造をきちんと
きれいに出力することができませんでした。
5.002 以降のデバッガでは複雑なデータ構造をダンプするための
x
コマンドやコマンドライン編集などの幾つかの新たな機能を
取り込んでいます。
たとえば、先の例にあった $AoA に対する代入をデバッガに渡したとき、
その出力はこうなります。
DB<1> x $AoA $AoA = 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 'elroy' 3 'judy'
ここにあるちょっとしたコメント(将来は独立したマニュアルページと なるでしょう)は、様々な形式のデータ構造へのアクセスを 例示するサンプルです。
@AoA = ( [ "fred", "barney" ], [ "george", "jane", "elroy" ], [ "homer", "marge", "bart" ], );
# reading from file while ( <> ) { push @AoA, [ split ]; }
# calling a function for $i ( 1 .. 10 ) { $AoA[$i] = [ somefunc($i) ]; }
# using temp vars for $i ( 1 .. 10 ) { @tmp = somefunc($i); $AoA[$i] = [ @tmp ]; }
# add to an existing row push @{ $AoA[0] }, "wilma", "betty";
# one element $AoA[0][0] = "Fred";
# another element $AoA[1][1] =~ s/(\w)/\u$1/;
# print the whole thing with refs for $aref ( @AoA ) { print "\t [ @$aref ],\n"; }
# print the whole thing with indices for $i ( 0 .. $#AoA ) { print "\t [ @{$AoA[$i]} ],\n"; }
# print the whole thing one at a time for $i ( 0 .. $#AoA ) { for $j ( 0 .. $#{ $AoA[$i] } ) { print "elt $i $j is $AoA[$i][$j]\n"; } }
%HoA = ( flintstones => [ "fred", "barney" ], jetsons => [ "george", "jane", "elroy" ], simpsons => [ "homer", "marge", "bart" ], );
# reading from file # flintstones: fred barney wilma dino while ( <> ) { next unless s/^(.*?):\s*//; $HoA{$1} = [ split ]; }
# reading from file; more temps # flintstones: fred barney wilma dino while ( $line = <> ) { ($who, $rest) = split /:\s*/, $line, 2; @fields = split ' ', $rest; $HoA{$who} = [ @fields ]; }
# calling a function that returns a list for $group ( "simpsons", "jetsons", "flintstones" ) { $HoA{$group} = [ get_family($group) ]; }
# likewise, but using temps for $group ( "simpsons", "jetsons", "flintstones" ) { @members = get_family($group); $HoA{$group} = [ @members ]; }
# append new members to an existing family push @{ $HoA{"flintstones"} }, "wilma", "betty";
# one element $HoA{flintstones}[0] = "Fred";
# another element $HoA{simpsons}[1] =~ s/(\w)/\u$1/;
# print the whole thing foreach $family ( keys %HoA ) { print "$family: @{ $HoA{$family} }\n" }
# print the whole thing with indices foreach $family ( keys %HoA ) { print "family: "; foreach $i ( 0 .. $#{ $HoA{$family} } ) { print " $i = $HoA{$family}[$i]"; } print "\n"; }
# print the whole thing sorted by number of members foreach $family ( sort { @{$HoA{$b}} <=> @{$HoA{$a}} } keys %HoA ) { print "$family: @{ $HoA{$family} }\n" }
# print the whole thing sorted by number of members and name foreach $family ( sort { @{$HoA{$b}} <=> @{$HoA{$a}} || $a cmp $b } keys %HoA ) { print "$family: ", join(", ", sort @{ $HoA{$family} }), "\n"; }
@AoH = ( { Lead => "fred", Friend => "barney", }, { Lead => "george", Wife => "jane", Son => "elroy", }, { Lead => "homer", Wife => "marge", Son => "bart", } );
# reading from file # format: LEAD=fred FRIEND=barney while ( <> ) { $rec = {}; for $field ( split ) { ($key, $value) = split /=/, $field; $rec->{$key} = $value; } push @AoH, $rec; }
# reading from file # format: LEAD=fred FRIEND=barney # no temp while ( <> ) { push @AoH, { split /[\s+=]/ }; }
# calling a function that returns a key/value pair list, like # "lead","fred","daughter","pebbles" while ( %fields = getnextpairset() ) { push @AoH, { %fields }; }
# likewise, but using no temp vars while (<>) { push @AoH, { parsepairs($_) }; }
# add key/value to an element $AoH[0]{pet} = "dino"; $AoH[2]{pet} = "santa's little helper";
# one element $AoH[0]{lead} = "fred";
# another element $AoH[1]{lead} =~ s/(\w)/\u$1/;
# print the whole thing with refs for $href ( @AoH ) { print "{ "; for $role ( keys %$href ) { print "$role=$href->{$role} "; } print "}\n"; }
# print the whole thing with indices for $i ( 0 .. $#AoH ) { print "$i is { "; for $role ( keys %{ $AoH[$i] } ) { print "$role=$AoH[$i]{$role} "; } print "}\n"; }
# print the whole thing one at a time for $i ( 0 .. $#AoH ) { for $role ( keys %{ $AoH[$i] } ) { print "elt $i $role is $AoH[$i]{$role}\n"; } }
%HoH = ( flintstones => { lead => "fred", pal => "barney", }, jetsons => { lead => "george", wife => "jane", "his boy" => "elroy", }, simpsons => { lead => "homer", wife => "marge", kid => "bart", }, );
# reading from file # flintstones: lead=fred pal=barney wife=wilma pet=dino while ( <> ) { next unless s/^(.*?):\s*//; $who = $1; for $field ( split ) { ($key, $value) = split /=/, $field; $HoH{$who}{$key} = $value; }
# reading from file; more temps while ( <> ) { next unless s/^(.*?):\s*//; $who = $1; $rec = {}; $HoH{$who} = $rec; for $field ( split ) { ($key, $value) = split /=/, $field; $rec->{$key} = $value; } }
# calling a function that returns a key,value hash for $group ( "simpsons", "jetsons", "flintstones" ) { $HoH{$group} = { get_family($group) }; }
# likewise, but using temps for $group ( "simpsons", "jetsons", "flintstones" ) { %members = get_family($group); $HoH{$group} = { %members }; }
# append new members to an existing family %new_folks = ( wife => "wilma", pet => "dino", );
for $what (keys %new_folks) { $HoH{flintstones}{$what} = $new_folks{$what}; }
# one element $HoH{flintstones}{wife} = "wilma";
# another element $HoH{simpsons}{lead} =~ s/(\w)/\u$1/;
# print the whole thing foreach $family ( keys %HoH ) { print "$family: { "; for $role ( keys %{ $HoH{$family} } ) { print "$role=$HoH{$family}{$role} "; } print "}\n"; }
# print the whole thing somewhat sorted foreach $family ( sort keys %HoH ) { print "$family: { "; for $role ( sort keys %{ $HoH{$family} } ) { print "$role=$HoH{$family}{$role} "; } print "}\n"; }
# print the whole thing sorted by number of members foreach $family ( sort { keys %{$HoH{$b}} <=> keys %{$HoH{$a}} } keys %HoH ) { print "$family: { "; for $role ( sort keys %{ $HoH{$family} } ) { print "$role=$HoH{$family}{$role} "; } print "}\n"; }
# establish a sort order (rank) for each role $i = 0; for ( qw(lead wife son daughter pal pet) ) { $rank{$_} = ++$i }
# now print the whole thing sorted by number of members foreach $family ( sort { keys %{ $HoH{$b} } <=> keys %{ $HoH{$a} } } keys %HoH ) { print "$family: { "; # and print these according to rank order for $role ( sort { $rank{$a} <=> $rank{$b} } keys %{ $HoH{$family} } ) { print "$role=$HoH{$family}{$role} "; } print "}\n"; }
以下に示すのは、多くの異なった種類のフィールドを持ったレコードの 作成と使用のサンプルです。
$rec = { TEXT => $string, SEQUENCE => [ @old_values ], LOOKUP => { %some_table }, THATCODE => \&some_function, THISCODE => sub { $_[0] ** $_[1] }, HANDLE => \*STDOUT, };
print $rec->{TEXT};
print $rec->{SEQUENCE}[0]; $last = pop @ { $rec->{SEQUENCE} };
print $rec->{LOOKUP}{"key"}; ($first_k, $first_v) = each %{ $rec->{LOOKUP} };
$answer = $rec->{THATCODE}->($arg); $answer = $rec->{THISCODE}->($arg1, $arg2);
# careful of extra block braces on fh ref print { $rec->{HANDLE} } "a string\n";
use FileHandle; $rec->{HANDLE}->autoflush(1); $rec->{HANDLE}->print(" a string\n");
%TV = ( flintstones => { series => "flintstones", nights => [ qw(monday thursday friday) ], members => [ { name => "fred", role => "lead", age => 36, }, { name => "wilma", role => "wife", age => 31, }, { name => "pebbles", role => "kid", age => 4, }, ], },
jetsons => { series => "jetsons", nights => [ qw(wednesday saturday) ], members => [ { name => "george", role => "lead", age => 41, }, { name => "jane", role => "wife", age => 39, }, { name => "elroy", role => "kid", age => 9, }, ], },
simpsons => { series => "simpsons", nights => [ qw(monday) ], members => [ { name => "homer", role => "lead", age => 34, }, { name => "marge", role => "wife", age => 37, }, { name => "bart", role => "kid", age => 11, }, ], }, );
# ファイルからの読み込み # これはファイルそれ自身が、先の例で示したように raw data # format になっているのでとても簡単です。perlは、データのよ # うに宣言されているのであれば、複雑なデータ構造を喜んで解 # 析します。ですからとても簡単に済むことがあるのです。
# フィールド毎に構築する $rec = {}; $rec->{series} = "flintstones"; $rec->{nights} = [ find_days() ];
@members = (); # このファイルは フィールド=値 という構文となっていると仮定 while (<>) { %fields = split /[\s=]+/; push @members, { %fields }; } $rec->{members} = [ @members ];
# now remember the whole thing $TV{ $rec->{series} } = $rec;
########################################################### # ここで、あなたは同じデータへの戻るポインターのような興味 # 深い追加フィールドを作成したいと思うかもしれません。これ # により、ある一つを変更するとすべてが変更されます。この例 # で言えば、{kid}というフィールドは子供のレコードの配列で # すが、これをレコードの重複をなくして更新の問題もなくすこ # とができるようなものです。 ########################################################### foreach $family (keys %TV) { $rec = $TV{$family}; # temp pointer @kids = (); for $person ( @{ $rec->{members} } ) { if ($person->{role} =~ /kid|son|daughter/) { push @kids, $person; } } # REMEMBER: $rec and $TV{$family} point to same data!! $rec->{kids} = [ @kids ]; }
# あなたは配列をコピーしました。でも、配列それ自身はコピ # ーされていないオブジェクトへのポインターから構成されていま # す。これはあなたがbartを作ったら古いものを通してで取得され # るということです。
$TV{simpsons}{kids}[0]{age}++;
# そしてこれは変更されたものです print $TV{simpsons}{members}[2]{age};
# なぜなら $TV{simpsons}{kids}[0] と $TV{simpsons}{members}[2] # は両方とも同じ無名のハッシュテーブルを指しているからです。
# 全体を表示する foreach $family ( keys %TV ) { print "the $family"; print " is on during @{ $TV{$family}{nights} }\n"; print "its members are:\n"; for $who ( @{ $TV{$family}{members} } ) { print " $who->{name} ($who->{role}), age $who->{age}\n"; } print "it turns out that $TV{$family}{lead} has "; print scalar ( @{ $TV{$family}{kids} } ), " kids named "; print join (", ", map { $_->{name} } @{ $TV{$family}{kids} } ); print "\n"; }
(ハッシュのハッシュのような)複数レベルのデータ構造を dbm ファイルに tie することは簡単にはできません。 問題は、GDBM と Berkeley DB はサイズに制限があり、それを超えることが できないということで、また、ディスク上にあるものを参照する方法についても 問題があります。 部分的にこれを解決しようとしている実験的なモジュールの一つに、 MLDBM というものがあります。 ソースコードは perlmodlib にあるように、 あなたのお近くの CPAN サイトを確かめてください。
perlref(1)
, perllol(1)
, perldata(1)
, perlobj(1)
Tom Christiansen <tchrist@perl.com>
Last update: Wed Oct 23 04:57:50 MET DST 1996