Pythonで〇×ゲームのAIを一から作成する　その43　AI の視点からの結果の表示

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/93fb9b6daa59149514e9

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

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

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

https://github.com/ysgeso/marubatsu/blob/master/043/ai.py

## これまでに作成した AI

これまでに作成した AI の __アルゴリズム__ は以下の通りです。

| 関数名 | アルゴリズム |
|:-:|:--|
| __`ai1`__  | 左上から順に空いているマスを探し、最初に見つかったマスに着手する |
| __`ai2`__  | ランダムなマスに着手する  |


# `ai_match` の改良 その 6（AI の視点からの結果の表示）

前回の記事から引き続き、__`ai_match`__ の __改良__ を行います。

__`ai_match`__ で __AI どうし__ の __対戦__ を行う __目的__ は、__ある AI__ が、__別の AI__ に対して __どれくらいの強さ__ を持つかを __調べる__ ことにあります。しかし、__現状__ の __`ai_match`__ は、特定の AI からの視点で結果を表示するのではなく、__〇__ と __×__ を __担当__ した __AI からの視点__ で __結果を表示__ しています。そのため、__`ai_match`__ の __表示__ から、__どちら__ の AI が __強いか__ を __知る__ ことは __困難__ です。

__`ai_match`__ の __表示__ を、例えば下記のように、__`ai[0]`__ からの __視点__ で __勝ち__、__負け__、__引き分け__ の __回数と比率__ を __表示__ すれば、__`ai[0]`__ と __`ai[1]`__ の __どちらが強い__ かが __わかりやすく__ なります。

```
ai1 VS ai2
count
win    435 lose    110 draw     22
win    237 lose    303 draw     27

ratio
win  76.7% lose  19.4% draw   3.9%
win  41.8% lose  53.4% draw   4.8%
```

## `ai[0]` からの視点の通算成績の回数の計算

上記のような表示を行うためには、__`ai[0]`__ からの __視点__ での __通算成績__ の __回数__ を __計算__ する必要があります。そこで、下記のプログラムのように、__`count_list`__ を __計算__ した __後で__、__`count_list_ai0`__ という __変数__ に、__`ai[0]`__ からの __視点__ の __通算成績__ を __計算__ した結果を __代入__ することにします。なお、このプログラムを __繰り返し__ を使って __記述__ することは __可能__ ですが、かえって __わかりづらくなる__ ので、本記事では下記のように記述することにします。

* __3 ~ 7 行目__：__win__、__lose__、__draw__ のそれぞれの __キー__ の __値__ に、__`ai[0]` VS `ai[1]`__ の、__`ai[0]` から見た通算成績__ の __勝数__、__敗数__、__引き分け数__ を __代入__ する __dict__
* __9 ~ 13 行目__：__win__、__lose__、__draw__ のそれぞれの __キー__ の __値__ に、__`ai[1]` VS `ai[0]`__ の、__`ai[0]` から見た通算成績__ の __勝数__、__敗数__、__引き分け数__ を __代入__ する __dict__

```python
 1  count_list_ai0 = [
 2      # ai[0] VS ai[1] の場合の、ai[0] から見た通算成績
 3      { 
 4          "win": count_list[0][Marubatsu.CIRCLE],
 5          "lose": count_list[0][Marubatsu.CROSS],
 6          "draw": count_list[0][Marubatsu.DRAW],
 7      },
 8      # ai[1] VS ai[0] の場合の、ai[0] から見た通算成績
 9      { 
10          "win": count_list[1][Marubatsu.CROSS],
11          "lose": count_list[1][Marubatsu.CIRCLE],
12          "draw": count_list[1][Marubatsu.DRAW],
13      },
14  ]    
```

## ratio の比率の計算の修正

__ratio__ の比率の __計算__ は、__`count_list_ai0`__ を __使って計算__ する __必要__ があるので、下記のプログラムのように、__比率__ を __計算__ する __部分__ の __`count_list`__ を __`count_list_ai0`__ に __修正__ します。

```python
for i in range(2):
    for key in count_list_ai0[i]:
        ratio_list[i][key] = count_list_ai0[i][key] / match_num
```

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

```diff_python
for i in range(2):
-   for key in count_list[i]:
+   for key in count_list_ai0[i]:
-       ratio_list[i][key] = count_list[i][key] / match_num
+       ratio_list[i][key] = count_list_ai0[i][key] / match_num
```
</details>

## 結果の表示の修正

次に、`ai_match` を、先程示した、下記のように表示するように修正します。

```
ai1 VS ai2
count
win    435 lose    110 draw     22
win    237 lose    303 draw     27

ratio
win  76.7% lose  19.4% draw   3.9%
win  41.8% lose  53.4% draw   4.8%
```

__通算成績__ の __回数__ を __代入__ する __変数__ が、__`count_list`__ から __`count_list_ai0`__ に __変化__ したので、__結果を表示__ する部分のプログラムをそのように __修正__ します。なお、__`ratio_list`__ は、__`count_list_ai0`__ を __使って計算__ するように __修正済__ なので、__変更__ する __必要__ は __ありません__。

また、__`count_list_ai0`__ の __キー__ が __`"win"`__、__`"lose"`__、__`"draw"`__ に変化したので、__`order_list`__ の値も、下記のプログラムのように __修正__ する __必要__ があります。

```python
order_list = [ "win", "lose", "draw" ]
```
<details><summary><strong>修正箇所</strong></summary>

```diff_python
-order_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS, Marubatsu.DRAW ]
+order_list = [ "win", "lose", "draw" ]
```
</details>

下記は、`ai_match` の通算成績を表示する部分を修正したプログラムです。

* __2 行目__：__`order_list`__ を上記のように __修正__ する
* __6 行目__：__`count_list`__ を __`count_list_ai0`__ に __修正__ する

```python
 1  # 〇の勝利、×の勝利、引き分けの順番で表示することを表す list 
 2  order_list = [ "win", "lose", "draw" ]
 3   
 4  # 通算成績の回数と比率の表示
 5  width = max(len(str(match_num)), 6)
 6  diff_list = [ ("count", count_list_ai0, f"{width}d"),
 7                ("ratio", ratio_list, f"{width}.1%") ]
 8  for title, data, format in diff_list:
 9      print(title)
10      for i in range(2):
11          for key in order_list:
12              print(f" {key} {data[i][key]:{format}}", end="")
13          print()
14      print()
```

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

```diff_python
# 〇の勝利、×の勝利、引き分けの順番で表示することを表す list 
-order_list = [Marubatsu.CIRCLE, Marubatsu.CROSS, Marubatsu.DRAW]
+order_list = ["win", "lose", "draw"] 

# 通算成績の回数と比率の表示
width = max(len(str(match_num)), 6)
-diff_list = [ ("count", count_list, f"{width}d"),
+diff_list = [ ("count", count_list_ai0, f"{width}d"),
              ("ratio", ratio_list, f"{width}.1%") ]
for title, data, format in diff_list:
    print(title)
    for i in range(2):
        for key in order_list:
            print(f" {key} {data[i][key]:{format}}", end="")
        print()
    print()
```
</details>

## `ai_match` の修正

下記は、`ai_match` に対して上記の修正を行ったプログラムです。

```python
from marubatsu import Marubatsu
from collections import defaultdict

def ai_match(ai, match_num=10000):
    print(f"{ai[0].__name__} VS {ai[1].__name__}")
    
    mb = Marubatsu()

    # ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える
    count_list = [ defaultdict(int), defaultdict(int)]
    for _ in range(match_num):
        count_list[0][mb.play(ai, verbose=False)] += 1
        count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1

    # ai[0] から見た通算成績を計算する
    count_list_ai0 = [
        # ai[0] VS ai[1] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[0][Marubatsu.CIRCLE],
            "lose": count_list[0][Marubatsu.CROSS],
            "draw": count_list[0][Marubatsu.DRAW],
        },
        # ai[1] VS ai[0] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[1][Marubatsu.CROSS],
            "lose": count_list[1][Marubatsu.CIRCLE],
            "draw": count_list[1][Marubatsu.DRAW],
        },
    ]           

    # それぞれの比率を計算し、ratio_list に代入する
    ratio_list = [ {}, {} ]
    for i in range(2):
        for key in count_list_ai0[i]:
            ratio_list[i][key] = count_list_ai0[i][key] / match_num

    # ai[0] の勝利、勝利、引き分けの順番で表示することを表す list 
    order_list = ["win", "lose", "draw"] 

    # 通算成績の回数と比率の表示
    width = max(len(str(match_num)), 7)
    diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    for title, data, format in diff_list:
        print(title)
        for i in range(2):
            for key in order_list:
                print(f" {key} {data[i][key]:{format}}", end="")
            print()
        print()
```


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

```diff_python
from marubatsu import Marubatsu
from collections import defaultdict

def ai_match(ai, match_num=10000):
    print(f"{ai[0].__name__} VS {ai[1].__name__}")
    
    mb = Marubatsu()

    # ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える
    count_list = [ defaultdict(int), defaultdict(int)]
    for _ in range(match_num):
        count_list[0][mb.play(ai, verbose=False)] += 1
        count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1

    # ai[0] から見た通算成績を計算する
+   count_list_ai0 = [
+       # ai[0] VS ai[1] の場合の、ai[0] から見た通算成績
+        { 
+           "win": count_list[0][Marubatsu.CIRCLE],
+           "lose": count_list[0][Marubatsu.CROSS],
+           "draw": count_list[0][Marubatsu.DRAW],
+       },
+       # ai[1] VS ai[0] の場合の、ai[0] から見た通算成績
+       { 
+           "win": count_list[1][Marubatsu.CROSS],
+           "lose": count_list[1][Marubatsu.CIRCLE],
+           "draw": count_list[1][Marubatsu.DRAW],
+       },
+   ]           

    # それぞれの比率を計算し、ratio_list に代入する
    ratio_list = [ {}, {} ]
    for i in range(2):
-       for key in count_list[i]:
+       for key in count_list_ai0[i]:
-           ratio_list[i][key] = count_list[i][key] / match_num
+           ratio_list[i][key] = count_list_ai0[i][key] / match_num

    # ai[0] の勝利、勝利、引き分けの順番で表示することを表す list 
-   order_list = [Marubatsu.CIRCLE, Marubatsu.CROSS, Marubatsu.DRAW]
+   order_list = ["win", "lose", "draw"] 

    # 通算成績の回数と比率の表示
    width = max(len(str(match_num)), 7)
-   diff_list = [ ("count", count_list, f"{width}d"),
+   diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    for title, data, format in diff_list:
        print(title)
        for i in range(2):
            for key in order_list:
                print(f" {key} {data[i][key]:{format}}", end="")
            print()
        print()
```
</details>

下記のプログラムを実行することで、__`ai[0]`__ からの __視点__ で、通算成績の __回数__ と __比率__ の __表示__ が行われることが __確認__ できます。なお、__`match_num`__ の __デフォルト値__ である、__10000 回__ の __対戦__ を行うと、__数秒__ ほど時 __間がかかる__ ので、__対戦回数__ に __`1000` を指定__ しています。

```python
from ai import ai1, ai2

ai_match(ai=[ai1, ai2], match_num=1000)
```
実行結果（実行結果はランダムなので下記とは異なる場合があります）
```
ai1 VS ai2
count
 win     756 lose     203 draw      41
 win     446 lose     514 draw      40

ratio
 win   75.6% lose   20.3% draw    4.1%
 win   44.6% lose   51.4% draw    4.0%
```

## `order_list` の廃止

[前回の記事](https://qiita.com/ysgeso/items/bf42b7b578bc2d5da949#%E7%B5%90%E6%9E%9C%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B%E5%87%A6%E7%90%86%E3%81%AE%E6%94%B9%E8%89%AF)で説明したように、__`order_list`__ は、__`count`__ の __要素の値__ を、__dict__ から __defaultdict__ に __変更__ した結果、__`items`__ メソッドを使った __繰り返し処理__ で、__`count`__ の __キーの値__ の __表示の順番__ が __思い通り__ の順番に __ならない場合がある__ ため __導入した__ ものです。

__`count_list_ai0`__ の __各要素__ に代入された __dict__ は、dict の __リテラル__ で __`"win"`__、 __`"lose"`__、 __`"draw"`__ の __キー__ を __記述__ した __順番通り__ で __キー__ に __値を代入__ するので、__`items`__  メソッドを __利用__ した __繰り返し処理__ では、__必ず `"win"`__、 __`"lose"`__、 __`"draw"`__ の __順番__ で __キー__ と __キーの値__ が __取り出され__ ます。従って、__`count_list_ai0`__ の場合は __`order_list`__ は __不要__ になります。

また、__`ratio_list`__ の __各要素__ に代入される __dict__ は、__`count_list_ai0`__ の __各要素__ に対する __for 文__ の __繰り返し処理__ によって __キー__ に __値が代入__ されるので、__`"win"`__、 __`"lose"`__、 __`"draw"`__ の __順番__ で __キー__ に __値が代入__ されます。従って、__比率の表示__ でも __`order_list`__ は __不要__ です。

下記は、__`order_list`__ を __使わない__ ように __`ai_match`__ を __修正__ したプログラムです。

* __`order_list = ["win", "lose", "draw"]`__ を削除する
* __5 行目__：__`order_list`__ を __`data[i].items()`__ に修正し、__繰り返しのたび__ に取り出す __キー__ と __キーの値__ を __`key`__ と __`value`__ に代入する
* __6 行目__：__`data[i][key]`__ を __`key`__ に __修正__ する

この修正で、__`order_list`__ を __削除__ するだけでなく、__6 行目__ が __簡潔__ になります。

```python
1  def ai_match(ai, match_num=10000):
元と同じなので省略
2      for title, data, format in diff_list:
3          print(title)
4          for i in range(2):
5              for key, value in data[i].items():
6                  print(f" {key} {value:{format}}", end="")
7              print()
8      print()
```

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

```python
def ai_match(ai, match_num=10000):
    print(f"{ai[0].__name__} VS {ai[1].__name__}")
    
    mb = Marubatsu()

    # ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える
    count_list = [ defaultdict(int), defaultdict(int)]
    for _ in range(match_num):
        count_list[0][mb.play(ai, verbose=False)] += 1
        count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1

    # ai[0] から見た通算成績を計算する
    count_list_ai0 = [
        # ai[0] VS ai[1] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[0][Marubatsu.CIRCLE],
            "lose": count_list[0][Marubatsu.CROSS],
            "draw": count_list[0][Marubatsu.DRAW],
        },
        # ai[1] VS ai[0] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[1][Marubatsu.CROSS],
            "lose": count_list[1][Marubatsu.CIRCLE],
            "draw": count_list[1][Marubatsu.DRAW],
        },
    ]           

    # それぞれの比率を計算し、ratio_list に代入する
    ratio_list = [ {}, {} ]
    for i in range(2):
        for key in count_list_ai0[i]:
            ratio_list[i][key] = count_list_ai0[i][key] / match_num

    # 通算成績の回数と比率の表示
    width = max(len(str(match_num)), 7)
    diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    for title, data, format in diff_list:
        print(title)
        for i in range(2):
            for key, value in data[i].items():
                print(f" {key} {value:{format}}", end="")
            print()
        print()
```
</details>

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

```diff_python
def ai_match(ai, match_num=10000):
元と同じなので省略
-   # ai[0] の勝利、勝利、引き分けの順番で表示することを表す list 
-   order_list = ["win", "lose", "draw"] 

    # 通算成績の回数と比率の表示
    width = max(len(str(match_num)), 7)
    diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    for title, data, format in diff_list:
        print(title)
        for i in range(2):
-           for key, value in order_list:
+           for key, value in data[i].items():
-               print(f" {key} {data[i][key]:{format}}", end="")
+               print(f" {key} {value:{format}}", end="")
            print()
        print()
```
</details>

下記のプログラムを実行することで、修正後も正しく表示されることが確認できます。

```python
ai_match(ai=[ai1, ai2], match_num=1000)
```

実行結果（実行結果はランダムなので下記とは異なる場合があります）

```
ai1 VS ai2
count
 win     798 lose     155 draw      47
 win     457 lose     504 draw      39

ratio
 win   79.8% lose   15.5% draw    4.7%
 win   45.7% lose   50.4% draw    3.9%
```

## 表示の修正 その 1 （担当するマークの表示）

上記の表示では、__それぞれ__ の __行__ のデータで、__`ai[0]`__ が __どのマーク__ を __担当__ しているかが __わかりづらい__ ので、下記のように、各 __行の先頭__ に、__`ai[0]`__ が __担当__ する __マークを表示__ するように __修正__ することにします。

```
ai1 VS ai2
count
o win     798 lose     152 draw      50
x win     436 lose     528 draw      36

ratio
o win   79.8% lose   15.2% draw    5.0%
x win   43.6% lose   52.8% draw    3.6%
```

そのためには、__繰り返しの処理の中__ で、各 __行の先頭__ に __表示__ する __文字列__ を __要素__ とする __list__ が __必要__ になります。下記のプログラムでは、そのデータを __`item_text_list`__ という名前の変数に __代入__ しています。__名前__ は、__項目__（item）の __文字__（text）のリストから付けました。

```python
item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]
```

次に、下記のプログラムのように、__結果__ を __表示する処理__ を __修正__ します。

* __4 行目__：それぞれの __行を表示__ する __繰り返し処理__ の __直前__ で、__`item_text_list[i]`__ を __表示__ するように __修正__ する。その際に、__改行しない__ ように __`end=""`__ を __記述__ する

```python
 1  for title, data, format in diff_list:
 2      print(title)
 3      for i in range(2):
 4          print(item_text_list[i], end="")
 5          for key, value in data[i].items():
 6              print(f" {key} {value:{format}}", end="")
 7          print()
 8      print()
```

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

```diff_python
for title, data, format in diff_list:
    print(title)
    for i in range(2):
+       print(item_text_list[i], end="")
        for key, value in data[i].items():
            print(f" {key} {value:{format}}", end="")
        print()
    print()
```
</details>

下記は、上記の修正を行った `ai_match` のプログラムです。

```python
def ai_match(ai, match_num=10000):
元と同じなので省略
    # 各行の先頭に表示する文字列のリスト
    item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]    
    
    # 通算成績の回数と比率の表示
    width = max(len(str(match_num)), 7)
    diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    for title, data, format in diff_list:
        print(title)
        for i in range(2):
            print(item_text_list[i], end="")
            for key, value in data[i].items():
                print(f" {key} {value:{format}}", end="")
            print()
        print()
```

<details><summary><strong>全体のプログラム</strong></summary>

```python
def ai_match(ai, match_num=10000):
    print(f"{ai[0].__name__} VS {ai[1].__name__}")

    mb = Marubatsu()

    # ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える
    count_list = [ defaultdict(int), defaultdict(int)]
    for _ in range(match_num):
        count_list[0][mb.play(ai, verbose=False)] += 1
        count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1

    # ai[0] から見た通算成績を計算する
    count_list_ai0 = [
        # ai[0] VS ai[1] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[0][Marubatsu.CIRCLE],
            "lose": count_list[0][Marubatsu.CROSS],
            "draw": count_list[0][Marubatsu.DRAW],
        },
        # ai[1] VS ai[0] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[1][Marubatsu.CROSS],
            "lose": count_list[1][Marubatsu.CIRCLE],
            "draw": count_list[1][Marubatsu.DRAW],
        },
    ]           

    # それぞれの比率を計算し、ratio_list に代入する
    ratio_list = [ {}, {} ]
    for i in range(2):
        for key in count_list_ai0[i]:
            ratio_list[i][key] = count_list_ai0[i][key] / match_num

    # 各行の先頭に表示する文字列のリスト
    item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]    
    
    # 通算成績の回数と比率の表示
    width = max(len(str(match_num)), 7)
    diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    for title, data, format in diff_list:
        print(title)
        for i in range(2):
            print(item_text_list[i], end="")
            for key, value in data[i].items():
                print(f" {key} {value:{format}}", end="")
            print()
        print()
```
</details>

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

```diff_python
def ai_match(ai, match_num=10000):
元と同じなので省略
    # 各行の先頭に表示する文字列のリスト
+   item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]    
    
    # 通算成績の回数と比率の表示
    width = max(len(str(match_num)), 7)
    diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    for title, data, format in diff_list:
        print(title)
        for i in range(2):
+           print(item_text_list[i], end="")
            for key, value in data[i].items():
                print(f" {key} {value:{format}}", end="")
            print()
        print()
```
</details>

下記のプログラムを実行することで、__行の先頭__ に __マークが表示__ されることが __確認__ できます。

```python
ai_match(ai=[ai1, ai2], match_num=1000)
```

実行結果（実行結果はランダムなので下記とは異なる場合があります）

```
ai1 VS ai2
count
o win     804 lose     164 draw      32
x win     448 lose     508 draw      44

ratio
o win   80.4% lose   16.4% draw    3.2%
x win   44.8% lose   50.8% draw    4.4%
```

## 表示の修正 その 2 （表形式での表示）

上記の表示では、win などの __文字__ と __数字__ が __交互__ に __並んでいる__ 点が少し __わかりにくい__ という問題があります。そこで、下記のように、__表__ のような __形式__ で __表示__ することにします。

```
count    win   lose   draw
o        435    110     22
x        237    303     27

ratio    win   lose   draw
o      76.7%  19.4%   3.9%
x      41.8%  53.4%   4.8%
```

下記は、`ai_match` をそのように修正したプログラムです。

* __3 行目__：__`end=""`__ を __記述__ して、count や ratio の表示の __直後__ で __改行しない__ ようにする
* __4、5 行目__：__for 文__ による __繰り返し処理__ によって、__`data[0]`__ の __キー__ を __順番に取り出して__、__改行せず__ に __表示__ する。その際に、__キー__ の __直前__ に __半角の空白__ を __表示__ することで、__キーの表示__ が __くっつかない__ ようにしている
* __6 行目__：__`print()`__ を実行して __改行__ する
* __9 行目__：__数字__ を __表示__ する __直前__ に、__キー__ を __表示__ する __必要が無くなった__ ので、__`items`__ ではなく、__`values`__ メソッドを使って、__キーの値のみ__ を __取り出す__ ように __修正__ する
* __10 行目__：__キー__ を __表示しない__ ように __修正__ する

```python
 1  def ai_match(ai, match_num=10000):
元と同じなので省略
 2      for title, data, format in diff_list:
 3          print(title, end="")
 4          for key in data[0]:
 5              print(f" {key}", end="")
 6          print()
 7          for i in range(2):
 8              print(item_text_list[i], end="")
 9              for value in data[i].values():
10                  print(f" {value:{format}}", end="")
11              print()
12          print()
```

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

```python
def ai_match(ai, match_num=10000):
    print(f"{ai[0].__name__} VS {ai[1].__name__}")
    
    mb = Marubatsu()

    # ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える
    count_list = [ defaultdict(int), defaultdict(int)]
    for _ in range(match_num):
        count_list[0][mb.play(ai, verbose=False)] += 1
        count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1

    # ai[0] から見た通算成績を計算する
    count_list_ai0 = [
        # ai[0] VS ai[1] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[0][Marubatsu.CIRCLE],
            "lose": count_list[0][Marubatsu.CROSS],
            "draw": count_list[0][Marubatsu.DRAW],
        },
        # ai[1] VS ai[0] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[1][Marubatsu.CROSS],
            "lose": count_list[1][Marubatsu.CIRCLE],
            "draw": count_list[1][Marubatsu.DRAW],
        },
    ]           

    # それぞれの比率を計算し、ratio_list に代入する
    ratio_list = [ {}, {} ]
    for i in range(2):
        for key in count_list_ai0[i]:
            ratio_list[i][key] = count_list_ai0[i][key] / match_num
            
    # 各行の先頭に表示する文字列のリスト
    item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]    
    
    # 通算成績の回数と比率の表示
    width = max(len(str(match_num)), 7)
    diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    for title, data, format in diff_list:
        print(title, end="")
        for key in data[0]:
            print(f" {key}", end="")
        print()
        for i in range(2):
            print(item_text_list[i], end="")
            for value in data[i].values():
                print(f" {value:{format}}", end="")
            print()
        print()
```
</details>

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

```diff_python
def ai_match(ai, match_num=10000):
元と同じなので省略
    for title, data, format in diff_list:
-       print(title)
+       print(title, end="")
+       for key in data[0]:
+           print(f" {key}", end="")
+       print()
        for i in range(2):
            print(item_text_list[i], end="")
            for value in data[i].values():
-               print(f" {key} {value:{format}}", end="")
+               print(f" {value:{format}}", end="")
            print()
        print()
```
</details>

上記の __4 行目__ の __for 文__ の __反復可能オブジェクト__ に __`data[0]`__ を __記述__ する点が __わかりづらい__ と思いますので説明します。文章で説明すると長くなるので、箇条書きで説明します。

* __for 文__ の __繰り返し処理__ によって、 __`data`__ には、__`count_list_ai0`__ と __`ratio_list`__ が __順番__ に __代入__ される
* この __2 つ__ の __データ__ は、いずれも __2 つの要素__ を持つ __list__ であり、その __2 つの要素__ は __それぞれ `data[0]`__、__`data[1]`__ のように __記述__ できる
* 先ほど説明したように、__どの要素__ も __`"win"`__、__`"lose"`__、__`"draw"`__ の __順番__ で __キー__ に __値が代入__ される __dict__ である
* 従って、4 行目の __for 文__ の __反復可能オブジェクト__ に、__`data[0]`__ と __`data[1]`__ の __どちらを記述__ しても、キーは __`"win"`__、__`"lose"`__、__`"draw"`__ の __順番__ で __取り出される__

上記から、4 行目の __for 文__ の __反復可能オブジェクト__ には __`data[0]`__ または __`data[1]`__ の __どちらを記述__ しても __構いません__ が、このような場合は、__先頭__ の __要素__ の __`data[0]`__ を記述するのが一般的でしょう。なお、__反復可能オブジェクト__ に __`count_list_ai0[0]`__ や、__`ratio_list[0]`__ などを __記述__ しても __かまいません__。__わかりやすい__ と思った __記述__ を __採用__ して下さい。 

下記のプログラムを実行することで、__表の形式__ で __表示__ されることが __確認__ できますが、__count__ と __ratio__ の __行__ と、__データの表示__ の __行__ の __表示内容__ が __上下でずれる__ という __問題__ があります。このような、上下のずれが起きる __原因__ について少し考えてみて下さい。

```python
ai_match(ai=[ai1, ai2], match_num=1000)
```
実行結果（実行結果はランダムなので下記とは異なる場合があります）
```
ai1 VS ai2
count win lose draw
o     762     195      43
x     448     516      36

ratio win lose draw
o   76.2%   19.5%    4.3%
x   44.8%   51.6%    3.6%
```

## 上下のずれの問題の修正

__上下のずれ__ がおきる __原因__ と __修正方法__ は以下の通りです。

* __count__、__ratio__ と、__`o`__、__`x`__ の __表示幅__ が __異なる__。__`o`__ と __`x`__ の __表示幅__ を __count__、__ratio__ と __同じ 5 文字__ にすることで解決できる
* __win__、__lose__、__draw__ の __表示幅__ が、__数字の表示幅__ と __異なる__。これらの __表示幅__ を、__数字__ の __表示幅__ である __`width`__ に __合わせる__ ことで解決できる

下記は、そのように `ai_match` を修正したプログラムです。

* __5 行目__：__`key`__ の __書式指定__ の __文字幅__ に __`width`__ を __指定__ するように __修正__ する
* __8 行目__：__`item_text_list[i]`__ の __表示__ を __f 文字列__ を使って、__書式指定__ の __文字幅__ に __`5`__ を __指定__ するように __修正__ する

```python
 1  def ai_match(ai, match_num=10000):
元と同じなので省略
 2      for title, data, format in diff_list:
 3          print(title, end="")
 4          for key in data[0]:
 5              print(f" {key:{width}}", end="")
 6          print()
 7          for i in range(2):
 8              print(f"{item_text_list[i]:5}", end="")
 9              for value in data[i].values():
10                  print(f" {value:{format}}", end="")
11              print()
12          print()
```

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

```python
def ai_match(ai, match_num=10000):
    print(f"{ai[0].__name__} VS {ai[1].__name__}")

    mb = Marubatsu()

    # ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える
    count_list = [ defaultdict(int), defaultdict(int)]
    for _ in range(match_num):
        count_list[0][mb.play(ai, verbose=False)] += 1
        count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1

    # ai[0] から見た通算成績を計算する
    count_list_ai0 = [
        # ai[0] VS ai[1] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[0][Marubatsu.CIRCLE],
            "lose": count_list[0][Marubatsu.CROSS],
            "draw": count_list[0][Marubatsu.DRAW],
        },
        # ai[1] VS ai[0] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[1][Marubatsu.CROSS],
            "lose": count_list[1][Marubatsu.CIRCLE],
            "draw": count_list[1][Marubatsu.DRAW],
        },
    ]           

    # それぞれの比率を計算し、ratio_list に代入する
    ratio_list = [ {}, {} ]
    for i in range(2):
        for key in count_list_ai0[i]:
            ratio_list[i][key] = count_list_ai0[i][key] / match_num

    # 各行の先頭に表示する文字列のリスト
    item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]    
    
    # 通算成績の回数と比率の表示
    width = max(len(str(match_num)), 7)
    diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    for title, data, format in diff_list:
        print(title, end="")
        for key in data[0]:
            print(f" {key:{width}}", end="")
        print()
        for i in range(2):
            print(f"{item_text_list[i]:5}", end="")
            for value in data[i].values():
                print(f" {value:{format}}", end="")
            print()
        print()
```
</details>

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

```diff_python
def ai_match(ai, match_num=10000):
元と同じなので省略
    for title, data, format in diff_list:
        print(title, end="")
        for key in data[0]:
-           print(f" {key}", end="")
+           print(f" {key:{width}}", end="")
        print()
        for i in range(2):
-           print(item_text_list[i], end="")
+           print(f"{item_text_list[i]:5}", end="")
            for value in data[i].values():
                print(f" {value:{format}}", end="")
            print()
        print()
```
</details>

下記のプログラムを実行すると、__win__、__lose__、__draw__ と、その __下の数字__ の __ずれが解消されていない__ ことがわかります。その __原因__ について少し考えてみて下さい。

```python
ai_match(ai=[ai1, ai2], match_num=1000)
```
実行結果（実行結果はランダムなので下記とは異なる場合があります）
```
ai1 VS ai2
count win     lose    draw   
o         787     167      46
x         453     519      28

ratio win     lose    draw   
o       78.7%   16.7%    4.6%
x       45.3%   51.9%    2.8%
```

### 揃えの修正

ずれが解消されない __原因__ は、[前回の記事](https://qiita.com/ysgeso/items/bf42b7b578bc2d5da949#%E6%8F%83%E3%81%88) で説明したように、__f 文字列__ では、__書式指定__ に __揃えを指定しない__ 場合は、__文字列型__ のデータは __左揃え__ で、__数値型__ のデータは __右揃え__ が __揃えに指定__ されたことになるからです。従って、下記のプログラムのように、__win__、__lose__、__draw__ の __書式指定__ の __揃え__ を __右揃え__ に __指定__ することでこの問題を __解決__ することができます。

* __5 行目__：__書式指定__ の __揃え__ に __`>`__ を __指定__ することで、__`key`__ を __右揃え__ で __表示__ する

```python
 1  def ai_match(ai, match_num=10000):
元と同じなので省略
 2      for title, data, format in diff_list:
 3          print(title, end="")
 4          for key in data[0]:
 5              print(f" {key:>{width}}", end="")
 6          print()
 7          for i in range(2):
 8              print(f"{item_text_list[i]:5}", end="")
 9              for value in data[i].values():
10                  print(f" {value:{format}}", end="")
11              print()
12          print()
```

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

```python
def ai_match(ai, match_num=10000):
    print(f"{ai[0].__name__} VS {ai[1].__name__}")

    mb = Marubatsu()

    # ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える
    count_list = [ defaultdict(int), defaultdict(int)]
    for _ in range(match_num):
        count_list[0][mb.play(ai, verbose=False)] += 1
        count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1

    # ai[0] から見た通算成績を計算する
    count_list_ai0 = [
        # ai[0] VS ai[1] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[0][Marubatsu.CIRCLE],
            "lose": count_list[0][Marubatsu.CROSS],
            "draw": count_list[0][Marubatsu.DRAW],
        },
        # ai[1] VS ai[0] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[1][Marubatsu.CROSS],
            "lose": count_list[1][Marubatsu.CIRCLE],
            "draw": count_list[1][Marubatsu.DRAW],
        },
    ]           

    # それぞれの比率を計算し、ratio_list に代入する
    ratio_list = [ {}, {} ]
    for i in range(2):
        for key in count_list_ai0[i]:
            ratio_list[i][key] = count_list_ai0[i][key] / match_num
    
    # 各行の先頭に表示する文字列のリスト
    item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]    
    
    # 通算成績の回数と比率の表示
    width = max(len(str(match_num)), 7)
    diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    for title, data, format in diff_list:
        print(title, end="")
        for key in data[0]:
            print(f" {key:>{width}}", end="")
        print()
        for i in range(2):
            print(f"{item_text_list[i]:5}", end="")
            for value in data[i].values():
                print(f" {value:{format}}", end="")
            print()
        print()
```
</details>

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

```diff_python
def ai_match(ai, match_num=10000):
元と同じなので省略
    for title, data, format in diff_list:
        print(title, end="")
        for key in data[0]:
-           print(f" {key:{width}}", end="")
+           print(f" {key:>{width}}", end="")
        print()
        for i in range(2):
            print(f"{item_text_list[i]:5}", end="")
            for value in data[i].values():
                print(f" {value:{format}}", end="")
            print()
        print()
```
</details>

下記のプログラムを実行するとで、__意図通り__ に __表示__ が行われることが __確認__ できます。

```python
ai_match(ai=[ai1, ai2], match_num=1000)
```

実行結果（実行結果はランダムなので下記とは異なる場合があります）

```
ai1 VS ai2
count     win    lose    draw
o         780     173      47
x         436     537      27

ratio     win    lose    draw
o       78.0%   17.3%    4.7%
x       43.6%   53.7%    2.7%
```

## 行の先頭に表示する文字の、文字数が異なる場合の対処法

__`ai_match`__ では、__行の先頭__ に __表示__ する、__count__ と __ratio__ の文字列が __偶然同じ 5 文字__ だったので __必要ありません__ でしたが、それらの __文字列の長さ__ が __異なる__ 場合は、下記のプログラムのように、__表示__ する __文字幅__ を __変数__ に代入し、その __変数を使って__ 書式指定の __文字幅__ を __指定__ する必要があります。なお、__変数__ に __文字幅__ を __代入__ しておくことで、__後から__ 文字幅を __変更したい__ と思った時に、__簡単__ に __変更できるようになる__ という __利点__ が得られます。

* __2 行目__：__文字幅__ を表す __数値__ を __`title_width`__ に代入する
* __4、9 行目__：__`title_width`__ を使って、__書式指定__ の __文字幅__ を __指定__ するように __修正__ する

```python
 1  def ai_match(ai, match_num=10000):
元と同じなので省略
 2      title_width = 5
 3      for title, data, format in diff_list:
 4          print(f"{title:{title_width}}", end="")
 5          for key in data[0]:
 6              print(f" {key:>{width}}", end="")
 7          print()
 8          for i in range(2):
 9              print(f"{item_text_list[i]:{title_width}}", end="")
10              for value in data[i].values():
11                  print(f" {value:{format}}", end="")
12              print()
13          print()
```

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

```python
def ai_match(ai, match_num=10000):
    print(f"{ai[0].__name__} VS {ai[1].__name__}")
    
    mb = Marubatsu()

    # ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える
    count_list = [ defaultdict(int), defaultdict(int)]
    for _ in range(match_num):
        count_list[0][mb.play(ai, verbose=False)] += 1
        count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1

    # ai[0] から見た通算成績を計算する
    count_list_ai0 = [
        # ai[0] VS ai[1] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[0][Marubatsu.CIRCLE],
            "lose": count_list[0][Marubatsu.CROSS],
            "draw": count_list[0][Marubatsu.DRAW],
        },
        # ai[1] VS ai[0] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[1][Marubatsu.CROSS],
            "lose": count_list[1][Marubatsu.CIRCLE],
            "draw": count_list[1][Marubatsu.DRAW],
        },
    ]           

    # それぞれの比率を計算し、ratio_list に代入する
    ratio_list = [ {}, {} ]
    for i in range(2):
        for key in count_list_ai0[i]:
            ratio_list[i][key] = count_list_ai0[i][key] / match_num

    # 各行の先頭に表示する文字列のリスト
    item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]    
    
    # 通算成績の回数と比率の表示
    width = max(len(str(match_num)), 7)
    diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    title_width = 5
    for title, data, format in diff_list:
        print(f"{title:{title_width}}", end="")
        for key in data[0]:
            print(f" {key:>{width}}", end="")
        print()
        for i in range(2):
            print(f"{item_text_list[i]:{title_width}}", end="")
            for value in data[i].values():
                print(f" {value:{format}}", end="")
            print()
        print()
```
</details>

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

```diff_python
def ai_match(ai, match_num=10000):
元と同じなので省略
+   title_width = 5
    for title, data, format in diff_list:
-       print(title, end="")
+       print(f"{title:{title_width}}", end="")
        for key in data[0]:
            print(f" {key:>{width}}", end="")
        print()
        for i in range(2):
-           print(f"{item_text_list[i]:5", end="")
+           print(f"{item_text_list[i]:{title_width}}", end="")
            for value in data[i].values():
                print(f" {value:{format}}", end="")
            print()
        print()
```
</details>

下記のプログラムを実行するとで、__意図通り__ に __表示__ が行われることが __確認__ できます。興味がある方は、__`title_width`__ の値を __変更__ したり、__`diff_list`__ の中の __`"count"`__ と __`"ratio"`__ を __長さの異なる文字列__ に __変更__ しても __意図通り__ に __表示__ されることを __確認__ してみて下さい。

```python
ai_match(ai=[ai1, ai2], match_num=1000)
```

実行結果（実行結果はランダムなので下記とは異なる場合があります）

```
ai1 VS ai2
count     win    lose    draw
o         788     192      20
x         440     534      26

ratio     win    lose    draw
o       78.8%   19.2%    2.0%
x       44.0%   53.4%    2.6%
```

# `ai_match` の改良 その 7（両方の対戦を合わせた結果の表示）

`ai_match` は、__手番を入れ替えない場合__ と、__手番を入れ替えた場合__ の __表示__ を行いますが、__〇×ゲーム__ は、__〇 の手番__ と __× の手番__ で、__有利不利__ が __大きく異なる__ ゲームなので、__それだけ__ では __どちらが強いか__ が __はっきりとしません__。そこで、その __両方__ の __対戦を合わせた__ 成績を __計算__ して __表示__ することで、__どちらが強いか__ を __明確にする__ ことにします。

__両方の対戦__ を合わせた __回数__ と __比率__ は、__`count_list_ai0`__ と __`ratio_list`__ の __要素__ に __代入__ するのが __自然__ なので、それらの __2 番__ の __要素__ に __代入__ することにします。

下記の表は、__`count_list_ai0`__ と __`ratio_list`__ の __インデックス__ と __要素__ をまとめたものです。

| インデックス | 要素 |
|:-:|:--|
| __0__  | 順番を __入れ替えない__ 場合の対戦結果  |
| __1__  | 順番を __入れ替えた__ 場合の対戦結果  |
| __2__  | __両方の対戦__ を __合わせた__ 場合の対戦結果  |

## 両方の対戦を合わせた回数の計算

__両方の対戦__ を __合わせた回数__ の __計算__ は、__`count_list_ai0`__ の __0 番__ と __1 番__ の __要素__ の __win__、__lose__、__draw__ の __それぞれ__ の __値の合計__ なので、下記のプログラムのように、__繰り返し処理__ を使って __計算__ することが __できます__。

* __1 行目__：__`count_list_ai0`__ の __2 番__ の __要素__ として、__空の dict__ を __追加__ する
* __2、3 行目__：__`count_list_ai0`__ の __0 番__ の __要素__ に代入された __dict__ から __順番__ に __キーを取り出し__、__2 番__ の __要素__ の __dict__ の __そのキーの値__ に、__0 番__ と __1 番__ の __要素__ の __そのキーの値__ の __合計__ を計算して __代入__ する

:::note
2 行目の __for 文__ の __反復可能オブジェクト__ に、__`count_list_ai[0]`__ を __記述__ した __理由__ は、先程の「__表示の修正その 2__」で __説明__ した理由と __同じ__ です。同様の理由で、__反復可能オブジェクト__ に __`count_list_ai[1]`__ を __記述__ しても __構いません__。
:::

```python
count_list_ai0.append({})
for key in count_list_ai0[0]:
    count_list_ai0[2][key] = count_list_ai0[0][key] + count_list_ai0[1][key]
```

__`ratio_list`__ の __2 番__ の要素は、__0 番__ と __1 番__ の __要素__ と __同じ方法__ で __計算__ できるので、__`ratio_list`__ に関する __プログラム__ は以下のように __修正__ します。

* __1 行目__：__3 つ__ の __空の dict__ を __要素__ として持つ __list__ に __修正__ する
* __2 行目__：__繰り返し__ の __回数__ を 2 回から __3 回__ に __修正__ する

```python
    ratio_list = [ {}, {}, {} ]
    for i in range(3):
        for key in ai_count_list[i]:
            ratio_list[i][key] = ai_count_list[i][key] / sum(ai_count_list[i])
```

次に、__両方の対戦__ を __合わせた__ 結果の __行の先頭__ に __表示__ する __文字列__ を __決める__ 必要があります。__両方の対戦__ の __合計__ なので、__`"total"`__ と表示することにし、下記のプログラムのように、各行の __先頭に表示__ する __文字列__ を __代入__ する __`item_text_list`__ を __修正__ します。

```python
item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS, "total" ] 
```

最後に、下記のプログラムの 1 行目のように、__結果の行__ を __表示__ する __繰り返し処理__ の __回数__ を __2 から 3__ に __修正__ することで、__両方の対戦を合わせた結果__ が __表示__ されるようになります。

```python
for i in range(3):
    print(f"{item_text_list[i]:5}", end="")
    for value in data[i].values():
        print(f" {value:{format}}", end="")
    print()
```            

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

```diff_python
-for i in range(2):
+for i in range(3):
    print(f"{item_text_list[i]:5}", end="")
    for value in data[i].values():
        print(f" {value:{format}}", end="")
    print()
```
</details>

下記は、`ai_match` を上記のように修正したプログラムです。

```python
def ai_match(ai, match_num=10000):
元と同じなので省略
    # 両方の対戦の通算成績の合計を計算する
    count_list_ai0.append({})
    for key in count_list_ai0[0]:
        count_list_ai0[2][key] = count_list_ai0[0][key] + count_list_ai0[1][key]

    # それぞれの比率を計算し、ratio_list に代入する
    ratio_list = [ {}, {}, {} ]
    for i in range(3):
        for key in count_list_ai0[i]:
            ratio_list[i][key] = count_list_ai0[i][key] / match_num
            
    # 各行の先頭に表示する文字列のリスト
    item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS, "total" ]    
    
    # 通算成績の回数と比率の表示
    width = max(len(str(match_num)), 7)
    diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    for title, data, format in diff_list:
        print(title, end="")
        for key in data[0]:
            print(f" {key:>{width}}", end="")
        print()
        for i in range(3):
            print(f"{item_text_list[i]:5}", end="")
            for value in data[i].values():
                print(f" {value:{format}}", end="")
            print()
        print()
```

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

```python
def ai_match(ai, match_num=10000):
    print(f"{ai[0].__name__} VS {ai[1].__name__}")

    mb = Marubatsu()

    # ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える
    count_list = [ defaultdict(int), defaultdict(int)]
    for _ in range(match_num):
        count_list[0][mb.play(ai, verbose=False)] += 1
        count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1

    # ai[0] から見た通算成績を計算する
    count_list_ai0 = [
        # ai[0] VS ai[1] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[0][Marubatsu.CIRCLE],
            "lose": count_list[0][Marubatsu.CROSS],
            "draw": count_list[0][Marubatsu.DRAW],
        },
        # ai[1] VS ai[0] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[1][Marubatsu.CROSS],
            "lose": count_list[1][Marubatsu.CIRCLE],
            "draw": count_list[1][Marubatsu.DRAW],
        },
    ]           

    # 両方の対戦の通算成績の合計を計算する
    count_list_ai0.append({})
    for key in count_list_ai0[0]:
        count_list_ai0[2][key] = count_list_ai0[0][key] + count_list_ai0[1][key]

    # それぞれの比率を計算し、ratio_list に代入する
    ratio_list = [ {}, {}, {} ]
    for i in range(3):
        for key in count_list_ai0[i]:
            ratio_list[i][key] = count_list_ai0[i][key] / match_num
            
    # 各行の先頭に表示する文字列のリスト
    item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS, "total" ]    
    
    # 通算成績の回数と比率の表示
    width = max(len(str(match_num)), 7)
    diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    for title, data, format in diff_list:
        print(title, end="")
        for key in data[0]:
            print(f" {key:>{width}}", end="")
        print()
        for i in range(3):
            print(f"{item_text_list[i]:5}", end="")
            for value in data[i].values():
                print(f" {value:{format}}", end="")
            print()
        print()
```
</details>

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

```diff_python
def ai_match(ai, match_num=10000):
元と同じなので省略
+   # 両方の対戦の通算成績の合計を計算する
+   count_list_ai0.append({})
+   for key in count_list_ai0[0]:
+       count_list_ai0[2][key] = count_list_ai0[0][key] + count_list_ai0[1][key]

    # それぞれの比率を計算し、ratio_list に代入する
-   ratio_list = [ {}, {} ]
+   ratio_list = [ {}, {}, {} ]
-   for i in range(2):
+   for i in range(3):
        for key in count_list_ai0[i]:
            ratio_list[i][key] = count_list_ai0[i][key] / match_num
            
    # 各行の先頭に表示する文字列のリスト
-   item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS ]    
+   item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS, "total" ]    
    
    # 通算成績の回数と比率の表示
    width = max(len(str(match_num)), 7)
    diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    for title, data, format in diff_list:
        print(title, end="")
        for key in data[0]:
            print(f" {key:>{width}}", end="")
        print()
-       for i in range(2):
+       for i in range(3):
            print(f"{item_text_list[i]:5}", end="")
            for value in data[i].values():
                print(f" {value:{format}}", end="")
            print()
```
</details>

下記のプログラムを実行することで、__total の行__ が __表示__ されるようになることが __確認__ できますが、__total__ の __win__ の __数字__ が __121.7%__ のように、__100%__ を __超えてしまう__ という __問題__ があることがわかります。このようなことが起きる原因について少し考えてみて下さい。

```python
ai_match(ai=[ai1, ai2], match_num=1000)
```
実行結果（実行結果はランダムなので下記とは異なる場合があります）
```
ai1 VS ai2
count     win    lose    draw
o         780     180      40
x         437     533      30
total    1217     713      70

ratio     win    lose    draw
o       78.0%   18.0%    4.0%
x       43.7%   53.3%    3.0%
total  121.7%   71.3%    7.0%
``` 

## 比率の計算の修正

問題は、下記の __total__ の __比率__ を __計算__ するプログラムの __3 行目__ の __計算式__ にあります。

```python
for i in range(3):
    for key in count_list_ai0[i]:
        ratio_list[i][key] = count_list_ai0[i][key] / match_num
```

具体的には、__total__ の __試合数__ は、__入れ替えない場合__ の __`match_num` 回__ と、__入れ替えた場合__ の __`match_num` 回__ の __合計__ の、__`match_num * 2` 回__ です。それにも関わらず、上記のプログラムの __3 行目__ では、__比率を計算__ する際に、__`match_num`__ で __割っている__ 点が __バグの原因__ です。

このバグを修正する方法の一つは、下記のプログラムのように __if 文__ を使って、__total の場合__ の比率を計算する __`i` が `2`__ の __場合__ に __`match_num * 2`__ で __割る__ という方法です。

```python
for i in range(3):
    for key in count_list_ai0[i]:
        if i != 2:
            ratio_list[i][key] = count_list_ai0[i][key] / match_num
        else:
            ratio_list[i][key] = count_list_ai0[i][key] / (match_num * 2)
```

ただし、__それぞれ__ の __試合数の合計__ が __`count_list_ai0`__ の __要素__ の __win__ と __lose__ と __draw__ の __キーの値__ の __合計__ であることに __気が付く__ ことができれば、[以前の記事](https://qiita.com/ysgeso/items/a97a3cdf54087e662230#is_same-%E3%81%AE%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0-%E3%81%9D%E3%81%AE-3-list-%E3%81%A8-sum%E3%81%AE%E5%88%A9%E7%94%A8)で紹介した、__反復可能オブジェクト__ の __要素の合計__ を __計算__ する組み込み関数 __`sum`__ を使って、上記のプログラムの処理を、下記のプログラムのように __簡潔に記述__ することができます。

* __3 行目__：__`count_list_ai0`__ の __`i` 番__ の __要素__ に代入された __dict__ の __すべて__ の __キーの値__ の __合計__ を __`sum`__ と __`values`__ メソッドを使って __計算__ した値で、__割り算__ するように __修正__ する

```python
for i in range(3):
    for key in count_list_ai0[i]:
        ratio_list[i][key] = count_list_ai0[i][key] / sum(count_list_ai0[i].values())
```

__dict__ の __キーの値__ の __合計__ を __計算__ する場合は、上記のプログラムのように、__`sum`__ の __実引数__ に、__dict__ の __キーの値__ を __要素__ として持つ __反復可能オブジェクト__ を返す __`values`__ メソッドの __返り値__ を __記述__ する __必要__ がある点に __注意__ して下さい。これは、下記のプログラムの 1 行目のように、__`sum`__ の __実引数__ に、__dict__ を __そのまま記述__ すると、__dict__ の __キーの値ではなく__、__キー__ の __合計__ が __計算__ されるからです。

```python
a = { 1: 10, 2: 20, 3: 30 }
print(sum(a))           # キーの合計の 1 + 2 + 3 が計算される
print(sum(a.values()))  # キーの値の合計の 10 + 20 + 30 が計算される
```
実行結果
```
6
60
```

### 表示幅の修正

気づいていない方が多いのではないかと思いますが、実は、__total__ の __表示__ を __導入__ したことによって、もう一つ __表示__ に関する __バグ__ が __発生する可能性__ が生じています。

それは、__回数__ の __total__ の行に __表示__ される __数字__ の __最大値__ が `match_num * 2` であることから、__数字__ の __表示幅__ が __`width = max(len(str(match_num)), 7)`__ という計算式で計算される幅 __よりも大きく__ なってしまう __可能性が生じるか__ らです。そのため、__`width`__ を下記のように、__`match_num * 2`__ を __使って計算__ するように __修正__ する必要があります。

```python
width = max(len(str(match_num * 2)), 7)
```
<details><summary><strong>修正箇所</strong></summary>

```diff_python
-width = max(len(str(match_num)), 7)
+width = max(len(str(match_num * 2)), 7)
```
</details>

### `ai_match` の修正

下記は、`ai_match` を上記のように修正したプログラムです。

```python
def ai_match(ai, match_num=10000):
元と同じなので省略
    for i in range(3):
        for key in count_list_ai0[i]:
            ratio_list[i][key] = count_list_ai0[i][key] / sum(count_list_ai0[i].values())

    # 各行の先頭に表示する文字列のリスト
    item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS, "total" ]    
    
    # 通算成績の回数と比率の表示
    width = max(len(str(match_num)), 7)
元と同じなので省略
```

<details><summary><strong>全体のプログラム</strong></summary>

```python
def ai_match(ai, match_num=10000):
    print(f"{ai[0].__name__} VS {ai[1].__name__}")
    
    mb = Marubatsu()

    # ai[0] VS ai[1] と ai[1] VS a[0] の対戦を match_num 回行い、通算成績を数える
    count_list = [ defaultdict(int), defaultdict(int)]
    for _ in range(match_num):
        count_list[0][mb.play(ai, verbose=False)] += 1
        count_list[1][mb.play(ai=[ai[1], ai[0]], verbose=False)] += 1

    # ai[0] から見た通算成績を計算する
    count_list_ai0 = [
        # ai[0] VS ai[1] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[0][Marubatsu.CIRCLE],
            "lose": count_list[0][Marubatsu.CROSS],
            "draw": count_list[0][Marubatsu.DRAW],
        },
        # ai[1] VS ai[0] の場合の、ai[0] から見た通算成績
        { 
            "win": count_list[1][Marubatsu.CROSS],
            "lose": count_list[1][Marubatsu.CIRCLE],
            "draw": count_list[1][Marubatsu.DRAW],
        },
    ]           

    # 両方の対戦の通算成績の合計を計算する
    count_list_ai0.append({})
    for key in count_list_ai0[0]:
        count_list_ai0[2][key] = count_list_ai0[0][key] + count_list_ai0[1][key]

    # それぞれの比率を計算し、ratio_list に代入する
    ratio_list = [ {}, {}, {} ]
    for i in range(3):
        for key in count_list_ai0[i]:
            ratio_list[i][key] = count_list_ai0[i][key] / sum(count_list_ai0[i].values())
            
    # 各行の先頭に表示する文字列のリスト
    item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS, "total" ]    
    
    # 通算成績の回数と比率の表示
    width = max(len(str(match_num * 2)), 7)
    diff_list = [ ("count", count_list_ai0, f"{width}d"),
                  ("ratio", ratio_list, f"{width}.1%") ]
    for title, data, format in diff_list:
        print(title, end="")
        for key in data[0]:
            print(f" {key:>{width}}", end="")
        print()
        for i in range(3):
            print(f"{item_text_list[i]:5}", end="")
            for value in data[i].values():
                print(f" {value:{format}}", end="")
            print()
        print()
```
</details>

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

```diff_python
def ai_match(ai, match_num=10000):
元と同じなので省略
    for i in range(3):
        for key in count_list_ai0[i]:
-           ratio_list[i][key] = count_list_ai0[i][key] / match_num
+           ratio_list[i][key] = count_list_ai0[i][key] / sum(count_list_ai0[i].values())

    # 各行の先頭に表示する文字列のリスト
    item_text_list = [ Marubatsu.CIRCLE, Marubatsu.CROSS, "total" ]    
    
    # 通算成績の回数と比率の表示
-   width = max(len(str(match_num)), 7)
+   width = max(len(str(match_num * 2)), 7)
元と同じなので省略
```
</details>

下記のプログラムを実行することで、__total の行__ が __正しく表示__ されるようになったことが __確認__ できます。

```python
ai_match(ai=[ai1, ai2], match_num=1000)
```

実行結果（実行結果はランダムなので下記とは異なる場合があります）

```
ai1 VS ai2
count     win    lose    draw
o         770     196      34
x         456     506      38
total    1226     702      72

ratio     win    lose    draw
o       77.0%   19.6%    3.4%
x       45.6%   50.6%    3.8%
total   61.3%   35.1%    3.6%
``` 

# `ai1` と `ai2` の対戦

__AI どうし__ の __対戦__ を行い、__結果を表示__ する __関数__ が __完成__ したので、これまでに作成した __`ai1`__ と __`ai2`__ で __対戦__ を行うことにします。[以前の記事](https://qiita.com/ysgeso/items/5725504c97a4490fbadb#%E4%BB%8A%E5%9B%9E%E3%81%AE%E8%A8%98%E4%BA%8B%E3%81%AE%E3%81%BE%E3%81%A8%E3%82%81) で説明したように、__基準__ となる __`ai2`__ に対し、__`ai1` VS `ai2`__ と __`ai2` VS `ai2`__ で対戦を行います。なお、まだ __強い AI__ は __作成していない__ ので、それまでに作成した __最も強い AI__ との __対戦__ を __行う必要__ は __まだありません__。

下記は、その対戦結果です。

```python
ai_match(ai=[ai1, ai2])
```
実行結果（実行結果はランダムなので下記とは異なる場合があります）
```
ai1 VS ai2
count     win    lose    draw
o        7811    1753     436
x        4467    5156     377
total   12278    6909     813

ratio     win    lose    draw
o       78.1%   17.5%    4.4%
x       44.7%   51.6%    3.8%
total   61.4%   34.5%    4.1%
``` 

```python
ai_match(ai=[ai2, ai2])
```
実行結果（実行結果はランダムなので下記とは異なる場合があります）
```
ai2 VS ai2
count     win    lose    draw
o        5867    2876    1257
x        2908    5858    1234
total    8775    8734    2491

ratio     win    lose    draw
o       58.7%   28.8%   12.6%
x       29.1%   58.6%   12.3%
total   43.9%   43.7%   12.5%
``` 

この __結果__ を __表にまとめます__。この表は、__関数名__ の __関数__ と、__基準__ となる __`ai2`__ で __複数回対戦__ した際の、「__o を担当__ した場合」、「__x を担当__ した場合」、「__両方を合わせた__ 場合」、のそれぞれの __勝率__、__敗率__、__引き分け率__ を __% の単位__ で __表記__ したものです。なお、__回数__ の __データ__ は __強さを評価__ する際に __必要がない__ ので __表には入れない__ ことにします。

また、__`ai2` VS `ai2`__ の __対戦結果__ を __基準__ とし、それよりも __勝率が高い__、または、__敗率が低い__ 数値は、基準となる __`ai2` に対して強い__ AI であることを表すため、 __太字で表記__ します。

| 関数名 | o 勝 | o 負 | o 分 | x 勝 | x 負 | x 分 | 勝 | 負 | 分 |  
|:-:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| __`ai1`__  | __78.1__ | __17.5__ | 4.4 | __44.7__ | __51.6__ | 3.8 | __61.4__ | __34.5__ | 4.1 |
| __`ai2`__  | 58.7 | 28.8 | 12.6 | 29.1 | 58.6 | 12.3 | 43.9 | 43.7 | 12.5 |

表から、__`ai1`__ は __`ai2`__ に __対して強い__ AI であることが __わかります__。ただし、[以前の記事](https://qiita.com/ysgeso/items/5725504c97a4490fbadb#ai1-%E3%81%A8-ai2-%E3%81%AE%E5%BC%B7%E3%81%95%E8%80%83%E5%AF%9F)で説明したように、__基準__ となる __`ai2` よりも強い__ からと言って、その AI が __他の `ai2` より弱い AI より強い__ とは __限らない__ 点に __注意__ して下さい。__`ai1`__ が __実際にそうである__ ように、__`ai2`__ と __相性が良い__ せいで、__`ai1`__ が `ai2` に __大きく勝ち越す__ ような __場合がある__ からです。

また、__`ai2` VS `ai2`__ の __合計__ の __勝率と敗率__ が __約 43%__ で __ほぼ同じ__ であるという、__同じ AI どうし__ で __対戦__ した場合の __当然の結果__ が得られたことが __確認__ できます。

以後は、__新しい AI を作成__ する __たび__ に、この __表を更新__ していくことで、__作成した AI__ の __強さの評価__ を __行います__。

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

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

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

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

以下のリンクは、今回の記事で更新した ai.py です。

https://github.com/ysgeso/marubatsu/blob/master/043/ai_new.py

# 次回の記事

https://qiita.com/ysgeso/items/1e8732db6cd1b0104276