Pythonで〇×ゲームのAIを一から作成する　その10　正しい初期化されたゲーム盤の作成方法

# 目次と前回の記事

https://qiita.com/ysgeso/items/2381dd4e3283cbed49a0

https://qiita.com/ysgeso/items/c6d4a2da7b8118a5a143

# 正しい初期化されたゲーム盤の作成方法

前回の記事で、下記の欠陥のあるゲーム盤を初期化するプログラムで行われる処理を説明するために必要な前提知識の説明が終わりました。

```python
board = [[" " * 3] * 3]
```

実は、上記のプログラムで行われる処理を理解するためには、まだいくつか説明しなければならないことがあります。それはこのプログラムで記述されている __`*` 演算子__ についての説明です。また、`*` 演算子を説明するためには、list に対する __`+` 演算子__ の説明をする必要があります。

## `+` 演算子による list の結合

2 つの list に対して `+` 演算子で計算を行うと、2 つの list の要素を __結合__ する処理が行われます。list の結合とは、list の要素の末尾に、別の list の要素を加えた __新しい list を作成する__ という処理の事です。

下記のプログラムで具体例を示します。1 行目の処理は比較的簡単に理解できると思いますが、2 行目は少しわかりづらいかもしれません。

```python
print([1, 2, 3] + [4, 5])
print([1, 2, 3] + [[1, 2], [3, 4]])
```
実行結果
```
[1, 2, 3, 4, 5]
[1, 2, 3, [1, 2], [3, 4]]
```
2 行目の処理を理解するためには、list の結合処理の手順を以下のように考えると良いでしょう。

1. 結合する 2 つの list の外側の `[]` をそれぞれ削除して __要素を並べる__
2. 上記の 1 つ目の list の要素を並べたものの __末尾に `,` を加え__、その後に 2 つめの list の要素を並べたものを並べる
3. 全体を `[]` で囲う

この手順で実際に上記のプログラムの 2 つの処理を行ってみます。なお、背景が灰色で表示されていない部分は、結合処理をわかりやすく説明するための __架空のデータ__ で、結合処理の過程で実際にそのようなデータが作られているわけではない点に注意して下さい。

* 1 行目の処理
    1. 　`[1, 2, 3]` → 1, 2, 3
　`[4, 5]` → 4, 5
    2. 　1, 2, 3, 4, 5
    3. 　`[1, 2, 3, 4, 5]` 
* 2 行目の処理
    1. 　`[1, 2, 3]` → 1, 2, 3
　`[[1, 2], [3, 4]]` → [1, 2], [3, 4] 
    2. 　1, 2, 3, [1, 2], [3, 4]
    3. 　`[1, 2, 3, [1, 2], [3, 4]]`

:::note
`+` 演算子による list の結合処理は、__新しい list を作成する__ 処理です。式に記述した list は  __いずれも変更されない__ 点に注意して下さい。

よくある __勘違い__ として、下記のようなプログラムを実行すると、2 行目の `a + [2]` の部分の処理で、`a` に代入された list が変更されるというものがあります。

実行結果からわかるように、実際には `a` の値は `[1]` のまま __変化せず__、`b` には `a + [2]`、すなわち `[1] + [2]` という結合処理によって __新しく作成された__ `[1, 2]` が代入されます。

```python
a = [1]
b = a + [2]
print(a)
print(b)
```
実行結果
```
[1]
[1, 2]
```
:::

:::note
`+` 演算子と異なり、`append` は list の末尾に __要素を追加__ して、__list の要素を変更する__ 処理です。list の要素を変更する処理は、`append` 以外では、`+=` という演算子で行うことができます。具体的には以下のように記述することで、list が代入された変数に、反復可能オブジェクトから取り出したデータを順番に要素として追加するという処理が行われます。なお、このような処理の事を、__list の拡張__ と呼ぶことがあります。

```python
listが代入された変数 += 反復可能オフジェクト
```

下記のプログラムは、`+=` 演算子を使ったプログラムの具体例です。

```python
a = [1, 2]
a += [3, 4, 5]
print(a)
```
実行結果 
```
[1, 2, 3, 4, 5]
```

また、`+=` と同様の処理を `extend` を使って以下のように記述することもできます。

```python
a = [1, 2]
a.extend([3, 4, 5])
print(a)
```
実行結果 
```
[1, 2, 3, 4, 5]
```
:::

:::note
list と list 以外のデータを `+` 演算子で計算するとエラーが発生します。下記のプログラムでは、list と 整数型のデータを `+` 演算子で計算したためエラーが発生しています。

```python
[1] + 2
```
実行結果
```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[5], line 1
----> 1 [1] + 2

TypeError: can only concatenate list (not "int") to list
```

上記のエラーメッセージは、以下のような意味を持ちます。

* __TypeError__
データ型（Type）に関するエラー
* __can only concatenate list (not "int") to list__
list には（to）、整数型（"int"）ではなく、list しか（only）結合（concatenate）できない（can）
:::

## `*` 演算子による list の結合

list に対して `*` 演算子で計算を行うと、その list と __同一のデータ__ を `*` 演算子の右に記述された __整数回__ だけ `+` 演算子で __結合__ するという処理が行われます。ここでいう同一のデータとは、__同じオブジェクトが管理する__ データの事です。

この、__同一のデータ__ に対して結合処理が行われるという点を __理解せず__ に `*` 演算子を使用すると、__思わぬバグの原因__ になってしまう点に注意が必要です。この後で詳しく説明しますが、`board = [[" " * 3] * 3]` が間違った初期化されたゲーム盤のデータになるのは、__そのことが原因__ になっています。

`*` 演算子でよくある誤解が、下記の 1 行目と 2 行目が同じ処理を行う式だと思ってしまうことです。実行結果を見ると、`a` と `b` の表示が同じになるので、同じ処理を表す式のように __見える__ かもしれませんが、この 2 つの式は __異なる結合処理__ を表しています。

```python
a = [1] * 3
b = [1] + [1] + [1]
print(a)
print(b)
```
実行結果
```
[1, 1, 1]
[1, 1, 1]
```

2 つの処理の違いは、`[1] * 3` が、__必ず同一の__ オブジェクトが管理するデータ を 3 回結合するのに対して、`[1] + [1] + [1]` は __異なる__ オブジェクトが管理するデータを結合するという点にあります。

:::note
上記で、`[1] + [1] + [1]` が __異なる__ オブジェクトが管理するデータを結合すると説明しましたが、実際にはプログラムの __処理の効率化__ などの __目的__ で、同一のオブジェクトが管理するデータを結合する場合があります。ただし、そのような現象が起きるのは、同一のオブジェクトが管理するデータを結合しても、その後の __プログラムの処理に影響が出ない__ 場合に限られます。

なお、どのような場合にプログラムの処理に影響が出ないかについては、次回の記事で説明します。
:::

:::note
参考までに、`[1] + [1] + [1]` のような処理で、同一のオブジェクトが管理するデータが結合される場合と、必ず異なるオブジェクトが管理するデータが結合される場合の例を紹介します。

`[1] + [1] + [1]` や、`[" "] + [" "] + [" "]` のように、list の要素に「同じ内容の __数値型__」や、「同じ内容の __文字列型__」のデータが代入された複数の list を `+` 演算子で結合すると、同一のオブジェクトが管理するデータが結合される場合があります。

下記の実行結果は、筆者のコンピューターで下記のプログラムを実行した場合の結果です。コンピューターの OS や Python のバージョンによって異なる id が表示される場合があります。

実行結果から、結合された list が代入された `a` の 3 つの要素が、__全て同じ__ オブジェクトを __参照__ することがわかります。`b` に関しても同様です。

```python
a = [1] + [1] + [1]
b = [" "] + [" "] + [" "]
print(id(a[0]), id(a[1]), id(a[2]))
print(id(b[0]), id(b[1]), id(b[2]))
```
実行結果
```
140731432669992 140731432669992 140731432669992
140731432712392 140731432712392 140731432712392
```

一方、`[[" "]] + [[" "]] + [[" "]]` のように、「__同じ値の要素を持つ list__」を要素として持つ __複数の list__ を `+` 演算子で結合すると、__必ず異なった__ オブジェクトが管理するデータが結合されます。下記のプログラムの実行結果からそのことを確認できます。

```python
a = [[" "]] + [[" "]] + [[" "]]
print(id(a[0]), id(a[1]), id(a[2]))
```
実行結果
```
2115175036160 2115175118784 2115174884416
```
:::

`[1] * 3` と同じ処理を行うプログラムは、以下のように記述できます。2 行目で、list の 3 つの要素に、`1` が代入された `a` を記述することで、`1` を管理する __同一の__ オブジェクトを参照する 3 つの要素を持つ list が `b` に代入されます。


```python
a = 1
b = [a, a, a]
print(b)
```
実行結果
```
[1, 1, 1]
```

__`*` 演算子で結合__ された list と、__同じ内容の list を `+` 演算子__ で結合して作られた list に大きな違いはないと思うかもしれませんが、この違いが __重要__ になる場合とそうでない場合があります。

## 欠陥のあるゲーム盤を初期化するプログラムの処理

欠陥のあるゲーム盤を初期化するプログラムである、下記のプログラムの 1 行目で行われる処理を順を追って説明します。その後で、2 行目の処理を実行した結果、複数のセルを表す要素が同時に変更されてしまう理由について説明します。

```python
board = [[" "] * 3] * 3
board[1][0] = "〇"
print(board)
```
実行結果
```
[['〇', ' ', ' '], ['〇', ' ', ' '], ['〇', ' ', ' ']]
```
### [" "] * 3 で行われる処理

`[[" "] * 3] * 3` は、list の中に list が __入れ子__ になっている点が __わかりづらい__ ので、このプログラムを list が __入れ子にならないように見える__ プログラムに変更することにします。

`[[" "] * 3] * 3` の中に記述されている、`[" "] * 3` は、ゲーム盤の __列（row）__ を表すデータです。そこで、`[" "] * 3` を `row` という変数に代入することで、上記のプログラムを以下のように list が __入れ子にならないように見える__ プログラムに変更することができます。

:::note
下記のプログラムの 2 行目は、list が入れ子になっていないように見えるかもしれませんが、実際には `row` に代入された値は list なので、list の入れ子が __解消されているわけではありません__。
:::

```python
row = [" "] * 3
board = [row] * 3
```

先ほど説明したように、上記のプログラムの 1 行目の `row = [" "] * 3` は、下記のプログラムの 1、2 行目のように変更することができます。

```python
a = " "
row = [a, a, a]
board = [row] * 3
```

上記のプログラムの 1 行目の処理は、単純な代入文なので説明を省略し、2 行目で行われる処理について説明します。下図は、上記のプログラムの 2 行目の処理を図示したものです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e3bd408e-7a4a-1ece-3d3c-51ff5184afed.png" width="500">

`row` に代入されている list の 3 つの要素である `row[0]`、`row[1]`、`row[2]` は、同一のオブジェクトを参照することで、同一の `" "` というデータを共有しています。この 3 つの要素はゲーム盤の列の __セル__ を表すデータなので、それぞれに __別々のデータを代入__ できる必要があります。

そのことを下記のプログラムで確認します。下記のプログラムは、上記のプログラムの 2 行目の後で、`row[0] = "〇"` という処理を追加していますが、実行結果から値が変更されるのは __`row[0]` だけ__ で `row[1]` や `row[2]` の値は __変化しない__ ことがわかります。実際のプログラムは示しませんが、`row[1] = "〇"` のような処理を行った場合でも同様です。

上記の事から `row` を __初期化された列のデータ__ として __利用できる__ ことが確認できました。


```python
a = " "
row = [a, a, a]
print(row)
row[0] = "〇"
print(row)
```
実行結果
```
[' ', ' ', ' ']
['〇', ' ', ' ']
```

これは、`row[0] = "〇"` という代入処理で、下図のような処理が行われるからです。なお、 `a` はこのプログラムではもう利用しないので、図から削除しました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b34401ba-d4ea-2b7b-f922-0719e1ef32ad.png" width="500">

下図はプログラム A の 3 行目で行われる処理です。一見すると上図と下図で異なる種類の処理が行われているように見えるかもしれませんが、「上図のインデックスが 0 の要素と、下図の `a`」、「上図のインデックスが 1、2 の要素と、下図の `b`」が対応していると考えて見比べてみると、この 2 つの処理が __同じ種類の処理__ を行っていることがわかります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/3f2c7da3-121a-4a52-1873-5ebd12416f44.png" width="350">

:::note warn
`[" "] * 3` の処理の要点をまとめると以下のようになります。

* `row = [" "] * 3` で行われる処理は プログラム A の 1、2 行目で行われる処理と __同じ種類の処理__ である
* 従って、プログラム A の `a` と `b` のように、__ゲーム盤のセル__ を表す __`row` の要素__ に __別々の値を代入__ することができる
* 従って、初期化されたゲーム盤の __列__ を表す __1 次元配列__ のデータとして、`[" "] * 3` を利用することができる
:::

また、上記の要点から、`[[" "] * 3] * 3` の中で、`[" "] * 3` の部分は　__間違っていない__ ことがわかります。

### `[[" "] * 3] * 3` で行われる処理

`[[" "] * 3] * 3` と同じ処理を行う下記のプログラムの 1、2 行目で行われる処理の説明をしましたので、次は下記の 3 行目で行われる処理を説明します。

```python
a = " "
row = [a, a, a]
board = [row] * 3
```

先程と同様に、上記の 3 行目のプログラムは、下記の 3 行目のプログラムのように変更することができます。2 行目のプログラムと 3 行目のプログラムを見比べると、__変数名が異なる__ 以外は __全く同じ処理__ が記述されており、「2 行目の `row` と 3 行目の `board`」、「2 行目の `a` と 3 行目の `row`」が対応していることがわかります。

```python
a = " "
row = [a, a, a]
board = [row, row, row]
```

下図は、上記のプログラムの 3 行目の処理を図示したものです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/de8a670a-3d4b-42ed-2d42-dd4cfcd198c3.png" width="500">

下図は、上記のプログラムの 2 行目の処理を図示したものです。上図と下図を見比べてみて、プログラムの 2 行目と 3 行目の対応通りに、「上図の `board` が 下図の `row`」に、「上図の `row` が 下図の `a`」に対応していることを確認して下さい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e3bd408e-7a4a-1ece-3d3c-51ff5184afed.png" width="500">

`board` に代入されている list の 3 つの要素である `board[0]`、`board[1]`、`board[2]` は、同一のオブジェクトを参照することで、同一の `[" ", " ", " "]` というデータを共有しています。この 3 つの要素は __ゲーム盤の列__ を表すデータです。

下記のプログラムは、__ゲーム盤の列__ を表す 3 つの要素に格納されているオブジェクトの id を表示しています。実行結果で同じ id が表示されることから、それぞれの要素が実際に __同一の__ オブジェクトを参照していることがわかります。

```python
a = " "
row = [a, a, a]
board = [row, row, row]
print(id(board[0]))   # ゲーム盤の 0 列を表す list を管理するオブジェクトの id 
print(id(board[1]))   # ゲーム盤の 1 列を表す list を管理するオブジェクトの id 
print(id(board[2]))   # ゲーム盤の 2 列を表す list を管理するオブジェクトの id 
```
実行結果
```
2115175037952
2115175037952
2115175037952
```

### `board[1][0] = "〇"` で行われる処理

`board = [[" "] * 3] * 3` を実行した際に、`board` にどのようなデータが代入されているかがわかりましたので、いよいよ問題となっている `board[1][0] = "〇"` を実行した時にどのような処理が行われるかについて説明します。

```python
a = " "
row = [a, a, a]
board = [row, row, row]
board[1][0] = "〇"
print(board)
```
実行結果
```
[['〇', ' ', ' '], ['〇', ' ', ' '], ['〇', ' ', ' ']]
```

下図は、上記のプログラムの 4 行目の `board[1][0] = "〇"` で行われる処理を図示したものです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b69ba4ed-fc22-a9ed-12e3-236497fa1727.png" width="500">

一方、下図はプログラム B の 3 行目の `a[0] = 2` で行われている処理です。`board[1][0]` は上図の ID が `117` のオブジェクトが管理するデータの 0 番の要素で、`a[0]` は下図の ID が `150` のオブジェクトが管理するデータの 0 番の要素です。この 2 つを対応させて 上図と下図を見比べると、この 2 つの処理が __同じ種類の処理__ を行っていることがわかります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b0f33550-e69a-dccb-f6b2-8a2141424db9.png" width="500">

このように、`board = [[" "] * 3] * 3` と、プログラム B では、同じ種類の処理が行われています。そのため、プログラム B の `a[0]` に対応する `board[1][0]` に値を代入すると、プログラム B の `a` と `b` に対応する `board[0]`、`board[1]`、`board[2]` の値が同時に変更されるように見えます。実際には、プログラム B と同様に、`board[0]`、`board[1]`、`board[2]` は __同じデータ共有__ しているためそのような現象がおきるのです。

`board[1][0]` は __ゲーム盤のセル__ を表すため、他のセルを表す `board` の要素の要素に __別々のデータを代入__ できる必要があります。しかし、`board[0][0]` と `board[1][0]` と `board[2][0]` は __同じデータを共有__ してるため、別々のデータを代入することはできません。そのため、`board` をゲーム盤を表すデータとして使用することは __できない__ のです。

:::note warn
`[[" "] * 3] * 3` の処理の要点をまとめると以下のようになります。

* `board = [[" "] * 3] * 3` で行われる処理は プログラム B の 1、2 行目で行われる処理と __同様の処理__ である
* 従って、プログラム B のように、__ゲーム盤のセル__ を表す __`board` の要素の要素__ に値を代入すると、同時に __ゲーム盤の列__ を表す __`board` の要素__ が変更されてしまう
* 従って、初期化された __ゲーム盤__ を表す __2 次元配列__ のデータとして、`[[" "] * 3] * 3` を利用することはできない
:::

下記は、先ほど示した `[" "] * 3` の要点です。下記では、上記と __異なっている__ 部分を __太字__ で表記しました。下記の太字の部分が上下でどのように異なっているかに注目して上下の要点を見比べてみて下さい。

:::note warn
__`[" "] * 3`__ の処理の要点をまとめると以下のようになります。

* __`row = [" "] * 3`__ で行われる処理は プログラム __A__ の 1、2 行目で行われる処理と同様の処理である
* 従って、プログラム __A の `a` と `b`__ のように、ゲーム盤のセルを表す __`row` の要素__ に __別々の値を代入することができる__
* 従って、初期化された ゲーム盤 __の列__ を表す __1__ 次元配列のデータとして、__`[" "] * 3`__ を利用することが __できる__
:::

上記の 2 つの要点の重要な相違点は以下の 2 点です。

1 つは 3 つめの箇条書きから、 `[" "] * 3` が __ゲーム盤の列__ を表す __1 次元配列__ のデータであるのに対し、`[[" "] * 3] * 3` は __ゲーム盤そのもの__ を表す __2 次元配列__ のデータであるということです。この 2 つのデータは、どちらも同じような list に見えるかもしれませんが、__データの意味__ と配列の __次元の数__ が異なります。

もう 1 つは、2 つめの箇条書きの「ゲーム盤のセルを表す」の直後の「__`board` の要素の要素__」と「__`row` の要素__」の部分です。「board の __要素の要素__」という表現がわかりにくいと思いますが、`board` は list の要素の中に list が代入された __2 次元配列__ を表すデータです。`board` の __1 つ目__ のインデックスで「__ゲーム盤の列__」のデータが代入された __要素__ を参照し、2 つ目のインデックスで「__ゲーム盤のセル__」が代入された要素を参照しているので、「__要素の要素__」という表現になっています。

この違いを表にすると以下のようになります

|  | `[" "] * 3` | `[[" "] * 3] * 3` |
|:--|:--|:--|
| データを代入する変数名  | `row`  | `board`  |
| データの意味  | ゲーム盤の列  | ゲーム盤  |
| ゲーム盤のセルを表すデータ  | `row` の要素  | `board` の要素の要素  |
| セルの値の変更で行われる処理  | 変更するのは、`row` の __要素__ なので、他のセルを表す `row` の要素の値は __変更されない__  | 変更するのは `board` の __要素の要素__ である。ゲーム盤の列を表す `board` の __要素__ は、__同じデータを共有__ しており、セルの値を変更する際に `board` の要素は __どれも変更されない__  |

:::note warn
`*` 演算子を使って list を結合する場合は、以下の点に注意して下さい。

* 結合する list の要素に、数値型や文字列型のデータが代入されている場合は、`*` 演算子でその list を結合しても問題は発生しない
* 結合する list の要素に、__list__ が代入されていた場合は、結合された list の要素は、__同一の list を共有__ してしまうため、思わぬバグの原因になってしまう可能性が高いので __避けたほうが良い__

数値型、文字列型、list 以外のデータの場合については、次回の記事で説明します。
:::

## `row` と `board` の違い

`row = [a, a, a]` では、ゲーム盤のセルを表す要素に異なる値を代入できるのに、`board = [row, row, row]` ではそれができないことの理由がまだピンとこない人がいるかもしれないので補足します。

そのような人は、おそらく `row[0] = "〇"` という処理と、`board[1][0] = "〇"` という処理が似ているように見えることが混乱の原因になっているのではないかと思います。確かにどちらも、__ゲーム盤のセルを表す要素__ に値を代入するという意味では同じ種類の処理を行っています。

しかし、`row[0] = "〇"` は、「`row` に代入された __list の要素__」に値を代入する、`board[1][0] = "〇"` は、「`board` に代入された __list の要素の要素__」に値を代入するという意味では __異なる種類の処理__ が行われています。

「__変数に代入された list の要素__」に値を代入するという意味では、`row[0] = "〇"` に対応する処理は、`board[1] = ["〇", " ", " "]` です。この処理によって、ゲーム盤の x 座標が 1 の列を表す要素に新しい list が代入され、`board[1]` と `board[0]`、`board[2]` のデータの共有が解除されます。そのため、__`board` の要素__ にゲーム盤の列を表す list を代入することで、`board[0]`、`board[1]`、`board[2]` に __別々の__ 列のデータを代入することができます。

:::note
この説明と同じような説明をどこかで見たような気がした人はいませんか？これは、前回までの記事で、プログラム A と B の処理の違いを説明する際に行った、「`a = 2` と `a[0] = 2` は __見た目は似ている__ が、__異なる処理__ を行う」、「プログラム A の `a = 2` に対応するプログラム B の処理は `a = [4, 5, 6]` である」という説明と同じです。
:::

下記は、欠陥のあるゲーム盤を初期化するプログラムの後で、上記の `board[1] = ["〇", " ", " "]` を実行するプログラムです。実行結果からわかるように、__ゲーム盤の列__ を表す要素に __別々のデータを代入__ することができます。

```python
a = " "
row = [a, a, a]
board = [row, row, row]
board[1] = ["〇", " ", " "]
print(board)
```
実行結果
```
[[' ', ' ', ' '], ['〇', ' ', ' '], [' ', ' ', ' ']]
```

ゲーム盤に対して行う処理の内容が、「ゲーム盤の __列__ を変更する」という __処理だけ__ であれば、`board = [[" "] * 3] * 3` を使って、初期化されたゲーム盤を作成しても問題はありません。しかし、実際には「ゲーム盤の __セル__ を変更する」という処理が必要です。そのため、`board = [[" "] * 3] * 3` をゲーム盤のデータとして利用することはできません。

## 正しく動作するプログラムへの修正方法

`board = [[" "] * 3] * 3` の問題点は、この処理を以下のように変更した際の 3 行目で、`board` の要素に、__同一のオブジェクト__ を参照する list を代入している点にあります。

```python
a = " "
row = [a, a, a]
board = [row, row, row]
```

上記のプログラムの 3 行目で、`board` の要素に `[" ", " ", " "]` という、__同じ内容の list を管理__ する、__異なるオブジェクト__ を参照する list を代入すればこの問題を解決することができます。

`*` 演算子を使って list を作成すると、__必ず同一の__ list を参照するオブジェクトが要素に代入されるので、上記のプログラムを `*` 演算子を __使わない__ ように修正する必要があります。

そのような修正は、以下のプログラムのように、__リスト内包表記__ を使うことで行うことができます。下記のプログラムを実行すると、実行結果から (1, 0) を表す要素のみが `"〇"` になっており、正しい初期化されたゲーム盤のデータが作成されていることを確認できます。

```python
board = [[" "] * 3 for x in range(3)]
board[1][0] = "〇"
print(board)
```
実行結果
```
[[' ', ' ', ' '], ['〇', ' ', ' '], [' ', ' ', ' ']]
```

上記のプログラムが正しく動作する理由は、リスト内包表現を使わないように、上記のプログラムを変更すると理解しやすいでしょう。下記のプログラムはそのように変更したプログラムです。

```python
board = []
for x in range(3):
    board.append([" "] * 3)
board[1][0] = "〇"
print(board)
```
実行結果
```
[[' ', ' ', ' '], ['〇', ' ', ' '], [' ', ' ', ' ']]
```
プログラムの 3 行目の `board.append([" "] * 3)` は、3 回繰り返して実行されます。__list の結合処理__ は、__新しい list を作成する処理__ であることを思い出してください。従って、繰り返しのたびに `[" "] * 3` の部分で、同じ内容の list を管理する __異なる__ オブジェクトが作成されて、`board` の要素に追加されます。そのため、`board[1][0] = "〇"` のように、ゲーム盤のセルを表す、`board` の要素の要素に値を代入した際に、他のセルを表す要素が同時に変更されるという問題点が解消されています。

下記のプログラムは、ゲーム盤の列を表す 3 つの要素に格納されているオブジェクトの id を表示しています。実行結果から、表示される id が異なることから、それぞれの要素が実際に異なるオブジェクトを参照していることがわかります。

```python
board = [[" "] * 3 for x in range(3)]
print(id(board[0]))   # ゲーム盤の 0 列を表す list を管理するオブジェクトの id 
print(id(board[1]))   # ゲーム盤の 1 列を表す list を管理するオブジェクトの id 
print(id(board[2]))   # ゲーム盤の 2 列を表す list を管理するオブジェクトの id 
```
実行結果
```
2115175038912
2115174964672
2115174967360
```

## 次回の記事の内容について

これで、`board = [[" "] * 3] * 3` が欠陥のある処理を行っている理由と、正しい初期化されたゲーム盤を記述する方法を示すことができました。

ここまでの記事では、__数値型__、__文字列型__、__list__ の の 3 つのデータ型に関する代入処理しか説明しませんでした。そのため、他のデータ型の代入処理でどのような処理が行われるかについて、疑問に思っている方も多いのではないかと思います。

そこで、次回の記事では今回までの記事で説明できなかった、代入処理に関する補足説明を行い、〇×ゲームの実装の再開は、その次の記事から行うことにします。

# 本記事で入力したプログラム

以下のリンクから、本記事で入力して実行した JupyterLab のファイルを見ることができます。

https://github.com/ysgeso/marubatsu/blob/master/010/marubatsu.ipynb

# 次回の記事

https://qiita.com/ysgeso/items/cc9e67c16b0814c8bc2e

| 更新日時 | 更新内容 |
|:--|:--|
| 2023/09/29 | `for y in range(3)` → `for x in range(3)` |