追記(2022/5/2)
コメントにてpythonの配列の処理について、分かりやすく図にて説明いただきました。
具体的な内部の処理について知りたい方はぜひご覧ください。
目的
pythonを使用すると、配列を代入した際に値が書き換わってしまい困ってしまうケースがあると思います。
そこで、どのような場合に値が書き換わるのか、どのように対処できるのかをまとめました。
環境
AtCoderでの使用を前提としているため、AtCoderのコードテストで実行しています。
言語は実行速度を考慮しPypy3(7.3.0)としています。
一次元配列
代入される例
A = [0,1,2]
B = A
B[0] = 3
print(A)
#[3, 1, 2]
上記の例では、BにAを代入するとBの要素を変更してもAの配列が書き換わります。
これはBへはAの配列のコピーでなく、Aの配列の参照を代入しているからと考えられます。
よって、AとBはともに同一の配列を参照しているため、配列の値を書き替えるとA、Bともに変化します。
代入されない例
A = [0,1,2]
B = A[0]
B = 3
print(A)
#[0, 1, 2]
ここではBに配列ではなく、Aの配列内のA[0]が参照しているint型の値の参照が代入されます。
よって、Bの値を変更しても参照先が変化するためAの配列は変化しません。
対処法
A = [0,1,2]
B = A.copy()
B[0] = 3
print(A)
#[0, 1, 2]
copy()を使用することにより、BにはAの配列でなくAの配列のコピーが代入されたため、
Bの値を変更してもAの配列が変化することがなくなります。
多次元配列
代入される例
A = [[0,1],[2,3]]
B = A
B[0][0] = 3
print(A)
#[[3, 1], [2, 3]]
同様にBの値を変更するとAの値も変更されます。
copy
A = [[0,1],[2,3]]
B = A.copy()
B[0][0] = 3
print(A)
#[[3, 1], [2, 3]]
単純なcopyでは、Bの要素を変更するとAの要素も変更されます。
これは、Bの一次元目の配列のみがコピーされるためです。
対処法
import copy
A = [[0,1],[2,3]]
B = copy.deepcopy(A)
B[0][0] = 3
print(A)
#[[0, 1], [2, 3]]
copyをimportし、deepcopy を実行することで多次元配列の場合も値が変更されてしまうことを防ぎます。
copyの問題点
計算時間
A = [[0] for _ in range(10 ** 6)]
#実行時間 208 ms
A = [[0] for _ in range(10 ** 6)]
A = A.copy()
#実行時間 220 ms
A = [[0] for _ in range(10 ** 6)]
A = copy.copy(A)
#実行時間 210 ms
A = [[0] for _ in range(10 ** 6)]
A = copy.deepcopy(A)
#実行時間 1817 ms
上記のように、deepcopyは配列数に関わらず実行時間が増える場合があります。
そのため、copyと使い分けることが必要となります。
その他注意ケース
def MakeB(ListA):
ListA[0] = 3
return ListA
A = [0,1,2]
B = MakeB(A)
print(A)
#[3, 1, 2]
配列を関数に入れた場合も同様に値が変更されます。
これは関数内で配列をそのまま使用しているためです。
関数内でcopyするなどで対処可能です。