Pythonで〇×ゲームのAIを一から作成する　その41　比率の表示と書式の指定

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/5725504c97a4490fbadb

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

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

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

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

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

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

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

# `ai_match` の改良 その 1（勝率などの比率の表示）

前回の記事で作成した、__`ai_match`__ は、AI どうしの __評価を行うため__ にはいくつかの __問題点__ があるので、今回の記事ではそれらの __問題を解決__ するために __`ai_match`__ を __改良__ します。

`ai_match` は、〇 の勝利数、× の勝利数、引き分け数を表示しますが、それらは、__対戦回数__ を表す __`match_num`__ によって __変わる__ ので、その数から __強さを評価__ することは __困難__ です。

対戦の __回数__ に __依存しない__ 強さの __指標__ として良く使われるのが、勝率などの __比率__ で、それぞれの __回数__ を、__対戦回数__ で __割る__ ことで __計算__ できます。例えば、下記の実行結果のように、対戦の __回数__ が __`10000`__ のような __割り算__ を __計算しやすい数字__ の場合は、は 〇 の __勝率__ が 78.2 % であることを、__暗算__ で __簡単に計算__ できます。

```python
from ai import ai_match, ai1, ai2

ai_match(ai=[ai1, ai2])
```

実行結果（実行結果はランダムなので下記とは異なる場合があります）
```
o 7820 x 1764 draw 416
```

一方、下記のプログラムのように __`match_num`__ が __`567`__ のような、__割り算__ を __計算しづらい__ 数値の場合は、〇 の勝率などを __暗算__ で __計算__ することは __困難__ です。


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

実行結果（実行結果はランダムなので下記とは異なる場合があります）
```
o 452 x 93 draw 22
```

## `ai_match` の修正

そこで、__`ai_match`__ を、〇 の勝ち数、× の勝ち数、引き分けの数に __加えて__、__〇 の 勝率__、__× の勝率__、__引き分けの率__ という、それぞれの __比率__ を __計算して表示__ するように __修正__ することにします。下記はそのような計算を行うように `ai_match` を修正したプログラムです。

* __10 行目__：__回数__ であることを __明確__ にするために、__最初__ に __`"count"` を表示__ する
* __12 ~ 14 行目__：それぞれの __比率__ は、__試合数__ である __`match_num`__ で __割る__ ことで __計算__ できる。また、__比率__（ratio）であることを __明確__ にするために、__最初__ に __`"ratio"` を表示__ した

```python
 1  from marubatsu import Marubatsu
 2  from collections import defaultdict
 3
 4  def ai_match(ai, match_num=10000):
 5      mb = Marubatsu()
 6      count = defaultdict(int)
 7      for _ in range(match_num):
 8          count[mb.play(ai, verbose=False)] += 1
 9
10      print("count", "o", count[Marubatsu.CIRCLE], "x", count[Marubatsu.CROSS],
11            "draw", count[Marubatsu.DRAW])
12      print("ratio", "o", count[Marubatsu.CIRCLE] / match_num,
13            "x", count[Marubatsu.CROSS] / match_num,
14            "draw", count[Marubatsu.DRAW] / match_num)
```

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

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

def ai_match(ai, match_num=10000):
    mb = Marubatsu()
    count = defaultdict(int)
    for _ in range(match_num):
        count[mb.play(ai, verbose=False)] += 1

    print("count", "o", count[Marubatsu.CIRCLE], "x", count[Marubatsu.CROSS],
          "draw", count[Marubatsu.DRAW])
    print("ratio", "o", count[Marubatsu.CIRCLE] / match_num,
          "x", count[Marubatsu.CROSS] / match_num,
          "draw", count[Marubatsu.DRAW] / match_num)
```
</details>

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

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

def ai_match(ai, match_num=10000):
    mb = Marubatsu()
    count = defaultdict(int)
    for _ in range(match_num):
        count[mb.play(ai, verbose=False)] += 1

-   print("o", count[Marubatsu.CIRCLE], "x", count[Marubatsu.CROSS],
+   print("count", "o", count[Marubatsu.CIRCLE], "x", count[Marubatsu.CROSS],
          "draw", count[Marubatsu.DRAW])
+   print("ratio", "o", count[Marubatsu.CIRCLE] / match_num,
+         "x", count[Marubatsu.CROSS] / match_num,
+         "draw", count[Marubatsu.DRAW] / match_num)
```
</details>


下記のプログラムを実行することで、それぞれの __比率__ が __表示__ されることが __確認__ できます。

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

実行結果（実行結果はランダムなので下記とは異なる場合があります）
```
count o 434 x 107 draw 26
ratio o 0.7654320987654321 x 0.18871252204585537 draw 0.04585537918871252
```

## 比率の計算の改良

上記のプログラムでは、それぞれの __比率__ を __表示__ する 9 ~ 11 行目の __プログラムの中__ でそれぞれの __比率__ を __計算__ していますが、下記のプログラムのように、__先に__ それぞれの __比率__ を __計算__ しておくことで、9 ~ 11 行目のプログラムが少し __わかりやすく__、__簡潔__ になります。

* __7 行目__：それぞれの __比率__ を __計算__ して __記録__ するための __空の dict__ を __`ratio`__ とという名前の変数に __代入__ する。なお、__`ratio`__ は、__`count`__ と __異なり__、この後の処理で、__値が代入されていないキーを参照__ する __ことはない__ ので、__defaultdict__ を __使う必要__ は __ない__
* __8、9 行目__：__比率__ は「__回数 ÷ 試合数__」で __計算__ できるので、__`count`__ の __それぞれ__ の __キーの値__ に対してその __計算__ を行い、__計算結果__ を __`ratio`__ の __対応するキー__ に __代入__ する
* __13、14 行目__：__`ratio`__ を __使って__ それぞれの __比率を表示__ する

```python
 1  def ai_match(ai, match_num=10000):
 2      mb = Marubatsu()
 3      count = defaultdict(int)
 4      for _ in range(match_num):
 5          count[mb.play(ai, verbose=False)] += 1
 6
 7      ratio = {}
 8      for key in count.keys():
 9          ratio[key] = count[key] / match_num
10
11      print("count", "o", count[Marubatsu.CIRCLE], "x", count[Marubatsu.CROSS],
12            "draw", count[Marubatsu.DRAW])
13      print("ratio", "o", ratio[Marubatsu.CIRCLE], "x", ratio[Marubatsu.CROSS],
14            "draw", ratio[Marubatsu.DRAW])
```

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

```python
def ai_match(ai, match_num=10000):
    mb = Marubatsu()
    count = defaultdict(int)
    for _ in range(match_num):
        count[mb.play(ai, verbose=False)] += 1

    ratio = {}
    for key in count.keys():
        ratio[key] = count[key] / match_num

    print("count", "o", count[Marubatsu.CIRCLE], "x", count[Marubatsu.CROSS],
          "draw", count[Marubatsu.DRAW])
    print("ratio", "o", ratio[Marubatsu.CIRCLE], "x", ratio[Marubatsu.CROSS],
          "draw", ratio[Marubatsu.DRAW])
```
</details>

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

```diff_python
def ai_match(ai, match_num=10000):
    mb = Marubatsu()
    count = defaultdict(int)
    for _ in range(match_num):
        count[mb.play(ai, verbose=False)] += 1

+   ratio = {}
+   for key in count.keys():
+       ratio[key] = count[key] / match_num

    print("count", "o", count[Marubatsu.CIRCLE], "x", count[Marubatsu.CROSS],
          "draw", count[Marubatsu.DRAW])
-   print("ratio", "o", count[Marubatsu.CIRCLE] / match_num,
-         "x", count[Marubatsu.CROSS] / match_num,
-         "draw", count[Marubatsu.DRAW] / match_num)
+   print("ratio", "o", ratio[Marubatsu.CIRCLE], "x", ratio[Marubatsu.CROSS],
+         "draw", ratio[Marubatsu.DRAW])
```
</details>


下記のプログラムを実行することで、正しい表示が行われることが確認できます。実行結果は先ほどと同様なので省略します。

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

## 結果を表示する処理の改良

上記のプログラムは、11 ~ 14 行目の __`print`__ の __実引数の記述__ が __大変__ なので __改良__ することにします。[前回の記事](https://qiita.com/ysgeso/items/5725504c97a4490fbadb#dict-%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F%E9%9B%86%E8%A8%88)では、__最初__ は、__回数__ の __集計結果__ の __表示__ を、下記のプログラムのように、__for 文__ を使って __表示__ していました。

```python
for key, value in count.items():
    print(key, value)
```

しかし、[前回の記事の途中](https://qiita.com/ysgeso/items/5725504c97a4490fbadb#defaultdict-%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F%E9%9B%86%E8%A8%88)から、__defaultdict__ を使って __集計を行う__ ようにした結果、上記の __for 文__ を使った場合は、__表示の順番__ が `"o"`、`"x"`、`"draw"` の __順番に[ならない場合](https://qiita.com/ysgeso/items/5725504c97a4490fbadb#defaultdict-%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E8%A3%9C%E8%B6%B3)__ があります。先ほどのプログラムで、__for 文__ を __使わず__ に __集計結果を表示__ したのは __そのため__ です。


__for 文__ を使った場合でも、__表示の順番__ を __固定__ したい場合は、下記のプログラムのように、__表示の順番__ を __表す list__ を __作成__ し、その __list__ を使って __繰り返しの処理__ を行います。

* __15 行目__：表示する __`count`__ の __キーの順番__（order）を表す __list__ を __`order_list`__ に __代入__ する
* __18 行目__：これから __回数__ を __表示__ することを __明確__ にするために、__`"count"` を表示__ する
* __19、20行目__：__for 文__ と __`order_list`__ を使って __繰り返し処理__ を行うことで、__`order_list`__ の __キーの順番__ で、`count` の __キー__ と、__キーの値__ を __表示__ する
* __22 ~ 24 行目__：__比率__ の __表示__ を、18 ~ 20 行目と __同様の方法__ で行う

なお、プログラムが長くなってわかりづらくなってきたのでコメントを追加しました。

```python
 1  def ai_match(ai, match_num=10000):
 2      mb = Marubatsu()
 3
 4      # ai[0] VS ai[1] の対戦を match_num 回行い、通算成績を数える
 5      count = defaultdict(int)
 6      for _ in range(match_num):
 7          count[mb.play(ai, verbose=False)] += 1
 8
 9      # それぞれの比率を計算し、ratio に代入する
10      ratio = {}
11      for key in count.keys():
12          ratio[key] = count[key] / match_num
13
14      # 〇の勝利、×の勝利、引き分けの順番で表示することを表す list 
15      order_list = [Marubatsu.CIRCLE, Marubatsu.CROSS, Marubatsu.DRAW]
16
17      # 通算成績の回数の表示
18      print("count")
19      for key in order_list:
20          print(key, count[key])
21      # 通算成績の比率の表示
22      print("ratio")
23      for key in order_list:
24          print(key, ratio[key])
```

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

```python
def ai_match(ai, match_num=10000):
    mb = Marubatsu()

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

    # それぞれの比率を計算し、ratio に代入する
    ratio = {}
    for key in count.keys():
        ratio[key] = count[key] / match_num

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

    # 通算成績の回数の表示
    print("count")
    for key in order_list:
        print(key, count[key])
    # 通算成績の比率の表示
    print("ratio")
    for key in order_list:
        print(key, ratio[key])
```
</details>

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

```diff_python
def ai_match(ai, match_num=10000):
    mb = Marubatsu()

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

    # それぞれの比率を計算し、ratio に代入する
    ratio = {}
    for key in count.keys():
        ratio[key] = count[key] / match_num

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

    # 通算成績の回数の表示
-   print("count", "o", count[Marubatsu.CIRCLE], "x", count[Marubatsu.CROSS],
-         "draw", count[Marubatsu.DRAW])
+   print("count")
+   for key in order_list:
+       print(key, count[key])
    # 通算成績の比率の表示
-   print("ratio", "o", ratio[Marubatsu.CIRCLE], "x", ratio[Marubatsu.CROSS],
-         "draw", ratio[Marubatsu.DRAW])+   print("count")
+   print("ratio")
+   for key in order_list:
+        print(key, ratio[key])
```
</details>


下記のプログラムを実行し、表示内容を確認すると、それぞれの __`print`__ で __改行__ が __行われてしまう__ という __問題__ があることが __判明__ します。

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

実行結果（実行結果はランダムなので下記とは異なる場合があります）
```
count
o 442
x 90
draw 35
ratio
o 0.7795414462081128
x 0.15873015873015872
draw 0.06172839506172839
```

### 表示の修正 その 1

この問題は、__`print`__ で __改行を行わない__ ようにすれば良いので、[以前の記事](https://qiita.com/ysgeso/items/c5dbe62b329f7936181a#print-%E3%81%A7%E6%94%B9%E8%A1%8C%E3%82%92%E8%A1%8C%E3%82%8F%E3%81%AA%E3%81%84%E6%96%B9%E6%B3%95) で説明したように、下記のプログラムの 3、5、7、9 行目の __`print`__ の __実引数__ に __`end=""`__ を __記述__ します。

```python
1  def ai_match(ai, match_num=10000):
元と同じなので省略
2      # 通算成績の回数の表示
3      print("count", end="")
4      for key in order_list:
5          print(key, count[key], end="")
6      # 通算成績の比率の表示
7      print("ratio", end="")
8      for key in order_list:
9          print(key, ratio[key], end="")
```

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

```python
def ai_match(ai, match_num=10000):
    mb = Marubatsu()

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

    # それぞれの比率を計算し、ratio に代入する
    ratio = {}
    for key in count.keys():
        ratio[key] = count[key] / match_num

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

    # 通算成績の回数の表示
    print("count", end="")
    for key in order_list:
        print(key, count[key], end="")
    # 通算成績の比率の表示
    print("ratio", end="")
    for key in order_list:
        print(key, ratio[key], end="")
```
</details>

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

```diff_python
def ai_match(ai, match_num=10000):
元と同じなので省略
    # 通算成績の回数の表示
-   print("count")
+   print("count", end="")
    for key in order_list:
-       print(key, count[key])
+       print(key, count[key], end="")
    # 通算成績の比率の表示
-   print("ratio")
+   print("ratio", end="")
    for key in order_list:
-       print(key, ratio[key])
+       print(key, ratio[key], end="")
```
</details>


下記のプログラムを実行し、表示内容を確認すると、今度は、「__`"o"`__ などの __キーの表示__ と、__その前の表示__ が __くっついて表示__ される」、「__`ratio` の直前__ で __改行されない__」という 2 つの問題があることが判明します。

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

実行結果（実行結果はランダムなので下記とは異なる場合があります）
```
counto 430x 114draw 23ratioo 0.7583774250440917x 0.20105820105820105draw 0.04056437389770723
```

### 表示の修正その 2

この問題を解決するためには、__適切な場所__ に __改行__ と __半角の空白__ を入れる必要があります。その方法は 1 つではありませんが、本記事では下記のプログラムのように修正します。

* __5, 10 行目__：__`print`__ の __最初の実引数__ に __`""`__ を記述することで、2 つ目の実引数の __キー__ を __表示する前__ に __半角の空白文字__ を __表示__ するようにする。これは、__`print`__ に __複数__ の __実引数__ を __記述__ すると、__間__ に __半角の空白文字__ を __表示__ するという __性質を利用__ している
* __6 行目__：__`print()`__ を __記述__ することで、__`"ratio"`__ の __表示__ の __直前で改行__ する。なお、__6、8 行目__ は、[以前の記事](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)で説明した、__改行__ を表す __`\n`__ という __エスケープシーケンス__ を使って、__`print("\nratio")`__ のように __1 行__ に __まとめて記述__ することもできる

```python
 1  def ai_match(ai, match_num=10000):
元と同じなので省略
 2      # 通算成績の回数の表示
 3      print("count", end="")
 4      for key in order_list:
 5          print("", key, count[key], end="")
 6      print()
 7      # 通算成績の比率の表示
 8      print("ratio", end="")
 9      for key in order_list:
10          print("", key, ratio[key], end="")
```

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

```python
def ai_match(ai, match_num=10000):
    mb = Marubatsu()

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

    # それぞれの比率を計算し、ratio に代入する
    ratio = {}
    for key in count.keys():
        ratio[key] = count[key] / match_num

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

    # 通算成績の回数の表示
    print("count", end="")
    for key in order_list:
        print("", key, count[key], end="")
    print()
    # 通算成績の比率の表示
    print("ratio", end="")
    for key in order_list:
        print("", key, ratio[key], end="")
```
</details>

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

```diff_python
def ai_match(ai, match_num=10000):
元と同じなので省略
    # 通算成績の回数の表示
    print("count", end="")
    for key in order_list:
-       print(key, count[key], end="")
+       print("", key, count[key], end="")
+   print()
    # 通算成績の比率の表示
    print("ratio", end="")
    for key in order_list:
-       print(key, ratio[key], end="")
+       print("", key, ratio[key], end="")
```
</details>


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

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

実行結果（実行結果はランダムなので下記とは異なる場合があります）
```
count o 452 x 95 draw 20
ratio o 0.7971781305114638 x 0.1675485008818342 draw 0.03527336860670194
```

上記の __修正作業__ は、[以前の記事](https://qiita.com/ysgeso/items/c5dbe62b329f7936181a#cui-%E3%81%A7%E3%81%AE%E3%82%B2%E3%83%BC%E3%83%A0%E7%9B%A4%E3%81%AE%E8%A1%A8%E7%A4%BA)で紹介した、__ゲーム盤の表示__ を行う __関数を定義__ する際に行った __作業__ に __似ている__ のではないかと思った人がいるかもしれませんが、__その通り__ です。

以前の記事で紹介したのと __同じような内容__ をここで __もう一度説明__ したのは、画面に __文字を表示__ するプログラムは、__一回__ で __思い通り__ に __表示できない__ ことが __多い__ ためで、このような __修正の手順__ をなるべく多く __体験しておく__ ことが __重要__ だと思ったからです。

## 数値型のデータの種類

前回までの記事のプログラムでは、基本的には __数値__ として、__整数のみ__ 扱ってきましたが、今回の記事で、__比率__ のような、__小数点以下__ の数値を __含む数値__ を扱ったので、__数値型__ のデータの __種類__ について __簡単に紹介__ します。

Python には、__数値を扱う__ 主な __データ型__ として __整数型__（int）、__浮動小数点数型__（float）、__複素数型__（complex）の 3 つがあります。__括弧の中__ の __英語__ は、それぞれの __データ型__ の __Python__ での __正式な名称__ で、その __名前と同じ組み込みクラス__ が __定義__ されています。なお、このうちの複素数型は、本記事では利用する予定はないので説明は省略します。

数値を扱うデータ型の詳細については、下記のリンク先を参照して下さい。

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

### 整数型（int）
__整数型__ は、文字通り __整数のみを扱う__ データ型です。他の多くのプログラム言語では、整数型のデータで扱える数値の範囲が限られていますが、__Python の数値型__ のデータは、扱える __整数の範囲__ に __制限はありません__。

### 浮動小数点数型（float）

__浮動小数点数型__ は、__小数点以下__ を __含む__ 数値や、__指数__ を使って表現[^1]された数値を扱うデータ型です。浮動小数点数は、__仮数部__ と呼ばれる、__特定の桁数の数値__ と、__指数部__ と呼ばれる、__小数点の位置__ を __どれだけずらすか__ を表す __2 つのデータ__ から __構成__ されます。__指数部__ を使って __小数点の位置__ が __浮動__（移動）するので、__浮動小数点数__ と呼ばれます。

__浮動小数点数型__ の数値は、整数型と異なり、__特定の桁数まで__ しか __数値を表現__ することは __できません__。例えば、__`1 / 3`__ を 10 進数で表記すると、`0.3333333・・・` のように、__小数点以下__ に `3` が __無限に続く数値__ になりますが、__無限に続く数値__ をコンピューターで __扱う__ ことは __不可能__ なので、下記のプログラムのように、`1 / 3` を計算すると、__特定の桁数まで__ しか数値が表示されません。__数値__ を __何桁の数字__ で __表現__ するかを __精度__ と呼びます。

```python
print(1 / 3)
```
実行結果
```
0.3333333333333333
```

__浮動小数点数__ には __精度__ があるので、__`1 / 3`__ のような、__精度を超える__ ような __桁数の数値__ を __正確に表現__ することは __できません__。__浮動小数点数__ が表す __数値__ と、__本当の数値__ の間の __差__ の事を __誤差__ と呼びます。__コンピューター__ が行う数値計算のうち、特に、__割り切れない割り算__ を行うものや、__浮動小数点数を扱う__ ものなどには、__誤差がつきもの__ です。そのため、__厳密な計算__ を行いたい場合は、__誤差__ の事を __考慮する必要__ があります。

__浮動小数点数__ は、__少々難しい概念__ なので、上記の説明では意味が分からない人も多いのではないかと思います。ただし、__厳密な数値計算__ を行う際には、__浮動小数点数__ の __正確な知識__ が __必要__ となりますが、`ai_match` で計算する比率の計算では、__厳密な数値計算__ を __行う必要はない__ ので、__現時点__ では、単に __小数点以下を含む数値__ を扱う __データ型__ であることだけを理解しておけばよいでしょう。より詳細な説明は、必要になった時点で行う予定です。

参考までに、浮動小数点数の Wikipedia のリンクを下記に記します。

https://ja.wikipedia.org/wiki/%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0

### 整数型と浮動小数点数型どうしの計算

__Python__ では、__整数型__ と __浮動小数点数型__ のデータに対して、四則演算などの __多くの演算__ を、__データ型__ を __意識することなく行う__ ことができます。また、その際の __計算結果__ の __データ型__ は、__原則__ として以下の表のようになります。なお、下記の表は __あくまで原則__ なので、下記の表とは __異なるデータ型__ になる __場合がある__ 点に注意して下さい。

|  | 計算結果のデータ型 |
|:--|:-:|
| __int と int__ で __計算結果__ が __整数__ の場合  | int  |
| __int と int__  で __計算結果__ が __整数でない__ 場合  | float  |
| __int と float__  | float  |
| __float と int__  | float  |
| __float と float__  | float  |

下記のプログラムは上記の演算の具体例です。なお、下記のプログラムでは、__データ型__ を調べるために、[以前の記事](https://qiita.com/ysgeso/items/1a8a4319ef3c896ef271#%E6%96%87%E5%AD%97%E5%88%97%E5%9E%8B%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92-for-%E6%96%87%E3%81%AB%E8%A8%98%E8%BF%B0%E3%81%97%E3%81%9F%E9%9A%9B%E3%81%AE%E6%B3%A8%E6%84%8F%E7%82%B9) で紹介した、__`type`__ という組み込み関数を利用しています。

```python
print(type(1 + 1))
print(type(1 / 2))
print(type(1 + 1.5))
print(type(1.5 + 1))
print(type(1.5 + 1.7))
print(type(1.5 * 2))   # 答えが整数であっても float になる
print(type(1.5 + 1.5)) # 答えが整数であっても float になる
```
実行結果
```
<class 'int'>
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>
<class 'float'>
```

上記で、整数型と浮動小数点数型の計算結果のデータ型について説明しましたが、実際には、__ほとんどの場合__ で、__数値の計算__ を行う際に、__計算を行う数値__ と、__計算結果の数値__ の __データ型__ を __意識する必要__ は __ありません__。


本記事で、これまで __整数型__ と __浮動小数点数型__ を __区別せず__ に、__数値型__ と __記述__ してきたのは、このことが理由です。今後も __区別する必要がない__ 場合は、__整数型__ という __表記を続ける__ ことにします。ただし、__整数型__ と __浮動小数点型__ の違いを __意識しない__ と、__意図しない計算__ が行われたり、__エラーが発生__ する場合があるので、__そのような場合__ は、__整数型__ と __浮動小数点数型__ を __区別して表記__ します。

# `ai_match` の改良 その 2（比率の表示の改良）

先程のプログラムでは、__比率__ が __`0.7971781305114638`__ のような、__小数点以下__  10 桁 __以上__ の数値で __表示__ されますが、__AI__ の __強さを測る__ ためには、__小数点以下第 4 桁__ を __四捨五入__ した、__`0.797`__ 程度の __精度__ の __数値__ が __表示__ されれば __十分__ です。また、`0.797` よりも、__`79.7%`__ のような、__%（百分率）__ で __表示__ したほうが __わかりやすい__ でしょう。

:::note
先程、「__`ai_match`__ で計算する比率の計算では、__厳密な数値計算__ を __行う必要はない__」と説明したのは、上記のように、計算された __比率を表す数値__ のうち、__小数点以下第 4 桁__ までしか __利用しない__ ためです。
:::

## 書式と書式フォーマット済文字列リテラル

「数値型の __データ__ を、__小数点以下第 3 桁まで表示__ する」のように、__データ__ を __特定の形式__ の __文字列に変換__ する際の、__形式__ の事を __書式__（format） と呼びます。__書式__ は「__文字列型__ のデータを __5 文字__ で __右揃え__ で __表示__ する」など、__任意__ の __データ型__ に対して __設定__ できます。

Python では、__書式フォーマット済文字列リテラル__ という __記述方法__ で、__指定したデータ__ を、__指定した書式__ の __文字列__ に __変換__ することができます。また、書式フォーマット済文字列リテラルは、__複数のデータ__ を __まとめて文字列__ に __変換__ する __処理__ を、__簡潔__ に __分かりやすく記述__ することが __できる__ ので、実際に __良く使われます__。

書式フォーマット済文字列リテラルの詳細については、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/reference/lexical_analysis.html#formatted-string-literals

:::note warn
「フォーマット済文字リテラル」という表記は長いので、「__f 文字列__」や「__f-string__」と呼ぶ場合が多いようです。以後は、「__f 文字列__」と表記することにします。
:::

## print を使った方法の問題点

__f 文字列__ を説明する前に、これまでのプログラム利用してきた、__複数のデータ__ を __まとめて一度で表示__ する __方法__ の __欠点__ について説明します。

これまでのプログラムでは、__複数のデータ__ を __まとめて一度__ で __表示__ する際に、__`print`__ の __実引数__ に、__それぞれ__ の __データを記述__ していました。例えば、先程のプログラムでは、下記のように、__`print`__ の __実引数__ に、__`""`__、__`key`__、__`data[key]`__ の __3 つ__ のデータを __記述__ しています。

```python
print("", key, data[key], end="")
```

この方法の __欠点__ の一つは、__`print`__ が __それぞれ__ の __実引数__ を __表示__ する際に、__間__ に __半角の空白文字__ を __必ず表示__ してしまう点にあります。上記の場合は、間に空白文字を記述したほうが良いので問題ありませんが、__間__ に __半角の空白文字__ を __表示したくない__ 場合は `print` の実引数にそれぞれのデータを記述するという方法は __利用できません__。

他には、この方法では表示するデータの __書式__ を __指定できない__ という __欠点__ があります。

## 文字列の連結の問題点

__複数__ の __データ__ を、__間__ に __空白を開けず__ に __文字列に変換__ する方法として、__文字列__ を __`+` 演算子__ で __連結__ するという方法がありますが、この方法にも __いくつかの欠点__ があります。

__欠点__ の一つは、下記のプログラムのように、__文字列型__ のデータと、__数値型__ のデータを __`+` 演算子__ で __連結__ すると、__エラーが発生__ するという点です。

```python
applenum = 5
applevalue = 100
text = applenum + "個のりんごの値段は" + applenum * applevalue + "円です"
print(text)
```

実行結果
```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[15], line 3
      1 applenum = 5
      2 applevalue = 100
----> 3 text = applenum + "個のりんごの値段は" + applenum * applevalue + "円です"
      4 print(text)

TypeError: unsupported operand type(s) for +: 'int' and 'str'
```

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

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

エラーが発生しないようにするためには、下記のプログラムのように、__組み込み関数 `str`__ を使って、__文字列型__ のデータに __変換してから結合__ する必要がありますが、これは __面倒__ です。

```python
applenum = 5
applevalue = 100
text = str(applenum) + "個のりんごの値段は" + str(applenum * applevalue) + "円です"
print(text)
```

実行結果
```
5個のりんごの値段は500円です
```

また、__`+` 演算子__ を使った __結合__ には、他にも以下のような __欠点__ があります。

* プログラムが __長くなる__ ので __わかりづらくなる__
* __多く__ の __文字列__ を __結合__ する場合は、__`"`__ を __数多く記述__ する __必要__（上記では 4 つ `"` が記述されている）があり、どの `"` と `"` が __ペアであるか__ が __わかりづらくなる__
* データの __書式__ を __指定できない__

## フォーマット済文字列リテラル（f 文字列）

上記の __問題点__ は、__f 文字列__ を利用することで __改善__ できます。

__f 文字列__ は、__文字列__ を __囲う__ `"` や `'` などの __記号の直前__ に __`f`__ を記述する __文字列のリテラル__ で、__その中__ に __任意の数__ だけ __`{式}`__ を __記述__ することが __できます__。

__f 文字列__ の __中に記述__ された __`{式}`__ は、__式__ の __計算結果__ を表す __文字列__ に __置き換え__ られます。例えば、先程のプログラムは、下記のように記述できます。

実行結果からわかるように、3 行目の __`{applenum}`__ が __`"5"`__ に、__`{applenum * applevalue}`__ が、__`5 * 100`__ の __計算結果__ である __`"500"`__ という __文字列__ に __置き換え__ られます。

```python
applenum = 5
applevalue = 100
text = f"{applenum}個のりんごの値段は{applenum * applevalue}円です"
print(text)
```

実行結果
```
5個のりんごの値段は500円です
```

__`+` 演算子__ で __文字列__ を __連結__ する下記のプログラム __比較__ して、__f 文字列__ を利用した上記のプログラムには以下の __利点__ があります。

```python
text = str(applenum) + "個のりんごの値段は" + str(applenum * applevalue) + "円です"
```

* __`str`__ を __使って__、数値型のデータを文字列に __変換__ する __必要がない__
* __`"`__ は、文字列全体を囲う __2 箇所だけ__ に __記述すれば良い__
* __`+`__ を使った式より __簡潔に記述__ でき、プログラムの意味が __分かりやすくなる__

## 書式指定

__f 文字列__ では、__`{}` の中__ の __式の直後__ に、下記のように記述することで、`{}` の中の __式の計算結果__ を表す __文字列__ の __書式__ を __指定__ することができます。

```python
{式:書式指定}
```

__書式指定__ には、__様々な指定__ を行うことができますが、本記事ではその中で __良く使われるもの__ を紹介します。他の書式指定について知りたい方は、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/library/string.html#format-specification-mini-language

本記事では、下記に関する書式指定の記述方法を紹介します。

__揃え__、__符号__、__文字幅__、__精度__、__表現型__

書式指定の記述は、以下の方法で行います。

* 書式指定の __種類ごと__ に __決められたルール__ で __記述__ する
* __複数の種類__ の __書式指定__ を __記述__ する場合は、__間を空けず__ に、__上記の順番__ で記述する
* __それぞれ__ の __種類__ の __書式指定__ は __省略__ することが __できる__。__省略__ した場合は、それぞれの種類によって、決められた __規定値が指定__ がされたものと __みなされる__


### 揃え

下記の指定を行うことができます。__文字幅の中__ での __揃えを指定__ するので、__多くの場合__ は、後述の __文字幅を指定しない__ と __意味がない__ 指定になります。

揃えを行う際に、__文字数が足りない部分__ には、__半角の空白文字__ が __挿入__ されます。

| 記号 | 意味 |
|:-:|:--|
| __`<`__  | __文字幅の中__ で、__左揃え__ で文字列を生成する<br>__ほとんどのデータ型__ は、揃えを __指定しない場合__ は __左揃え__ になる  |
| __`>`__  | __文字幅の中__ で、__右揃え__ で文字列を生成する<br>__数値型__ などの、一部のデータは、揃えを __指定しない場合__ は __右揃え__ になる  |
| __`^`__  | __文字幅の中__ で、__中央揃え__ で文字列を生成する  |

下記は、__揃えを指定__ したプログラムの例です。なお、下記の例では、揃えは文字幅を指定しないと意味をなさないので、後述の方法で __文字幅__ を __9 に指定__ しています。

```python
applenum = 5
applevalue = 100
name = "りんご"
print(f"{name:<9}の値段は{applenum * applevalue:<9}円です") # りんご、値段を左揃えで表示
print(f"{name:>9}の値段の{applenum * applevalue:>9}円です") # りんご、値段を右揃えで表示
print(f"{name:^9}の値段の{applenum * applevalue:^9}円です") # りんご、値段を中央揃えで表示
# 揃えを指定しないと文字列型は左揃え、数値型は右揃えになる
print(f"{name:9}の値段の{applenum * applevalue:9}円です") 
```
実行結果
```
りんご      の値段は500      円です
      りんごの値段の      500円です
   りんご   の値段の   500   円です
りんご      の値段の      500円です
```

### 符号

下記の指定を行うことで、__数値型__ のデータを __文字列型__ のデータに __変換__ する際に、__符号__ を __入れるかどうか__ を __指定__ できます。

| 記号 | 意味 |
|:-:|:--|
| __`+`__  | 正の数でも、負の数でも、0 でも、__必ず符号を入れる__ ようになる  |
| __`-`__  | __負の数__ の場合 __のみ__、__符号を入れる__<br>符号の __指定を記述しない場合__ は、__この指定になる__  |

下記は、符号を指定したプログラムの例です。

```python
plusnum = 100
minusnum = -100
print(f"{plusnum}")    # 符号を指定しない場合は、負の数のみ符号が表示される
print(f"{minusnum}") 
print(f"{plusnum:+}")  # 符号として + を指定した場合は、常に符号が表示される
print(f"{minusnum:+}") 
print(f"{plusnum:-}")  # 符号として - を指定した場合は、負の数のみ符号が表示される
print(f"{minusnum:-}") # （符号を指定しない場合と同じになる）
```
実行結果
```
100
-100
+100
-100
100
-100
```

なお、下記のプログラムのように、__数値型以外__ のデータに対して __符号を記述__ すると __エラーが発生__ します。

```python
name = "りんご"
print(f"{name:+}")
```
実行結果
```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[20], line 2
      1 name = "りんご"
----> 2 print(f"{name:+}")

ValueError: Sign not allowed in string format specifier
```

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

* __ValueError__
値（value）に関するエラー
* __Sign not allowed in string format specifier__
符号（Sing）は、文字列型（string）の書式（format）指定（specifier）として利用することはできない（not allowed in）
:::

### 文字幅

__文字列に変換__ した際の、__文字列の長さ__ の __最小値__ を __整数で指定__ します。文字幅を __指定しなかった場合__ は文字列に変換した際の __長さ__ は __変化しません__。

__文字列に変換__ した際に、変換した __文字列の長さ__ が、指定された __文字幅より短い__ 場合は、__揃えの指定__ に従って __文字列の配置__ が決められ、__足りない部分__ の __文字__ には __半角の空白文字__ が挿入されます。文字幅の指定の __具体例__ については __前述__ の __揃えの所__ を見て下さい。後述の __精度を指定した場合__ の文字幅の指定の __具体例__ は、次の __精度の所__ を見て下さい。

なお、__変換__ した __文字列の長さ__ が、__指定した文字幅__ より __長い場合__ に、指定した文字幅に文字列が __切り詰められる__ ことは __ありません__。下記のプログラムのように、__6 文字__ の __文字列__ が代入された `name` に対して、__文字幅__ を __`3` に指定__ しても、文字列が __すべて表示__ されます。

```python
name = "りんごは赤い"
print(f"{name:3}")
```

実行結果
```
りんごは赤い
```

__文字列の長さ__ を、__指定した文字数__ で __切り詰めたい__ 場合は、次の __精度で指定__ します。

### 精度

__精度の指定__ は、__数値型__ のデータと __文字列型__ のデータによって行う __処理が異なります__。

#### 数値型のデータの場合

__精度__ は __浮動小数点数型__（float）のデータに対して __設定できます__ が、後述で示すように、__数値型__ のデータに対して __精度を設定__ すると __エラーが発生__ します。ただし、これも後述しますが、__表現型の f__ を設定することで、__整数型__ に対しても __精度を設定__ することが __できます__。

__式の計算結果__ が __浮動小数点数型__（float）のデータの場合は、__半角のピリオド `.` の 直後__ に __整数を記述__ することで、文字列に変換した際の __小数点以下__ の __桁数を指定__ することができます。その際に、__指定__ した __小数点以下__ の __次の桁の数__ で __四捨五入__ を行います。__精度を省略__ した場合は、四捨五入は行われず、__すべての桁数__ が __文字列に変換__ されます。

下記は、__割り切れない `1 / 7`__ に対して、__さまざまな精度__ を指定したプログラムの例です。下記の 4 行目の __`10.5`__ のように __文字幅__ と __精度__ の __両方を指定__ した場合、__見た目__ から、__整数の部分__ の桁数が __10 桁__、__小数点以下__ の桁数が __5 桁__ のように __見えるかもしれません__ が、__実際__ には __`10`__ は __全体の文字列の長さ__ を表す点に __注意して下さい__。

```python
num = 1 / 7
print(f"1 / 7 = {num}です")        # 何も指定しない場合は、すべての桁数が表示される
print(f"1 / 7 = {num:.5}です")     # 小数点以下 5 桁まで表示する
print(f"1 / 7 = {num:10.5}です")   # 10 文字で、右揃えで小数点以下 5 桁まで表示する
print(f"1 / 7 = {num:<10.5}です")  # 10 文字で、左揃えで小数点以下 5 桁まで表示する
print(f"1 / 7 = {num:+10.5}です")  # 10 文字で、符号付きで小数点以下 5 桁まで表示する
```
実行結果
```
1 / 7 = 0.14285714285714285です
1 / 7 = 0.14286です
1 / 7 =    0.14286です
1 / 7 = 0.14286   です
1 / 7 =   +0.14286です
```

__精度を指定__ しても、__必ず__ 小数点以下に __指定した桁数__ の __数字が埋められる__ わけでは __ありません__。例えば、下記のプログラムのように、__`1.1`__ に対して、__精度に 3 を指定__ しても、__`"1.1"`__ という __文字列に変換__ されます。__`1.1`__ を __`"1.100"`__ のように、__小数点以下 3 桁まで__ に __数字が記述__ された __文字列に変換__ したい場合は、後述の __表現型の f__ を指定します。

```python
a = 1.1
print(f"a = {a:.3}")
```
実行結果
```
a = 1.1
```

__整数型__ のデータに対して、__精度を指定__ すると、下記のプログラムのように __エラーが発生__ します。__整数型__ のデータに対して __精度を指定する__ 場合は、後述の __表現型の f__ を指定します。

```python
num = 10
print(f"{num:3.1}")
```
実行結果
```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[24], line 2
      1 num = 10
----> 2 print(f"{num:3.1}")

ValueError: Precision not allowed in integer format specifier
```

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

* __ValueError__
値（value）に関するエラー
* __Precision not allowed in integer format specifier__
精度（Precision）は、整数型（integer）の書式（format）指定（specifier）として利用することはできない（not allowed in）
:::

#### 文字列型のデータの場合

__文字列型__ のデータに対して __精度を指定__ すると、__文字列の最大長__ を __指定__ したことになります。__文字列の長さ__ が __精度より__ も __長い__ 場合は、__先頭から精度分__ の __長さ__ の __文字列__ に __切り詰め__ られます。下記のプログラムは、__文字列型__ のデータに対して __精度を指定__ した例です。

```python
name = "りんごは赤い"
print(f"{name:.3}です")  # 先頭の 3 文字が表示される
print(f"{name:7.3}です") # 先頭の 3 文字が取り出され、7 文字の文字列として左揃えで表示される
```
実行結果
```
りんごです
りんご    です
```

### 表現型

データを __文字列__ として、__どのように表現__ するかを、__1 文字__ の __半角のアルファベット__ で __指定__ します。下記の表は __主な表現型__ の一覧です。表の __利用可能なデータ型以外__ のデータに対して __表現型を指定__ すると、__エラーが発生__ します。

| 表現型 | 意味 | 利用可能なデータ型 |
|:-:|:--| :-- |
| __s__  | __文字列__ として表現する<br>文字列に指定できる表現型はこれだけなので、__一般的には省略__ する  | 文字列型 |
| __d__  | __10 進数の整数__ として表現する | 整数型 |
| __b__  | __2 進数の整数__ として表現する  | 整数型 |
| __x__  | __16 進数の整数__ として表現する | 整数型 |
| __f__  | __固定小数点数__ として表現する<br>詳細は下記を参照  | 整数型<br>浮動小数点数型 |
| __%__  | __f と同じ__ だが、__百分率__（100 倍して % を表記する）で __表記__ する  | 整数型<br>浮動小数点数型 |
| __e__  | __指数表記__  | 整数型<br>浮動小数点数型 |
| __g__  | __汎用表記__<br>数値の値によって、f と g を自動的に使い分ける | 整数型<br>浮動小数点数型 |

以下、いくつかの表現型の __補足__ と __使用例__ を記述します。

#### d、b、x

下記のプログラムのように、それぞれ、__整数__ を __10 進数__、__2 進数__、__16 進数__ として __文字列で表現__ します。2 進数と 16 進数の意味については話が長くなるので省略します。

```python
num = 100
print(f"{num:d} は 2 進数では {num:b}、16 進数では {num:x} と表記する")
```

実行結果

```
100 は 2 進数では 1100100、16 進数では 64 と表記する
```

#### f

__数値__ を、__固定小数点数__ という __表記__ の文字列に __変換__ します。

__固定小数点数__ とは、__数値__ を __表現__ する際に、__小数点__ の __位置を固定__ して __表現__ する方法で、__小数点以下__ の __桁数__ は、__必ず精度で指定した数__ だけ __表記__ されます。__f__ は __整数型__ と、__浮動小数点数型__ の __両方__ の __データ型__ を __固定小数点数__ の表記に __変換__ することが __できます__。なお、__精度の指定__ を __省略__ した場合は、精度に __6 が指定__ されたものと __みなされます__。

下記は、__整数型__ と __浮動小数点数型__ のデータに対して、__表現型__ と、__精度__ に __さまざまな指定__ して __表示__ するプログラムで、以下の点に __注目__ して下さい。

* 先ほどの場合と異なり、3 行目で __表現型__ に __f を指定__ することで、__整数型__ のデータに対して __精度を指定__ しても __エラーは発生しない__
* __同じ `num2`__ という __浮動小数点数型__ の数値に対して、4 行目の __精度__ と __表現型__ に __f を指定__ した場合と、5 行目の __精度のみを指定__ した場合の __表示が異なる__
* 6 行目のように、__精度を省略__ して __表現型__ に __f だけを記述__ した場合は、7 行目のように、__精度__ に __6 を指__ した場合と __同じ文字列__ に __変換__ される

```python
1  num1 = 1
2  num2 = 1.1
3  print(f"{num1:.3f}")  # 整数型に対して精度と f を指定した場合
4  print(f"{num2:.3f}")  # 浮動小数点数型に対して精度と f を指定した場合
5  print(f"{num2:.3}")   # 浮動小数点数型に対して精度のみを指定した場合
6  print(f"{num1:f}")    # 精度を省略した場合は、6 が指定されたものとみなされる
7  print(f"{num1:.6f}")  # 実際に精度に 6 を指定した場合。上記と同じ表示になる
```

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

```python
num1 = 1
num2 = 1.1
print(f"{num1:.3f}")  # 整数型に対して精度と f を指定した場合
print(f"{num2:.3f}")  # 浮動小数点数型に対して精度と f を指定した場合
print(f"{num2:.3}")   # 浮動小数点数型に対して精度のみを指定した場合
print(f"{num1:f}")    # 精度を省略した場合は、6 が指定されたものとみなされる
print(f"{num1:.6f}")  # 実際に精度に 6 を指定した場合。上記と同じ表示になる
```
</details>

実行結果
```
1.000
1.100
1.1
1.000000
1.000000
```

__一般的__ に、__数値型__ のデータに対して __精度を指定__ して __表示__ する場合は、__指定した桁数__ を __必ず表示したい__ 場合が多いので、__精度と f__ は __セットで記述__ することが __多い__ と思います。

#### %

__%__ は、__数値__ を __百分率で表示__ したい場合に指定します。__数値__ を __100 倍__ して、__最後に `%` を表記__ する点を除けば、__f と同じ__ です。具体例は、`ai_match` を修正する際に示します。

#### e

__数値__ を、__指数表記__ という __形式__ の文字列に __変換__ します。

`10000000000` や、`0.0000000001` のような、__非常に大きな__ 数値や、__0 に非常に近い__ 数値などを __そのまま表記__ すると __長く__ 、__わかりづらい__ ので、__指数表記__ が良く __使われます__。

__指数表記__ は、__`1e10`__ のように、__数値__ の __後__ に __`e`__ と __整数__ を __表記__ し、__e の前__ の __数値__ の __小数点__ を、__e の後__ の __数値の数__ だけ __右に移動__ した __数値__ であるという __意味__ を持ちます。__e の前__ に表記する __数値__ を __仮数部__、__e の後__ に表記する __整数__ を __指数部__ と呼びます。

__指数表記__ で表記された __数値__ は、__下記の式__ で __表す__ ことができます。

仮数部e指数部 = $仮数部 × 10^{指数部}$

__指数表記を指定__ した際に、__精度の指定__ を行うと、__仮数部の表記__ に __適用__ されます。また、__精度の指定__ を __省略__ すると、__f と同様__ に、精度に 6 が指定されたものとみなされます。

また、__指数表記__ で __数値__ を __文字列に変換__ する際に、__指数部__ が __正の整数__ の場合は、__正であること__ を __明確にする__ ために、__`+` が記述__ されます。

下記は、__`10000000000`__ と __`0.0000000001`__ を __指数表記__ で __表示__ するプログラムです。

```python
num1 = 10000000000
num2 = 0.0000000001
print(f"{num1} の指数表記は {num1:.0e}") # 仮数部の小数点以下の表示を行わない指数表記
print(f"{num2} の指数表記は {num2:.0e}")
print(f"{num1} の指数表記は {num1:e}")   # 精度の指定を省略した場合
print(f"{num2} の指数表記は {num2:e}")
```
実行結果
```
10000000000 の指数表記は 1e+10
1e-10 の指数表記は 1e-10
10000000000 の指数表記は 1.000000e+10
1e-10 の指数表記は 1.000000e-10
```

#### g

__数値__ を、__汎用表記__ という __形式__ の文字列に __変換__ します。

ただし、__汎用表記__ の表記の __ルールは複雑__ なので、説明は省略します。興味がある方は、下記のリンク先を下にスクロールした先にある、「利用可能な float と Decimal の表現型は以下です」の下にある表の、g の項目の説明を参照して下さい。

https://docs.python.org/ja/3/library/string.html#format-specification-mini-language

## `ai_match` の修正


下記は、__f 文字列__ で `ai_match` を __修正__ したプログラムです。

* __5、10 行目__：__f 文字列__ を使って、__キー__ と __回数__ や __比率__ を __表示__ するように __修正__ する。10 行目の __比率__ の方は、__書式指定__ で、__精度に `1`__、__表現型に `%`__ を __指定__ することで、__百分率__ で、__小数点以下 1 桁まで表示__ するようにする

```python
 1  def ai_match(ai, match_num=10000):
元と同じなので省略
 2      # 通算成績の回数の表示
 3      print("count", end="")
 4      for key in order_list:
 5          print(f" {key} {count[key]}", end="")
 6      print()
 7      # 通算成績の比率の表示
 8      print("ratio", end="")
 9      for key in order_list:
10          print(f" {key} {ratio[key]:.1%}", end="")
```

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

```python
def ai_match(ai, match_num=10000):
    mb = Marubatsu()

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

    # それぞれの比率を計算し、ratio に代入する
    ratio = {}
    for key in count.keys():
        ratio[key] = count[key] / match_num

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

    # 通算成績の回数の表示
    print("count", end="")
    for key in order_list:
        print(f" {key} {count[key]}", end="")
    print()
    # 通算成績の比率の表示
    print("ratio", end="")
    for key in order_list:
        print(f" {key} {ratio[key]:.1%}", end="")
```
</details>

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

```diff_python
def ai_match(ai, match_num=10000):
元と同じなので省略
    # 通算成績の回数の表示
    print("count", end="")
    for key in order_list:
-       print("", key, count[key], end="")
+       print(f" {key} {count[key]}", end="")
    print()
    # 通算成績の比率の表示
    print("ratio", end="")
    for key in order_list:
-       print("", key, ratio[key], end="")
+       print(f" {key} {ratio[key]:.1%}", end="")
```
</details>


下記のプログラムを実行することで、__比率__ が __百分率__ で、__小数点以下 1 桁__ の数値で __表示__ されることが __確認__ できます。

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

実行結果（実行結果はランダムなので下記とは異なる場合があります）
```
count o 440 x 101 draw 26
ratio o 77.6% x 17.8% draw 4.6%
```

## f 文字列を使わない方法

__f 文字列__ は、Python の __バージョン 3.6 から利用できる__ ようになったものなので、__それ以前__ のバージョンでは、__文字列__ の __`format` メソッド__ を使って __書式を指定__ していました。そのため、__それ以前のバージョン__ で __記述__ された __書式指定__ のプログラムを __理解するため__ には、__`format` メソッド__ の __利用方法__ について __理解__ する必要があります。`format` メソッドの詳細について興味がある方は、下記のリンク先を参照して下さい。

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

https://docs.python.org/ja/3/library/string.html#format-examples

なお、`format` メソッドより、__f 文字列__ のほうが __便利で分かりやすい__ ので、__3.6 以降__ のバージョンで __`format` メソッド__ を __利用する必要__ は __全くありません__。

# `ai_match` の改良 その 3（回数と比率の処理をまとめる）

下記は、__`ai_match`__ の、__回数__ と __比率__ の __表示__ を行う __部分__ です。__2 ~ 5 行目__ と、__7 ~ 9 行目__ の部分が良く似ているので、__for 文__ による __繰り返し処理__ で __まとめる__ ことができます。

__for 文__ によって処理を __まとめるため__ には、__2 ~ 5 行目__ と、__7 ~ 9 行目__ の処理の __異なる部分__ を __調べる必要__ があるので、異なる部分が何であるかについて少し考えてみて下さい。

```python
1  # 通算成績の回数の表示
2  print("count", end="")
3  for key in order_list:
4      print(f" {key} {count[key]}", end="")
5  print()
6  # 通算成績の比率の表示
7  print("ratio", end="")
8  for key in order_list:
9      print(f" {key} {ratio[key]:.1%}", end="")
```

2 ~ 5 行目と、7 ~ 9 行目の違いは以下の通りです。

1. __2、7 行目__ の __表示内容__
2. __5 行目__ の __`print()`__ が __9 行目の後__ に __存在しない__
3. __4、9 行目__ の __`count` と `ratio`__
4. __9 行目__ の __`ratio[key]`__ の __書式指定__ の __`.1%`__ が __4 行目__ には __存在しない__

## 2 番目の違いの解消

上記の違いのうちの、2 番目の「__5 行目__ の __`print()`__ が __9 行目の後__ に __存在しない__」という違いは、下記のプログラムのように、__10 行目__ に __`print()` を追加__ することで __簡単に解消__ することができます。また、10 行目に __`print()` を追加__ しても、__最後に空行が表示されるだけ__ なので、__表示内容__ に __大きな影響を与えない__ ので、下記のように修正することにします。

```python
 1  # 通算成績の回数の表示
 2  print("count", end="")
 3  for key in order_list:
 4      print(f" {key} {count[key]}", end="")
 5  print()
 6  # 通算成績の比率の表示
 7  print("ratio", end="")
 8  for key in order_list:
 9      print(f" {key} {ratio[key]:.1%}", end="")
10  print()
```

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

```diff_python
    # 通算成績の回数の表示
    print("count", end="")
    for key in order_list:
        print(f" {key} {count[key]}", end="")
    print()
    # 通算成績の比率の表示
    print("ratio", end="")
    for key in order_list:
        print(f" {key} {ratio[key]:.1%}", end="")
+   print()
```
</details>

## 異なる部分のデータの抜き出し

__繰り返し__ を使って __処理をまとめる__ ためには、__異なる部分__ を __抜き出したデータ__ を __要素__ として持つ __list__ を用意する必要があります。下記は、異なる部分を表にまとめたものです。

2、7 行目の __`"count"`__ と、4、9 行目の __`count`__ は __似ているように見える__ かもしれませんが、前者は __文字列__、後者は __変数名__ という、__全く異なるデータ__ である点に __注意__ して下さい。

| 行数 | 回数 | 比率 |
|:-:|:-:|:-:|
| __2、7 行目の文字列__  | `"count"`  | `"ratio"`  |
| __4、9 行目の変数__  | `count`  |  `ratio` |
| __4、9 行目の書式指定__  | なし  | `.1%`  |

### 書式指定の違いの解消

上記の違いのうち、__書式指定__ に関しては、__片方__ は __存在しない__ ので、このままでは、うまく __まとめる__ ことが __できません__。そこで、__回数__ のほうの __`count[key]`__ にも __書式指定を記述__ することにします。__`count[key]`__ には、__整数型__ のデータが __代入__ されているので、下記のプログラムの 4 行目のように、__10 進数を表記__ する __書式指定__ の __`d` を記述__ することにします。

```python
 1  # 通算成績の回数の表示
 2  print("count", end="")
 3  for key in order_list:
 4      print(f" {key} {count[key]:d}", end="")
 5  print()
 6  # 通算成績の比率の表示
 7  print("ratio", end="")
 8  for key in order_list:
 9      print(f" {key} {ratio[key]:.1%}", end="")
10  print()
```

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

```diff_python
    # 通算成績の回数の表示
    print("count", end="")
    for key in order_list:
-       print(f" {key} {count[key]}", end="")
+       print(f" {key} {count[key]:d}", end="")
    print()
    # 通算成績の比率の表示
    print("ratio", end="")
    for key in order_list:
        print(f" {key} {ratio[key]:.1%}", end="")
    print()
```
</details>

上記の __修正__ によって、それぞれの __違い__ が以下の表のようになります。

| 行数 | 回数 | 比率 |
|:-:|:-:|:-:|
| __2、7 行目の文字列__  | `"count"`  | `"ratio"`  |
| __4、9 行目の変数__  | `count`  |  `ratio` |
| __4、9 行目の書式指定__  | `d`  | `.1%`  |

### 異なる部分のデータを要素とする list の作成

次に、__異なる部分__ のデータを __集めたデータ__ を __作成__ し、__そのデータ__ を __要素__ とする __list を作成__ することにします。__異なる部分__ のデータを __集めたデータ__ の __データ構造__ としては、__list__、__tuple__、__dict__ などを利用する方法が考えられますが、dict はキーを記述するのが面倒なので、今回の記事では、__簡単に記述__ できる __tuple を利用__ することにします。

:::note
list を利用することもできますが、__list__ は __同じ種類のデータ__ を __扱う__ という __目的__ で __利用__ するのが __一般的__ なので、今回の記事では tuple を利用することにします。
:::

異なる部分を集めたデータは、以下の表のようになります。

|  | 異なるデータを集めた tuple |
|:-:|:-:|
| 回数の表示処理  | `("count", count, "d")`  |
| 比率の表示処理  | `("ratio", ratio, "r")`  |

下記のプログラムのように、この 2 つのデータを要素として持つ list を、__`diff_list`__ という名前の __変数__ に __代入__ します。__diff__ は __差異__ を表す __`difference`__ の __略__ です。

```python
diff_list = [ ("count", count, "d"), ("ratio", ratio, "r") ]
```

## 回数と比率の表示処理を繰り返し処理でまとめる

この list を使って、__回数と比率__ の __表示処理__ は、下記プログラムのように記述できます。

* __2 行目__：__`diff_list`__ から __順番__ に __要素を取り出し__、取り出した tuple の __3 つの要素__ を __`title`__、__`data`__、__`format`__ の __3 つの変数__ に __代入__ するという __繰り返し処理__ を行う
* __3 行目__：元は __`"count"`__ や __`"ratio"`__ が __記述__ されていた部分を __`title`__ に __置き換える__
* __4 行目__：元は __`count`__ や __`ratio`__ が __記述__ されていた部分を __`data`__ に __置き換える__。元は __`d`__ や __`.1%`__ が __記述__ されていた部分を __`{format}`__ に __置き換える__

```python
1  diff_list = [ ("count", count, "d"), ("ratio", ratio, "r") ]
2  for title, data, format in diff_list:
3      print(title, end="")
4      for key in order_list:
5          print(f" {key} {data[key]:{format}}", end="")
6      print()
```

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

```python
diff_list = [ ("count", count, "d"), ("ratio", ratio, "r") ]
for title, data, format in diff_list:
    print(title, end="")
    for key in order_list:
        print(f" {key} {data[key]:{format}}", end="")
    print()
```
</details>

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

下記の __for 文__ の __ブロックの中__ の __修正箇所__ は、__回数の表示の処理__ のプログラムに対する修正箇所です。

```diff_python
+diff_list = [ ("count", count, "d"), ("ratio", ratio, "r") ]
+for title, data, format in diff_list:
-   print("count", end="")
+   print(title, end="")
    for key in order_list:
-       print(f" {key} {count[key]}", end="")
+       print(f" {key} {data[key]:{format}}", end="")
    print()
```
</details>

### 書式指定の部分を式で記述する方法

上記のプログラムの 4 行目の補足ですが、__f 文字列__ の中の、__書式指定__ の __部分__ にも、__`{式}`__ を __記述__ することができ、その場合も __その部分__ が __`式`__ の __計算結果__ で __置き換わります__。この場合は、__`{}` の中__ に __`{}` が入れ子になる__ 点に __注意が必要__ です。

例えば、下記のプログラムの 3 行目と 4 行目は __同じ処理__ を行います。

```python
num = 0.5
format = ".1%"
print(f"{num:.1%}")
print(f"{num:{format}}")
```

実行結果
```
50.0%
50.0%
```

### `ai_match` の修正

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

```python
def ai_match(ai, match_num=10000):
元と同じなので省略
    # 通算成績の回数と比率の表示
    diff_list = [ ("count", count, "d"), ("ratio", ratio, ".1%") ]
    for title, data, format in diff_list:
        print(title, end="")
        for key in order_list:
            print(f" {key} {data[key]:{format}}", end="")
        print()
```

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

```python
def ai_match(ai, match_num=10000):
    mb = Marubatsu()

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

    # それぞれの比率を計算し、ratio に代入する
    ratio = {}
    for key in count.keys():
        ratio[key] = count[key] / match_num

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

    # 通算成績の回数と比率の表示
    diff_list = [ ("count", count, "d"), ("ratio", ratio, ".1%") ]
    for title, data, format in diff_list:
        print(title, end="")
        for key in order_list:
            print(f" {key} {data[key]:{format}}", end="")
        print()
```
</details>

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

```python
ai_match(ai=[ai1, ai2], match_num=567)
```
実行結果（実行結果はランダムなので下記とは異なる場合があります）
```
count o 437 x 97 draw 33
ratio o 77.1% x 17.1% draw 5.8%
```

# 今回の記事のまとめ

長くなったので、`ai_match` の改良の続きは次回の記事で行うことにします。

今回の記事では、__比率の表示__ を行うように `ai_match` を改良しました。

また、その際に、__フォーマット済文字列リテラル__を使って数値の __書式を指定__ する __方法__ について紹介しました。この方法は __良く使われる__ ので、覚えておくことをお勧めします。

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

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

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

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

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

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

# 次回の記事

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

[^1]:指数を使った数値の表記については、今回の記事の「書式の指定」の所で説明します