#navi_header|Perl| 配列とハッシュの基本的な作成・アクセス方法を確認する。 #outline ----- * 参考リンク - Perl&CGI最強講座 -- http://www.rfs.jp/sb/perl/index.html -- 正直なところ、本ドキュメントが無くとも、こちらさえあれば基本的な部分は全て押さえられる。 -- 配列周り → http://www.rfs.jp/sb/perl/02/04.html -- ハッシュ周り → http://www.rfs.jp/sb/perl/02/05.html - Effective Perlにおいて注目すべき項番を以下に示す。 -- 2項「要素を使いたいとき、スライスを使わないようにする」 -- 3項「空リストがほしいときに、undefを代入しない」 -- 12項「foreach, map, grepを、うまく使い分けよう」 -- 31項「リファレンスで、リストのリストを作る」 -- 32項「名無し配列を、リストリテラルと混同しないこと」 -- 33項「名無しハッシュによってC言語スタイルの構造体を構築する」 - perldocにおいて注目すべきドキュメントを以下に示す。 -- perldata : http://perldoc.perl.org/perldata.html --- Perlのデータ型に関する基本・基盤ドキュメント -- perlref : http://perldoc.perl.org/perlref.html --- Perlのリファレンスに関する基本・基盤ドキュメント -- perlfaq4 : http://perldoc.perl.org/perlfaq4.html --- Perlのデータ構造に関するFAQ -- Data:Arrays : http://perldoc.perl.org/perlfaq4.html#Data%3a-Arrays --- 配列に関するFAQ -- Data:Hashes : http://perldoc.perl.org/perlfaq4.html#Data%3a-Hashes-(Associative-Arrays) --- ハッシュに関するFAQ -- perllol : http://perldoc.perl.org/perllol.html --- 配列の配列を作るには。 -- perldsc : http://perldoc.perl.org/perldsc.html --- 配列とハッシュが混ざった複雑なデータ構造を作るには。 -- defined : http://perldoc.perl.org/functions/defined.html --- definedは、5.004以降はスカラー変数でしか使用できなくなっている点に注意。 * 配列 ** コードピース01 : 配列の実体コンストラクタ"(", ")" - コードピース use strict; use warnings; use Data::Dumper; my @ary1 = (1, 2, 3, (4, 5, 6), (7, (8, 9), ), ); print @ary1, "\n"; print Dumper(@ary1), "\n"; print $ary1[0], "\n"; print $ary1[@ary1-1], "\n"; - 出力 123456789 # ← print @ary1 $VAR1 = 1; $VAR2 = 2; $VAR3 = 3; $VAR4 = 4; $VAR5 = 5; $VAR6 = 6; $VAR7 = 7; $VAR8 = 8; $VAR9 = 9; #(ここまでがDumper(@ary1)の出力) 1 # ← print $ary1[0] 9 # ← print $ary1[@ary1-1] 配列作成時の"(...)"は、配列の実体(("Effective Perl"上では"リストリテラル"と表記されているが・・・上手い日本語が分かりません。))を作成するコンストラクタ。 従って、変数の"ary1"は、常にリストとして"@"でアクセスする必要がある。 print Dumper($ary1), "\n"; は、 Global symbol "$ary1" requires explicit package name at hoge.pl line 7. Execution of hoge.pl aborted due to compilation errors. となる。すなわちスカラーとしての"ary1"をシンボルテーブルから探そうとしてしまう。 次の記法にも注意。 print $ary1[@ary1-1], "\n"; 要するに配列の最大要素数-1により、配列の末尾にアクセスしている。ここで@ary1 を -1 することで、 + 無理矢理スカラーコンテキストで評価 + → @ary1はスカラーコンテキストでは配列の要素数を表す + → -1 することで0始まりの配列の末尾INDEXになる。 ということで、配列の末尾要素にアクセスできている。 ** コードピース02 : 配列のリファレンスコンストラクタ"[", "]" - コードピース my $ary2 = [ 1, 2, 3, (4, 5, 6), [7, 8, 9] ]; print $ary2, "\n"; print Dumper($ary2), "\n"; print $ary2->[0], "\n"; print $ary2->[6], "\n"; print $ary2->[6][0], "\n"; my $ary_buf = $ary2->[6]; print $ary_buf->[0], "\n"; print $ary_buf->[@$ary_buf - 1], "\n"; - 出力 ARRAY(0x18c35ac) # ← print $ary2 $VAR1 = [ 1, 2, 3, 4, 5, 6, [ 7, 8, 9 ] ]; 1 # ← print $ary2->[0] ARRAY(0x274e6c) # ← print $ary2->[6] 7 # ← print $ary2->[6][0] 7 # ← print $ary_buf->[0] 9 # ← print $ary_buf->[@$ary_buf - 1] "[", "]"は''名無し配列コンストラクタ''(Effective Perl)であり、その返すものはリファレンスである。従って、リファレンス全般で要素アクセスに使われる"->"を用いて各インデックスの値にアクセスできる。 前掲のコードピースでは名無し配列を多段に組んでいる。この場合、入れ子になった配列のリファレンスを取り出し、$ary_bufとしてアクセスできている。 $ary2->[6][0] も、 $ary_buf->[0] も、両方同じ"7"を取得できている。 ** コードピース03 : ループアクセス(for, foreach) - コードピース my @ary3 = (1, 2, 3); print join(" ", @ary3), "\n"; # ... (a) for(my $i = 0; $i < @ary3; $i++) { $ary3[$i]++; } print join(" ", @ary3), "\n"; # ... (b) foreach my $e (@ary3) { $e++; } print join(" ", @ary3), "\n"; # ... (c) foreach (@ary3) { $_++; } print join(" ", @ary3), "\n"; # ... (d) for my $i (@ary3) { $i++; } print join(" ", @ary3), "\n"; # ... (e) for (@ary3) { $_++; } print join(" ", @ary3), "\n"; # ... (f) - 出力 1 2 3 # (a) 2 3 4 # (b) 3 4 5 # (c) 4 5 6 # (d) 5 6 7 # (e) 6 7 8 # (f) (a)は基本中の基本であるが、よりエレガントに見える書き方は (d) や (f) であろう。いずれにせよ、どの書き方も正しく配列の要素を+1できている。 なお、前掲のコードピースは配列の実体を対象にしてのコードだった。下記に、同じ出力結果になる、名無し配列リファレンスを使用した場合のコードを示す。 my $ary3b = [1, 2, 3]; print join(" ", @$ary3b), "\n"; for(my $i = 0; $i < @$ary3b; $i++) { $ary3b->[$i]++; } print join(" ", @$ary3b), "\n"; foreach my $e (@$ary3b) { $e++; } print join(" ", @$ary3b), "\n"; foreach (@$ary3b) { $_++; } print join(" ", @$ary3b), "\n"; for my $i (@$ary3b) { $i++; } print join(" ", @$ary3b), "\n"; for (@$ary3b) { $_++; } print join(" ", @$ary3b), "\n"; ** コードピース04 : 間違いやすい配列のスライスについて($ary[x]と@ary[x]の違い) print するだけのミニマムテストでは分かりづらいが、 print $ary[0] と print @ary[0] は異なる。 前者は"$"が付いている為スカラーコンテキストで評価され、配列の要素としてアクセスされる。後者は"@"が付いている為、配列、すなわち''スライス''としてアクセスされる。 - コードピース my @ary4 = ("abc", "def", "ghi", "jkl", "mno"); print $ary4[0], "\n"; print @ary4[0], "\n"; # ← (a) print $ary4[1, 2], "\n"; # ← (b) print join(" ", @ary4[1, 2]), "\n"; print join(" ", @ary4[1 ... 4]), "\n"; print join(" ", @ary4[1 ... 5]), "\n"; # ← (c) - 出力 Scalar value @ary4[0] better written as $ary4[0] at ... (a) Multidimensional syntax $ary4[1, 2] not supported at ... (b) abc # ← print $ary4[0] abc # ← print @ary4[0] ... (a) ghi # ← print $ary4[1, 2] ... (b) def ghi # ← print join(" ", @ary4[1, 2]) def ghi jkl mno # ← print join(" ", @ary4[1 ... 4]) Use of uninitialized value in join or string at ... (c) def ghi jkl mno (a), (b) のようにスカラーが予想される値を"@"としてアクセス(a)したり、スライスが予想される値を"$"としてアクセス(b)すると、警告が発せられる。 またスライスでも、要素INDEXを越えてアクセスしようとすると(c)のように警告が発せられる。 書いている本人自身も実は何回も間違えているように、Perl初学者にとってはとにかく間違いやすい。 '' Perlにおいてはコンテキストが重要であり、変数がどのコンテキストで評価されるのかは、その冒頭に付く文字($, @, %, *)で判断される '' (それに加えて、前後のコードも関係する)。これについて、人間側で感情的な割り切りを行えれば、大分Perlの学習と理解も進むのかも知れない。 ** コードピース05 : ( 先頭 | 末尾 ) 要素の追加削除 - コードピース my @ary5 = ("abc", "def", "ghi", "jkl", "mno"); #--------------------- (a) : 末尾要素を削除 my $buf = pop @ary5; print join(" ", @ary5), "\n"; print $buf, "\n"; #--------------------- (b) : 末尾要素に追加 $buf = push(@ary5, 7); print join(" ", @ary5), "\n"; print $buf, "\n"; #--------------------- (c) : 先頭要素を削除 $buf = shift @ary5; print join(" ", @ary5), "\n"; print $buf, "\n"; #--------------------- (d) : 先頭要素に追加 $buf = unshift(@ary5, "xyz"); print join(" ", @ary5), "\n"; print $buf, "\n"; #--------------------- (e) : スカラーコンテキストで評価 print scalar(@ary5), "\n"; - 出力 #--------------------- (a) : 末尾要素を削除 abc def ghi jkl mno # 削除された要素の値 #--------------------- (b) : 末尾要素に追加 abc def ghi jkl 7 5 # 追加後の配列の要素数 #--------------------- (c) : 先頭要素を削除 def ghi jkl 7 abc # 削除された要素の値 #--------------------- (d) : 先頭要素に追加 xyz def ghi jkl 7 5 # 追加後の配列の要素数 #--------------------- (e) : スカラーコンテキストで評価 5 ** コードピース06 : 配列へのundefの代入と、空リストの判別 undef代入の幾つかのパターンと、空のリストで有ることをどうやって判別を示す。 - コードピース #--------------------- (a) my @ary6 = (); if(@ary6) { print Dumper @ary6, "\n"; } else { print "ary6 is not defined.(a)\n"; } #--------------------- (b) @ary6 = undef; if(@ary6) { print Dumper @ary6, "\n"; } else { print "ary6 is not defined.(b)\n"; } #--------------------- (c) @ary6 = 1 .. 5; @ary6[2, 4] = undef; print Dumper @ary6, "\n"; #--------------------- (d) undef @ary6; if(@ary6) { print Dumper @ary6, "\n"; } else { print "ary6 is not defined.(d)\n"; } - 出力 #--------------------- (a) ary6 is not defined.(a) #--------------------- (b) $VAR1 = undef; $VAR2 = ' '; #--------------------- (c) $VAR1 = 1; $VAR2 = 2; $VAR3 = undef; $VAR4 = 4; $VAR5 = undef; $VAR6 = ' '; #--------------------- (d) ary6 is not defined.(d) - (a) : 空のリスト"()"で初期化した直後の配列は、if()のスカラーコンテキストでは要素数が"0"、すなわち偽として評価される。 - (b) : その後、undefを代入してみると、興味深いことに要素数が2で末尾の要素が空になる。 - (c) : また、配列のスライスを利用してundefを代入すると、該当要素のみundefになっている。 - (d) : undef動詞((Effective Perlの訳書の表現))を使用すると、if()のスカラーコンテキストで偽として評価される。 注意すべきは、 '' defined は Perl5.004以降、スカラー変数を対象とするようになり、配列やハッシュは非推奨になっている '' 点である。 - http://perldoc.perl.org/functions/defined.html - http://perldoc.perl.org/perlfaq4.html#Why-does-defined%28%29-return-true-on-empty-arrays-and-hashes%3F このため、下記のコードはwarningを生成する。 if(defined(@ary6)) { ... → defined(@array) is deprecated at ... (Maybe you should just omit the defined()?) では、厳密な意味で "@ary6" がundefなのか、空リストであるのか、をどうやって見分けるのか?については残念ながら現段階では調べ切れていない。%::などを用いて直接シンボルテーブルにアクセス・・・しても、lexical変数であればそれも無意味である。 とりあえず、 my @ary = (); # or undef @ary; は偽になるが、 my @ary = undef; は偽にならないという点に十分注意する。 * ハッシュ ハッシュについてはその基本操作の殆どが、冒頭でリンクとして挙げたPerl講座で網羅されている。ここでは実体のコンストラクタである"(", ")"と、リファレンスコンストラクタ"{", "}"の差異をメインに、ざっと流すに留める。((というか正直、smartのPerl講座がわかりやすすぎて、自分で実験・検証する気力が無くなりました。やっぱり本職のドキュメントは素晴らしいです。)) ** コードピース01 : ハッシュの実体コンストラクタ"(", ")" とループアクセス(while, each) - コードピース my %h1 = ( abc => 123, "def" => 456, 'ghi' => 789 ); #--------------------- (a) print Dumper(%h1), "\n"; #--------------------- (b) print $h1{'abc'}, "\n"; #--------------------- (c) while(my ($key, $val) = each(%h1)) { print "[$key] = $val\n"; } - 出力 #--------------------- (a) $VAR1 = 'def'; $VAR2 = 456; $VAR3 = 'abc'; $VAR4 = 123; $VAR5 = 'ghi'; $VAR6 = 789; #--------------------- (b) 123 #--------------------- (c) [def] = 456 [abc] = 123 [ghi] = 789 - (a) を見てみると、Dumperの出力上は通常の配列と何ら変わりないようにも見える。出典は失念したが、どこかのドキュメントにハッシュは内部的には配列の形で保持されていると書かれていたのをおぼえている。その記憶をこの出力は補強している。((おそらくEffective Perlの33項「名無しハッシュによってC言語スタイルの構造体を構築する」の"*5"の箇所と思われる。リファレンスを用いたアクセス($h->{hoge})は配列アクセスに最適化される、という箇所。)) - (b) では、普通にキーを指定してスカラーコンテキストでアクセスする方法を示している。代入式の左辺(LVALUE)としても使える。 - (c) では、whileとeachを組み合わせて、キーと値のペアをループでアクセスする方法を示している。 -- 但し、出力がそうであるように、while-eachの組み合わせではキー値のソートが保証されない。単純な"for $key (keys(%hash)) "でもそのようである。 -- キー値をソートして用いる場合は下記コードのようになる。詳しくはsmartのPerl講座を参照。 for my $key (sort(keys(%h1))) { print "[$key] = ", $h1{$key}, "\n"; } → [abc] = 123 [def] = 456 [ghi] = 789 ** コードピース02 : ハッシュのリファレンスコンストラクタ"{", "}" "(", ")"を用いるとハッシュの実体を作成できた。続いて、 '' 名無しハッシュ '' のコンストラクタである"{", "}"の使用例を示す。 - コードピース my $h2 = { abc => 123, "def" => (4, 5, 6), "ghi" => [7, 8, 9 ] }; #--------------------- (a) print Dumper($h2), "\n"; #--------------------- (b) print $h2->{'abc'}, "\n"; print $h2->{'def'}, "\n"; print $h2->{'ghi'}, "\n"; print $h2->{'ghi'}->[0], "\n"; print $h2->{'ghi'}->[1], "\n"; print $h2->{'5'}, "\n"; #--------------------- (c) while(my ($key, $val) = each(%$h2)) { print "[$key] = $val\n"; } - 出力 #--------------------- (a) $VAR1 = { 'def' => 4, 'abc' => 123, 'ghi' => [ 7, 8, 9 ], '5' => 6 # ← !!注意!! }; #--------------------- (b) 123 # $h2->{'abc'} 4 # $h2->{'def'} ARRAY(0x275028) # $h2->{'ghi'} 7 # $h2->{'ghi'}->[0] 8 # $h2->{'ghi'}->[1] 6 # $h2->{'5'} #--------------------- (c) [def] = 4 [abc] = 123 [ghi] = ARRAY(0x275028) [5] = 6 # ← !!注意!! - (a) : コンストラクタでリストを用いたり、名無し配列リファレンスを用いた例を示す。 '' リスト部分(4,5,6)が分割されてしまっている点に注意!! '' - (b) : ハッシュリファレンスを用いた一般的なアクセス例を示す。 - (c) : ハッシュリファレンスのループ処理の例を示す。eachでは、明示的に'%'を頭に付けてハッシュコンテキストで評価させている点に注意。 - この、(4, 5, 6)が展開されている箇所は、次のコードピースを見てみると実に興味深い。((けど、実際の内部処理の仕掛けはまだ未調査です・・・。)) ** コードピース03 : ハッシュのコンストラクタの亜種(配列風味) 実際のところ、"=>"を使用せずともハッシュは作れるようである。要は変数が"%"としてハッシュコンテキストで実体が代入されるか、"{", "}" で囲まれたリストとしてハッシュのリファレンスで代入されるか、どちらでもハッシュとして扱えるようである。 - コードピース my %h3 = ( "abc", 123, "def", 4, 5, 6, "ghi", [7, 8, 9] ); my $h4 = { "abc", 123, "def", 4, 5, 6, "ghi", [7, 8, 9] }; #--------------------- (a) print Dumper(%h3), "\n"; print Dumper(%$h4), "\n"; #--------------------- (b) print Dumper(\%h3), "\n"; print Dumper($h4), "\n"; - 出力 #--------------------- (a) # %h3 $VAR1 = 'def'; $VAR2 = 4; $VAR3 = 'abc'; $VAR4 = 123; $VAR5 = 'ghi'; $VAR6 = [ 7, 8, 9 ]; $VAR7 = '5'; $VAR8 = 6; # %$h4(ハッシュコンテキストで評価) $VAR1 = 'def'; $VAR2 = 4; $VAR3 = 'abc'; $VAR4 = 123; $VAR5 = 'ghi'; $VAR6 = [ 7, 8, 9 ]; $VAR7 = '5'; $VAR8 = 6; #--------------------- (b) # \%h3(リファレンス経由で評価) $VAR1 = { 'def' => 4, 'abc' => 123, 'ghi' => [ 7, 8, 9 ], '5' => 6 }; # $h4 $VAR1 = { 'def' => 4, 'abc' => 123, 'ghi' => [ 7, 8, 9 ], '5' => 6 }; 見ての通り、コンテキストさえ合わせればどちらも同じ形式で扱えるようである。 ** コードピース04 : ハッシュのスライスとundef - コードピース my %h5 = ("abc" => 123, "def" => 456, "ghi" => 789); my $h6 = {"abc" => 123, "def" => 456, "ghi" => 789}; #--------------------- (a) print join(" ", @h5{"abc", "ghi"}), "\n"; print join(" ", @$h6{"def", "ghi"}), "\n"; #--------------------- (b) @h5{"ghi", "abc"} = @h5{"abc", "ghi"}; @$h6{"def", "abc"} = @$h6{"abc", "def"}; for my $key (sort(keys(%h5))) { print "[$key] = ", $h5{$key}, "\n"; } for my $key (sort(keys(%$h6))) { print "[$key] = ", $h6->{$key}, "\n"; } #--------------------- (c) @h5{"abc", "ghi"} = (undef, undef); @$h6{"abc", "def"} = (undef, undef); print Dumper(%h5), "\n"; print Dumper(%$h6), "\n"; #--------------------- (d) my %h7 = (); if(keys(%h7)) { print Dumper(%h7); } else { print "%h7 is empty.\n"; } - 出力 #--------------------- (a) 123 789 456 789 #--------------------- (b) # %h5 [abc] = 789 [def] = 456 [ghi] = 123 # $h6 [abc] = 456 [def] = 123 [ghi] = 789 #--------------------- (c) # %h5 $VAR1 = 'def'; $VAR2 = 456; $VAR3 = 'abc'; $VAR4 = undef; $VAR5 = 'ghi'; $VAR6 = undef; # $h6 $VAR1 = 'def'; $VAR2 = undef; $VAR3 = 'abc'; $VAR4 = undef; $VAR5 = 'ghi'; $VAR6 = 789; #--------------------- (d) %h7 is empty. - (a) : 任意のキーをつなげて、"@"によりスライスとして評価され、キーの値のリストが取得できている。 - (b) : スライスを利用して、任意のキー間で値のswapを行える。 - (c) : スライスに対しundefのリストを代入することで、undefを設定できる。(キー自体の削除は、deleteを使用する。) 例:delete @hash_slice{'keyA', 'keyC'}; - (d) : キーが空の場合は、"keys"を使用して、空のハッシュを判別できる。 #navi_footer|Perl|