• ベストアンサー

eachとイテレーター

each関数について質問させていただきます。perl5.8.0です。 eachに渡す引数を、ハッシュ“変数”ではなくハッシュ記法を直接指定する方法はないでしょうか。 具体的には、 %hash = (a=>1,b=>2); while (($k,$v)=each %hash) { ~ ではなく、 while (($k,$v)=each (a=>1,b=>2)){ ~ というように、eachに直接キーと要素を渡したいのです。 以下のようにいろいろ試したのですが、うまくいきません。 【1】 perl -e 'while (($k,$v)=each (a=>1,b=>2)){print "$k:$v\n";}' Type of arg 1 to each must be hash (not list) at -e line 1, near "2)" Execution of -e aborted due to compilation errors. 【2】 perl -e 'while (($k,$v)=each %{a=>1,b=>2}){print "$k:$v\n";}' syntax error at -e line 1, near "%{" Execution of -e aborted due to compilation errors. 【3】 perl -e 'while (($k,$v)=each %{(a=>1,b=>2)}){print "$k:$v\n";}' (何も出力されない) 【4】 perl -e 'while (($k,$v)=each %{{a=>1,b=>2}}){print "$k:$v\n";}' a:1 a:1 a:1 a:1 : (無限ループ) 予想では【3】の書き方が正しいような気がしましたが出力されず、そして【4】は少なくとも参照はできているようなのに、イテレーターが正しく動作していないような感じです。 (そもそもイテレーターというのは変数じゃなく無名ハッシュのようなものでも有効なのでしょうか?) また、試しにkeysで同じことをしてみると、 【5】 perl -e 'foreach (keys (a=>1,b=>2)){print "$_\n";}' Type of arg 1 to keys must be hash (not list) at -e line 1, near "2)" Execution of -e aborted due to compilation errors. 【6】 perl -e 'foreach (keys %{a=>1,b=>2}){print "$_\n";}' syntax error at -e line 1, near "%{" Execution of -e aborted due to compilation errors. 【7】 perl -e 'foreach (keys %{(a=>1,b=>2)}){print "$_\n";}' (何も出力されない) 【8】 perl -e 'foreach (keys %{{a=>1,b=>2}}){print "$_\n";}' a b keysでは【8】が期待どおりの動作をします。 どうぞよろしくお願いいたします。

質問者が選んだベストアンサー

  • ベストアンサー
  • pipipi523
  • ベストアンサー率40% (148/365)
回答No.1

eachに無名変数を使った場合リファレンスが毎回変わるのでダメです 具体的にはこんな感じです ($k,$v)=each %{$a={a=>1,b=>2}}; print "$k $v\n"; ($k,$v)=each %{$a}; print "$k $v\n"; 結果(OK) a:1 b:2 ($k,$v)=each %{$a={a=>1,b=>2}}; print "$k $v\n"; ($k,$v)=each %{$a={a=>1,b=>2}};#$aの内容が変わる print "$k $v\n"; 結果(NG:whileにすると永久ループ) a:1 a:1 これではダメなのでしょうか? perl -e "%hash=(a=>1,b=>2);while(($k,$v)=each %hash){print \"$k:$v\n\";}"

SV576
質問者

お礼

そもそも何故こんなことをしたいのかというと、単にコードの視認性だけなんです(^^; ある設定内容を保持している %conf というハッシュがあって、値はハッシュリファレンスで2階層のレベルがあります。 %conf = ( setA => {conf1 => 'valueA1', conf2 => 'valueA2', conf3 => 'valueA3'}, setB => {conf1 => 'valueB1', conf2 => 'valueB2', conf3 => 'valueB3'}, setC => {conf1 => 'valueC1', conf2 => 'valueC2', conf3 => 'valueC3'}, ); というように初期化されているとします。 で、処理の過程で設定を追加する(つまり再初期化ではなく元の内容を保持させたまま追加する)、しかもある程度まとまった数、というコードを書きたいのですが、普通に書けば、conf4とconf5を追加するなら ${$conf{'setA'}}{'conf4'} = 'valueA4'; ${$conf{'setA'}}{'conf5'} = 'valueA5'; ${$conf{'setB'}}{'conf4'} = 'valueB4'; ${$conf{'setB'}}{'conf5'} = 'valueB5'; ${$conf{'setC'}}{'conf4'} = 'valueC4'; ${$conf{'setC'}}{'conf5'} = 'valueC5'; という感じでしょうか。 簡潔化するなら (...) = (...) という書き方もできるかもしれません。 これを、 while (my ($name, $value) = each { setA => {conf4 => 'valueA4', conf5 => 'valueA5'}, setB => {conf4 => 'valueB4', conf5 => 'valueB5'}, setC => {conf4 => 'valueC4', conf5 => 'valueC5'}, }) { $conf{$name} = {%{$conf{$name}}, %$value}; } と書けたら視認性も上がってメンテやいろいろな面で良いだろうと考えたのです。 一時的なハッシュを用意して %tmp = ( setA => {conf4 => 'valueA4', conf5 => 'valueA5'}, setB => {conf4 => 'valueB4', conf5 => 'valueB5'}, setC => {conf4 => 'valueC4', conf5 => 'valueC5'}, ); while (my ($name, $value) = each %tmp) { $conf{$name} = {%{$conf{$name}}, %$value}; } と書けばいいんですが、可能なら極力シンプルにしたかったもので。

SV576
質問者

補足

(長すぎると怒られたため補足欄に分けさせていただきます) ご回答ありがとうございます。 すみません、なんとなく解ったような解らないような…苦笑 リファレンスが変わるというのは、参照される側つまり被リファレンスのデータが、eachの実行のたびに作り直されてしまう=イテレーターも初期状態なので無限ループ、という理解でよろしいでしょうか。 で、keysは期待どおり動いたのは、参照するのは初めの1回だけで、取得した内容をメモリ上のどこかに保持している、ということなんですかね? 言い換えれば、eachを使うなら変数を用意しなきゃいけない、と理解すればいいでしょうか?

その他の回答 (4)

  • kumoz
  • ベストアンサー率64% (120/185)
回答No.5

少し中途半端なコードになってしまったようで、申し訳ありません。下記のように訂正します。 なお、ご指摘の「[setA =>....」件はどちらでも大丈夫です。 use strict; my %conf = ( setA => {conf1 => 'valueA1', conf2 => 'valueA2', conf3 => 'valueA3'}, setB => {conf1 => 'valueB1', conf2 => 'valueB2', conf3 => 'valueB3'}, setC => {conf1 => 'valueC1', conf2 => 'valueC2', conf3 => 'valueC3'}, ); foreach my $ref ( [setA => {conf4 => 'valueA4', conf5 => 'valueA5'}], [setB => {conf4 => 'valueB4', conf5 => 'valueB5'}], [setC => {conf4 => 'valueC4', conf5 => 'valueC5'}]) { foreach my $key (keys %{$ref->[1]}) { $conf{$ref->[0]}->{$key} = $ref->[1]->{$key}; } } foreach my $key_1 (keys %conf) { print "$key_1 => ["; foreach my $key_2 (keys %{$conf{$key_1}}) { print " $key_2 => ", $conf{$key_1}->{$key_2}; } print "]\n"; } 次の、ループをいれ子にした理由は、処理効率を考えてのものです。 質問者が書かれている次のコードは、ハッシュに新しい要素を追加する方法としてあまり 好ましいものではありません。 $conf{$name} = {%{$conf{$name}}, %$value}; 上のコードは、結果的にはそうなるものの、処理の実態は既存のハッシュに新しい要素を 追加しているのではありません。既存のハッシュを破棄して、1からハッシュを作り直し ているものです。効率が悪く、ハッシュのサイズが大きく追加の回数が多い場合には、 大幅に処理速度が低下します。次の簡単なコードを実行してみて下さい。 1) 文字通りの「追加」 use strict; my %hash = (); my $start = time(); $hash{$_} = $_ foreach (1 .. 10000); print time() - $start, "\n"; 2) ハッシュの再作成 use strict; my %hash = (); my $start = time(); %hash = (%hash, $_ => $_) foreach (1 .. 10000); print time() - $start, "\n";

SV576
質問者

お礼

とても解りやすいご説明、ありがとうございます。 なるほど、確かにご指摘のとおり今回は視認性などばかり気にしていて効率をあまり考えていなかったかもしれません(苦笑) よく、ハッシュのマージは %hash3 = (%hash1, %hash2); と色々な文献に書いてあったのでそのまま使ってしまいました。 サイズや繰り返しのことを考えればご提示いただいた方法が良いのですね。 しかも今回はマージと言ってもマージ前を残す必要がないのだから、わざわざ第三の領域を(一時的でも)作ってしまうのは無駄ですね。 大変勉強になりました、ありがとうございます。 今回の目的である追加設定値部分のコードの書き方のほうも、ほぼこれで良さそうですので、もう少し待たせていただいてから、閉めさせていただきたいと思います。

  • kumoz
  • ベストアンサー率64% (120/185)
回答No.4

ハッシュに別のハッシュを取り込む場合、ハッシュからハッシュへ直接 代入した方が、コードとしては簡単になると思います。 %conf = ( setA => {conf1 => 'valueA1', conf2 => 'valueA2', conf3 => 'valueA3'}, setB => {conf1 => 'valueB1', conf2 => 'valueB2', conf3 => 'valueB3'}, setC => {conf1 => 'valueC1', conf2 => 'valueC2', conf3 => 'valueC3'}, ); foreach my $ref ( [setA => {conf4 => 'valueA4', conf5 => 'valueA5'}], [setB => {conf4 => 'valueB4', conf5 => 'valueB5'}], [setC => {conf4 => 'valueC4', conf5 => 'valueC5'}]) { foreach my $key (keys %{$ref->[1]}) { $conf{$ref->[0]}->{$key} = $ref->[1]->{$key}; } }

SV576
質問者

お礼

ご回答ありがとうございます。 しかし動きませんでした・・・・ [setC => ~  ↓ ['setA', ~ の間違いではないでしょうか? あとすみません、ループの入れ子にする理由が、ちょっとよく解りませんでした・・(^^;

  • kabaokaba
  • ベストアンサー率51% (724/1416)
回答No.3

真っ向から対立するような,余計なコメントですが(^^;;; >と書けたら視認性も上がってメンテやいろいろな面で良いだろうと考えたのです。 私は逆だと思いますよ. さらに追加する必要があった場合どうなるのでしょうか? 名前をきちんと定めた一時的なハッシュを構築するか, サブルーチンにまとめる方が, メンテナンスおよび視認性はあがるのではと思います. そこで個人的な趣味を加味して(^^;; リファレンスベースでいじってみました. もっといじるなら・・・ もともとのハッシュ(リファレンス)$confそのものの構築や 追加部分もYAMLとかにしてしまって, しかも外部から取り込ませるて,外部に保存するとかでしょうか. use strict; use warnings; use Data::Dumper;##確認用にデータ構造を出力するため ### 形式が決まったハッシュへのリファレンスに追加 ### 引数は追加対象と ### 追加するハッシュリファレンス sub add_to_conf{ my $ref_base = shift; my %added_hash =%{ shift @_ }; while (my ($name, $value) = each %added_hash ) { $ref_base->{$name} = { %{ $ref_base->{$name} }, %{ $value } }; } return $ref_base; } ###追加対象のハッシュリファレンス my $conf = { setA => {conf1 => 'valueA1', conf2 => 'valueA2', conf3 => 'valueA3'}, setB => {conf1 => 'valueB1', conf2 => 'valueB2', conf3 => 'valueB3'}, setC => {conf1 => 'valueC1', conf2 => 'valueC2', conf3 => 'valueC3'}, }; print Dumper($conf);### もともとのハッシュリファレンス ###追加する $conf = add_to_conf( $conf, { setA => {conf4 => 'valueA4', conf5 => 'valueA5'}, setB => {conf4 => 'valueB4', conf5 => 'valueB5'}, setC => {conf4 => 'valueC4', conf5 => 'valueC5'}, } ); ###追加後 print Dumper($conf);

SV576
質問者

お礼

ご回答ありがとうございます。いろいろな人の意見は大変参考になりますので,助かります。 ご提案いただいた方法,確かに汎用性はありそうですね。ぜひとも参考にさせていただきたいと思います。ありがとうございます ただやはり今回の件では 視認性,メンテの面は,正直,まったく変わってないのでは…(ごめんなさい^^;) コード上は確かにカッコの数が1つ減りましたが中身が同じで,それならANo.1お礼欄の「%tmpを使う方法」と同じというか…長くなってしまったというか… また > さらに追加する必要があった場合どうなるのでしょうか? 同じことをすれば良いだけで,特に問題はないかと思います 必要コードはたったの2行ですし(設定値の部分を除いて) せっかくご提案いただいたのに,なんだか文句のようになってしまいすみません。 一応補足させていただきますと いわゆる設定ファイルと呼ばれる別ファイルにデータを記述してコードから読み込む方法がありますが,今回はそのような性質のものではなくて,逆にハードコーディングのほうが適している類のものです。設置時や余程のことがなければ変更しないし,あくまでも処理の過程で“コードによって”変更されるだけです いわば, $week_days = 7; # 一週間の日数 ぐらい,外部からの変更や汎用性は不要なもの,と考えてください。 なのでIO処理は無駄に重くなるし,ロジックは単純なほうがいいし,先にも書いたように,とにかくシンプルなほうが視認性(設定値を見直すときも,ロジックを追うときも)の面も含めて都合がいい,ということです そもそも最初の質問をした理由も,極力シンプルにしたかったからでした(見た目もロジックも) そういう意味ではwhile(each ~)に直接記述する方法(がダメだったので,ANo.1お礼欄の「%tmpを使う方法」)が条件を満たしていますが,さらにもっとシンプルに変数すら残さない方法として,ハッシュをリストにしてforeachにしてみた,という次第です。 他にも,設定値部分をもっとシンプルに foreach (split /\n/, <<'CONF' setA:conf4,valueA4,conf5,valueA5 setB:conf4,valueB4,conf5,valueB5 setC:conf4,valueC4,conf5,valueC5 setA:conf6,valueA6,conf7,valueA7 CONF ){my($name,$value)=split /:/;$conf{$name}={%{$conf{$name}},split(/,/, $value)};} こんなのも考えてみたんですが書式ミスなど見つけにくいと思いやめました

  • pipipi523
  • ベストアンサー率40% (148/365)
回答No.2

>リファレンスが変わるというのは、参照される側つまり被リファレンスのデータが、eachの実行のたびに作り直されてしまう=イテレーターも初期状態なので無限ループ、という理解でよろしいでしょうか。 >で、keysは期待どおり動いたのは、参照するのは初めの1回だけで、取得した内容をメモリ上のどこかに保持している、ということなんですかね? その認識で正しいと思います >言い換えればeachを使うなら変数を用意しなきゃいけない 何か有るのかも知れませんが、上手い方法を思いつきませんでした 一時的なハッシュを用意するのがベストかと

SV576
質問者

お礼

ご回答ありがとうございます。 おかげでeachについては理解できました。 とりあえず暫定策として以下のようにしてみました。 ようは、ループに対して指示を与えることができさえすれば良いのだから、別にハッシュじゃなくてもいい、ループ内でハッシュ操作をするためのキーと値を得ることができさえすればいいので、配列にしました。 1個目の要素がキー、2個目が値として、リストリファレンスのリストにしました。 foreach ( ['setA', {conf4 => 'valueA4', conf5 => 'valueA5'}], ['setB', {conf4 => 'valueB4', conf5 => 'valueB5'}], ['setC', {conf4 => 'valueC4', conf5 => 'valueC5'}], ['setA', {conf6 => 'valueA6', conf7 => 'valueA7'}], ) { my ($name, $value) = @$_; $conf{$name} = {%{$conf{$name}}, %$value}; } 最善策かどうかは解りませんが、どなたかもっと良い方法をご存知でしたら、お願いいたします。

関連するQ&A