Ruby

RubyのArrayを理解する

More than 3 years have passed since last update.

RubyでArrayを複製するときにハマったのでメモしておきます。

変数

Arrayについて理解する前に、まずは変数について確認します。

変数が保持しているのはオブジェクトへの参照です。次の例のように、update1で渡された引数に新しいオブジェクトを代入する場合、元のオブジェクトが変更されることはありませんが、update2で渡された引数の参照に対して変更する場合、元の変数も変更されてしまいます。

def update1(var)
  p var.object_id # => 1
  var = "adc"
  # ローカル変数varに新しいオブジェクト"abc"が代入される
  p var.object_id # => 2
end

def update2(var)
  p var.object_id # => 1
  var[1] = "d"
  # ローカル変数varが参照しているオブジェクトに"d"が代入される
  p var.object_id # => 1
end

str = "abc"
p str.object_id # => 1
p str # => "abc"
update1(str)
p str # => "abc"
update2(str)
p str # => "adc"

# object_idは分かりやすくするために一桁にしています。

このように元の変数が変更されてしまうのを防ぐには、オブジェクトを複製(.dup)してから利用します。

def update1(var)
  p var.object_id # => 1
  # ローカル変数varに新しいオブジェクト"abc"
  var = "adc"
  p var.object_id # => 2
end

def update2(var)
  p var.object_id # => 1

  # オブジェクトを複製する
  var = var.dup
  p var.object_id # => 3

  # ローカル変数varが参照しているエリアに"d"が代入される
  var[1] = "d"
  p var.object_id # => 3
end

str = "abc"
p str.object_id # => 1
p str # => "abc"
update1(str)
p str # => "abc"
update2(str)
p str # => "abc"

# object_idは分かりやすくするために一桁にしています。

配列

配列が保持しているのは、変数と同じくオブジェクトへの参照です。元の配列の参照に対して更新する場合は、変数と同様にオブジェクトを複製(.dup)する必要があります。

def update1(ary)
  p ary.object_id # => 1
  # ローカル変数varに新しいオブジェクト"abc"
  ary = [1, 2, 3, 4]
  p ary.object_id # => 2
end

def update2(ary)
  p ary.object_id # => 1

  # オブジェクトを複製する
  ary = ary.dup
  p ary.object_id # => 3

  ary[3] = 4
  p ary.object_id # => 3
end

str = [1, 2, 3]
p str.object_id # => 1
p str # => [1, 2, 3]
update1(str)
p str # => [1, 2, 3]
update2(str)
p str # => [1, 2, 3]

# object_idは分かりやすくするために一桁にしています。

多次元配列

多次元配列を複製する場合、オブジェクトそのものを複製すると別のオブジェクトが作成されますが、中の配列オブジェクトは同じものが複製されます。

ary.object_id
ary.each{|i| p i.object_id}
ary = ary.dup
ary.object_id # 1行目と違う結果
ary.each{|i| p i.object_id} # 2行目と同じ結果

なので、配列の中身を複製します。

ary.object_id
ary.each{|i| p i.object_id}
ary = ary.map{|i| i = i.dup }
ary.object_id # 1行目と違う結果
ary.each{|i| p i.object_id} # 2行目と違う結果

また、その他の方法として、中の要素1つ1つを複製するのではなく、Marshal.load(Marshal.dump())を使うのも有効な方法です。

# ary = ary.map{|i| i = i.dup } # これの代わりに以下の書き方もできる
ary = Marshal.load(Marshal.dump(ary))