teppay’s log

セキュリティ、CTF、機械学習などに興味があります。情報系学生です。興味のあることを思い立った時に書きます。曖昧なことの整理にも使います。月1が目標です。

2次元配列( list )のコピーで気をつけること

環境

状況

  • Pythonであるパズルのソルバを書いている。
  • 2次元listをコピーしようとしてハマった。

コピーの種類

浅いコピー

>>> l = [0, 0, 0]
>>> l2 = l #here!
>>> l
[0, 0, 0]
>>> l2
[0, 0, 0]
>>> l2[0] = 2
>>> l
[2, 0, 0]
>>> l2
[2, 0, 0]
>>> id(l)
4440243912
>>> id(l2)
4440243912

これを見て分かる通り、ただ代入し直しただけでは、オブジェクトがコピーされるだけで、異なる変数が同じlistオブジェクトを参照している状態になっています。
通常listをコピーしたい時にこのような浅いコピーでは使い物になりません。
そこで深いコピーです。

深いコピー

>>> l = [0, 0, 0]
>>> l2 = l[:] #here!
>>> l
[0, 0, 0]
>>> l2
[0, 0, 0]
>>> l[0] = 2
>>> l
[2, 0, 0]
>>> l2
[0, 0, 0]
>>> id(l)
4440242504
>>> id(l2)
4440234312

1次元配列をコピーする際はsliceを使うのが一般的みたいです。

2次元listをコピーする際の注意点

では2次元配列をコピーする際はどうすればいいのでしょう。

>>> l = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> l2 = l[:]
>>> l
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> l2
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> l
[[2, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> l2
[[2, 0, 0], [0, 0, 0], [0, 0, 0]]

sliceを使っても無理です。 2次元listは深いコピーできているんですが、その要素であるそれぞれのlistが浅くコピーされています。

>>> id(l)
4440234440
>>> id(l2)
4440242504
>>> id(l[0])
4440243912
>>> id(l2[0])
4440243912

じつはlist内包表記と組み合わせることで2次元listを浅くコピーすることが出来るんです。

>>> l = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> l2 = [x[:] for x in l] #here!
>>> l[0][0] = 2
>>> l
[[2, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> l2
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> id(l[0])
4440234312
>>> id(l2[0])
4440243912

まとめ

  • sliceとlist内包表記を使うといいよ
  • 実はcopyモジュールで簡単に深いコピーできるらしいよ