Pythonで〇×ゲームのAIを一から作成する　その30　その他の座標を表すデータ構造

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/392fa297ba9e9108ffcb

# 実装の進捗状況と前回までのおさらい

## 〇×ゲームの仕様と進捗状況

1. `正方形で区切られた 3 x 3 の 2 次元のゲーム盤上でゲームを行う`
2. `ゲーム開始時には、ゲーム盤のすべてのマスは空になっている`
3. `2 人のプレイヤーが遊ぶゲームであり、一人は 〇 を、もう一人は × のマークを受け持つ`
4. `2 人のプレイヤーは、交互に空いている好きなマスに自分のマークを 1 つ置く`
5. `先手は 〇 のプレイヤーである`
6. __プレイヤーがマークを置いた結果、縦、横、斜めのいずれかの一直線の 3 マスに同じマークが並んだ場合、そのマークのプレイヤーの勝利とし、ゲームが終了する__
7. __すべてのマスが埋まった時にゲームの決着がついていない場合は引き分けとする__

仕様の進捗状況は、以下のように表記します。

* 実装が完了した部分を `背景が灰色の長方形` で記述する
* 実装の一部が完了した部分を、__太字__ で記述する

## これまでに作成したモジュール

以下のリンクから、これまでに作成したモジュールを見ることができます。

https://github.com/ysgeso/marubatsu/blob/master/029/marubatsu.py

なお、前回の記事で採用しなかった関数は、`test.py` から削除してあります。

https://github.com/ysgeso/marubatsu/blob/master/029/test.py

## 前回までのおさらい

前回の記事では、座標を表すデータ構造として、列も行も整数で表現し、その 2 つを並べた文字列で表現するというデータ構造と、それを処理するアルゴリズムを紹介しました。

今回の記事では、これまでに紹介したデータ構造と大きく異なるデータ構造と、それを処理するアルゴリズムを紹介します。

# 数値座標

これまでに紹介した、xy 座標、Excel 座標、xy 文字座標は、__いずれも__ x 座標と y 座標という、__2 つのデータ__ で __座標を表現__ していました。この方法は、__数学__ の __2 次元の座標__ と __同じ方法__ で座標を表現するので、__直観的に分かりやすい__ という __利点__ がある反面、2 つのデータを記述する必要があるため、__記述が長く__ なってしまうという __欠点__ があります。

__ゲーム盤の座標__ を表すデータ構造では、__すべてのマス__ の __座標__ に、__異なるデータ__ が __割り当て__ られていれば、それぞれのマスに __どのようなデータ__ を割り当てても __かまいません__。例えば、〇×ゲームのマスの座標として、下図のように、それぞれのマスの座標に __法則性のない__ でらめな __データを割り当てる__ ことは __可能__ です。ただし、図を見ればわかると思いますが、このようなデータ構造は、非常に __扱いづらい__ ので使われることは無いでしょう。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c4bb8471-ea3b-032f-d309-0dd823d9ac4b.png" width="200">

そこで、〇×ゲームのような、2 次元の座標を表すデータ構造構造では、__座標__ を表す __データ__ を、__一般的__ に以下のような __性質__ を持つように割り当てます。

* __同じデータ型__ で __統一__ する
* 何らかの規則で、__規則正しく__ 割り当てる

同じデータ型で統一する __理由__ は、プログラムでは、__データ型ごと__ に、行うことができる __処理が異なる__ からです。例えば、__数値型__ のデータは、`+` 演算子によって __加算__ という __処理__ を行うことができますが、__文字列型__ のデータや、__list__ は数値ではないので __加算__ を行うことは __できず__ 、`+` 演算子によって、__結合__ という、__全く異なる処理__ が行われます。__データ型を統一__ することで、__すべてのデータ__ に対して __同じ処理__ を行うことができるようになります。

__規則正しく__ 割り当てる理由は、__繰り返し__ の処理を行ったり、__計算__ を行うことによって座標を __別の__ 座標を表す __データ構造__ に __変換__ することができるようになるからです。

__これまでに紹介__ してきた xy 座標、Excel 座標、xy 文字座標は、__いずれも__ 同じデータ型で __統一__ しており、データを __規則正しく__ 割り当てています。

今回の記事では、座標を表すデータを、下記のような性質を持つ、__1 つ__ の __整数型__ のデータで表現する __データ構造__ を紹介します。

* __1 つ__ の __整数型__ のデータで表現する
* __左上のマス__ の座標を __`0`__  とし、__右方向__ にマスが __ずれる__ と __`1` ずつ増える__ ように割り当てる。ただし、__右端__ のマスと、__次の行の左端__ のマスが __つながっている__ ものとして考える

下図は、上記の方法で 〇× ゲームのマスの座標にデータを割り当てたものです。__上記の__ 方法で座標を表現する __データ構造__ の事を、本記事では __数値座標__ と表記することにします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/8930ae16-efc9-e09b-c759-6b8e367305e3.png" width="150">

:::note
他にも、「左上のマスを `1` にする」、「下方向にマスがずれると `1` ずつ増やす」、「`2` ずつ増やす」など、__無数の__ 割り当ての __方法__ が考えられます。ただし、割り当ての __方法によって__、数値座標を表すデータが、__負の整数__ や、__2 桁以上の整数__ になる場合があります。そのような場合は、この後で説明する、__複数__ の __数値座標__ を __直接連結__ する __データ構造__ が __使えなくなる__ 点に __注意__ する必要があります。
:::

## 利点と欠点

上記の数値座標の利点と欠点は以下の通りです。

__利点__

* 座標を __1 文字__ で __短く記述__ できる

__欠点__

* __慣れないと__ 座標の __意味__ が __分かりづらい__

__これまでに紹介__ してきた __〇×ゲーム__ の __マスの座標__ を表す __データ構造__ は、__いずれも__ プログラムで __記述__ する際に、__2 文字以上__ で記述する必要がありますが、__数値座標__ では、__1 文字__ で __短く記述__ できるという __利点__ があります。

一方、数値座標は、__慣れるまで__ は、それを見ただけでどのマスを表しているのかが __わかりづらい__ という __欠点__ があります。〇×ゲームのような __小さい__ ゲーム盤の場合は __マスの数が少ない__ ので、__人によって__ は __慣れる__ ことでこの __欠点__ は __解消__ できる __かもしれません__。


# 複数の数値座標を list で表現するデータ構造

次に、__複数__ の __数値座標__ を表現する __データ構造__ と、それを処理する __アルゴリズム__ をいくつか紹介します。

__複数__ の __数値座標__ を表現する __データ構造__ の 1 つに、__list を使う__ 方法があります。これは、[以前の記事](https://qiita.com/ysgeso/items/273ed49a00b90c753031#%E8%A4%87%E6%95%B0%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88%E3%82%B1%E3%83%BC%E3%82%B9%E3%82%92%E3%81%BE%E3%81%A8%E3%82%81%E3%81%9F%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0)で __複数__ の __xy 座標__ を表現した方法と __同じ__ です。下記のプログラムは、list を使って複数の数値座標を記述した場合のテストケースです。__慣れないと__ 数値座標の __意味が分かりづらい__ 点を __除けば__、比較的 __簡潔に記述__ することができます。



```python
from marubatsu import Marubatsu

testcases = [
    # 〇 の勝利のテストケース
    [                      # 着手順とゲーム盤
        [ 0, 3, 1, 4, 2 ], # 135  ooo
        Marubatsu.CIRCLE,  # 24.  xx. 
    ],                     # ...  ...
    # × の勝利のテストケース
    [                          # 着手順とゲーム盤
        [ 3, 0, 4, 1, 6, 2 ],  # 246  xxx 
        Marubatsu.CROSS,       # 13.  oo.
    ],                         # 5..  ...
    # 引き分けのテストケース
    [                                  # 着手順とゲーム盤
        [ 0, 3, 1, 4, 5, 2, 6, 7, 8 ], # 136  oox 
        Marubatsu.DRAW,                # 245  xxo
    ],                                 # 789  oxo
]
```

## `num_to_xy` の定義その 1（if 文を使う方法）

__数値座標__ を __利用__ するために、[以前の記事](https://qiita.com/ysgeso/items/51c2eba867aa560808c6#%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E5%A4%89%E6%8F%9B) で、Excel 座標を xy 座標に __変換__ する __関数を定義__ した場合と __同様__ に、__数値座標__ を __xy 座標__ に __変換__ する、__`num_to_xy`__ という名前の __関数を定義__ することにします。この関数の定義の方法についていくつか紹介します。

__if 文__ を使って `num_to_xy` を定義する方法は、[以前の記事](https://qiita.com/ysgeso/items/51c2eba867aa560808c6#excel_to_xy-%E3%81%AE%E5%AE%9A%E7%BE%A9%E3%81%9D%E3%81%AE-1if-%E6%96%87%E3%82%92%E4%BD%BF%E3%81%86%E6%96%B9%E6%B3%95) で紹介したif 文を使って `excel_to_xy` を実装する方法と __ほぼ同じ__ で、下記のプログラムのように定義できます。

```python
def num_to_xy(coord):
    if coord == 0:
        return 0, 0
    elif coord == 1:
        return 0, 1
    elif coord == 2:
        return 0, 2
    elif coord == 3:
        return 1, 0
    elif coord == 4:
        return 1, 1
    elif coord == 5:
        return 1, 2
    elif coord == 6:
        return 2, 0
    elif coord == 7:
        return 2, 1
    elif coord == 8:
        return 2, 2
    else:
        print("invalid coord", coord)
```

__`test_judge`__ は下記のように、__`x, y = num_to_xy(coord)`__ の部分だけを修正します。
下記の修正箇所の、修正元のプログラムは、[以前記事](https://qiita.com/ysgeso/items/51c2eba867aa560808c6#%E5%8B%95%E4%BD%9C%E3%81%AE%E7%A2%BA%E8%AA%8D) で、xy 座標でテストケースを記述した場合の `test_judge` です。__`testdata`__ に __代入__ されたデータは __list__ なので、__for 文__ の __反復可能オブジェクト__ には、__`testdata`__ を __そのまま記述__ することができます。

```python
def test_judge(testcases):
    for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
        for coord in testdata:
            x, y = num_to_xy(coord)
            mb.move(x, y)
        print(mb)

        if mb.judge() == winner:
            print("ok")
        else:
            print("error!")
```

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

```diff_python
def test_judge(testcases):
    for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
        for coord in testdata:
-           x, y = coord_to_xy(coord)
+           x, y = num_to_xy(coord)
            mb.move(x, y)
        print(mb)

        if mb.judge() == winner:
            print("ok")
        else:
            print("error!")
```
</details>

下記のプログラムを実行することで、修正したデータ構造で、`test_judge` が正しく動作することが確認できます。

```python
test_judge(testcases)
```
実行結果
```
Turn x
ox.
ox.
o..

ok
Turn o
xoo
xo.
x..

ok
Turn x
oxo
oxx
xoo

ok
```

### 確認作業の大切さ

:::note alert
上記の実行結果を見て、正しく処理が行われたことが確認できたと __思った方はいませんか__？__それは本当ですか__？そう思った方は、__もう一度__ 実行結果を __ゆっくり見て下さい__。
:::

実行結果を __よく見る__ と、__上の 2 つ__ のゲーム盤の表示が、__本当__ は 〇 と × が __横に__ 並んでいなければならないのに、__縦に__ 並んでいる点が __おかしい__ ことがわかるはずです。

人間は、__同じような状況__ を __何度も体験__ すると、__慣れ__ が生じて、__確認を怠ってしまう__ 傾向があります。イソップ童話の、[オオカミ少年](https://ja.wikipedia.org/wiki/%E5%98%98%E3%82%92%E3%81%A4%E3%81%8F%E5%AD%90%E4%BE%9B) の逸話を思い出してください。これまでの記事で、__何度も__ `test_judge` メソッドを実行し、__そのたび__ に __正しい動作__ が行われることを __確認__ してきました。そのため、`test_judge` を実行して __それらしい表示__ が行われると、それが正しい動作が行われたことによる表示であるという __思い込み__ が生まれてしまうようになります。恥ずかしながら、__筆者__ もこの記事を __最初に記述__ した際は、このバグに __気づきませんでした__。このバグは、この記事を公開する前に __見直した際__ に気づいたので、__確認作業の大切さ__ を __改めて実感__ しました。

:::note warn
人間である以上、このような慣れを __完全に無くす__ ことは __不可能__ ですが、確認作業の際には、このようなことが起きる __可能性がある__ ということを __念頭に置く__ ようにすることで、このような見落としを __減らす__ ことができるようになります。
:::

### プログラムをコピーして入力する際の注意点

筆者が、上記のような __バグ__ を __発生__ させた __原因__ について説明します。

プログラムでは、上記の `num_to_xy` のように、__過去に記述__ した `excel_to_xy` のような関数と、__同様の処理__ を行うプログラムを __記述する__ 場面が __良くあります__。そのような場合は、`excel_to_xy` を __コピー__ し、関数の名前や条件式などの、__異なる部分を修正__ するという方法が良く行われます。

:::note warn
この方法は、プログラムを __効率的に入力__ できるので、__頻繁に__ 行われますが、その際に、異なる部分を __正しく修正__ しなければ __思わぬエラーが発生__ する点に __注意__ して下さい。
:::

下記のプログラムは、元の `excel_to_xy` の定義です。

```python
def excel_to_xy(coord):
    if coord == "A1":
        return 0, 0
    elif coord == "A2":
        return 0, 1
    elif coord == "A3":
        return 0, 2
    elif coord == "B1":
        return 1, 0
    elif coord == "B2":
        return 1, 1
    elif coord == "B3":
        return 1, 2
    elif coord == "C1":
        return 2, 0
    elif coord == "C2":
        return 2, 1
    elif coord == "C3":
        return 2, 2
    else:
        print("invalid coord", coord)
```

このプログラムを __コピー__ して __`num_to_xy` を記述__ する際に、下記のプログラムのように、__条件式__ を __上から順番__ に `coord == 0`、`coord == 1`、`coord == 2` のように修正する人が __多いのではないか__ と思います。実は、__筆者も__ この記事を書く際に、__最初は__ そのように __修正__ してしまいました。

```python
def num_to_xy(coord):
    if coord == 0:
        return 0, 0
    elif coord == 1:
        return 0, 1
    elif coord == 2:
        return 0, 2
    elif coord == 3:
        return 1, 0
    elif coord == 4:
        return 1, 1
    elif coord == 5:
        return 1, 2
    elif coord == 6:
        return 2, 0
    elif coord == 7:
        return 2, 1
    elif coord == 8:
        return 2, 2
    else:
        print("invalid coord", coord)
```

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

```diff_python
-def excel_to_xy(coord):
+def num_to_xy(coord):
-   if coord == "A1":
+   if coord == 0:
        return 0, 0
-   elif coord == "A2":
+   elif coord == 1:
        return 0, 1
-   elif coord == "A3":
+   elif coord == 2:
        return 0, 2
-   elif coord == "B1":
+   elif coord == 3:
        return 1, 0
-   elif coord == "B2":
+   elif coord == 4:
        return 1, 1
-   elif coord == "B3":
+   elif coord == 5:
        return 1, 2
-   elif coord == "C1":
+   elif coord == 6:
        return 2, 0
-   elif coord == "C2":
+   elif coord == 7:
        return 2, 1
-   elif coord == "C3":
+   elif coord == 8:
        return 2, 2
    else:
        print("invalid coord", coord)
```
</details>

このバグの原因は、__数字__ は __順番に並ぶ__ のが __自然__ だという、人間の __先入観__ が __原因__ です。

元の `excel_to_xy` では、if 文で、`"A1"`、`"A2"`、`"A3"` の __順番__ で Excel 座標を __チェック__ しましたが、__`"A2"`__ に __対応__ する __数値座標__ は、`1` ではなく、__`4` です__。元の `excel_to_xy` では、左上のマスから __下方向__ にマスを調べているのに対し、数値座標は、左上のマスから __右方向__ に数値を増やしている点が __嚙み合っていない__ ことがこのバグの原因です。

このバグを修正するためには、下記のプログラムのように、if 文の条件式に、return 文で返す __xy 座標__ に __対応__ した __数値座標を記述__ するという方法があります。

```python
def num_to_xy(coord):
    if coord == 0:
        return 0, 0
    elif coord == 3:
        return 0, 1
    elif coord == 6:
        return 0, 2
    elif coord == 1:
        return 1, 0
    elif coord == 4:
        return 1, 1
    elif coord == 7:
        return 1, 2
    elif coord == 2:
        return 2, 0
    elif coord == 5:
        return 2, 1
    elif coord == 8:
        return 2, 2
    else:
        print("invalid coord", coord)
```

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

```diff_python
def excel_to_xy(coord):
    if coord == 0:
        return 0, 0
-   elif coord == 1:
+   elif coord == 3:
        return 0, 1
-   elif coord == 2:
+   elif coord == 6:
        return 0, 2
-   elif coord == 3:
+   elif coord == 1:
        return 1, 0
    elif coord == 4:
        return 1, 1
-   elif coord == 5:
+   elif coord == 7:
        return 1, 2
-   elif coord == 6:
+   elif coord == 2:
        return 2, 0
-   elif coord == 7:
+   elif coord == 5:
        return 2, 1
    elif coord == 8:
        return 2, 2
    else:
        print("invalid coord", coord)
```
</details>

下記のプログラムを実行することで、修正したデータ構造で、`test_judge` が正しく動作することが確認できます。

```python
test_judge(testcases)
```

実行結果
```
Turn x
ooo
xx.
...

ok
Turn o
xxx
oo.
o..

ok
Turn x
oox
xxo
oxo

ok
```

チェックする __数値座標__ の __順番__ を `0`、`1`、`2` のように __1 ずつ増やしたい__ 場合は、下記のプログラムのように、__return 文__ で返す __xy 座標を修正__ するという方法も考えられます。どちらの方法でも __正しい処理__ が行われるので、自分が __わかりやすいと思った__ ほうを __採用__ すれば良いでしょう。

```python
def num_to_xy(coord):
    if coord == 0:
        return 0, 0
    elif coord == 1:
        return 1, 0
    elif coord == 2:
        return 2, 0
    elif coord == 3:
        return 0, 1
    elif coord == 4:
        return 1, 1
    elif coord == 5:
        return 2, 1
    elif coord == 6:
        return 0, 2
    elif coord == 7:
        return 1, 2
    elif coord == 8:
        return 2, 2
    else:
        print("invalid coord", coord)
```

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

```diff_python
def num_to_xy(coord):
    if coord == 0:
        return 0, 0
    elif coord == 1:
-       return 0, 1
+       return 1, 0
    elif coord == 2:
-       return 0, 2
+       return 2, 0
    elif coord == 3:
-       return 1, 0
+       return 0, 1
    elif coord == 4:
        return 1, 1
    elif coord == 5:
-       return 1, 2
+       return 2, 1
    elif coord == 6:
-       return 2, 0
+       return 0, 2
    elif coord == 7:
-       return 2, 1
+       return 1, 2
    elif coord == 8:
        return 2, 2
    else:
        print("invalid coord", coord)
```
</details>

下記のプログラムを実行することで、修正したデータ構造で、`test_judge` が正しく動作することが確認できます。実行結果は先程と同様になるので省略します。

```python
test_judge(testcases)
```

## `num_to_xy` の定義その 2（テーブルを使う方法）

次に、__テーブル__ を使って実装する方法を考えることにします。数値座標では、座標を __0 以上の整数__ で表現しているので、下記のプログラムのように、dict ではなく、__list__ を使うことにします。なお、座標を負の整数や、整数でない数値を使って表現する場合は dict を使う必要があります。

__分かりやすさ__ を重視して、下記のプログラムでは、`num_to_xy_table` の要素を、ゲーム盤の __マスの配置に対応__ させて、3 つの要素ごとに __改行__ して記述していますが、9 つの要素を改行せずに並べても、1 つの要素ごとに改行してもかまいません。

```python
def num_to_xy(coord):
    num_to_xy_table = [
        [0, 0], [1, 0], [2, 0],
        [0, 1], [1, 1], [2, 1],
        [0, 2], [1, 2], [2, 2],
    ]
    return num_to_xy_table[coord]
```

:::note
先程の if 文の場合と __同様の理由__ で、[以前の記事](https://qiita.com/ysgeso/items/51c2eba867aa560808c6#excel_to_xy-%E3%81%AE%E5%AE%9A%E7%BE%A9%E3%81%9D%E3%81%AE-2%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E4%BD%BF%E3%81%86%E6%96%B9%E6%B3%95) の `excel_to_xy_table` では、キーを `"A1"`、`"A2"` のように __縦方向__ に順番に並べて記述していましたが、`num_to_xy_table` の要素は `[0, 0]`、`[1, 0]` のように __横方向__ の順番に並べる必要があります。

先程と同様に、__`excel_to_xy_table`__ のデータを __コピー__ し、その __中身を修正__ するという方法で、`num_to_xy_table` の中身を __記述する__ 場合は、その点に __気を付けて修正__ しないと __間違ったデータ__ になってしまう点に注意して下さい。
:::

下記のプログラムを実行することで、修正した `test_judge` が正しく動作することが確認できます。実行結果は先程と同様になるので省略します。

```python
test_judge(testcases)
```

## `num_to_xy` の定義その 3 （計算を使う方法）

下図のように、〇×ゲームのマスに数値座標を __規則正しく__ 割り当てた場合は、__テーブル__ を __使わず__ に、__計算によって__ 数値座標を xy 座標に __変換__ することができます。その方法について少し考えてみて下さい。なお、特定の法則によって規則正しく並んでいる数字に対する計算を行う方法が __わからない__ 場合は、__図を書いて考える__ ことをお勧めします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/8930ae16-efc9-e09b-c759-6b8e367305e3.png" width="150">

上図から、__y 座標__ が __1 増える__ と、__数値座標__ が __3 増える__ ことがわかります。また、この  __3__ という __数字__ は、__ゲーム盤の横のサイズ__ を表す数字です。従って、__数値座標__ を __ゲーム盤のサイズ__ で __除算__（割り算）した時の __商__ が __y 座標__ になります。

python では、__`//`__ という演算子を使うことで、__整数__ である割り算の __商__ を __計算__ することができます。`5 // 3` の計算結果は `1` になります。__`/`__ では、`1 / 3` のように、割り切れない場合に __整数にならない__ 点に注意して下さい。従って、__y 座標__ は __`coord // 3`__ によって計算できます。

__x 座標__ が __`0`__ の場合の __数値座標__ は図からわかるように、__3 の倍数__ になるので、3 で割った時の __余り__ は __`0`__ になります。__x 座標__ が __`1`__ の場合の __数値座標__ は、__左隣__ の数値座標に __1 を足した__ ものになるので、3 で割った時の __余り__ は __`1`__ になります。x 座標 が `2` の場合も同様で、3 で割った時の __余り__ は __`2`__ になります。従って、__x 座標__ は、__数値座標__ を __3 で割った__ 時 の __余り__ になります。

python では __`%`__ という演算子を使うことで、割り算の __余り__ を __計算__ することができます。従って、__x 座標__ は、__`coord % 3`__ によって計算できます。

:::note warn
プログラムで __数字__ を `1` からではなく、__`0` から数える理由__ の 1 つは、このような計算を行うことができるからです。
:::

下記のプログラムは、`num_to_xy` を上記の計算を使うように修正したプログラムです。__テーブル__ が __必要なくなった__ ので、プログラムを __短く記述__ できます。なお、`num_to_xy` の内容はほとんどすべてが修正されているので、修正箇所は示しません。

```python
def num_to_xy(coord):
    x = coord % 3
    y = coord // 3
    return x, y
```

:::note
下記のように、`num_to_xy` を __1 行で記述__ することもできますが、プログラムが __わかりにくくなる__ という欠点があります。

```python
def num_to_xy(coord):
    return coord % 3, coord // 3
```
:::

下記のプログラムを実行することで、修正した `test_judge` が正しく動作することが確認できます。実行結果は先程と同様になるので省略します。

```python
test_judge(testcases)
```

:::note warn
__数値座標__ を使って __2 次元の座標__ を表現する場合は、上記のように、x 座標と y 座標を __簡単に計算__ できるような、__規則正しい法則__ で座標を表すデータを決めると良い。
:::

:::note
下図のように、__縦方向__ に数値座標を表す整数を増やした場合の `num_to_xy` の定義は下記のプログラムのように、x 座標と y 座標の計算が逆になります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/725194b2-c8d7-4573-6b78-4db20811dd5a.png" width="150">

なお、`num_to_xy` の定義を変更すると、この後のプログラムの __実行結果__ が __おかしくなってしまう__ ので、下記のプログラムでは、関数名を `num_to_xy_2` という名前にしました。

```python
def num_to_xy_2(coord):
    x = coord // 3
    y = coord % 3
    return x, y
```
<details><summary><strong>修正箇所</strong></summary>

```diff_python
def num_to_xy_2(coord):
-   x = coord % 3
+   x = coord // 3
-   y = coord // 3
+   y = coord % 3
    return x, y
```
</details>


興味と時間の余裕がある方は、`1` から数え始めた場合など、他の数値座標に対する `num_to_xy` の実装方法についても考えてみて下さい。
:::

# 複数の数値座標を文字列で表現するデータ構造

Excel 座標や xy 数値座標のように、__複数__ の __数値座標__ を __文字列__ で __表現__ することもできます。その場合は、[前回の記事](https://qiita.com/ysgeso/items/392fa297ba9e9108ffcb#%E8%A4%87%E6%95%B0%E3%81%AE-xy-%E6%96%87%E5%AD%97%E5%BA%A7%E6%A8%99%E3%82%92%E8%A1%A8%E3%81%99%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0) と同様に、__数値座標__ を __直接連結__ した __文字列__ で表現する方法と、`","` などの __文字列で区切って連結__ する方法があります。

## 数値座標を直接連結するデータ構造

下記のプログラムは、__数値座標__ を __直接連結__ した __文字列__ で __記述__ した場合のテストケースです。__文字列の長さ__ が、__座標の数__ と __等しく__ なるので、これまでの中で __最も短く記述__ できます。

```python
testcases = [
    # 〇 の勝利のテストケース
    [                      # 着手順とゲーム盤
        "03142",           # 135  ooo
        Marubatsu.CIRCLE,  # 24.  xx. 
    ],                     # ...  ...
    # × の勝利のテストケース
    [                      # 着手順とゲーム盤
        "304162",          # 246  xxx 
        Marubatsu.CROSS,   # 13.  oo.
    ],                     # 5..  ...
    # 引き分けのテストケース
    [                      # 着手順とゲーム盤
        "031452678",       # 136  oox 
        Marubatsu.DRAW,    # 245  xxo
    ],                     # 789  oxo
]
```

__数値座標__ を __直接連結__ した場合は、`test_judge` メソッドの __for 文__ の __反復可能オブジェクト__ に、数値座標を連結した __文字列__ を __そのまま記述__ することができます。その理由は、__文字列型__ のデータを __for 文__ の __反復可能オブジェクト__ の部分に __記述__ した場合は、文字列の __先頭の文字__ から __順番__ に for 文の __変数に代入__ して __繰り返し処理__ が行われるからです。下記のプログラムは、for 文を使って `"ABC"` という文字列の先頭の文字から順番に表示するプログラムです。

```python
for txt in "ABC":
    print(txt)
```
実行結果
```
A
B
C
```

従って、`test_judge` __修正せず__ に __テストを行う__ ことができると __思うかもしれません__ が、下記のプログラムを実行すると、__エラーが発生__ します。その理由について少し考えてみて下さい。

```python
test_judge(testcases)
```
実行結果
```
略
c:\Users\ys\ai\marubatsu\030\marubatsu.ipynb セル 18 line 2
      1 def num_to_xy(coord):
----> 2     return coord % 3, coord // 3

TypeError: not all arguments converted during string formatting
```

:::note
実行結果のエラーメッセージは、__文字列型__ のデータに対して __`%` 演算子__ で演算を行う際に __表示される__ ものです。これは __古いバージョン__ の Python で、__文字列の書式化__ を行う際に __`%` 演算子__ が __使われていた__ 時の __名残り__ のようなものです。__現在のバージョン__ の Python でも、文字列の書式化に `%` 演算子を __利用することはできます__ が、__現在では__ 、__文字列の書式化__ には __別の__ もっと便利な __方法が使われる__ ので、このエラーメッセージについての説明は省略します。ただし、__古い__ ウェブページや書籍などで、`%` 演算子を使った文字列の書式化が __使われている場合がある__ ので、そのようなプログラムを理解する必要が生じた場合は下記のページのリンク先の説明などを見て下さい。

現在使われている文字列の書式化の方法については必要になった時点で紹介します。
:::

https://docs.python.org/ja/3/library/stdtypes.html#old-string-formatting

### 文字列型のデータを for 文に記述した際の注意点

エラーの原因は、`num_to_xy` の実引数 __`coord`__ に、__文字列型__ のデータが __代入__ されていることにあります。`coord` に文字列型のデータが代入される __原因__ について説明します。

下記のプログラムのように、__文字列型__ の __`"012"`__ を __for 文__ の __反復可能オブジェクト__ に記述し、繰り返しのたびに __取り出される値__ が __代入__ された __`i` を表示__ すると、実行結果のように、`0`、`1`、`2` が表示されます。そのため、__`i`__ には __数値型__ のデータが __代入__ されるように __見えるかもしれません__ が、実際には __文字列型__ のデータが代入されます。

```python
for i in "012":
    print(i)
```
実行結果
```
0
1
2
```

:::note warn
__文字列型__ のデータを __for 文__ の __反復可能オブジェクト__ に __記述__ した場合は、取り出される値の __データ型__ は __常に文字列型__ のデータになる。

このことは、__勘違いしやすく__、__エラーの原因__ に __なりやすい__ 点に __注意__ して下さい。
:::

このことは、__実引数__ に記述されたデータの __データ型__ を __返り値として返す__、組み込み関数 __`type`__ を使って、下記のプログラムを実行することで __確認__ することができます。`type` の返り値を `print` で表示すると、下記の実行結果のように __`<class 'str'>`__ が表示されます。これは、`i` に代入された値が、__文字列型__ を表す __`str`__ という __クラス__ の __インスタンス__ であることを表します。

```python
for i in "012":
    print(i, type(i))
```
実行結果
```
0 <class 'str'>
1 <class 'str'>
2 <class 'str'>
```

参考までに、for 文の反復可能オブジェクトに __`range` を記述__ した場合は、`i` に __数値型__ のデータ（`int` のインスタンス）が __代入__ されることを、下記のプログラムで確認できます。

```python
for i in range(3):
    print(i, type(i))
```
実行結果
```
0 <class 'int'>
1 <class 'int'>
2 <class 'int'>
```

`type` に関する詳細は、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/library/functions.html?highlight=%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF#type

この問題を解決する方法として、以下の 2 種類の方法があります。どちらの方法を選択すべきかについて少し考えてみて下さい。

* `num_to_xy` の __実引数を記述__ する際に、__`int`__ を使って __整数型に変換__ する
* `num_to_xy` の __ブロックの中__ で、__`int`__ を使って __仮引数 `coord`__ を __整数型に変換__ する

__前者__ の方法は、__`num_to_xy`__ をプログラムの __複数の場所__ から __呼び出す__ 場合、その __すべてで修正__ を行う __必要__ がありますが、__後者__ の方法は、`num_to_xy` の __ブロックの中__ で __データ型の変換__ の処理を行っているので、`num_to_xy` の __ブロック以外__ のプログラムを __修正__ する __必要はありません__。従って、一般的には、__後者の修正__ を行ったほうが良いでしょう。

下記のプログラムは、`num_to_xy` のブロックの 2 行目で __`int`__ を使って __仮引数 `coord`__ の値を __整数型__ に __変換__ する処理を __追加__ するという修正を行っています。

```python
def num_to_xy(coord):
    coord = int(coord)
    x = coord % 3
    y = coord // 3
    return x, y
```
<details><summary><strong>修正箇所</strong></summary>

```diff_python
def num_to_xy(coord):
+   coord = int(coord)
    x = coord % 3
    y = coord // 3
    return x, y
```
</details>

下記のプログラムを実行することで、修正した `test_judge` が正しく動作することが確認できます。実行結果は先程と同様になるので省略します。

```python
test_judge(testcases)
```

### 数値座標を直接連結するデータ構造の注意点

__数値座標__ を __直接連結__ した文字列で表現する __データ構造__ は、〇×ゲームのように、__マスの数__ が __10 未満__ の __場合__ でしか使えません。マスの数が __10 以上__ の場合は、__数値座標__ が __2 桁以上__ になる場合があるため、__どこで__ 数値座標が __区切られているか__ が __わからなくなる__ からです。例えば、__`"12"`__ が記述された場合に、`1` と `2` の __2 つ__ の __数値座標__ が記述されているのか、`12` という __1 つ__ の __数値座標__ が記述されているのかを __区別__ することは __できません__。

そのような場合は、__すべて__ の __数値座標__ を `"02"` のように、__2 桁の文字列__ で記述するという方法も考えられますが、その場合は、__数値座標__ を __並べた__ 場合に `"123456"` のように、__どこで__ 数値座標が __区切られ__ るかが __わかりづらくなる__ という欠点があります。

従って、__2 桁以上__ の数値座標を表記する __必要がある__ 合は、次に説明する、数値座標を __`","` で区切って連結__ するデータ構造を __採用したほうが良い__ でしょう。

### 利点と欠点

上記の事から、このデータ構造の利点と欠点は以下のようになります。

__利点__

* __最も短く__ 記述できる
* `test_judge` を __変更する必要がない__

__欠点__

* __2 桁以上__ の数値座標の場合は、文字数を揃える必要があり、__わかりづらくなる__

## 数値座標を `","` で区切って連結するデータ構造

下記のプログラムは、__数値座標__ を __`","` で区切って連結__ した文字列で記述した場合のテストケースです。__少し長く__ なりますが、数値座標の __区切り__ が __わかりやすく__ なります。

```python
testcases = [
    # 〇 の勝利のテストケース
    [                      # 着手順とゲーム盤
        "0,3,1,4,2",       # 135  ooo
        Marubatsu.CIRCLE,  # 24.  xx. 
    ],                     # ...  ...
    # × の勝利のテストケース
    [                      # 着手順とゲーム盤
        "3,0,4,1,6,2",     # 246  xxx 
        Marubatsu.CROSS,   # 13.  oo.
    ],                     # 5..  ...
    # 引き分けのテストケース
    [                        # 着手順とゲーム盤
        "0,3,1,4,5,2,6,7,8", # 136  oox 
        Marubatsu.DRAW,      # 245  xxo
    ],                       # 789  oxo
]
```

`","` で数値座標を区切った場合は、[以前の記事](https://qiita.com/ysgeso/items/6ca248133ff4d59a81d4#split-%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AB%E3%82%88%E3%82%8B-list-%E3%81%B8%E3%81%AE%E5%A4%89%E6%8F%9B) と __同様の方法__ で、下記のプログラムのように __`split` メソッド__ を使って、__数値座標__ を __要素__ とする __list に変換__ することができます。

また、__`split` メソッド__ を使った場合は、[前回の記事](https://qiita.com/ysgeso/items/392fa297ba9e9108ffcb#xytext_to_xy-%E3%81%AE%E5%AE%9A%E7%BE%A9-1) で説明したように、数値座標を __2 文字以上__ で記述した場合でも正しく __動作__ します。

```python
def test_judge(testcases):
    for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
        for coord in testdata.split(","):
            x, y = num_to_xy(coord)
            mb.move(x, y)
        print(mb)

        if mb.judge() == winner:
            print("ok")
        else:
            print("error!")
```

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

```diff_python
def test_judge(testcases):
    for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
-       for coord in testdata:
+       for coord in testdata.split(","):
            x, y = num_to_xy(coord)
            mb.move(x, y)
        print(mb)

        if mb.judge() == winner:
            print("ok")
        else:
            print("error!")
```
</details>

下記のプログラムを実行することで、修正した `test_judge` が正しく動作することが確認できます。実行結果は先程と同様になるので省略します。

```python
test_judge(testcases)
```

### 利点と欠点

上記の事から、このデータ構造の利点と欠点は以下のようになります。

__利点__

* __2 桁以上__ の数値座標の場合も __扱える__
* `","` で区切るので、数値座標の __区切り__ が __わかりやすい__

__欠点__

* `","` を記述する分だけ、記述が __少し長くなる__

## 数値座標を使うべきかどうか

今回の記事の最初で説明した、__数値座標__ の __利点と欠点__ を再掲します。なお、先ほど説明した内容を、__利点に追記__ しています。

__利点__

* 座標を __1 文字__ で __短く記述__ できる
* Excel 座標のように、__アルファベットが含まれない__ ので、__テーブル__ を用意する __必要がなく__、__計算だけで__ xy 座標に __変換__ できる

__欠点__

* __慣れないと__ 座標の __意味__ が __分かりづらい__

〇×ゲームのような、__小さいゲーム盤__ の場合は、__慣れれば__ 欠点はあまり __問題にならない__ ので、この欠点が __気にならない人__ は数値座標を __採用しても良い__ のではないかと思います。

また、__2 次元の座標__ を 1 つの __1 次元の数値座標__ に変換して表現することは、__実際__ に __よくある事__ なので、数値座標の __考え方__ は __覚えて__ おいて __損はない__ と思います。

__テストケースを記述__ する際に __重要__ な __性質__ は、__短く記述できる__ ことと、__間違わない__ ように __記述できる__ ことです。数値座標は、Excel 座標より短く記述できますが、__慣れない__ と __間違った座標を記述__ してしまう __可能性が高い__ ので、本記事では __Excel 座標を採用する__ ことにします。

もちろん、数値座標でテストケースを表現してはいけないということは無いですし、__様々なデータ構造__ を実際に記述して __体験__ するのは、プログラミングの __技術の向上__ につながるので、余裕がある方は、__数値座標__ でテストケースを表すことにも __チャレンジ__ してみて下さい。

# 着手する順番を表現するデータ構造

__これまで紹介__ した __データ構造__ は、__いずれも__ 着手を行う __座標__ を、着手する順番で __表記__ するというものでしたが、__それ以外__ のデータ構造で __複数の着手__ を表すことができます。その方法について少し考えてみて下さい。

本記事では、テストケースのコメントに、下記のように、それぞれのマスに __何番目__ に __着手を行う__ かを __表す文字列__ を記述してきました。__このデータ__ 使って、__着手を行う__ ことができます。

```python
    # 〇 の勝利のテストケース
    [                      # 着手順とゲーム盤
        "0,3,1,4,2",       # 135  ooo
        Marubatsu.CIRCLE,  # 24.  xx. 
    ],                     # ...  ...
```

## 着手順を直接連結するデータ構造

以下のようなデータ構造で、__着手__ を表す __複数のデータ__ を __記述__ するという方法があります。

* __先頭の文字__ を、__左上のマス__ に __対応__ させ、__1 文字ごと__ に、__右のマス__ に __対応__ させる。ただし、__右端__ のマスと、__次の行の左端__ のマスが __つながっている__ ものとして考える
* __文字__ は、`1` そのマスに __着手__ を行う __1 から__ はじまる __順番__ を表す __整数__ を記述する
* そのマスに __着手を行わない__ 場合は、__`.` を記述__ する

本記事では、このデータ構造の事を、__着手順の文字列__ と表記することにします。

上記の 〇 の勝利のテストケースの場合、__着手順の文字列__ をプログラムで __記述__ すると以下のようになります。なお、このデータは、着手（move）の順番（order）を表すので、__`move_order`__ という名前の __変数に代入__ します。

```python
move_order = "13524...."
```

行数が増えるので、本記事ではそのように記述しませんが、分かりにくいと思った方は、このデータを下記のプログラムのように、3 文字ごとに __改行して記述__ することもできます。__文の途中で改行__ する際に、__改行の直前__ で __`\` を記述する__ 必要がある点に注意して下さい。

```python
move_order = "135" + \
             "24." + \
             "..."
```

下記のプログラムは、着手順の文字列で記述した場合のテストケースです。__着手順の文字列__ では、__着手の数__ に関わらず、__常に 9 文字__ の __文字列__ でデータを __記述__ します。

```python
testcases = [
    # 〇 の勝利のテストケース
    [                      # 着手順とゲーム盤
        "13524....",       # 135  ooo
        Marubatsu.CIRCLE,  # 24.  xx. 
    ],                     # ...  ...
    # × の勝利のテストケース
    [                      # 着手順とゲーム盤
        "24613.5..",       # 246  xxx 
        Marubatsu.CROSS,   # 13.  oo.
    ],                     # 5..  ...
    # 引き分けのテストケース
    [                      # 着手順とゲーム盤
        "136245789",       # 136  oox 
        Marubatsu.DRAW,    # 245  xxo
    ],                     # 789  oxo
]
```

なお、__着手順の文字列__ のデータ構造は、__複数__ の __数値座標__ を __直接連結__ するデータ構造の場合と __同じ理由__ で、__マスの数__ が __2 桁以上__ になるようなゲーム盤を表現する場合は __適していません__。

### `n` 番目に着手を行う座標の計算

このデータ構造の、下記の __性質__ から、__左上__ の __マスの座標__ は、__文字列__ の __先頭__ の __0 番__ の __インデックス__、__その右__ の __マスの座標__ は、__文字列__ の __1 番__ の __インデックス__ ・・・のように、__マスの座標__ と __インデックスの番号__ がそれぞれ __対応する__ ことがわかります。

* __先頭の文字__ を、__左上のマス__ に __対応__ させ、__1 文字ごと__ に、__右のマス__ に __対応__ させる。ただし、__右端__ のマスと、__次の行の左端__ のマスが __つながっている__ ものとして考える

この性質と、__数値座標__ の下記の __性質__ が __似ている__ と思いませんか？そのことに __気づけば__、__着手順の文字列__ の __それぞれ__ の文字の __インデックス__ が、__数値座標__ に __対応する__ ことがわかります。

* __左上のマス__ の座標を __`0`__  とし、__右方向__ にマスが __ずれる__ と __`1` ずつ増える__ ように割り当てる。ただし、__右端__ のマスと、__次の行の左端__ のマスが __つながっている__ ものとして考える


つまり、__`n` 番目__ に __着手__ する __マス__ は、__着手順の文字列__ の中で、__`n` が記述__ されている __文字のインデックス__ を __計算__ することで __知る__ ことができます。

### `index` メソッドによる数値座標の計算

[以前の記事](https://qiita.com/ysgeso/items/51c2eba867aa560808c6#excel_to_xy-%E3%81%AE%E5%AE%9A%E7%BE%A9%E3%81%9D%E3%81%AE-5%E6%96%87%E5%AD%97%E5%88%97%E3%81%AE%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B) で説明したように、__シーケンス型__ のデータは、__`index`__ というメソッドを使って、__実引数に記述__ した __データ__ を __先頭の要素から探し__、__最初に見つかった__ データの __インデックスを計算__ することができるので、先程の __`move_order`__ から、__3 番目に着手__ を行う __数値座標__ は、下記の 1 行目のプログラムで __計算する__ ことができます。3 行目で __`num_to_xy`__ で __数値座標__ を __xy 座標__ に __変換__ することで、実行結果から、__3 番目の着手__ を __(1, 0) のマス__ に行うという、正しい計算が行われていることが確認できます。

```python
coord = move_order.index("3")
print(coord)
print(num_to_xy(coord))
```
実行結果
```
1
(1, 0)
```

〇×ゲームのゲーム盤の __マスの数__ は __9 マス__ なので、__`1` から `9`__ までの __繰り返し処理__ を行うことで、上記の方法で、__1 手目から 9 手目まで__ の __着手__ を行う __座標__ を __順番に計算__ することができます。従って、下記のプログラムで __`move_order` の順番__ で __着手を行う__ ことができます。

下記のプログラムで行われる処理は、以下の通りです。

* __1 行目__：新しい `Marubatsu` クラスのインスタンスを作成する
* __2 行目__：for 文で __1 から 9 まで__ の繰り返しを行う。この場合は、`range(9)` ではなく、__`range(1, 10)`__[^1] のように記述する点に注意が必要である
* __3 行目__：__`i` 番の着手__ を表す __数値座標__ を __計算__ する
* __4 行目__：__数値座標__ から __x 座標__ と __y 座標__ を __計算__ する
* __5 行目__：__(x, y)__ のマスに __着手__ を行う
* __6 行目__：正しいマスに着手が出来たかどうかを __確認__ できるように、__ゲーム盤を表示__ する

:::note
`range(9)` のように記述したい場合は、__`i` の値__ が __1 つ小さくなる__ ので、3 行目を `coord = move_order.index(i + 1)` のように修正して __調整__ します。個人的には __どちらでも良い__ と思いますので、__分かりやすい__ と __思ったほう__ を __採用__ して下さい。
:::

```python
1  mb = Marubatsu()
2  for i in range(1, 10):
3      coord = move_order.index(i)
4      x, y = num_to_xy(coord)
5      mb.move(x, y)
7      print(mb)
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
mb = Marubatsu()
for i in range(9):
    coord = move_order.index(i + 1)
    x, y = num_to_xy(coord)
    mb.move(x, y)
    print(mb)
```
</details>

実行結果
```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\030\marubatsu.ipynb セル 31 line 3
      1 mb = Marubatsu()
      2 for i in range(9):
----> 3     coord = move_order.index(i + 1)
      4     x, y = num_to_xy(coord)
      5     mb.move(x, y)

TypeError: must be str, not int
```

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

* __TypeError__
データ型（type）に関するエラー
* __must be str, not int__
整数型（int）ではなく（not）、文字列型（str）でなければならない（must be）
:::

プログラムを実行すると、上記の実行結果のような __エラーが発生__ します。__エラーメッセージ__ から、__何か__ が __整数型ではなく__ 、__文字列型でなければならない__ ことがわかります。3 行目の中で、`move_order` には文字列型のデータが代入されていますが、__`i`__ には __整数型__ のデータが __代入__ されているので、このことが __原因__ であることが __推測__ されます。

__文字列型__ のデータである `move_order` に対して、__`index` メソッド__ で __文字列を探す__ 場合は、`index` メソッドの __実引数__ も __文字列型__ である __必要__ があり、そのことが __エラーの原因__ です。従って、__数値型__ の __`i`__ を __文字列型__ のデータに __変換__ する必要があります。

### 組み込み関数 `str`

Python では、組み込み関数 __`str`__ を利用することで、__実引数__ に記述したデータを、__文字列型__ のデータに __変換__ することができます。

:::note
厳密には、__`str`__ は __組み込みクラス__ で、`str` を呼び出すことで、実引数に記述したデータを表す、__文字列型__ の __インスタンスを作成__ します。これは、__`int` も同様__ です。
:::

`str` に関する詳細は、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/library/functions.html#func-str

従って、下記のプログラムのように、先程のプログラムの __`index`__ メソッドの __実引数__ を __`str(i)` に修正__ することで、このエラーが発生しないようになりますが、__別のエラーが発生__ してしまいます。その原因について少し考えてみて下さい。

```python
mb = Marubatsu()
for i in range(1, 10):
    coord = move_order.index(str(i))
    x, y = num_to_xy(coord)
    mb.move(x, y)
    print(mb)
```

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

```diff_python
mb = Marubatsu()
for i in range(1, 10):
-   coord = move_order.index(i)
+   coord = move_order.index(str(i))
    x, y = num_to_xy(coord)
    mb.move(x, y)
    print(mb)
```
</details>

実行結果
```
Turn x
o..
...
...

略

Turn x
ooo
xx.
...

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\030\marubatsu.ipynb セル 32 line 3
      1 mb = Marubatsu()
      2 for i in range(1, 10):
----> 3     coord = move_order.index(str(i))
      4     x, y = num_to_xy(coord)
      5     mb.move(x, y)

ValueError: substring not found
```

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

* __ValueError__
    値（value）に関するエラー
* __substring not found__
substring（[] の中に記述するインデックスの事）が見つからなかった（not found）
:::

上記の実行結果から、__5 手目まで__ は __正しく動作__ することが確認できますが、その __次の着手__ で __エラーが発生__ しています。このエラーは、[以前の記事](https://qiita.com/ysgeso/items/51c2eba867aa560808c6#index-%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E8%A3%9C%E8%B6%B3)で説明したように、__`index`__ メソッドに記述した __実引数__ が、シーケンス型の __要素の中__ に __見つからなかった__ ことを表します。エラーが発生した時点で __5 つの着手__ が __行われている__ ので、__`str(i)`__ は次の __`"6"` になります__ が、実際に、`move_order` に代入されている __`"13524...."`__ の中に __`"6"`__ という文字列は __存在しません__。

このエラーを発生させないようにするためには、__`move_order` の中__ に、__`str(i)`__  が __存在するかどうか__ を __チェック__ する必要があります。また、__存在しない場合__ は、それ __以降の着手__ を __行う必要がない__ ので for 文による __繰り返し処理__ をその時点で __終了する__ 必要があります。

### `in` 演算子と `not in` 演算子

__シーケンス型__ のデータの __要素__ に、__特定のデータ__ が __存在するかどうか__ を、__`in` 演算子__ で __調べる__ ことができます。`in` 演算子は、下記のように記述し、シーケンス型のデータの要素にデータが __存在する__ 場合は __`True`__、__存在しない__ 場合は __`False`__ が __計算__ されます。

```python
データ in シーケンス型
```

シーケンス型のデータの要素に、特定のデータが __存在しないこと__ を、__`not in` 演算子__ で、下記のように記述することで __調べる__ ことができます。計算結果は __同様__ に __`True` と `False`__ です。

```python
データ not in シーケンス型
```

:::note
`in` と `not in` 演算子は、__dict__ などの __マッピング型__ に対しても __利用できます__。
:::

シーケンス型に対する `in` 演算子の詳細については、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/library/stdtypes.html?highlight=%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9#sequence-types-list-tuple-range

### `break` 文と `continue` 文

for 文などによる __繰り返し処理__ の __ブロックの中__ で、__`break` を記述__ することで、__繰り返し処理__ を __終了__ することができます。`break` で __1 つの処理__ を表すので、__break 文__ と呼びます。

下記のプログラムは、for 文で __`0` から `9` まで__ の __繰り返し処理__ を行いますが、for 文の __ブロックの中__ の __if 文__ で、__`i` が `5` になった場合__ に __break 文を実行__ するので、実行結果には __`0` から `4` まで__ の数字しか表示されません。

```python
for i in range(10):
    if i == 5:
        break
    print(i)
```
実行結果
```
0
1
2
3
4
```

今回の記事では使用しませんが、`break` 文と __類似__ する文に __`continue` 文__ があります。

for 文などによる __繰り返し処理__ の __ブロックの中__ で、__`continue` を記述__ することで、__残り__ の繰り返し処理の __ブロックを実行せず__ に、__次の繰り返し処理を始める__ ことができます。

下記のプログラムは、for 文で __`0` から `9` まで__ の __繰り返し処理__ を行いますが、for 文の __ブロックの中__ の __if 文__ で、__`i` を 2 で割った余りが `0`__、すなわち __`i` が 偶数__ の場合に __continue 文を実行__ するので、実行結果には `0` から `9` までの __奇数のみが表示__ されます。

```python
for i in range(10):
    if i % 2 == 0:
        continue
    print(i)
```
実行結果
```
1
3
5
7
9
```

break 文の詳細については、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/reference/simple_stmts.html#break

https://docs.python.org/ja/3/tutorial/controlflow.html?highlight=continue#break-and-continue-statements-and-else-clauses-on-loops

continue 文の詳細については、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/reference/simple_stmts.html#continue

### `not in` 演算子と break 文を使った修正

__`not in` 演算子__ と __break 文__ を使って、先程のプログラムを以下のように修正します。

3 行目で、__`not in` 演算子__ を使って、__`str(i)`__ が __`move_order` の中に存在しない__ ことをチェックし、存在しない場合は、4 行目の __break 文__ で、__繰り返しを終了__ します。

実行結果から、正しい順番で 5 つの着手が行われることが確認できます。

```python
1  mb = Marubatsu()
2  for i in range(1, 10):
3      if str(i) not in move_order:
4          break
5      coord = move_order.index(str(i))
6      x, y = num_to_xy(coord)
7      mb.move(x, y)
8      print(mb)
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
mb = Marubatsu()
for i in range(1, 10):
    if str(i) not in move_order:
        break
    coord = move_order.index(str(i))
    x, y = num_to_xy(coord)
    mb.move(x, y)
    print(mb)
```
</details>

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

```diff_python
mb = Marubatsu()
for i in range(1, 10):
+   if str(i) not in move_order:
+       break
    coord = move_order.index(str(i))
    x, y = num_to_xy(coord)
    mb.move(x, y)
    print(mb)
```
</details>

実行結果
```
Turn x
o..
...
...

Turn o
o..
x..
...

Turn x
oo.
x..
...

Turn o
oo.
xx.
...

Turn x
ooo
xx.
...
```

### `test_judge` の修正

`test_judge` は以下のように修正します。修正内容は以下の通りです。

* __3 行目__：変数の名前を先程のプログラムに合わせて `testdata` から `move_order` に修正する[^2]
* __5 ~ 7 行目__：元の __for 文を削除__ し、__先ほど記述__ したプログラムを __ここに記述__ する

```python
 1  def test_judge(testcases):
 2      for testcase in testcases:
 3          move_order, winner = testcase
 4          mb = Marubatsu()
 5          for i in range(1, 10):
 6              if str(i) not in move_order:
 7                  break
 8              coord = move_order.index(str(i))
 9              x, y = num_to_xy(coord)
10              mb.move(x, y)
11          print(mb)
12
13          if mb.judge() == winner:
14              print("ok")
15          else:
16              print("error!")
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def test_judge(testcases):
    for testcase in testcases:
        move_order, winner = testcase
        mb = Marubatsu()
        for i in range(1, 10):
            if str(i) not in move_order:
                break
            coord = move_order.index(str(i))
            x, y = num_to_xy(coord)
            mb.move(x, y)
        print(mb)
 
        if mb.judge() == winner:
            print("ok")
        else:
            print("error!")
```
</details>

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

```diff_python
def test_judge(testcases):
    for testcase in testcases:
-       testdata, winner = testcase
+       move_order, winner = testcase
        mb = Marubatsu()
-       for coord in testdata.split(","):
+       for i in range(1, 10):
+           if str(i) not in move_order:
+               break
+           coord = move_order.index(str(i))
            x, y = num_to_xy(coord)
            mb.move(x, y)
        print(mb)

        if mb.judge() == winner:
            print("ok")
        else:
            print("error!")
```
</details>

下記のプログラムを実行することで、修正した `test_judge` が正しく動作することが確認できます。実行結果は先程と同様になるので省略します。

```python
test_judge(testcases)
```

### 利点と欠点

着手順の文字列のデータ構造の利点と欠点は以下の通りです。

__利点__

* __必ず 9 文字__ で __短く記述__ できる

__欠点__

* __途中で改行__ するという __工夫__ をしないと、__分かりづらい__
* __着手の数__ が __少ない場合__ でも、__9 文字を記述__ する __必要__ がある

:::note
実際には、着手順の文字列の __末尾の `"."`__ は __省略__ することができます。例えば、__`"13524...."`__ を __`"13524"`__ のように __省略して記述__ することができます。

その理由は以下の通りです。

* 末尾の `"."` を削除しても、文字列の中で __数字が記述__ された __位置が変化しない__
* 処理の中で、`index` メソッドで、__`"."`__ の位置を __検索しない__

__数字の位置が変化__ してしまうので、__数字より前__ に記述された `"."` は __削除できません__。

なお、末尾の `"."` を削除してしまうと、__3 文字ごとに改行__ を行うという __工夫が出来なくなってしまう__ ので、__見た目__ が __わかりづらくなる__ という __欠点__ が生じます。
:::

## 着手順を `","` で区切って連結するデータ構造

__表記が長くなる__ という __欠点__ がありますが、__これまでに紹介__ してきた場合と __同様__ に、__着手順__ を __`","` で区切って連結__ するという __データ構造__ で表現することもできます。下記のプログラムは、そのように記述した場合のテストケースです。この場合は、__必ず__ `","` を __8 つ記述する__ 必要があるので、__常に__ 先ほどの __ほぼ倍__ の __17 文字を記述__ する必要があります。

```python
testcases = [
    # 〇 の勝利のテストケース
    [                        # 着手順とゲーム盤
        "1,3,5,2,4,.,.,.,.", # 135  ooo
        Marubatsu.CIRCLE,    # 24.  xx. 
    ],                       # ...  ...
    # × の勝利のテストケース
    [                        # 着手順とゲーム盤
        "2,4,6,1,3,.,5,.,.", # 246  xxx 
        Marubatsu.CROSS,     # 13.  oo.
    ],                       # 5..  ...
    # 引き分けのテストケース
    [                        # 着手順とゲーム盤
        "1,3,6,2,4,5,7,8,9", # 136  oox 
        Marubatsu.DRAW,      # 245  xxo
    ],                       # 789  oxo
]
```

### `test_judge` の修正

着手順を `","` で区切って連結するデータ構造では、__着手順__ を表す文字の __インデックス__ と、__数値座標__ が __対応しなく__ なりますが、データを、__`split`__ メソッドによって __list に変換__ することで、__着手順__ を表すデータが __代入__ された __要素__ の __インデックス__ と、__数値座標__ が __再び対応する__ ようになります。従って、`test_judge` の修正は、下記のプログラムのように、4 行目に __その処理を追加する修正__ を行う __だけで済みます__。


```python
 1  def test_judge(testcases):
 2      for testcase in testcases:
 3          move_order, winner = testcase
 4          move_order = move_order.split(",")
 5          mb = Marubatsu()
 6          for i in range(1, 10):
 7              if str(i) not in move_order:
 8                  break
 9              coord = move_order.index(str(i))
10              x, y = num_to_xy(coord)
11              mb.move(x, y)
12          print(mb)
13
14          if mb.judge() == winner:
15              print("ok")
16          else:
17              print("error!")
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def test_judge(testcases):
    for testcase in testcases:
        move_order, winner = testcase
        move_order = move_order.split(",")
        mb = Marubatsu()
        for i in range(1, 10):
            if str(i) not in move_order:
                break
            coord = move_order.index(str(i))
            x, y = num_to_xy(coord)
            mb.move(x, y)
        print(mb)
 
        if mb.judge() == winner:
            print("ok")
        else:
            print("error!")
```
</details>

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

```diff_python
def test_judge(testcases):
    for testcase in testcases:
        move_order, winner = testcase
+       move_order = move_order.split(",")
        mb = Marubatsu()
        for i in range(1, 10):
            if str(i) not in move_order:
                break
            coord = move_order.index(str(i))
            x, y = num_to_xy(coord)
            mb.move(x, y)
        print(mb)
 
        if mb.judge() == winner:
            print("ok")
        else:
            print("error!")
```
</details>

下記のプログラムを実行することで、修正した `test_judge` が正しく動作することが確認できます。実行結果は先程と同様になるので省略します。

```python
test_judge(testcases)
```

### 利点と欠点

着手順を表す文字列を `","` で区切って連結するデータ構造の利点と欠点は以下の通りです。

__利点__

* __2 桁以上__ の着手順を __表現できる__

__欠点__

* __着手順の文字列__ と比べて __約 2 倍以上__ の __長さ__ になる
* __途中で改行__ するという __工夫__ をしないと、__意味が分かりづらい__

## 着手順を list で表現するデータ構造

__着手順__ を最初から __list で表記__ するという方法も考えられます。下記のプログラムは、そのように記述した場合のテストケースです。

```python
testcases = [
    # 〇 の勝利のテストケース
    [                        # 着手順とゲーム盤
        [1,3,5,2,4,0,0,0,0], # 135  ooo
        Marubatsu.CIRCLE,    # 24.  xx. 
    ],                       # ...  ...
    # × の勝利のテストケース
    [                        # 着手順とゲーム盤
        [2,4,6,1,3,0,5,0,0], # 246  xxx 
        Marubatsu.CROSS,     # 13.  oo.
    ],                       # 5..  ...
    # 引き分けのテストケース
    [                        # 着手順とゲーム盤
        [1,3,6,2,4,5,7,8,9], # 136  oox 
        Marubatsu.DRAW,      # 245  xxo
    ],                       # 789  oxo
]
```

__list の要素__ を `["1","3","5","2","4",".",".",".","."]` のように、__文字列で記述__ することもできますが、そうすると __すべての要素の前後__ に __`"` を記述する__ 必要があるため __かなり長く__ なってしまいます。また、__list の要素__ を __整数型__ のデータで記述することで、`test_judge` の中で、__`move_order`__ の要素の中から __着手順__ を表すデータを `index` メソッドで __検索__ する際に、`coord = move_order.index(str(i))` のように、__`i`__ を __文字列型に変換__ する __必要__ が __なくなります__。従って、__list の要素__ は __整数型で記述するべき__ です。

今回の記事の最初の方で説明したように、座標を表す __データ型の種類__ は __統一するべき__ です。これまでは、__着手を行わないマス__ に対応する __データ__ を __`"."`__ という __文字列型__ のデータで記述していたので、このデータを __数値型__ のデータで記述するように __修正する__ 必要があります。そこで、本記事では、`"."` という文字列を、__`0`__ という __整数に変更__ しています。

### `test_judge` の修正

下記は、`test_judge` を修正したプログラムです。

着手順を __list で表現__ する __データ構造__ では、__最初から__ データが __list で記述__ されているので、`split` メソッドで文字列型のデータを __list に変換__ する __必要がなくなります__。

__list の要素__ には、__最初から数値型__ のデータが __代入__ されているので、6 行目と 8 行目で __行っていた__  `str` を使って文字列型のデータを数値型に __変換する必要__ も __ありません__。

```python
 1  def test_judge(testcases):
 2      for testcase in testcases:
 3          move_order, winner = testcase
 4          mb = Marubatsu()
 5          for i in range(1, 10):
 6              if i not in move_order:
 7                  break
 8              coord = move_order.index(i)
 9              x, y = num_to_xy(coord)
10              mb.move(x, y)
11          print(mb)
12 
13          if mb.judge() == winner:
14              print("ok")
15          else:
16              print("error!")
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def test_judge(testcases):
    for testcase in testcases:
        move_order, winner = testcase
        mb = Marubatsu()
        for i in range(1, 10):
            if i not in move_order:
                break
            coord = move_order.index(i)
            x, y = num_to_xy(coord)
            mb.move(x, y)
        print(mb)
 
        if mb.judge() == winner:
            print("ok")
        else:
            print("error!")
```
</details>

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

```diff_python
def test_judge(testcases):
    for testcase in testcases:
        move_order, winner = testcase
-       move_order = move_order.split(",")
        mb = Marubatsu()
        for i in range(1, 10):
-           if str(i) not in move_order:
+           if i not in move_order:
                break
-           coord = move_order.index(str(i))
+           coord = move_order.index(i)
            x, y = num_to_xy(coord)
            mb.move(x, y)
        print(mb)
 
        if mb.judge() == winner:
            print("ok")
        else:
            print("error!")
```
</details>

下記のプログラムを実行することで、修正した `test_judge` が正しく動作することが確認できます。実行結果は先程と同様になるので省略します。

```python
test_judge(testcases)
```

### 利点と欠点

着手順を list で表現するデータ構造の利点と欠点は以下の通りです。

__利点__

* __2 桁以上__ の着手順を __表現できる__
* 最初から list と数値型のデータで記述するので、__処理が行いやすい__

__欠点__

* __着手順の文字列__ と比べて __約 2 倍以上__ の長さになる
* __途中で改行__ するという __工夫__ をしないと、__意味が分かりづらい__

## 着手順の 3 つのデータ構造の比較

〇の勝利のテストケースのデータを、3 つのデータ構造で並べて記述すると以下のようになります。__着手順の文字列__ が __最も短く記述できる__ ので、〇×ゲームのように、__1 文字__ で __着手順を記述できる__ 場合は、__着手順の文字列__ は __有力な候補__ になると思います。

一方、`","` で区切って並べる場合と、list で表現する場合の __記述の長さ__ は __全く同じ__ ですが、__list で表現__ するほうが __プログラムで扱いやすい__ ので、着手順を `","` で区切って連結するデータ構造を使う __理由はあまりない__ と思います。

```python
"13524...."           # 着手順の文字列
"1,3,5,2,4,.,.,.,."   # 着手順を "," で区切って連結する
[1,3,5,2,4,0,0,0,0]   # 着手順を list で表現する
```

# 今回の記事のまとめ

今回の記事では、__数値座標__ と、__着手順__ の __データ構造__ とアルゴリズムを紹介しました。

__数値座標__ は、Excel 座標や xy 文字座標と __比較__ して、__さらに短く記述__ できるという利点がありますが、__分かりやすさ__ の点では、Excel 座標や xy 座標に __劣る__ と __感じる人が多い__ のではないかと思います。ただし、〇×ゲームのような __小さなゲーム盤__ の場合の場合は、__慣れれば十分実用的__ だと思いますので、こちらのほうが __良いと思った方__ は、ぜひ __採用してみて下さい__。

__着手順を表現__ する __データ構造__ は、これまでとは __全く異なる観点__ で着手を表すデータを __表現__ します。こちらは、着手の数に関わらず、__常に一定数__ のデータを __記述__ しなければならないという __欠点__ がありますが、__着手の数が多い__ 場合は、Excel 座標や xy 文字座標と比較して __短く記述できる__ という利点があります。また、__着手の数__ が __1 桁__ の場合は、__人によって__ はこちらのほうが __見た目が分かりやすい__ 場合もあるのではないかと思います。

__本記事__ では __Excel 座標__ を __`","` で区切って連結__ する方法を __採用__ しますが、本記事で紹介したデータ構造の中で __気に入ったもの__ があれば __それを採用して下さい__。また、本記事で紹介したよりも __効率的なデータ構造__ を __思いついた場合__ は、もちろん __そちらを採用__ して下さい。

次回の記事では、__今回までの知識を利用__ して、中断していた __テストの作業を再開__ します。

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

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

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

今回の記事では、marubatsu.py は更新していないので、marubatsu_new.py はありません。

以下のリンクは、今回の記事で作成した test.py です。データ構造ごとに名前を変えてそれぞれの関数を定義しています。また、docstring などは省略します。

https://github.com/ysgeso/marubatsu/blob/master/030/test_new.py

# 次回の記事

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

[^1]:`range(1, 10)` の意味については、[以前の記事](https://qiita.com/ysgeso/items/6ca248133ff4d59a81d4#range-%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9)を参照して下さい
[^2]:名前を `testdata` のままにしたい場合は、6 行目と 8 行目の `move_order` を `testdata` に修正して下さい