Pythonで〇×ゲームのAIを一から作成する　その6　list と繰り返し処理

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/0d64d1ddba1982b5a5d3

# 前回のおさらい

前回の記事では、〇×ゲームのゲーム盤を表す __データ構造__ を決め、そのデータ構造に従って __全てのマスが空のゲーム盤のデータ__ をプログラムで記述しました。

ただし、前回の記事で紹介したプログラムの記述方法は __効率が良くない__ ため、繰り返し処理を使って効率よく記述する方法を紹介することにしました。

そのための前提知識として、前回の記事では Python の繰り返し処理の基本的な説明を行いました。

:::note
「全てのマスが空のゲーム盤のデータ」という表現は冗長なので、以後は「__初期化されたゲーム盤__」と表記することにします。
:::

# 繰り返し処理を使った Python の list の作成

今回の記事では、初期化されたゲーム盤を表す 2 次元配列の list を、繰り返し処理を使って記述する方法について説明します。

## 空の list

前回の記事では、下記のプログラムのように、list の要素に代入したいデータを __全て__ プログラムで __記述する__ ことで、初期化されたゲーム盤を表す 2 次元配列の list を記述していました。

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

繰り返し処理で上記と同じ list を作成する場合は、__要素が一つもない__ list を作成し、その list に __後から要素を追加__ するという方法でプログラムを記述します。

:::note warn
要素が 1 つもない list のことを __空の list__ と呼び、Python では空の list を `[]` のように記述します。
:::

下記のプログラムは、`a` に空の list を代入する処理を行っています。

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

:::note warn
上記のプログラムのように、変数に初めてデータを代入する処理の事を __変数の初期化__ と呼びます。
:::

## list の要素の追加

Python の list は、下記のように記述することで、list の要素の __末尾__ に __新しい要素を追加__ することができます[^1]。

```
listのデータ.append(新しい要素に追加するデータ)
```

:::note
list には、他にもいくつか要素を追加する方法があります。それらについては、必要になった時点で説明します。
:::

:::note
配列の要素の末尾に新しい要素を追加する処理は、配列の種類やプログラム言語の違いによって、`add`、`push` など、異なる名前で記述する場合があるので、__配列の種類やプログラム言語ごとに覚える__ 必要があります。

また、配列の種類によっては、__最初に__ 配列の次元と要素数を __決めておく__ 必要があり、__後から新しい要素を追加することができない__ ようなものもあります。Python の場合は、numpy というモジュールが提供する配列のデータがそのような性質を持ちます。
:::

## ゲーム盤の列を表す list の記述方法

過去の記事で説明したように、初期化されたゲーム盤を表す __2 次元配列の list__ は、下記のプログラムのように、ゲーム盤の列ごとに __全てのマスが空の列のデータ__ を表す list を一旦変数に代入し、それらを list の要素とするようなプログラムで書き直すことができます。

:::note
「全てのマスが空の列のデータ」も冗長なので、以後は「__初期化された列__」と表現します。
:::

```python
row0 = [" ", " ", " "]       # 0 列のデータ
row1 = [" ", " ", " "]       # 1 列のデータ
row2 = [" ", " ", " "]       # 2 列のデータ
board = [row0, row1, row2]   # ゲーム盤のデータ  
```

上記のプログラムから、初期化されたゲーム盤を表す 2 次元配列の list を作成するためには、__先に__ 初期化された列を表す list を作成する必要があることがわかります。

初期化された列を表す list は、__空のリスト__ に `append` を使って __3 回__ 半角の空白文字が代入された __要素を追加__ するという処理で作成することができます。下記は、初期化された 0 列を表す list を作成するプログラムです。実行結果を見て、目的の list が `row0` に代入されていることを確認して下さい。

```python
row0 = []
row0.append(" ")
row0.append(" ")
row0.append(" ")
print(row0)
```
実行結果
```
[' ', ' ', ' ']
```

上記のプログラムは、`row0.append(" ")` という、__全く同じ処理__ を 3 回繰り返す処理なので、for 文を使って下記のように記述することができます。

```python
row0 = []
for y in range(3):
    row0.append(" ")
print(row0)
```
実行結果
```
[' ', ' ', ' ']
```
:::note
前回の記事で、for 文の中の、反復可能オブジェクトから取り出したデータを代入する変数の名前は、代入する __データの内容__ を表す __簡潔な名前__ を使うのが一般的だということをノートで説明しました。

上記のプログラムでは、for 文の繰り返しのブロックの中で、0 列の __0、1、2 行__ のデータを表す要素を追加しています。そのため、反復可能オブジェクトである `range(3)` から取り出したデータは、__行の番号__ を表します。ゲーム盤のセルの __y 座標__ が行の番号を表すので、変数の名前を `y` にしました。

なお、上記のプログラムでは、for 文の繰り返しのブロックの中で、この `y` という変数は __どこにも使われていません__。このような場合は、その変数が __どこにも使われていない__ ということを __明確にするため__ に Python では __`_`__[^2] という名前の変数を使うという __慣習__ があります。なお、これは慣習なので __必須ではありません__。

`_` という名前の変数は、上記以外の場合でも、変数に値を代入する __必要はある__ が、その変数をプログラムの中で __使用しない__ 場合でも使われます。その使用例については具体例が出てきた時に説明します。

ただし、上記のプログラムでは、変数名を `y` としたほうが、for 文で __何を繰り返しているか__ が __明確になる__ ため、`_` とするよりも __わかりやすい__ と思いましたので、`y` という名前の変数にしています。
:::

## 本記事でのプログラムの修正箇所の表記方法

プログラムを __少しずつ修正__ しながらプログラムを作り上げていく場合、プログラムが長くなると、修正の際にどの部分が __変更されたかがわかりにくく__ なります。

そこで以降の記事では、以前のプログラムを __修正した__ プログラムを表記する際に、新しいプログラムだけでなく、__修正箇所__ がわかるようなプログラムの表記も行うことにします。

ただし、以下の場合は修正箇所を表記しません。

* プログラムが短い場合など、修正箇所を __説明しなくても簡単に分かる__ ような場合
* 変更箇所が多く、修正箇所を示すと逆に __わかりづらくなる__ ような場合
* 新しいプログラムを記述した場合

具体的には、以下のように修正箇所を表記します。なお、下記の表記方法は、プログラムの修正箇所を表記する方法として実際に良く使われる方法の一つです。

* 新しく追加された __行の先頭に +__ を表示し、その行を __緑色の背景__ で表示する
* 前のプログラムから削除された __行の先頭に -__ を表示し、その行を __赤色の背景__ で表示する
* 前のプログラムから __行の一部が変更__ された場合は、__行の先頭に -__ を表示して __変更前の行__ を表示し、__その次の行の先頭に +__ を表示して __変更後の行__ を表示する

:::note
追加、または削除されたことを表す行の先頭に表記される `+` または `-` は __プログラムの一部ではない__ 点に注意して下さい。
:::

言葉の説明だけではわかりづらいと思いますので、以下に具体例を示します。下記では修正前のプログラムに対し、以下のような修正が行われています。

* 1 行目の直後に `b = a + 1` を __追加する__
* 2 行目を `print(a)` から `print(b)` に __変更する__
* 3 行目の `print(a * a)` を __削除する__

修正前のプログラム
```python
a = 1          # この行は変更されない
print(a)       # この行は print(b) に変更される
print(a * a)   # この行は削除される
```
修正後のプログラム
```python
a = 1          # この行は変更されない
b = a + 1      # この行が追加される
print(b)       # この行は print(a) から変更される
```
修正箇所は以下のように表示されます
```diff_python
a = 1            # この行は変更されない
+ b = a + 1      # この行が追加される
- print(a)       # この行は print(b) に変更される
+ print(b)       # この行は print(a) から変更される
- print(a * a)   # この行は削除される
```

修正箇所を常に表示すると、どうしても __記事が長く__ なってしまうので、普段は修正箇所を __折りたたんで表示されない状態__ にします。

折りたたまれた修正箇所は、下記のように __左端に三角形のマーク__ と「__修正箇所__」のような文字が表示された行を __クリック__ することで表示されるようになります。また、もう一度同じ行をクリックすると再び折りたたまれます。実際に下記の「修正箇所」と表記された部分をクリックして確認して下さい。

<details><summary><strong>修正箇所</strong></summary>

```diff_python
a = 1
+ b = a + 1
- print(a)
+ print(b)
```
</details>

## 2 次元配列を表す list の記述方法

初期化されたゲーム盤を表す 2 次元配列の list を作成するプログラムは、for 文を使って、下記のように記述し直すことができます。なお、下記のプログラムは修正箇所が多いので修正箇所は表記しません。

```python
row0 = []              # 0 列のデータを作成する
for y in range(3):
    row0.append(" ")

row1 = []              # 1 列のデータを作成する
for y in range(3):
    row1.append(" ")

row2 = []              # 2 列のデータを作成する
for y in range(3):
    row2.append(" ")

table = [row0, row1, row2]
print(table)
```
実行結果
```
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

:::note
上記のプログラムでは、列ごとの処理を区別できるように __間に何もプログラムを記述しない行__ を入れています。このような、__何もプログラムを記述しない行__ の事を __空行__ と呼びます。空行は、__プログラムを読みやすくする__ ために良く使われます。本記事でもプログラムが読みやすくなるように、必要に応じてプログラムに空行を記述します。
:::

上記のプログラムは同じような処理を 3 回行っていますが、その 3 回の処理では `row0`、`row1`、`row2` という __異なる名前__ の変数が使われています。そのため、繰り返し処理でまとめることができないように思えるかもしれませんが、実際には __繰り返し処理でまとめる__ ことができます。その理由とまとめる方法について説明します。

上記のプログラムでは、`row0` などに初期化された列を代入する処理を、繰り返し処理を使って行うように __元のプログラムから変更__ していますが、`table` の処理は元のままで __変更していません__。繰り返し処理を使って list を作成するためには、「空の list を代入」し、「要素を `append` で追加する」という処理を行う必要があるので、`table` の処理を下記のプログラムのように修正します。


```python
table = []

row0 = []              # 0 列のデータを作成する
for y in range(3):
    row0.append(" ")
table.append(row0)     # table に 0 列のデータを追加する

row1 = []              # 1 列のデータを作成する
for y in range(3):
    row1.append(" ")
table.append(row1)     # table に 1 列のデータを追加する

row2 = []              # 2 列のデータを作成する
for y in range(3):
    row2.append(" ")
table.append(row2)     # table に 2 列のデータを追加する

print(table)
```

<details><summary><strong>修正箇所</strong></summary>

```diff_python
+ table = []

row0 = []              # 0 列のデータを作成する
for y in range(3):
    row0.append(" ")
+ table.append(row0)   # table に 0 列のデータを追加する

row1 = []              # 1 列のデータを作成する
for y in range(3):
    row1.append(" ")
+ table.append(row1)   # table に 1 列のデータを追加する

row2 = []              # 2 列のデータを作成する
for y in range(3):
    row2.append(" ")
+ table.append(row2)   # table に 2 列のデータを追加する

- table = [row0, row1, row2]
print(table)
```

</details>

実行結果
```
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

上記のように修正したプログラムでも、各列の処理で `row0`、`row1`、`row2` という __異なる名前__ の変数が使われているので繰り返し処理でまとめられないように思えるかもしれませんが、以下の手順でうまくまとめることができるようになります。

下記は、先ほどのプログラムから「0 列のデータを作成し、`table` の要素に追加」する処理を抜き出したものです。

```python
row0 = []
for y in range(3):
    row0.append(" ")
table.append(row0)
```

このプログラムは 0 列のデータを作成する処理なので、__`row0`__ という名前の変数を使っていますが、この変数の名前を以下のプログラムのように __`row` に変更__ しても __全く同じ処理__ が行われます。

```python
row = []
for y in range(3):
    row.append(" ")
table.append(row)
```

同様に、残りのプログラムの `row1` と `row2` を `row` に変更すると以下のようなプログラムになります。

```python
table = []

row = []               # 0 列のデータを作成する
for y in range(3):
    row.append(" ")
table.append(row)      # table に 0 列のデータを追加する

row = []               # 1 列のデータを作成する
for y in range(3):
    row.append(" ")
table.append(row)      # table に 1 列のデータを追加する

row = []               # 2 列のデータを作成する
for y in range(3):
    row.append(" ")
table.append(row)      # table に 2 列のデータを追加する

print(table)
```
<details><summary><strong>修正箇所</strong></summary>

```diff_python
table = []

- row0 = []               # 0 列のデータを作成する
+ row = []                # 0 列のデータを作成する
for y in range(3):
-    row0.append(" ")
+    row.append(" ")
-table.append(row0)       # table に 0 列のデータを追加する
+table.append(row)        # table に 0 列のデータを追加する

- row1 = []               # 1 列のデータを作成する
+ row = []                # 1 列のデータを作成する
for y in range(3):
-    row1.append(" ")
+    row.append(" ")
- table.append(row1)      # table に 1 列のデータを追加する
+ table.append(row)       # table に 1 列のデータを追加する

- row2 = []               # 2 列のデータを作成する
+ row = []                # 2 列のデータを作成する
for y in range(3):
-    row2.append(" ")
+    row.append(" ")
- table.append(row2)      # table に 2 列のデータを追加する
+ table.append(row)       # table に 2 列のデータを追加する

print(table)
```

</details>

実行結果
```
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

上記のプログラムでは、__同じ名前__ の `row` という変数を __使いまわして__ 0、1、2 列を表す list を作成し、`table` の要素に追加していますが、このような変数の使いまわしをしても大丈夫なのでしょうか？結論から言うと、この場合は同じ変数を __使いまわしても問題はありません__。その理由を説明します。

このプログラムの __目的__ は、初期化されたゲーム盤を表す 2 次元配列の list を作成し、__`table` という変数に代入する__ ことです。`row` という変数は __その処理を行う過程__ で、__初期化された列__ を表す list を __一時的に保存しておくための入れ物__ に過ぎません。

```python
row = []
for y in range(3):
    row.append(" ")
```

上記の繰り返し処理によって `row` に代入された、初期化された列を表す list  は、その次の `table.append(row)` で `table` の要素として __追加された後__ は、`row` の中に __記録しておく必要はなくなります__。そのため、次の列の処理の最初で `row = []` を実行して、`row` に __次の列のデータ__ を表す空の list を代入して中身を __上書き__ することで、`row` を別の用途で使いまわして __再利用する__ ことができます。

現実の例で例えると、メモ用紙に伝えたい内容を書きこんで、その内容を見せて伝える場合に似ています。メモの内容を見せて伝えた後は、そのメモ用紙に __書かれた内容は用済み__ になります。そのため、そのメモ用紙に __別の内容を書きこんで__ 見せるという、別の用途で使いまわして __再利用する__ ことができます。

従って、0、1、2 列の処理を行うプログラムで記述されている `row` は、いずれも __同じ変数__ ですが、中に代入されている list はそれぞれ __異なる__ 列のデータを表しています。そのため、上記のプログラムは正しく動作します。

0、1、2 列の処理を行うプログラムの記述が、__全く同じ__ になったので、上記のプログラムを for 文を使って以下のようにまとめることができます。わかりやすいように、プログラムにはコメントを入れておきました。

```python
# table を空の list で初期化する
table = []
# 各列の繰り返しを行う
for x in range(3):
    # x 列のデータを表す row を、空の list で初期化する
    row = []
    # x 列の各行の繰り返しを行う
    for y in range(3):
        # x 列を表す list の要素に y 行のセルを表す半角の空白文字を追加する
        row.append(" ")
    # table に 初期化された x 列を表す row を追加する
    table.append(row)
print(table)
```
実行結果
```
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

:::note warn
このように、2 次元配列を表す list は、__2 重に入れ子にした for 文__ による繰り返し処理を使って記述することができます。

2 重の for 文による繰り返し処理は、プログラミングにおいて __頻繁に__ 出てくる __重要な処理__ なので、その意味と使い方を必ず理解して下さい。
:::

:::note
プログラミングでは一般的に、__同じ名前の変数__ を __異なる用途__ で __使いまわすべきではない__ と言われています。その理由は、そのようなプログラムを記述すると、プログラムの中で使いまわした変数がどのような __意味を持つ__ かが __非常に分かりにくくなる__ からです。

ただし、同じ名前の変数に __同種の__ 異なるデータを代入して使いまわすということは実際に良く行われます。その具体例の一つが、__繰り返し処理のブロックの中だけ__ で使われる変数です。

上記のプログラムでは、__`row`__ という `for x in range(3):` の繰り返し処理の __ブロックの中だけ__ で使われている変数は、一見すると使いまわされていないように __見える__ かもしれませんが、実際にはこの繰り返し処理の中で __異なる__ 「初期化された列を代入する」という目的で、__同じ名前の変数__ が 3 回 __使いまわされています__。

他にも、同じ名前の変数を、プログラムの異なる場所で使いまわしても良い場合がありますが、それらについては具体例が出てきた時に説明します。
:::

## デバッグ出力を使った処理の流れの確認

2 重の for 文による繰り返し処理は、慣れるまでは __どのような処理__ が行われているかを __理解できない__ 人が多いと思います。プログラムの __処理の流れ__ を __確認__ するために、`print` などを使って __メッセージを出力__（表示）するという方法があり、そのような表示の事を __デバッグ出力__ または __デバッグ表示__ と呼びます。

:::note
デバッグ（debug）とは、プログラムのエラーを表す __バグ__ （bug）を __修正__ して取り除く作業のことを表します。このように書くと、デバッグのことを __既に発見されている__ バグを修正する作業のように思う人がいるかもしれませんが、デバッグにはプログラムに __発見されていないバグ__ がないかどうかを __確認する__ 作業も含まれます。

プログラムの処理の流れを確認するという作業は、プログラムに __バグがないかどうかを確認する__ ためにもよく行われます。そのため、プログラムの処理の流れを確認するための表示もデバッグ出力と呼びます。

他のデバッグ出力の例としては、__計算結果のみ__ を表示して、おかしな計算が行われていないことを __確認する__ というものがあります。これまでのプログラムの最後で `print(table)` のように、変数の値を表示していたのは、プログラムで正しい計算が行われているかどうかを確認するための __デバッグ出力__ です。
:::

デバッグ出力は、処理の流れを確認したい場所に __`print`__ で __処理に関係する変数の値__ などを表示するという方法で記述します。先ほどのプログラムの場合、`table` や `row` の中身が、プログラムの処理によってどのように __変化していく__ かがわかれば、プログラムで行われている処理の流れを理解しやすくなります。

そこで、下記のプログラムでは、以下の処理が行われた直後に、`print` を記述して、`x`、`y`、`row`、`table` などのデータを表示しています。なお、デバッグ出力の意味が分かりやすくなるように、表示内容を工夫をしました。

* 列の繰り返し処理を開始した後で `row` に `[]` を代入した直後
* 行の繰り返し処理の中で `row` の要素に半角の空白文字を追加した直後
* 列の繰り返し処理の最後で `table` の要素に `row` を追加した直後


```python
# table を空の list で初期化する
table = []
# 各列の繰り返しを行う
for x in range(3):
    # x 列のデータを表す row を空の list で初期化する
    row = []
    # row の値をデバッグ出力する
    print("列の処理の開始 x =", x, "row =", row)
    # x 列の各行の繰り返しを行う
    for y in range(3):
        # x 列を表す list の要素に y 行のデータを表す空白文字を追加する
        row.append(" ")
        # row の値をデバッグ出力する
        print("x =", x, "y =", y, "row =", row)
    # table に x 列のデータを表す row を追加する
    table.append(row)
    # table の値をデバッグ出力する
    print("列の処理の終了 x =", x, "table =", table)
print(table)
```
<details><summary><strong>修正箇所</strong></summary>

```diff_python
# table を空の list で初期化する
table = []
# 各列の繰り返しを行う
for x in range(3):
    # x 列のデータを表す row を空の list で初期化する
    row = []
    # row の値をデバッグ出力する
+   print("列の処理の開始 x =", x, "row =", row)
    # x 列の各行の繰り返しを行う
    for y in range(3):
        # x 列を表す list の要素に y 行のデータを表す空白文字を追加する
        row.append(" ")
        # row の値をデバッグ出力する
+       print("x =", x, "y =", y, "row =", row)
    # table に x 列のデータを表す row を追加する
    table.append(row)
    # table の値をデバッグ出力する
+   print("列の処理の終了 x =", x, "table =", table)
print(table)
```
</details>

実行結果
```
列の処理の開始 x = 0 row = []
x = 0 y = 0 row = [' ']
x = 0 y = 1 row = [' ', ' ']
x = 0 y = 2 row = [' ', ' ', ' ']
列の処理の終了 x = 0 table = [[' ', ' ', ' ']]
列の処理の開始 x = 1 row = []
x = 1 y = 0 row = [' ']
x = 1 y = 1 row = [' ', ' ']
x = 1 y = 2 row = [' ', ' ', ' ']
列の処理の終了 x = 1 table = [[' ', ' ', ' '], [' ', ' ', ' ']]
列の処理の開始 x = 2 row = []
x = 2 y = 0 row = [' ']
x = 2 y = 1 row = [' ', ' ']
x = 2 y = 2 row = [' ', ' ', ' ']
列の処理の終了 x = 2 table = [[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

実行結果のデバッグ出力を見て、`row` と `table` がどのようにプログラムの処理によって変化していくかを確認して下さい。

:::note
デバッグ出力は `print` を記述するだけで表示できるので、__簡単に記述__ できて便利なのですが、デバッグ出力が __表示されすぎて__ 訳が分からなくなることがあるなどの欠点があります。

例えば、上記のプログラムの、ゲーム盤の __縦横のサイズ__ を表す 2 つの `range(3)` を `range(100)` に変えて実行すると、100 × 100 = 10000 以上の __大量のデバッグ出力__ が表示されてしまいます。そのような場合は、条件分岐などを使って __重要な場合だけに絞って__ デバッグ出力を行うように工夫する必要があります。

デバッグ出力は、バグが発生した際に、プログラムのどこに __バグがあるかを探す__ 場合でも良く使われます。そのような場合は、プログラムの中で __バグの原因になりそうな場所__ でデバッグ出力を行うことで、エラーが起きている場所を __絞り込んでいく__ という方法でバグを探します。具体例については今後の記事で紹介する予定です。

プログラムの処理の流れを確認する他の方法としては、プログラムの開発環境の __デバッガ機能__ を使ってプログラムの処理を __1 行ずつ実行しながら追っていく__ という方法があります。こちらの方法はプログラムの流れを詳細に追っていくことができるので非常に __強力で便利__ なのですが、うまく使いこなすためには __ある程度の熟練__ が必要です。VSCode の JupyterLab で Python のプログラムを実行する際でもこの機能を使うことができますが、その説明を行うと非常に長くなってしまうのでここでは割愛します。興味がある方は調べてみると良いでしょう。
:::

## リスト内包表記

Python では、for 文と `append` を使って list を記述するプログラムを、__リスト内包表記__ という方法で __簡潔に記述__ することができます。リスト内包表記は、他のプログラム言語ではあまり見られない記法なので、初めて見る方は戸惑ったり、意味が分かりにくいと思うかもしれませんが、便利なので Python では __良く使われます__。

リスト内包表記は、下記のように記述します。

```python
[ 式 for 変数名 in 列挙可能オブジェクト ]
```

リスト内包表記で行われる処理は、それと __全く同じ処理__ を for 文 と `append` を使って記述するプログラムを比較するのが理解しやすいでしょう。

下記の 2 つのプログラムは __全く同じ処理__ を行うプログラムです。下のプログラムは 3 行のプログラムを記述する必要がありますが、上のリスト内包表記を使ったプログラムでは、__同じ処理を 1 行で簡潔に記述__ することができます。

上のプログラムと下のプログラがどのように対応するかを見比べてみて下さい。

```python
a = [ 式 for 変数名 in 列挙可能オブジェクト ]
```
```python
a = []
for 変数名 in 列挙可能オブジェクト:
    a.append(式)
```

:::note
list 内包表記を使わないプログラムでは、最初に __空の list を変数に代入__ する処理が __必要__ になります。そのため、上のプログラムでも、list 内包表記で作られた list を、__下のプログラムに合わせて__ `a` という変数に __代入__ しています。
:::

これだけではまだピンとこない人もいると思いますので、具体例をいくつか挙げます。

### 1 次元配列を表す list の例

下記の 2 つのプログラムは全く同じ処理を行います。2 つのプログラムを見比べて、どことどこが対応しているかについて確認して下さい。

```python
a = [ i * i for i in range(10)]
print(a)
```
```python
a = []
for i in range(10):
    a.append(i * i)
print(a)
```
実行結果はどちらも以下のようになります。
```
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
```
### 2 次元配列を表す list の例

リスト内包表記を使えば、以下の表のような __2次元配列を表す list__ も、下記のプログラムのように簡潔に記述することができます。

|  | __0__ | __1__ | __2__ |
|:-:|:-:|:-:|:-:|
| __0__  | 0 | 1 | 2 |
| __1__  | 3 | 4 | 5 |
| __2__  | 6 | 7 | 8 |

なお、下記のプログラムは、この表の __(x, y) のセル__ の内容が、__x + y * 3__ という式で計算できることを利用しています。

```python
table = [ [ x + y * 3 for y in range(3) ] for x in range(3)]
print(table)
```
実行結果
```
[[0, 3, 6], [1, 4, 7], [2, 5, 8]]
```

なぜこのプログラムがうまく動作するかがわからない人もいると思いますので、上記のプログラムを、リスト内包表記を使わないプログラムに修正してみます。

上記のプログラムでは、リスト内包表記が __入れ子で記述__ されている点がわかりにくさの原因になっています。そこで、プログラムをわかりやすくするために、__外側のリスト内包表記__ を、__リスト内包表記を使わない__ プログラムに __修正__ することにします。

下記はそのような修正を行ったプログラムです。この修正では、上記のプログラムの `[ x + y * 3 for y in range(3) ]` の部分を `table = [ 式 for 変数名 in 列挙可能オブジェクト ]` の「式」の部分だと考えると理解できると思います。

```python
table = []
for x in range(3):
    table.append([ x + y * 3 for y in range(3) ])
print(table)
```
実行結果
```
[[0, 3, 6], [1, 4, 7], [2, 5, 8]]
```

上記の `table.append` の __`()`中の__ リスト内包表記を修正すると下記のプログラムのようになります。下記のプログラムは、リスト内包表記を説明する前に説明した、2 次元配列を表す list を表記する方法で記述されたプログラムです。従って、`[ [ x + y * 3 for y in range(3) ] for x in range(3)]` というリスト内包表現で、2 次元配列を表す list を正しく記述できることがわかります。

```python
table = []
for x in range(3):
    row = []                  
    for y in range(3):          
        row.append(x + y * 3)
    table.append(row)
print(table)
```
実行結果
```
[[0, 3, 6], [1, 4, 7], [2, 5, 8]]
```

:::note
`[ x + y * 3 for y in range(3) ]` をリスト内包表記を使わないプログラムに修正する際には、その list を表すデータを __代入するための変数__ を用意する必要があります。このデータは、表の列（row）を表すデータなので、その変数の名前を `row` としました。
:::

### 〇×ゲームの初期化されたゲーム盤

〇×ゲームの初期化されたゲーム盤は、__すべての要素__ に __半角の空白文字__ が代入された 2次元配列の list なので、それを作成するプログラムは以下のように記述できます。

先程のプログラムよりは単純なプログラムなので、先ほどのプログラムが理解できれば特に難しい点はないでしょう。

```python
board = [ [ " " for y in range(3)] for x in range(3) ]
print(board)
```
実行結果
```
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```
:::note
繰り返し処理の便利な所は、繰り返す数を表す数値を __変更するだけ__ で、プログラムの他の部分を __一切変更することなく__ 、__様々な回数__ の繰り返し処理を行うことができる点にあります。

例えば、先ほどのプログラムの、繰り返しの回数を表す `range` の `()` の中の数字を __3 から 5 に変えるだけ__ で、下記のプログラムのように、5 x 5 のゲーム盤を初期化したデータを表す list を記述することができます。

```python
board = [ [ " " for y in range(5)] for x in range(5) ]
print(board)
```
実行結果（長いので横にスクロールする必要があります）
```
[[' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ']]
```
:::

初期化されたゲーム盤を作成するプログラムは、この記述方法でも充分に __簡潔に記述__ できているので、この記述方法を採用しても __かまわない__ のですが、Python では上記のプログラムを __さらに簡潔に記述__ する方法が用意されているので、次はその方法を紹介します。

## `*` 演算子を使った list の作成

Python では、要素に __同じデータ__ が複数格納された list を、__`*` 演算子__ を使ってより簡潔に記述することができます。

具体的には以下のように記述することで、listのデータの __要素__ を `*` 演算子の後に記述した整数回[^3]だけ繰り返した list が作成されます。

```python
listのデータ * 整数
```

いくつか具体例を挙げます。実行結果を見ればプログラムの意味はすぐに分かると思います。

```python
print([1] * 5)
print([" "] * 3)
```
実行結果
```
[1, 1, 1, 1, 1]
[' ', ' ', ' ']
```

:::note
上記の例では、list の中に __1 つの要素__ しかありませんが、複数の要素がある list に対しても同様の演算を行うことができます。そのような場合に行われる処理については後の記事で紹介します。
:::

勘の良い方は、この表記を応用して、`[[" "] * 3] * 3` のように記述すれば、初期化されたゲーム盤を表す 2 次元配列の list を記述できるのではないかと思うかもしれません。具体的には、以下のように考えることでそのようなことを思いつくことができます。

1. 目的のデータは、`[[" ", " ", " "], [" ", " ", " "], [" ", " ", " "]]` である
2. 目的のデータは、list の中に `[" ", " ", " "]` が代入された要素が 3 つ並んだものである
3. 従って、目的のデータは `[[" ", " ", " "]] * 3` のように記述できる
4. 目的のデータの中にある `[" ", " ", " "]` は、list の中に `" "` が代入された要素が 3 つ並んだものである
5. 従って、`[" ", " ", " "]` は `[" "] * 3` のように記述できる
6. 上記の 3 を、5 を使って置き換えると、`[[" "] * 3] * 3` になる

実際に `[[" "] * 3] * 3` を記述して `print` でその内容を表示すると、下記のプログラムの実行結果のように、初期化されたゲーム盤を表す 2 次元配列の list が `board` に代入されているように見えます。

```python
board = [[" "] * 3] * 3
print(board)
```
実行結果
```
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```
一見するとうまくいっている __ように見えます__ が、実はこの記述方法には __重大な欠陥__ があるため採用することはできません。

例えば、以下のように (1, 0) のマスを表す要素に `"〇"` を代入した後で、`board` を表示すると以下のように (0, 0)、(1, 0)、(2, 0) のマスを表す __3 つの要素__ が __同時に__ `"〇"` になってしまいます。

```python
board = [[" "] * 3] * 3
board[1][0] = "〇"
print(board)
```

実行結果
```
[['〇', ' ', ' '], ['〇', ' ', ' '], ['〇', ' ', ' ']]
```

初心者の方は何が起きているかが理解しづらいと思いますが、なぜこのようなことが起きるかについて __正しく理解する__ ことは、Python のプログラミングを行っていく上で __非常に重要__ で、__避けて通ることはできません__。

そこで、〇×ゲームの実装から一旦離れて、次回から数回にわたって何故このような現象がおきるかについて詳しく説明することにします。

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

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

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

# 次回の記事

https://qiita.com/ysgeso/items/911fafe3f80a805165d1

[^1]:append は「追加」という意味の英単語です
[^2]:`_` は、半角のアンダースコアという記号です。Python の変数名には、__記号は `_` のみ__ 使用することができ、`_` だけの変数名を使うことができます 
[^3]:0 以下の整数を指定した場合は、空の list が作られます