Ruby で map を用いて配列から新しい配列を作る場合、以下のように配列の中身に気をつける必要があります。
original = [
{key: "foo"},
{key: "bar"}
]
mapped = original.map do |obj|
obj[:hoge] = "fuga"
obj
end
original == mapped # => true
original # => [{:key=>"foo", :hoge=>"fuga"}, {:key=>"bar", :hoge=>"fuga"}]
配列内のハッシュに hoge: "fuga"
を追加した新しい配列を map で生成するコードですが、original
も書き換わってしまいます。
ハッシュの配列を map する場合
original
を書き換えないために dup
を使います。
original = [
{key: "foo"},
{key: "bar"}
]
mapped = original.map do |original_obj|
obj = original_obj.dup
obj[:hoge] = "fuga"
obj
end
original == mapped # => false
original # => [{:key=>"foo"}, {:key=>"bar"}]
何故 map
しているのに original
が書き換わるのか
original[0]
には ハッシュオブジェクト {key: "foo"}
が格納されているのではなく、ハッシュオブジェクト {key: "foo"}
の 参照 が格納されています。
sample1.rb の場合 map のブロック変数 obj
には、配列の各要素であるハッシュの 参照が渡される ため、obj
に対し代入等の操作を行うと、original
の各要素が参照しているハッシュオブジェクトが変更されます。
また、map の返り値は obj
となっているため mapped
の各要素は original
の各要素と同じハッシュオブジェクトの参照を格納している 事になります。
Ruby は値渡しですが、変数が参照を保持している場合は 参照の値渡し となることに注意が必要です。
詳しくは、Rubyist Magazine - 値渡しと参照渡しの違いを理解するを参照してください。
ちなみに original.dup.map
の場合
以下のように original
が書き換わってしまいます。
original = [
{key: "foo"},
{key: "bar"}
]
mapped = original.dup.map do |obj|
obj[:hoge] = "fuga"
obj
end
original == mapped # => true
original # => [{:key=>"foo", :hoge=>"fuga"}, {:key=>"bar", :hoge=>"fuga"}]
dup は浅い複製 (shallow copy)のため、original.dup
を行っても、配列内の各要素がハッシュオブジェクトの参照のままであるため、original
が書き換わってしまいます。
以下のように pry を使って object_id
を確認すると、original
と cloned
の object_id
は異なりますが、配列の第1要素は同じオブジェクトを指していることが分かります。
[1] pry(main)> original = [{key: "foo"}, {key: "bar"}]
=> [{:key=>"foo"}, {:key=>"bar"}]
[2] pry(main)> cloned = original.dup
=> [{:key=>"foo"}, {:key=>"bar"}]
[3] pry(main)> original.object_id
=> 70201156677940
[4] pry(main)> cloned.object_id
=> 70201161775220
[5] pry(main)> original[0].object_id
=> 70201156678620
[6] pry(main)> cloned[0].object_id
=> 70201156678620
[7] pry(main)>