#### 変数は箱である? `a = 1` と書いたら、箱 a に $1$ を入れる ![box.png](https://hackmd.io/_uploads/r1d2-wvm6.png) `b = a` と書いたら、箱 a に入っている $1$ をコピーして箱 b に入れる ![box.png](https://hackmd.io/_uploads/H1V9fwwXT.png) というのはよくありそうな説明ですが、 Python の実態に即していません。実際、リストを使った時に混乱を招くことになります。 ```python= a = [1, 2, 3] b = a # a を変更すると b も変更される a[0] = 0 print(b) ``` #### 変数はデータへのタグ付けである 実態に即した説明をしてみましょう。 `a = 1` と書いたら、**$1$ というデータを作り**、タグ a がそのデータを指すようにする ![box.png](https://hackmd.io/_uploads/HJ0IPDwm6.png) `b = a` と書いたら、タグ b が「タグ a が指すデータ」を指すようにする ![box.png](https://hackmd.io/_uploads/r1bgOPv76.png) #### リストの場合 試しにリストの例もこの形で表現してみましょう。 ```python= a = [1, 2, 3] b = a # a を変更すると b も変更される a[0] = 0 print(b) # [0, 2, 3] ``` リスト `[1, 2, 3]` を作るには、まず整数 $1, 2, 3$ の入ったデータを作る必要があります。 ![box.png](https://hackmd.io/_uploads/HyV_twvXp.png) そうしたら、 - 0 番目の要素が整数 $1$ の入ったデータを指し、 - 1 番目の要素が整数 $2$ の入ったデータを指し、 - 2 番目の要素が整数 $3$ の入ったデータを指す リストを作ります。 ![box.png](https://hackmd.io/_uploads/rkiU8KP7p.png) 最後に、このリストを指すタグ a を作ります。 ![box.png](https://hackmd.io/_uploads/ryx5vcP7p.png) これで `a = [1, 2, 3]` ができました。 続いて `b = a` をすると、タグ b が「タグ a が指すデータ」を指すようになります。 ![box.png](https://hackmd.io/_uploads/BJak_9D7T.png) この状態で、`a[0] = 0` をやってみましょう。 まず、整数 $0$ の入ったデータを作ります。 ![box.png](https://hackmd.io/_uploads/Bk7CmCDQ6.png) そうしたら、`a[0]` (赤い部分) が今作ったデータを指すようにします。 ![box.png](https://hackmd.io/_uploads/HkcuMbYXT.png) `a[0]` が $0$ になりましたが、同時に `b[0]` も $0$ になっていることがわかります。 #### 二次元配列の罠 ```python= a = [[0] * 3] * 3 # a[0] も a[1] も a[2] も同じリストなので、a[0] を変更すると a[1] と a[2] も同時に変更される a[0][0] = 1 print(a[1]) # [1, 0, 0] ``` よくあるやつですね。これを実行する前に、`*` の仕様を見ておきましょう。 1 次元の場合をやってみます。 ```python= [0, 1] * 3 ``` まず、整数 $0, 1$ の入ったデータを作ります。 ![box3.png](https://hackmd.io/_uploads/HkRjBxFXT.png) 次に、リスト `[0, 1]` を作ります。 ![box4.png](https://hackmd.io/_uploads/BylTpetXp.png) このリストに対し `* 3` を行います。 ![box3.png](https://hackmd.io/_uploads/SJnrieK7a.png) $0, 1$ はコピーされず、同じ $0$ と $1$ に 3 本ずつ矢印が引かれたリストができます。 さて、本題に戻って ```python= a = [[0] * 3] * 3 # a[0] も a[1] も a[2] も同じリストなので、a[0] を変更すると a[1] と a[2] も同時に変更される a[0][0] = 1 print(a[1]) # [1, 0, 0] ``` を実行してみましょう。まず、 - `a = [[0] * 3] * 3` をするための - `[[0] * 3] * 3` を作るための - `[[0] * 3]` を作るための - `[0] * 3` を作るための - `[0]` を作るための - `0` を作ります。 ![box2.png](https://hackmd.io/_uploads/Syq4XeFmT.png) `[0]` を作ります。 ![box2.png](https://hackmd.io/_uploads/BJqa7gFQT.png) `[0] * 3` を作ります。 ![box2.png](https://hackmd.io/_uploads/Sy-ACeFXp.png) `[[0] * 3]` を作ります。 ![box2.png](https://hackmd.io/_uploads/HkRvJ-tm6.png) `[[0] * 3] * 3` を作ります。 ![box2.png](https://hackmd.io/_uploads/HJJmlWtQ6.png) できた `[[0] * 3] * 3` を指すタグ a を作ります。 ![box2.png](https://hackmd.io/_uploads/BJc5g-FXT.png) `a[0]` も `a[1]` も `a[2]` も同じリストを指していますね。 `a[0][0] = 1` をすると、 ![box2.png](https://hackmd.io/_uploads/rksrUZt7a.png) `a[1][0]` も `a[2][0]` も $1$ になってしまいます。 #### mutable と immutable Python の型は大きく mutable (可変) なものと immutable (不変) なものに分けることができます。 | 例 | | | --- | --- | | immutable | int, float, bool, str, tuple など | | mutable | list, set, dict, collections.deque など | immutable な型のデータは、一度作られるとそれ以降変わることがありません。 ```python= a = 1 b = a a += 2 ``` を考えてみましょう。 まず、`a = 1` で、整数 $1$ の入ったデータを作り、タグ a をつけます。 ![box.png](https://hackmd.io/_uploads/HkTh_MYmp.png) `b = a` で、タグ b が「タグ a が指すデータ」を指すようにします。 ![box.png](https://hackmd.io/_uploads/H14btfFmp.png) ここで、`a += 2` をします。まず、整数 $2$ を作って… ![box.png](https://hackmd.io/_uploads/BJvSFMFXa.png) タグ a の指す $1$ に $2$ を足して $3$ に変更したいのですが、int 型は一度作ると変更できないので、整数 $1 + 2 = 3$ を新たに作ります。 ![box.png](https://hackmd.io/_uploads/BJaejGt76.png) そして、タグ a の指す先を新たに作った $3$ に変更します。 ![box.png](https://hackmd.io/_uploads/ryymjftQ6.png) このとき、`b` は $1$ のままです。 **immutable な型のデータは一度作ると変更できず、immutable な型の変数を変更しようとすると新しくデータが作られる** ことを覚えておきましょう。 もし、int 型が mutable であった場合はどうなるでしょうか? `a += 2` をすると… ![box.png](https://hackmd.io/_uploads/BJvSFMFXa.png) データの方が変更されて $3$ になります。 ![box](https://hackmd.io/_uploads/HJvWSRqma.png) このとき、`b` も $3$ に変わってしまいます。 **mutable な型の変数を変更しようとするとデータの方が変更され、同じデータを指している別の変数も同時に変わってしまう** ことを覚えておきましょう。 #### 計算量の違い mutable と immutable の違いは計算量の違いも生みます。 list の連結 `a += b` はデータの方を変更するので償却 $\Theta(|b|)$ 時間ですが、 str の連結 `a += b` はデータの方を変更できずコピーが必要なため、$\Theta(|a| + |b|)$ 時間になります。 したがって、list を $N$ 回連結する操作 ```python= a = [] for _ in range(N): a += [1] ``` は $\Theta(N)$ 時間ですが、str を $N$ 回連結する操作 ```python= a = "" for _ in range(N): a += "1" ``` は $\Theta(N^2)$ 時間になってしまいます。