Pythonで〇×ゲームのAIを一から作成する　その28　list と文字列の変換

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/51c2eba867aa560808c6

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

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

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

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

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

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

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

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

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

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

## 前回までのおさらい

前回の記事では、座標を表すデータ構造として、Excel 座標を紹介し、Excel 座標を xy 座標に変換する様々なアルゴリズムを紹介しました。

# シーケンス型の `[]` の中の表記

最初に、今回の記事の説明で必要になる、python の __シーケンス型__ の __`[]` の中の表記__ について説明します。ここで説明する表記は __便利__ なので、Python のプログラムでは __実際に良く使われます__。今後の記事でも __頻繁に利用する__ ので、しっかりと __理解して下さい__。

## シーケンス型とは何か

複数のデータを扱うことができる __文字列型__、__list__、__tuple__ のことを、__シーケンス型__ と呼び、以下のような __共通の性質__ を持ちます。なお、最後の 2 つの、`[]` の中の記述 __以外__ の性質は、これまでの記事で __説明済み__ です。

* __複数のデータ__ を __まとめて扱う__ ことができる
* 扱う __個別のデータ__ の事を、__要素__ と呼ぶ
* __要素__ は、__0 以上の整数__ で表される、__インデックス__ によって __対応づけ__ られる
* __[`+` 演算子](https://qiita.com/ysgeso/items/59e51d983a32ac4bc5db#-%E6%BC%94%E7%AE%97%E5%AD%90%E3%81%AB%E3%82%88%E3%82%8B-list-%E3%81%AE%E7%B5%90%E5%90%88)__ によって、前後のデータを __結合__ した __新しいデータを作成__ することができる
* __[`*` 演算子](https://qiita.com/ysgeso/items/59e51d983a32ac4bc5db#-%E6%BC%94%E7%AE%97%E5%AD%90%E3%81%AB%E3%82%88%E3%82%8B-list-%E3%81%AE%E7%B5%90%E5%90%88-1)__ によって、__同一__ のデータを __複数回結合__ した __新しいデータを作成__ することができる
* __[反復可能オフジェクト](https://qiita.com/ysgeso/items/0d64d1ddba1982b5a5d3#%E5%8F%8D%E5%BE%A9%E5%8F%AF%E8%83%BD%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88)__ なので、for 文の中で記述することで、__先頭__ の要素から __順番に取り出して__ 処理を行うことが出来る
* __`[]` の中__ に __インデックス__ を記述することで、対応する __要素を参照__ することができる
* __`[]` の中__ に、後述する __スライス表記__ を記述することで、対応する要素を持つ __新しいデータを作成__ することができる

__文字列型__ のデータの場合は、1 つ 1 つの __文字__ が、__要素__ に相当します。

また、list は [ミュータブル](https://qiita.com/ysgeso/items/cc9e67c16b0814c8bc2e#%E3%83%9F%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%96%E3%83%AB%E3%81%A8%E3%82%A4%E3%83%9F%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%96%E3%83%AB)、文字列型と tuple は [イミュータブル](https://qiita.com/ysgeso/items/cc9e67c16b0814c8bc2e#%E3%83%9F%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%96%E3%83%AB%E3%81%A8%E3%82%A4%E3%83%9F%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%96%E3%83%AB) なデータです。

シーケンス型のデータは、__上記以外__ にもいくつかの __共通する性質__ を持ちますが、それらについては必要になった時点で紹介します。

シーケンス型の詳細については、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/library/stdtypes.html#sequence-types-list-tuple-range

:::note
__dict__ は、__整数以外__ の __キー__ を使って中の 1 つのデータを参照することができるので、シーケンス型 __ではなく__、__マッピング型__ に __分類__ されます。
:::

以下の説明は、シーケンス型の __共通の性質__ の中の、__`[]` の中の表記__ についての説明です。

具体例を示したためにかなり長い内容になっていますが、__覚えること__ はそれほど __多くは無い__ のでその点は安心して下さい。

## 0 以上の整数のインデックス
これまでのプログラムでは、__シーケンス型__ のデータの __直後の `[]` の中__ に、__インデックス__ を表す __0 以上の整数__ を記述することで、指定した __インデックス番__ の __要素を参照__ しました。この記述方法は、他の多くのプログラム言語でも利用できる方法ですが、__Python__ では、`[]` の中に、この後で説明する、__それ以外の記述__ を行うことができます[^1]。

この後の説明に対応する公式のドキュメントについては、下記のリンク先のすぐ下にある表と、注釈の (3)、(4)、(5) を参照して下さい。

https://docs.python.org/ja/3/library/stdtypes.html#common-sequence-operations

## 負の整数のインデックス

`[]` の中に、__負の整数__ を記述することで、要素を __後ろから__ 数えて __指定__ することができます。具体的には、__`-1`__ が __後ろから__ 数えて __1 番目__（最後）の要素、`-2` が __後ろから__ 数えて __2 番目__ の要素のように指定します。

__先頭__ の要素のインデックスは __`0`__ ですが、__最後__ の要素のインデックスは、`-0` __ではなく__、 __`-1`__ である点に注意して下さい。そのことを __明確にする__ ために、正のインデックスの場合は __0 番__、負のインデックスで後ろから数えた場合は、__1 番目__ のように表記します。

:::note warn
__負の整数__ の __インデックス__ は、以下のような意味を持つ。

`n` を自然数（1 以上の整数の事）とした場合、`-n` は、要素を __後ろから__ 数えて __`-n` 番目__ の要素を表す。ただし、__最後の要素__ は __`-1`__ と表すものとする。
:::

下記のプログラムの、それぞれの行の意味は以下の通りです。

* __2 行目__：`"ABCDEF"` の __後ろから 1 番目__（最後）の文字である `"F"` が表示される
* __3 行目__：`"ABCDEF"` の __後ろから 3 番目__ の文字である `"D"` が表示される
* __4 行目__：`"ABCDEF"` の __後ろから 10 番目__ の文字を表示しようとしているが、文字列の長さは 6 で、そのようなインデックスに __対応する__ 文字は __存在しない__ ので、「インデックスが範囲外である（index out of range）」という意味の __エラーが発生__ する

```python
txt = "ABCDEF"
print(txt[-1])
print(txt[-3])
print(txt[-10])
```
実行結果
```
F
D
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\028\marubatsu.ipynb セル 1 line 4
      2 print(txt[-1])
      3 print(txt[-3])
----> 4 print(txt[-10])

IndexError: string index out of range
```

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

* __IndexError__
インデックス（index）に関するエラー
* __string index out of range__
文字列型（string）のデータのインデックス（index）が範囲外（out of range）である
:::

## スライス表記 その 1（start と end）

__スライス表記__ は、`[]` の中のインデックスの記述方法で、半角の __`:`__（コロン）で以下のように、__2 つ__ の __インデックス__ を __区切る__ ことで、`start` から `end` の __直前__ までのインデックスの __要素__ を持つ、__同じデータ型__ の __新しいデータを作成__ します。新しいデータには、__`end`__ のインデックスの __要素__ は __入らない__ 点に注意して下さい。

また、この後で詳しく説明しますが、`[]` の中に 1 つのインデックスを記述して __要素を参照__ する場合と __異なり__ 、__スライス表記__ では __新しいデータを作成__ する点に注意して下さい。

スライス表記には、先ほど説明した __負の整数のインデックス__ を記述することもできます。

```python
シーケンス型のデータ[start:end]
```

下記のプログラムの、それぞれの行の意味は以下の通りです。いずれも __`end`__ で指定したインデックスの __文字__ は __表示されない__ 点に注意して下さい。

* __2 行目__：`"ABCDEF"` の 2 番（`"C"`） から 5 番（`"F"`）の __直前__ までの文字列が表示される
* __3 行目__：`"ABCDEF"` の __最後から 5 番目__（`"B"`）から __最後から 1 番目__（`"F"`）の __直前__ までの文字が表示される


```python
txt = "ABCDEF"
print(txt[2:5])
print(txt[-5:-1])
```
実行結果
```
CDE
BCDE
```

:::note
list の場合に、下記のプログラムの 2 行目のように __特定の要素を参照__ した場合と、3 行目のように `[]` の中に 1 行目と __同じ要素__ を __範囲__ とする __スライス表記__ を記述した場合に、同一の処理が行われると __勘違い__ する人がいるかもしれません。

```python
a = [1, 2, 3]
print(a[0])
print(a[0:1])
```
実行結果
```
1
[1]
```
しかし、実際には上記の実行結果のように、__異なる処理__ が行われます。2 行目の処理は、list の __要素を参照__ するので、`1` が表示されますが、スライス表記は、先ほど説明したように、スライス表記で指定された範囲の要素を持つ、__同じ種類のデータ__ が __作成__ されます。__`a` は list__ なので、__`a[0:1]`__ によって __作成されるデータ__ も `[1]` のような __list になります__。この点は __勘違いしやすい__ ので注意が必要です。

上記は、__tuple__ の場合も __同様__ ですが、__文字列型__ のデータの場合は、下記のプログラムのように、`a[0]` によって参照されるデータは 1 文字の __文字列型のデータ__ になるので、2 行目と 3 行目では、__同じ表示__ が行われることになります。

```python
a = "abc"
print(a[0])
print(a[0:1])
```
実行結果
```
a
a
```
:::

## スライス表記 その 2（範囲外の start と end）

スライス表記を __使わない__ 場合で、`"ABC"[100]` のように、__対応__ する要素が __存在しない__ インデックスを記述した場合は、__エラーが発生__ しますが、__スライス表記__ の場合は __エラー__ は __発生せず__ に、下記のような処理が行われます。

* __`start`__ に、__対応__ する要素が __存在しない__ ような __負の整数__ を __記述__ した場合は、__先頭の文字__ を表す __`0` が記述__ されたものと __みなされ__ て新しいデータが作成される
* __`end`__ に __対応__ する要素が __存在しない__ ような __正の整数__ を記述した場合は、`start` から __最後の要素まで__ の要素を持つ新しいデータが作成される

下記のプログラムの、それぞれの行の意味は以下の通りです。

* __2 行目__：__最後から 100 番目__ の文字は __存在しない__ ので、`"ABCDEF"` の __先頭__（`"A"`） から 5 番（`"F"`）の直前までの文字列が表示される
* __3 行目__：__100 番__ の文字は __存在しない__ ので、`"ABCDEF"` の 2 番（`"C"`）から __最後まで__ の文字が表示される

```python
txt = "ABCDEF"
print(txt[-100:5])
print(txt[2:100])
```
実行結果
```
ABCDE
CDEF
```

## スライス表記 その 3（start が end 以降の場合）

__`start`__ を表す要素が、__`end`__ を表す要素 __以降__ の場合は、下記のプログラムのように、要素を __1 つも持たない__、__同じ種類__ の __新しいデータ__（下記の場合は空文字）が作成されます。

```python
print(txt[3:1])
```
実行結果
```

```

:::note
後述の __step を記述__ した場合は上記のようにならない場合があります。
:::

## スライス表記 その 4（start と end の省略）

スライス表記の、`:` の前後の __`start`__ と __`end`__ は、それぞれ __省略__ することができます。

* __`start` を省略__ した場合は、__先頭__ の要素を表す __`0` が記述__ されたものとみなされる
* __`end` を省略__ した場合は、`start` から __最後の要素まで__ を持つ新しいデータを作成する
* __両方を省略__ した場合は、__すべての要素を持つ__ 新しいデータを作成する

下記のプログラムの、それぞれの行の意味は以下の通りです。

* __2 行目__：__先頭から__ 5 番（`"F"`）の直前までの文字列が表示される
* __3 行目__：2 番（`"C"`）から __最後まで__ の文字列が表示される
* __4 行目__：先頭から最後までの、__すべて__ の文字列が表示される

```python
txt = "ABCDEF"
print(txt[:5])
print(txt[2:])
print(txt[:])
```
実行結果
```
ABCDE
CDEF
ABCDEF
```

## スライス表記によるデータの複製

スライス表記は、__一見する__ と __データの共有__ を行っているように __見える__ かもしれませんが、実際には __指定した範囲を複製__ した、__新しいデータが作成__ される点に注意して下さい。

例えば、下記のプログラムを実行した場合、2 行目で、`list2` に代入されるデータは、__3 行目の表示結果__ から、[以前の記事](https://qiita.com/ysgeso/items/cc9e67c16b0814c8bc2e#%E3%81%BE%E3%81%A8%E3%82%81) で説明した __`list2 = list1`__ による __代入文__ と同様に、データを共有するという処理が行われているように __見えるかもしれません__。

しかし、実際には `list1` と `list2` はデータを __共有していない__ ので、4 行目で `list2` の 0 番の要素に `5` を代入しても、実行結果のように、`list1` の値は __変化しません__。

```python
1  list1 = [ 1, 2, 3 ]
2  list2 = list1[:]
3  print(list2)
4  list2[0] = 5
5  print(list1)
6  print(list2)
```

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

```python
list1 = [ 1, 2, 3 ]
list2 = list1[:]
print(list2)
list2[0] = 5
print(list1)
print(list2)
```
</details>

実行結果
```
[1, 2, 3]
[1, 2, 3]
[5, 2, 3]
```

このような性質から、__スライス表記__ を使って __シーケンス型__ のデータの __一部__ または __全部__ を __複製__ することが出来ます。

:::note
__スライス表記__ によって行われる __データの複製__ は、__浅いコピー__ と呼ばれる種類の複製で、__完全な意味__ でデータを __複製__ する、__深いコピー__ を行っている __わけではない__ 点に注意が必要です。

ただし、__今回の記事で紹介するプログラム__ では、浅いコピーと深いコピーの違いについて __理解する必要__ は __まだありません__。浅いコピーと深いコピーの違いを区別することが __重要になる__ 場面があるので、必要になった時点で説明します。
:::

## スライス表記 その 5（step の指定）

頻繁に使われるわけではありませんが、スライス表記で、以下のように __`:` を 2 つ記述__ することで、`start` から `end` の直前までの要素を __`step` おき__ に取り出した要素を持つ、同じ種類の新しいデータを作成します。

```python
シーケンス型のデータ[start:end:step]
```

下記は、スライス表記によって、`"ABCDEF"` の 2 番（`"C"`） から 5 番（`"F"`）の直前までの文字列を　__2 文字おき__ 取り出して表示するプログラムです。

```python
print("ABCDEF"[2:5:2])
```
実行結果
```
CE
```

## スライス表記 その 6（step の省略と 0 を指定した場合）

スライス表記の __`step`__ の表記を __省略__ した場合は、`step` に __`1` を指定__ したとみなされます。
従って、下記のプログラムの実行結果のように、__`[start:end:]`__ と __`[start:end:1]`__ と __`[start:end]`__ は __全く同じ処理__ が行われます。

```python
txt = "ABCDEF"
print(txt[2:5:])
print(txt[2:5:1])
print(txt[2:5])
```
実行結果
```
CDE
CDE
CDE
```

スライス表記の __`step` に `0`__ を指定すると、`0` おきに要素を取り出すという、__無限に__ 要素を取り出すという __意味になる__ ので、下記のプログラムのように、__エラーが発生__ します。

```python
print("ABCDEF"[2:5:0])
```
実行結果
```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\028\marubatsu.ipynb セル 11 line 1
----> 1 print("ABCDEF"[2:5:0])

ValueError: slice step cannot be zero
```

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

* __ValueError__
値（value）に関するエラー
* __slice step cannot be zero__
スライス表記（slice）の step は 0（zero）になってはいけない（can not be）
:::

## スライス表記 その 7（負の step を指定した場合）

スライス表記の __`step` に負の整数__ を指定した場合は、`start` から `end` の __直後__ まで、__`-step` おき__ に取り出した要素を持つ、新しいデータを作成するという意味になります。

また、`start` と `end` に関して、以下のような処理が行われます。

* __`start` を省略__ した場合は、__最後の要素__ を表す __`-1` が記述__ されたとみなされる
* __`end` を省略__ した場合は、`start` から __先頭の要素__ までの __範囲を表す__ とみなされる
* __`start`__ の要素の方が、__`end`__ の要素 __以前__ であった場合は、__要素__ を __1 つも持たない__、同じ種類のデータが作成される

別の言葉で説明すると、__逆順で並んだ__ 要素を持つ新しいデータが作成されます。

下記のプログラムの、それぞれの行の意味は以下の通りです。この中で、__すべての要素を逆順__ にした新しいデータを __作成する__ 3 行目のプログラムは比較的良く使われます。

* __2 行目__：5 番（`"F"`）から __逆方向__ に 2 番（`"C"`) の __直後__ までの文字列が __2 文字おき__ で表示される
* __3 行目__：__最後から先頭__ までの文字列が __逆順__ で表示される
* __4 行目__：2 番の方が、5 番 __以前__ なので、__空の文字列__ が表示される

```python
txt = "ABCDEF"
print(txt[5:2:-2])
print(txt[::-1])
print(txt[2:5:-1])
```
実行結果
```
FD
FEDCBA

```

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

必要な説明が終わったので、前回の記事の続きを開始します。前回の記事では、下記のプログラムのように、テストケースで __複数の Excel 座標__ を __list の要素__ として記述しました。

```python
from marubatsu import Marubatsu

testcases = [
    # 〇 の勝利のテストケース
    [                                      # 着手順とゲーム盤
        [ "A1", "A2", "B1", "B2", "C1" ],  # 135  ooo
        Marubatsu.CIRCLE,                  # 24.  xx. 
    ],                                     # ...  ...
    # × の勝利のテストケース
    [                                            # 着手順とゲーム盤
        [ "A2", "A1", "B2", "B1", "A3", "C1" ],  # 246  xxx 
        Marubatsu.CROSS,                         # 13.  oo.
    ],                                           # 5..  ...
    # 引き分けのテストケース
    [                                                              # 着手順とゲーム盤
        [ "A1", "A2", "B1", "B2", "C2", "C1", "A3", "B3", "C3" ],  # 136  oox 
        Marubatsu.DRAW,                                            # 245  xxo
    ],                                                             # 789  oxo
]
```

## list と tuple の利点と欠点

複数のデータをまとめて扱うためのデータ構造として、__list__ や __tuple__ が良く使われますが、list や tuple には、以下のような __利点と欠点__ があります。

__利点__

* インデックスを使って、指定した要素のデータを __簡潔な記述で参照__ することが出来る
* __反復可能オブジェクト__ なので、for 文に記述することで、__先頭の要素から順番__ に取り出して、__繰り返しの処理を行う__ ことが出来る
* __list__ の様々な便利な __メソッドを利用__ することが出来る

__欠点__

* データを __プログラムに直接記述__ する際に、__`[]`__ や __`,`__ などの __記号を記述__ する必要があるため、__記述が長くなる__

上記のように、list は __プログラムの中で処理を行う__ 際には __便利な性質__ を持ちますが、プログラムで list を __直接記述する__ 場合は、__記述が長くなる__ という __欠点__ があります。

これは、[前回の記事](https://qiita.com/ysgeso/items/51c2eba867aa560808c6#xy-%E5%BA%A7%E6%A8%99%E3%81%AE%E5%88%A9%E7%82%B9%E3%81%A8%E6%AC%A0%E7%82%B9) で説明した、__xy 座標の欠点__ と __同じ__ なので、前回の記事と __同様の方法__ で、複数の Excel 座標を表す list を、__簡潔に記述する__ ことが出来ます。その方法について少し考えてみて下さい。

# 複数の Excel 座標を表現するデータ構造 その 1

__Excel 座標__ は、__x 座標__ を表す文字列と、__y 座標__ を表す文字列を、__連結する__ という __データ構造__でした。__同様の方法__ で、__Excel 座標__ を表す文字列を __連結した文字列__ を使って、__複数の Excel 座標__ を表すという __データ構造__ を考えることが出来ます。

例えば、〇 の勝利のテストケースの着手の座標を表す __`[ "A1", "A2", "B1", "B2", "C1" ]`__ は、それぞれの __Excel 座標を連結__ することで、__`"A1A2B1B2C1"`__ のように、__簡潔に記述__ することが出来ます。下記は、このデータ構造でテストケースを記述したプログラムです。

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

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

```diff_python
testcases = [
    # 〇 の勝利のテストケース
    [                                  # 着手順とゲーム盤
-       [ "A1", "A2", "B1", "B2", "C1" ],  # 135  ooo
+       "A1A2B1B2C1",                  # 135  ooo
        Marubatsu.CIRCLE,              # 24.  xx. 
    ],                                 # ...  ...
    # × の勝利のテストケース
    [                                  # 着手順とゲーム盤
-       [ "A2", "A1", "B2", "B1", "A3", "C1" ],  # 246  xxx 
+       "A2A1B2B1A3C1",                # 246  xxx 
        Marubatsu.CROSS,               # 13.  oo.
    ],                                 # 5..  ...
    # 引き分けのテストケース
    [                                  # 着手順とゲーム盤
-       [ "A1", "A2", "B1", "B2", "C2", "C1", "A3", "B3", "C3" ],  # 136  oox 
+       "A1A2B1B2C2C1A3B3C3",          # 136  oox 
        Marubatsu.DRAW,                # 245  xxo
    ],                                 # 789  oxo
]
```
</details>


## 利点と欠点

このデータ構造の利点と欠点は、以下の通りです。

__利点__

* list の場合と比較して、複数の Excel 座標を __短く記述__ できる

__欠点__

* Excel 座標を表す文字列が __直接隣り合っている__ ので、見た目が少し __わかりづらい__
* それぞれの __Excel 座標__ を __取り出して利用__ する処理を、list の場合のように、for 文を使って __簡潔に記述__ することが __出来ない__

上記の利点は、__xy 座標__ に対する __Excel 座標__ の __利点__ に __似ており__、__プログラム内で処理__ を行う場合は、__list__ を使ったほうが __便利__ ですが、プログラム内に __データを直接記述__ する場合は、__Excel 座標を連結__ した __データ構造__ を使ったほうが __便利__ です。

そこで、[前回の記事](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 座標を連結したデータを、__list に変換__ するための __関数を定義__ することで、__最小限の修正__ でこの __データ構造を利用できる__ ようにします。

## list への変換を行う関数の仕様

そのような関数を定義するためには、関数の名前、入力、処理、出力 の 仕様を決める 必要があり、本記事では、それぞれを以下のように決めることにします。

* __関数の名前__：__複数__ の __Excel 座標__ を __list__ に __変換__ するので、__`excels_to_list`__ にする
* __入力__：__`excels`__ という名前の __仮引数__ に、__Excel 座標を連結__ した文字列を __代入__ する
* __処理__：__`excels`__ を、__Excel 座標__ を要素とする __list に変換__ する
* __出力__：返り値 として、__Excel 座標__ を要素とする __list を返す__

`"A1A2B1B2C1"` を、`[ "A1", "A2", "B1", "B2", "C1" ]` に変換するための __アルゴリズム__ は、以下のようになります。

1. __空の list__ を __作成__ する
2. __先頭__ の文字から __順番__ に、__2 文字ずつ取り出す__
3. 取り出した __2 文字__ を __list に追加__ し、手順 2 へ戻る

このアルゴリズムの 手順 2 は、__繰り返し__ を行う __処理__ なので、`excels_to_list` は、下記のようなプログラムで実装することができます。下記のプログラムの 3、4 行目の、__日本語で記述されている部分__ をどのように記述すれば良いかについて少し考えてみて下さい。

```python
def excels_to_list(excels):
    excel_list = []
    for 文による繰り返し:
        excel = excels から 2 文字を取り出す処理
        excel_list.append(excel)
    return excel_list
```

## `excels_to_list` の定義 その 1

手順 2 の実装方法が思いつかない場合は、__具体的なデータ__ を使った場合に __行われる処理__ について __考えてみる__ と良いでしょう。 __`"A1A2B1B2C1"`__ の場合に手順 2 で取り出される文字を __順番に列挙__ すると以下のようになります。下記から何らかの __法則__ を __見つけて下さい__。

* 0、1 番の文字
* 2、3 番の文字
* 4、5 番の文字
* 6、7 番の文字
* 8、9 番の文字

上記から「__繰り返しのたび__ に、取り出す文字の、__先頭からの位置__ が、__0 から__ 始まり、__10__ になる __まで__、__2__ ずつ __増えていく__」ということがわかります。また、この __10__ という数字は、 `"A1A2B1B2C1"` の __文字列の長さ__ を表す数字です。

### `range` の使い方

__上記の繰り返し__ は、for 文と組み込み型である __`range`__ を使って記述することができます。

これまでの for 文の __反復可能オブジェクト__ に記述してきた __`range`__ は、__0 から 実引数__ に記述した数値に __なるまで__、__1 ずつ増えていく__ ような繰り返しを行いましたが、`range` を使うことで、__より柔軟な__ 繰り返し処理を行うことができます。

__`range`__ には、下記のような __3 つの実引数__ を記述することができ、__`start`__ からはじまり、__`stop` になるまで__ の間、__`step` ずつ増減__ していくような繰り返しで利用することが出来ます。なお、__`step`__ は __[デフォルト引数](https://qiita.com/ysgeso/items/c5dbe62b329f7936181a#%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E5%BC%95%E6%95%B0%E3%81%A8%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E5%BC%95%E6%95%B0%E5%80%A4)__ になっており、__省略__ すると __1 が代入__ されます。

`range(10)` が、`0` から `9` までの整数のように __`10` を生成しない__ のと同様に、`range(star, stop, step)` は、__`stop` を生成しない__ 点に __注意して下さい__。


```python
range(start, stop, step=1)
```

:::note
`range(x)` は、`range(0, x, 1)` と同じ処理を行います。
:::

下記は、for 文と `range` を使って、`10` から `20` まで、__`5` おきに__ 数字を表示するプログラムです。__`20`__ は実行結果に __表示されない__ 点に注意して下さい。

```python
for i in range(10, 20, 5):
    print(i)
```
実行結果
```
10
15
```

:::note
気づいている方が多いかもしれませんが、__`range`__ の __3 つの実引数__ は、__スライス表記__ で記述する __3 つの値__ に良く __似ています__。ただし、__`range`__ の __`start`__ や __`end`__ に __負の整数__ を記述した場合は、負のインデックスのような、後ろから数えるという特別な意味ではなく、__文字通りの負の値__ という __意味になる__ 点が __異なります__。
:::

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

https://docs.python.org/ja/3/library/stdtypes.html#typesseq-range

### `range` に関する補足 1（整数以外の実引数）

本記事の内容とは関係ありませんが、`range` に関するいくつかの補足説明を行います。

`range` の実引数に、__整数以外__ の数値を記述して実行すると、以下のプログラムのように __エラーが発生__ します。これは、`range` の __3 つの実引数すべて__ に当てはまります。


```python
for i in range(1.5):
    print(i)
```
実行結果
```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\028\marubatsu.ipynb セル 16 line 1
----> 1 for i in range(1.5):
      2     print(i)

TypeError: 'float' object cannot be interpreted as an integer
```

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

* __TypeError__
データ型（type）に関するエラー
* __'float' object cannot be interpreted as an integer__
浮動小数点数（float）のオブジェクト（object）は、整数（integer）として解釈（interpreted）できない（can not be）
:::

:::note
今回の記事で具体例は紹介しませんが、__整数以外__ の数値に対する `range` と同じような __反復可能オブジェクト__ は、__numpy__ というモジュールの __`arange`__ というメソッドを使って利用することが出来ます。
:::

### `range` に関する補足 2（`step` に `0` を指定した場合）

`range` の __`step`__ に対応する実引数に、__負の整数__ を指定することは __可能__ ですが、下記のプログラムのように、__`0` を指定__ すると __エラーが発生__ します。

```python
for i in range(0, 10, 0):
    print(i)
```
実行結果
```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\028\marubatsu.ipynb セル 17 line 1
----> 1 for i in range(0, 10, 0):
      2     print(i)

ValueError: range() arg 3 must not be zero
```

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

* __ValueError__
値（Value）に関するエラー
* __range() arg 3 must not be zero__
`range` の 3 つ目の仮引数（arg）は、`0`（zero）であってはならない（must not be）
:::

:::note
`step` に `0` を指定するということは、for 文で `range` から取り出される値が __常に `start` のまま__ になるということです。従って、いつまで繰り返しを行っても、取り出される値が __`stop` になることはない__ ため、__無限ループが発生__ するので、`0` を指定した場合はエラーが発生するようになっています。
:::

### `len` の使い方

文字列の長さは、__`len`__ という組み込み関数を利用することで計算することが出来ます。

下記は、`"A1A2B1B2C1"` の長さを計算するプログラムです。

```python
print(len("A1A2B1B2C1"))
```
実行結果
```
10
```

__`len`__ は、__シーケンス型__ のデータの __要素の数を計算__ する関数なので、__list__ や __tuple__ に対しても __利用__ することが __できます__。詳細は、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/library/functions.html#len

### 関数の定義

下記は先ほどの、`"A1A2B1B2C1"` を、`[ "A1", "A2", "B1", "B2", "C1" ]` に変換するためのアルゴリズムを再掲したものです。

1. __空の list__ を __作成__ する
2. __先頭__ の文字から __順番__ に、__2 文字ずつ取り出す__
3. 取り出した __2 文字__ を __list に追加__ し、手順 2 へ戻る

__`range`__ と __`len`__ を使って、__`excels_to_list`__ を下記のプログラムのように定義できます。下記のプログラムで行っている処理は以下の通りです。

* __2 行目__：__`excel_list`__ に __空の list__ を __代入__ することで __手順 1__ の処理を行う
* __4 行目__：`for i in range(0, len(excels), 2):` を記述することで、__手順 2__ の「先頭の文字から順番に 2 文字ずつ取り出す」ために必要となる、__取り出す 2 文字の先頭のインデックス__ を for 文の繰り返しのたびに取り出して __`i` に代入__ する
* __6 行目__：__取り出す 2 文字のインデックス__ である __`i`__ と __`i + 1`__ を使って __2 文字を参照__ し、__`+`__ 演算子で __連結__ することで、__`excel`__ に取り出した __2 文字を代入__ する
* __7 行目__：__`excel_list`__ に __`excel` を追加__ することで __手順 3__ の処理を行う
* __8 行目__：__`excels`__ を __list に変換__ した __`excel_list`__ を __返り値__ として返す

```python
1  def excels_to_list(excels):
2      excel_list = []
3      # excels の先頭から 2 文字ずつ文字を取り出す繰り返し処理
4      for i in range(0, len(excels), 2):
5          # i 文字目と、i + 1 文字目を連結した 2 文字を取り出す
6          excel = excels[i] + excels[i + 1]
7          excel_list.append(excel)
8      return excel_list
```

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

```python
def excels_to_list(excels):
    excel_list = []
    # excels の先頭から 2 文字ずつ文字を取り出す繰り返し処理
    for i in range(0, len(excels), 2):
        # i 文字目と、i + 1 文字目を連結した 2 文字を取り出す
        excel = excels[i] + excels[i + 1]
        excel_list.append(excel)
    return excel_list
```
</details>

下記は、`"A1A2B1B2C1"` を `excels_to_list` を使って変換する処理を行うプログラムです。実行結果から、正しく変換できることが確認できます。

```python
print(excels_to_list("A1A2B1B2C1"))
```
実行結果
```
['A1', 'A2', 'B1', 'B2', 'C1']
```

### `test_judge` の修正

複数の Excel 座標を表す __データ構造が変化__ したので、__`test_judge`__ をそれに合わせて __修正__ する必要があります。具体的には、[前回の記事](https://qiita.com/ysgeso/items/51c2eba867aa560808c6#%E9%96%A2%E6%95%B0%E3%81%AE%E4%BB%95%E6%A7%98) と同様に、__データ構造が変化したデータ__ が __代入__ された __変数__ を __利用する場所__ で、その変数を __実引数に記述__ して __`excels_to_list` を呼び出す__ ように修正するだけで済みます。`test_judge` の場合は、__`testdata`__ にそのデータが代入されており、下記の 7 行目の __for 文の反復可能オブジェクト__ の所でその変数が __利用__ されています。具体的な修正箇所については、下記の修正箇所をクリックして下さい。

なお、`test_judge` の中で、__前回の記事で定義__ した `excels_to_xy` を、今回の記事の中で __初めて使っている__ ので、1 行目で、__test モジュール__ から __`excel_to_xy` をインポート__ する必要があります。

```python
 1  from test import excel_to_xy
 2
 3  def test_judge(testcases):
 4      for testcase in testcases:
 5          testdata, winner = testcase
 6          mb = Marubatsu()
 7          for coord in excels_to_list(testdata):
 8              x, y = excel_to_xy(coord)            
 9              mb.move(x, y)
10          print(mb)
11
12          if mb.judge() == winner:
13              print("ok")
14          else:
15              print("error!")
```

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

```python
def test_judge(testcases):
    for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
        for coord in excels_to_list(testdata):
            x, y = excel_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
        mb = Marubatsu()
-       for coord in testdata:
+       for coord in excels_to_list(testdata):
            x, y = excel_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
ooo
xx.
...

ok
Turn o
xxx
oo.
o..

ok
Turn x
oox
xxo
oxo

ok
```

## `excels_to_list` の定義 その 2

上記では、for 文の中で、__1 文字ずつ__ 取り出した __文字__ に対して __`+` 演算子で連結__ することで、Excel 座標を表す 2 文字を取り出していましたが、この方法では、__3 文字以上__ の文字を取り出すのが __大変です__。今回の記事の最初で説明した、__スライス表記__ を使うことで、文字列の中から __複数の文字__ を、`+` 演算子を使うより __簡潔な記述__ で取り出すことが出来ます。__`i` 番__ から __2 文字分__ を __取り出す__ 場合は、下記の 6 行目のように __`[i:i+2]`__ を記述します。

```python
1  def excels_to_list(excels):
2      excel_list = []
3      # excels の先頭から 2 文字ずつ文字を取り出す繰り返し処理
4      for i in range(0, len(excels), 2):
5          # i 文字目から 2 文字分を取り出す
6          excel = excels[i:i+2]
7          excel_list.append(excel)
8      return excel_list
```

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

```python
def excels_to_list(excels):
    excel_list = []
    # excels の先頭から 2 文字ずつ文字を取り出す繰り返し処理
    for i in range(0, len(excels), 2):
        # i 文字目から 2 文字分を取り出す
        excel = excels[i:i+2]
        excel_list.append(excel)
    return excel_list
```
</details>

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

```diff_python

def excels_to_list(excels):
    excel_list = []
    # excels の先頭から 2 文字ずつ文字を取り出す繰り返し処理
    for i in range(0, len(excels), 2):
-       # i 文字目と、i + 1 文字目を連結した 2 文字を取り出す
+       # i 文字目から 2 文字分を取り出す
-       excel = excels[i] + excels[i + 1]
+       excel = excels[i:i+2]
        excel_list.append(excel)
    return excel_list
```
</details>

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

```python
test_judge(testcases)
```

文字列の中から、__複数の文字__ を取り出す際に、スライス表記の代わりに、`+` 演算子による連結を使う __メリットはほとんどない__ ので、本記事ではスライス表記を使うことにします。

## `excels_to_list` の定義 その 3

__for 文__ と __`append` メソッド__ を使った __list の作成__ は、[以前の記事](https://qiita.com/ysgeso/items/2a436b3819bef9a6a2b4#%E3%83%AA%E3%82%B9%E3%83%88%E5%86%85%E5%8C%85%E8%A1%A8%E8%A8%98) で説明した __list 内包表記__ を使うことで、__簡潔に記述__ することが出来ます。下記は、先程のプログラムを list 内包表記を使って修正したプログラムです。

```python
def excels_to_list(excels):
    excel_list = [ excels[i:i+2] for i in range(0, len(excels), 2) ]
    return excel_list
```

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

```diff_python

def excels_to_list(excels):
-   excel_list = []
-   # excels の先頭から 2 文字ずつ文字を取り出す繰り返し処理
-   for i in range(0, len(excels), 2):
-       # i 文字目から 2 文字分を取り出す
-       excel = excels[i:i+2]
-       excel_list.append(excel)
+   excel_list = [ excels[i:i+2] for i in range(0, len(excels), 2) ]
    return excel_list
```
</details>

__リスト内包表記__ はプログラムを __短く記述__ できますが、プログラムの意味が __分かりづらくなる__ という欠点があります。リスト内包表記に __慣れれば__、上記のプログラムは充分 __理解できる__ と思いますので、本記事では `excels_to_list` を上記のように定義する事にします。

## これまでに定義した `excels_to_list` の問題点

これまでに定義した __`excels_to_list`__ は、いずれも __Excel 座標__ を表す __文字列の長さ__ が __2 文字__ であることが __前提__ になっています。__〇×ゲーム__ の場合はこの __前提は正しい__ ので問題ありませんが、`"A19"` のような、__3 文字以上__ の Excel 座標が __存在する場合__ は、これまでに定義したどの `excels_to_list` でも __正しい処理__ を行うことは __できません__。

前回の記事のノートで紹介した、正規表現を使うことで、そのような場合にも対処することは可能ですが、「プログラムが複雑になる」、「〇×ゲームの場合は対処する必要がない」、「次に説明するデータ構造を使うことでこの問題点を簡単に解決することができる」などの理由から、本記事では具体的な方法については紹介しません。

〇×ゲームではこの問題は関係ありませんが、 `excels_to_list` には、上記の問題があるので、__ゲーム盤のサイズ__ によっては __利用できない場合がある__ ことは認識して下さい。

# 複数の Excel 座標を表現するデータ構造 その 2

先程の複数の Excel 座標を表現するデータ構造は、`"A1A2B1B2C1"` のように、Excel 座標を表す文字列を __直接並べて記述__ しているため、Excel 座標の __区切りがわかりづらい__ という欠点がありました。この問題を解決する方法として、下記のように、Excel 座標の __間に__ 半角の __`","`__（カンマ） などの __区切りの文字__ を __挿入__ して __連結する__ という方法があります。現実の世界でも、複数のデータを `","` で区切ることがあるので、難しくはないと思います。

下記は、テストケースをこのデータ構造で記述したプログラムです。

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

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

```diff_python
testcases = [
    # 〇 の勝利のテストケース
    [                                  # 着手順とゲーム盤
-       "A1A2B1B2C1",                  # 135  ooo
+       "A1,A2,B1,B2,C1",              # 135  ooo
        Marubatsu.CIRCLE,              # 24.  xx. 
    ],                                 # ...  ...
    # × の勝利のテストケース
    [                                  # 着手順とゲーム盤
-       "A2A1B2B1A3C1",                # 246  xxx 
+       "A2,A1,B2,B1,A3,C1",           # 246  xxx 
        Marubatsu.CROSS,               # 13.  oo.
    ],                                 # 5..  ...
    # 引き分けのテストケース
    [                                  # 着手順とゲーム盤
-       "A1A2B1B2C2C1A3B3C3",          # 136  oox 
+       "A1,A2,B1,B2,C2,C1,A3,B3,C3",  # 136  oox 
        Marubatsu.DRAW,                # 245  xxo
    ],                                 # 789  oxo
]
```
</details>

## 利点と欠点

このデータ構造を、Excel 座標を直接並べた場合のデータ構造と __比較した場合__ の利点と欠点は、以下の通りです。2 つ目の利点については、この後で説明します。

__利点__

* Excel 座標の __区切りが明確__ になるため、__わかりやすくなる__
* __list に変換__ する __処理__ が __記述しやすい__

__欠点__

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

## 区切りの文字について

上記では区切りの文字として半角の `","` を使いましたが、__データの中__ に __使われていない文字__ であれば、__どのような文字__ を、データを区切る文字として __使っても構いません__。

その理由は、例えば、__`"1,2"`__ と __`"3"`__ という 2 つのデータを、`"1,2"` の __中で使われている `","`__ で区切って連結すると `"1,2,3"` になりますが、__連結後__ の `"1,2,3"` __だけ__ を見た場合、__データ__ を表す `","` と、__区切り__ を表す `","` の __区別がつかなくなってしまう__ からです。

__良く使われる__ 区切りの文字としては、Tab キーを押すことで入力できる __Tab__ という文字、__半角の空白__ 文字、__改行を表す文字__ などが使われます。

なお、__Tab__ や、__改行__ を __文字列のデータ__ として __プログラムの中で記述__ する場合は、[以前の記事](https://qiita.com/ysgeso/items/ed1eb1cacf9a3e7dfa29#marubatsu-%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE-__str__-%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E5%AE%9A%E7%BE%A9) で紹介した __エスケープシーケンス__ という方法で記述する必要があります。具体的には、__Tab__ は __`\t`__、__改行__ は __`\n`__ のように記述します。下記は、1 行目は __半角の空白__、2 行目は __Tab__、3 行目は __改行__ で Excel 座標を区切って記述するプログラムです。

```python
"A1 A2 B1 B2 C1"
"A1\tA2\tB1\tB2\tC1"
"A1\nA2\nB1\nB2\nC1"
```

本記事では __分かりやすさを重視__ して、区切りの文字に __半角の `","`__ を使うことにします。

:::note
__半角の `","`__ によって __区切られた__ 複数の __データ__ の事を __CSV__、__Tab__ によって __区切られた__ 複数の __データ__ の事を __TSV__ と呼びます。CSV は __comma separated value__ の略で、カンマ（comma）によって区切られた（separated）値（value）の事を表します。TSV の T は Tab の略で、SV は CSV と同様です。

CSV や TSV は実際に __良く使われる__ ことが多く、例えば、__Excel のファイルを保存__ する際に、下図の __ファイルの種類__ の __メニュー__ から「テキスト（タブ区切り）」や、「CSV（コンマ区切り）」を選択することで、CSV や TSV の __形式__ で Excel のデータを __ファイルに保存__ することが出来ます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/67dc2629-808f-01bb-f480-e7fd2f581c8f.png" width="200">

今回の記事では具体的な方法については紹介しませんが、__テストケース__ を表す __データ__ を __Excel に入力__ して、__CSV として保存__ し、その __ファイルを読み込む__ ことで __変数__ に __テストケース__ を表すデータを __代入__ するという方法があります。そのようにすることで、__データ__ と __プログラム__ を __完全に分離__ することができます。

特に、__データの数が多くなる__ と、プログラム内に __データを直接記述__ するのが __現実的ではなくなる__ ので、データを __ファイルに保存__ するのが __一般的__ です。
:::

## `split` メソッドによる list への変換

Python では、__特定の文字列__ で __区切られた文字列__ を、__`split`__ メソッドを使って、__list に変換__ することが出来るので、上記のデータ構造を list に __簡単に変換__ することが __出来ます__。

__`split`__ は、__実引数__ に記述した __文字列__ を、__区切りの文字列__ として __文字列を分割__ し、分割したデータを __要素__ とする __list を作成__ して __返す__ 関数です。下記は、__`","`__ によって __区切られた__ `"A1,A2,B1,B2,C1"` を、__Excel 座標__ を __要素__ として持つ __list に変換__ するプログラムです。

```python
print("A1,A2,B1,B2,C1".split(","))
```
実行結果
```
['A1', 'A2', 'B1', 'B2', 'C1']
```

`split` メソッドの詳細については、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/library/stdtypes.html#str.split

:::note
__半角の空白__、__Tab__、__改行__ で区切られた場合は、それぞれ下記のように記述します。

```python
print("A1 A2 B1 B2 C1".split(" "))
print("A1\tA2\tB1\tB2\tC1".split("\t"))
print("A1\nA2\nB1\nB2\nC1".split("\n"))
```
実行結果
```
['A1', 'A2', 'B1', 'B2', 'C1']
['A1', 'A2', 'B1', 'B2', 'C1']
['A1', 'A2', 'B1', 'B2', 'C1']
```

また、`split` は、下記のプログラムのように、__2 文字以上__ の文字列で __区切られた__ 文字列を、__list に変換__ することもできます。

```python
print("A1::A2::B1::B2::C1".split("::"))
```
実行結果
```
['A1', 'A2', 'B1', 'B2', 'C1']
```

:::

## `join` メソッドによる文字列の変換

Python の __文字列__ には、__実引数__ で記述した __list の要素__ を、__その文字列__ で間を __区切って連結__ する __`join`__ というメソッドがあります。別の言葉で説明すると、__`join`__ メソッドは、__`split`__ メソッドの __逆の処理__ を行うメソッドで、`split` を使って変換したデータを `join` を使って __元に戻す__ ことや、__その逆__ を行うことが出来ます。

`split` メソッドと `join` メソッドが __用意__ されているのは、__list__ が表すデータを、__特定の文字列__ で __区切って連結__ した __文字列__ で __簡潔に記述する__ ことが __実際に良くある__ からです。

下記のプログラムは、__`['A1', 'A2', 'B1', 'B2', 'C1']`__ という list を、下記の方法でそれぞれ __文字列型__ のデータに __変換__ するプログラムです。5 行目のように、__2 文字以上__ の文字列で区切って連結することもできます。

* __2 行目__：要素を __直接連結__ する
* __3 行目__：要素を __半角の `","`__ で __区切って連結__ する
* __4 行目__：要素を __半角の空白__ で __区切って連結__ する
* __5 行目__：要素を __2 文字__ の __`"::"`__ で __区切って連結__ する

```python
coords = ['A1', 'A2', 'B1', 'B2', 'C1']
print("".join(coords))
print(",".join(coords))
print(" ".join(coords))
print("::".join(coords))
```
実行結果
```
A1A2B1B2C1
A1,A2,B1,B2,C1
A1 A2 B1 B2 C1
A1::A2::B1::B2::C1
```

要素を __直接連結__ した場合を __除き__、__`join`__ で文字列に __変換したデータ__ は、下記のプログラムのように、__`split`__ を使って __元の list に戻す__ ことができます。

```python
coords = ['A1', 'A2', 'B1', 'B2', 'C1']
print(",".join(coords).split(","))
print(" ".join(coords).split(" "))
```
実行結果
```
['A1', 'A2', 'B1', 'B2', 'C1']
['A1', 'A2', 'B1', 'B2', 'C1']
```

:::note
要素を __直接連結__ した場合は、__区切り__ の文字列が __存在しない__ ので、`split` メソッドを使って元の list に戻すことは __できません__。
:::

`join` メソッドの詳細については、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/library/stdtypes.html#str.join

:::note
list の __要素__ に、__文字列型以外__ のデータが存在する場合に __`join`__ メソッドを実行すると、下記のプログラムのように __エラーが発生__ します。

```
print(",".join([1, 2, 3]))
```
実行結果
```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\028\marubatsu.ipynb セル 33 line 1
----> 1 print(",".join([1, 2, 3]))

TypeError: sequence item 0: expected str instance, int found
```

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

* __TypeError__
データ型（type）に関するエラー
* __sequence item 0: expected str instance, int found__
シーケンス型（sequence）の 0 番の要素（item）は文字列型（str）でなければならない（expected）が、整数型（int）が記述されている（found）
:::

## `test_judge` の修正

__データ構造__ が __変わった__ ので、__`test_judge`__ を以下のように __修正__ します。

なお、__文字列から list__  への __変換__ は、__1 行で簡潔に記述__ できるので、`excels_to_list` のような __関数を定義せず__ に、`test_judge` の 5 行目に __直接__ その処理を __`testdata.split(",")`__ のように __記述__ することにします。

```python
 1  def test_judge(testcases):
 2      for testcase in testcases:
 3          testdata, winner = testcase
 4          mb = Marubatsu()
 5          for coord in testdata.split(","):
 6              x, y = excel_to_xy(coord)
 7              mb.move(x, y)
 8          print(mb)
 9
10          if mb.judge() == winner:
11              print("ok")
12          else:
13              print("error!")
```

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

```python
def test_judge(testcases):
    for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
        for coord in testdata.split(","):
            x, y = excel_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
        mb = Marubatsu()
-       for coord in excels_to_list(testdata):
+       for coord in testdata.split(","):
            x, y = excel_to_xy(coord)
            mb.move(x, y)
        print(mb)

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

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

```python
test_judge(testcases)
```

# どちらのデータ構造を使うべきか

__間を区切らない__ `"A1A2B1B2C1"` と、__間を `","` で区切る__ `"A1,A2,B1,B2,C1"` のどちらのデータ構造を __使うべきか__ については、__状況__ に応じて __自分で判断__ する必要があります。

:::note
今回の記事では、データ構造を選択する場合のこと説明していますが、データ構造が決まっている場合に __アルゴリズムを選択__ する場合や、アルゴリズムとデータ構造の __組み合わせを選択__ する場合も __同様の方法__ で行うことが出来ます。
:::

判断を __つけづらい__ 場合は、__状況__ からデータ構造に __求められる性質__ を __列挙__ し、それらの性質を、__候補__ となるデータ構造が __どれだけ満たす__ かを表す __表を作成する__ と良いでしょう。

今回の状況を具体例として説明します。

まず、今回の __状況を整理__ します。今回は、`test_judge` のテストを行うために必要な、__テストケース__ を表すデータを __記述する__ という状況です。その際に __求められる__ データ構造の __性質__ を考えると以下のようになります。他にもあると思った人は追加して下さい。

* __数多く__ のテストケースを __記述する必要__ があるので、できるだけ __短く__ 記述したい
* 同様の理由で、テストケースをできるだけ __わかりやすく__ 記述したい
* 記述したテストケースのデータが __間違っている場合__ は、__正しいテスト__ を行うことが __できない__ ので、記述したテストケースが __正しいかどうかを目で確認__ したい

次に、それぞれのデータ構造が上記の __性質__ を、どれだけ __満たすか__ について __考察__ します。

あたりまえのことですが、間を `","` で __区切らない__ データ構造の方がデータを __短く記述__ できます。ただし、区切った場合でも __list で記述__ する __よりは__ かなり __短く記述__ できます。

人によって違う可能性はあると思いますが、__多くの人__ にとっては、`"A1A2B1B2C1"` のような、__間を区切らない__ 記述は __わかりにくい__ ため、`"A1,A2,B1,B2,C1"` のように、間を `","` で区切る記述よりも __記述しづらい__ のではないかと思います。

同様に、記述した内容が __正しいかどうか__ を __目で確認__ する際も、間を `","` で区切ったほうが __確認しやすい__ のではないでしょうか。つまり、__短く記述できる__ という __利点__ が、__他の観点__ から見た場合に __欠点__ になる __場合がある__ ということです。

下記は、それぞれのデータ構造が上記の __性質をどれだけ満たすか__ を表にしたものです。いずれの性質も、それほど __大きな差__ がある __わけではない__ ので、劣る方に △ を記しました。

|  | 区切らないデータ構造 | `,` で区切るデータ構造 |
|:--|:-:|:-:|
| __記述の短さ__ | 〇 | △  |
| __記述のわかりやすさ__  | △  | 〇  |
| __確認のしやすさ__  | △  | 〇  |

:::note
上記の表では、__記号で評価__ を行いましたが、5 点満点のような __数字で評価__ を行うという方法も考えられます。__数字で評価__ を行った場合は、__合計を計算__ することにより __総合評価__ を __簡単に行う__ ことができるという __利点__ が得られますが、それぞれの項目の __重要度が異なる__ 場合は、__必ずしも__ 合計点を総合評価にすることが __良いとは限らない__ 点に注意が必要です。例えば、記述の短さが、__他の性質__ よりも __はるかに重要__ である場合などです。

そのような場合は、表に __重要度__ を表す __数字__ を記述する __列を加え__、その数字を __評価の数字に乗算__ したものの __合計を計算__ することで、__総合評価を求める__ という方法などが考えられるでしょう。__重要度__ を表す数字の事を __重み__ と呼びます。
:::

次に、上記の表を使って、__どちら__ のデータ構造を __選択すべき__ かを __考察__ します。間を区切らないデータ構造の方が、`","` を記述しない分だけ、数文字ほど __短く記述__ できますが、__その差__ はあまり __大きくありません__。__総合的に考える__ と、記述の長さの __欠点より__、記述のわかりやすさと確認しやすさの __利点のほうが上回る__ と考えられることから、本記事では、__`","` で間を区切るデータ構造を採用__ することにします。

なお、上記の __表の評価__ や、それに基づいた __総合的な判断__ は、__筆者の主観__ による評価なので、__人によって__ は __評価や結論__ が __異なる__ かもしれません。例えば、__記述のわかりやすさ__ は __どちらも変わらない__ と思った方は、__上記の表__ をそのように __修正__ して下さい。また、__記述の短さ__ が、他の性質よりも __はるかに重要__ だと思った方は、総合的に考えて __区切らないデータ構造__ を __採用するべき__ でしょう。

:::note
残念ながら、データ構造を __決める前__ に行った __考察__ が、__実際とは異なる__ ことは __良くあります__。例えば、人によっては、テストケースを実際に記述する際に、__`","` を記述__ する __手間__ が、思いのほか __面倒__ であったり、`","` で区切らない記述を試してみると、思いのほか __わかりくくないと感じる__ 場合などがあるでしょう。

現実の世界でも、__綿密に立てた計画__ が、__想定外の理由__ で __うまくいかない__ ことが __良くあります__。そのような場合は、その時点で __臨機応変__ に __データ構造を変更__ すれば良いと思います。もちろん、データ構造を変更した結果、やっぱり元のデータ構造のほうが良かったということもありますが、そのような __経験を積む__ ことで、どのようなデータ構造が __自分にとって使いやすい__ かが __わかるようになります__。__失敗から学ぶ__ ことができれば、失敗は決して __無駄ではなく__、__有益__ だと考えて下さい。
:::

# 今回の記事のまとめ

:::note warn
__複数の Excel 座標__ を、__半角の `","`__  で __区切って連結__ した __文字列__ で表現するという __データ構造__ を使うことで、下記のプログラムのように、前回の記事よりも、さらに __簡潔に__ テストケースを記述することが出来る。

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

__`split`__ メソッドを使うことで、上記のデータ構造を、__元の list__ に __変換__ できる。

```python
"A1,A2,B1,B2,C1".split(",")
```

`test_judge` の 5 行目の `testdata` を、`testdata.split(",")` に __修正するだけ__ で、他の部分を __一切変更することなく__、テストを行うことができる。

```python
 1  def test_judge(testcases):
 2      for testcase in testcases:
 3          testdata, winner = testcase
 4          mb = Marubatsu()
 5          for coord in testdata.split(","):
 6              x, y = excel_to_xy(coord)
 7              mb.move(x, y)
 8          print(mb)
 9
10          if mb.judge() == winner:
11              print("ok")
12          else:
13              print("error!")
```

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

```python
def test_judge(testcases):
    for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
        for coord in testdata.split(","):
            x, y = excel_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
        mb = Marubatsu()
-       for coord in testdata:
+       for coord in testdata.split(","):
            x, y = excel_to_xy(coord)
            mb.move(x, y)
        print(mb)

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

:::

長くなったので今回の記事はここまでにします。次回は、xy 座標を、Excel座標以外の方法で表現するデータ構造と、それを処理するアルゴリズムを紹介します。

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

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

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

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

以下のリンクは、今回の記事で作成した test.py です。前回の記事と同様に、今回の記事で採用しなかった `test_judge` や `excels_to_list` を、プログラムの最後に記述しておくことにします。

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

# 次回の記事

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

[^1]:他の多くのプログラム言語では、記述するとエラーが発生します