From: nagachika Date: 2011-01-26T12:59:48+09:00 Subject: [ruby-dev:43118] Re: Hashのイテレーション中の新規キー追加 はじめまして、近永と申します。 まず、畠山さんの提示されたパッチでを 1.8.7-p330 に適用して以下のような実験をしてみると、 手元ではちゃんと要素を1回ずつ yield できていないことがわかります。 ## hash_each.rb num = 66 tbl = {} num.times do |i| tbl[i.to_s] = i end cnt_tbl = Hash.new{ 0 } num.times do |i| cnt_tbl[i.to_s] = 0 end tbl.each do |k, v| cnt_tbl[k] += 1 if cnt == num/2 tbl[(num+1).to_s] = "new" end end p cnt_tbl ## 実行結果 {"15"=>1, "36"=>1, "8"=>1, "57"=>1, "16"=>0, "37"=>1, "60"=>1, "9"=>1, "58"=>1, "17"=>0, "40"=>0, "38"=>0, "61"=>1, "59"=>1, "20"=>0, "18"=>0, "41"=>0, "39"=>0, "62"=>0, "21"=>0, "19"=>0, "42"=>0, "63"=>0, "43"=>0, "64"=>1, "65"=>1, "22"=>2, "23"=>2, "44"=>2, "24"=>2, "45"=>2, "67"=>1, "25"=>2, "46"=>2, "26"=>2, "47"=>2, "0"=>1, "27"=>1, "50"=>2, "48"=>2, "30"=>1, "1"=>1, "28"=>1, "51"=>1, "49"=>2, "10"=>1, "31"=>1, "2"=>1, "29"=>1, "52"=>1, "32"=>1, "3"=>1, "53"=>1, "4"=>1, "54"=>1, "11"=>2, "5"=>1, "12"=>2, "33"=>2, "13"=>2, "34"=>2, "6"=>2, "55"=>2, "14"=>2, "35"=>2, "7"=>2, "56"=>2} 同じ要素が2度 yield されてたり、1度も yield されない要素ができてたりします。 また、Hash#each 中に新たに要素を追加した場合、その要素についてはその Hash#each 中に yield されるべきかどうかという仕様の問題もあるかと思います。 現実的には Hash#each の例については単に h.keys.each { ... } にすることで回避できます。 rb_hash_foreach() 中に Hash への要素追加がエラーになる現象で困ったことは 1.9 ですがわたしも経験があります。 Hash#each ではなくて、Marshal.dump と Hash#[]= を別のスレッドから呼んだ時の問題でした。 (参考: http://d.hatena.ne.jp/nagachika/20100630/ruby_hash_insert_during_marshal_dump) ここには書いていませんが、確か結局アプリケーション側でできるだけ dRuby 転送を抑えて、 Hash#[]= している箇所でリトライするようにして回避したと思います。 "can't add a new key into hash during iteration" の例外は専用の例外クラスだったらいいなとは思いました。 参考までに。