Rubyのエニュメレータ内での破壊行為は止めてください!
RubyのArrayにはrotate!という便利なメソッドがあるよ
このメソッドは文字通り配列の要素をローテートするんだ
a = [1,2,3] a.rotate! # => [2, 3, 1] a.rotate! # => [3, 1, 2] a # => [3, 1, 2]
メソッド名の最後に!(ビックリマーク)があるから
これは元のオブジェクト自身を変えるよ
昨日僕はこのrotate!メソッドにおける
ローテートの過程を取りたいと思ったんだよ
で次のようなコードを書いてみたんだ
a = [1,2,3] 3.times.map { a.rotate! }
そうしたら期待したものとは違う
次のような結果が返ってきたんだ
# => [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
あれ?
mapがいけないのかな..
q = [] 3.times { q << a.rotate! } q # => [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
ローテートしてないのかと思って
ブロック内でpしてみたらちゃんとしてるんだよ
a = [1,2,3] 3.times.map { p a.rotate! } # => [[1, 2, 3], [1, 2, 3], [1, 2, 3]] # >> [2, 3, 1] # >> [3, 1, 2] # >> [1, 2, 3]
なんか変だな..
で少し考えたら理由がわかったんだ
Array#rotate!はselfを返すんだったよ
a = [1,2,3] a.object_id # => 2151892940 3.times.map { a.rotate!.object_id } # => [2151892940, 2151892940, 2151892940]
つまりmapの返り値はa.rotate!の
スナップショットの配列を返すんじゃなくて
元オブジェクトの参照の配列を返すんだよ
でmapの返り値はすべての要素に対する
イテレートが終わってから返されるから(当然だよね)
その時点つまり最後のa.rotate!の後における
元オブジェクトの状態がすべての配列の要素として
返されることになるんだ
つまりこれは次のコードと同じようなことなんだよ
a = [1,2,3] b = a.rotate! c = a.rotate! d = a.rotate! [b, c, d] # => [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
だからスナップショットつまり途中経過がほしい場合は
さっきみたいにpしたり
to_sしたりdupしたりする必要があるんだね
a = [1,2,3] 3.times.map { a.rotate!.to_s } # => ["[2, 3, 1]", "[3, 1, 2]", "[1, 2, 3]"] a = [1,2,3] 3.times.map { a.rotate!.dup } # => [[2, 3, 1], [3, 1, 2], [1, 2, 3]]
同じことはほかのRubyの破壊的メソッドでも起きるよ
s = "hello, world!" s.size.times.map { p s.chop! } # => ["", "", "", "", "", "", "", "", "", "", "", "", ""] # >> "hello, world" # >> "hello, worl" # >> "hello, wor" # >> "hello, wo" # >> "hello, w" # >> "hello, " # >> "hello," # >> "hello" # >> "hell" # >> "hel" # >> "he" # >> "h" # >> ""
うっかりしてるとまたミスしそうだよ
分かってる人には当たり前のことなんだろうけど
僕はちょっと嵌っちゃったから書いてみたよ :)
だからビルのエレベーター内での危険行為はもう止めようよ!
だからRubyのエニュメレータ内での破壊行為はもう止めようよ!