全要素が別個のオブジェクトを参照する2次元配列を作りたいとき
Rubyにおける配列生成は参照するオブジェクトに注意
Rubyで2次元配列を作る時、初期化方法によっては複数要素が同じオブジェクトを参照するので注意が必要。知識として知ってはいたけど、実際コーディングしたら思いっきり詰まったので、忘れないうちに整理します。
まずは1次元配列について
# 各要素が同一の文字列オブジェクトを参照する配列の生成
ary = Array.new(3, "AAA") # 全要素が単一の文字列オブジェクト"AAA"を参照する
p ary # => ["AAA", "AAA", "AAA"]
ary[0].downcase! # 1要素に対する破壊的な変更は全要素に影響する
p ary # => ["aaa", "aaa", "aaa"]
# 各要素が別個の文字列オブジェクトを参照する配列の生成
ary = Array.new(3){"AAA"} # 各要素が別個の文字列オブジェクト"AAA"を参照する
p ary # => ["AAA", "AAA", "AAA"]
ary[0].downcase! # 1要素に対する破壊的な変更は個別の要素に反映される
p ary # => ["aaa", "AAA", "AAA"]
本題の2次元配列について
# 各要素が同一のオブジェクトを参照する配列の生成
ary = Array.new(3, Array.new(3, "AAA")) # 全要素が同一の文字列オブジェクト"AAA"を参照する
p ary
# => [["AAA", "AAA", "AAA"], ["AAA", "AAA", "AAA"], ["AAA", "AAA", "AAA"]]
ary[0][0].downcase! # 1要素に対する破壊的な変更は全要素に影響する
p ary
# => [["aaa", "aaa", "aaa"], ["aaa", "aaa", "aaa"], ["aaa", "aaa", "aaa"]]
# 外側の配列が同一の配列オブジェクト、内側の配列が別個の文字列オブジェクトを参照する配列の生成
ary = Array.new(3, Array.new(3){"AAA"}) # 外側の配列の全要素が同一の配列オブジェクトを参照する
p ary
# => [["AAA", "AAA", "AAA"], ["AAA", "AAA", "AAA"], ["AAA", "AAA", "AAA"]]
ary[0][0].downcase! # 1要素に対する破壊的な変更は外側の配列の全要素に影響する
p ary
# => [["aaa", "AAA", "AAA"], ["aaa", "AAA", "AAA"], ["aaa", "AAA", "AAA"]]
# 外側の配列が別個の配列オブジェクト、内側の配列が同一の文字列オブジェクトを参照する配列の生成
ary = Array.new(3){Array.new(3, "AAA")} # 内側の配列の全要素が同一の文字列オブジェクト"AAA"を参照する
p ary
# => [["AAA", "AAA", "AAA"], ["AAA", "AAA", "AAA"], ["AAA", "AAA", "AAA"]]
ary[0][0].downcase! # 1要素に対する破壊的な変更は内側の配列の全要素に影響する
p ary
# => [["aaa", "aaa", "aaa"], ["AAA", "AAA", "AAA"], ["AAA", "AAA", "AAA"]]
# 各要素が別個の文字列オブジェクトを参照する配列の生成
ary = Array.new(3){Array.new(3){"AAA"}} # 各要素が別個の文字列オブジェクト"AAA"を参照する
p ary
# => [["AAA", "AAA", "AAA"], ["AAA", "AAA", "AAA"], ["AAA", "AAA", "AAA"]]
ary[0][0].downcase! # 文字列に対する破壊的な変更は個別の要素に反映される
p ary
# => [["aaa", "AAA", "AAA"], ["AAA", "AAA", "AAA"], ["AAA", "AAA", "AAA"]]
ブロックの入れ子により、全要素別個のオブジェクトを参照するようにできます。
個別の要素に代入する場合はどうなる?
# 各要素が同一の文字列オブジェクトを参照する配列の生成
ary = Array.new(3, Array.new(3, "AAA")) # 全要素が同一の文字列オブジェクト"AAA"を参照する
ary[0][0] = "BBB"
p ary
# => [["BBB", "AAA", "AAA"], ["BBB", "AAA", "AAA"], ["BBB", "AAA", "AAA"]]
# 外側の配列が同一の配列オブジェクト、内側の配列が別個の文字列オブジェクトを参照する配列の生成
ary = Array.new(3, Array.new(3){"AAA"}) # 外側の配列の全要素が同一の配列オブジェクトを参照する
ary[0][0] = "BBB"
p ary
# => [["BBB", "AAA", "AAA"], ["BBB", "AAA", "AAA"], ["BBB", "AAA", "AAA"]]
# 外側の配列が別個の配列オブジェクト、内側の配列が同一の文字列オブジェクトを参照する配列の生成
ary = Array.new(3){Array.new(3, "AAA")} # 内側の配列の全要素が同一の文字列オブジェクト"AAA"を参照する
ary[0][0] = "BBB"
p ary
# => [["BBB", "AAA", "AAA"], ["AAA", "AAA", "AAA"], ["AAA", "AAA", "AAA"]]
# 各要素が別個のオブジェクトを参照する配列の生成
ary = Array.new(3){Array.new(3){"AAA"}} # 各要素が別個の文字列オブジェクト"AAA"を参照する
ary[0][0] = "BBB"
p ary
# => [["BBB", "AAA", "AAA"], ["AAA", "AAA", "AAA"], ["AAA", "AAA", "AAA"]]
考察
2次元配列の場合、「ある要素に別の値を代入する」という行為は、内側の配列から見れば「別のオブジェクトを参照する」ということなので、”破壊的な変更”には当たらない。しかし、外側の配列から見ると、「参照先の配列オブジェクトそのものが変更される」ということになるので、"破壊的な変更"に当たる。そのため、多次元配列の初期化方法によっては、一つの要素への代入が他の要素へも影響する場合がある。
結論
複数の要素が同一のオブジェクトを参照している配列って、構造が掴みづらいし、実際どういう使い方をした時に便利なのか、今はまだわからないです。当面はブロックを使って各要素を独立させておいたほうが無難かと思いました。