0
0

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

Last updated at Posted at 2023-12-31

目次と前回の記事

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

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

これまでに作成した AI

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

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

ai_match の改良 その 1(勝率などの比率の表示)

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

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

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

from ai import ai_match, ai1, ai2

ai_match(ai=[ai1, ai2])

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

o 7820 x 1764 draw 416

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

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" を表示 した
 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)
行番号のないプログラム
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)
修正箇所
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)

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

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 行目:それぞれの 比率計算 して 記録 するための 空の dictratio とという名前の変数に 代入 する。なお、ratio は、count異なり、この後の処理で、値が代入されていないキーを参照 する ことはない ので、defaultdict使う必要ない
  • 8、9 行目比率 は「回数 ÷ 試合数」で 計算 できるので、countそれぞれキーの値 に対してその 計算 を行い、計算結果ratio対応するキー代入 する
  • 13、14 行目ratio使って それぞれの 比率を表示 する
 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])
行番号のないプログラム
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])
修正箇所
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])

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

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

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

上記のプログラムは、11 ~ 14 行目の print実引数の記述大変 なので 改良 することにします。前回の記事では、最初 は、回数集計結果表示 を、下記のプログラムのように、for 文 を使って 表示 していました。

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

しかし、前回の記事の途中から、defaultdict を使って 集計を行う ようにした結果、上記の for 文 を使った場合は、表示の順番"o""x""draw"順番にならない場合 があります。先ほどのプログラムで、for 文使わず集計結果を表示 したのは そのため です。

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

  • 15 行目:表示する countキーの順番(order)を表す listorder_list代入 する
  • 18 行目:これから 回数表示 することを 明確 にするために、"count" を表示 する
  • 19、20行目for 文order_list を使って 繰り返し処理 を行うことで、order_listキーの順番 で、countキー と、キーの値表示 する
  • 22 ~ 24 行目比率表示 を、18 ~ 20 行目と 同様の方法 で行う

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

 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])
行番号のないプログラム
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])
修正箇所
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])

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

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改行を行わない ようにすれば良いので、以前の記事 で説明したように、下記のプログラムの 3、5、7、9 行目の print実引数end=""記述 します。

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="")
行番号のないプログラム
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="")
修正箇所
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="")

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

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 行目 は、以前の記事で説明した、改行 を表す \n という エスケープシーケンス を使って、print("\nratio") のように 1 行まとめて記述 することもできる
 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="")
行番号のないプログラム
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="")
修正箇所
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="")

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

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

上記の 修正作業 は、以前の記事で紹介した、ゲーム盤の表示 を行う 関数を定義 する際に行った 作業似ている のではないかと思った人がいるかもしれませんが、その通り です。

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

数値型のデータの種類

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

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

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

整数型(int)

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

浮動小数点数型(float)

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

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

print(1 / 3)

実行結果

0.3333333333333333

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

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

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

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

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

計算結果のデータ型
int と int計算結果整数 の場合 int
int と int計算結果整数でない 場合 float
int と float float
float と int float
float と float float

下記のプログラムは上記の演算の具体例です。なお、下記のプログラムでは、データ型 を調べるために、以前の記事 で紹介した、type という組み込み関数を利用しています。

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% のような、%(百分率)表示 したほうが わかりやすい でしょう。

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

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

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

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

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

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

print を使った方法の問題点

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

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

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

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

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

文字列の連結の問題点

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

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

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'

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

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

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

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" という 文字列置き換え られます。

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

実行結果

5個のりんごの値段は500円です

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

text = str(applenum) + "個のりんごの値段は" + str(applenum * applevalue) + "円です"
  • str使って、数値型のデータを文字列に 変換 する 必要がない
  • " は、文字列全体を囲う 2 箇所だけ記述すれば良い
  • + を使った式より 簡潔に記述 でき、プログラムの意味が 分かりやすくなる

書式指定

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

{:書式指定}

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

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

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

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

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

揃え

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

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

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

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

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 でも、必ず符号を入れる ようになる
- 負の数 の場合 のみ符号を入れる
符号の 指定を記述しない場合 は、この指定になる

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

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

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

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

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

  • ValueError
    値(value)に関するエラー
  • Sign not allowed in string format specifier
    符号(Sing)は、文字列型(string)の書式(format)指定(specifier)として利用することはできない(not allowed in)

文字幅

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

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

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

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

実行結果

りんごは赤い

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

精度

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

数値型のデータの場合

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

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

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

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 を指定します。

a = 1.1
print(f"a = {a:.3}")

実行結果

a = 1.1

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

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

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

  • ValueError
    値(value)に関するエラー
  • Precision not allowed in integer format specifier
    精度(Precision)は、整数型(integer)の書式(format)指定(specifier)として利用することはできない(not allowed in)

文字列型のデータの場合

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

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

実行結果

りんごです
りんご    です

表現型

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

表現型 意味 利用可能なデータ型
s 文字列 として表現する
文字列に指定できる表現型はこれだけなので、一般的には省略 する
文字列型
d 10 進数の整数 として表現する 整数型
b 2 進数の整数 として表現する 整数型
x 16 進数の整数 として表現する 整数型
f 固定小数点数 として表現する
詳細は下記を参照
整数型
浮動小数点数型
% f と同じ だが、百分率(100 倍して % を表記する)で 表記 する 整数型
浮動小数点数型
e 指数表記 整数型
浮動小数点数型
g 汎用表記
数値の値によって、f と g を自動的に使い分ける
整数型
浮動小数点数型

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

d、b、x

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

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 を指 した場合と 同じ文字列変換 される
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 を指定した場合。上記と同じ表示になる
行番号のないプログラム
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 を指定した場合。上記と同じ表示になる

実行結果

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 が指定されたものとみなされます。

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

下記は、100000000000.0000000001指数表記表示 するプログラムです。

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 の項目の説明を参照して下さい。

ai_match の修正

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

  • 5、10 行目f 文字列 を使って、キー回数比率表示 するように 修正 する。10 行目の 比率 の方は、書式指定 で、精度に 1表現型に %指定 することで、百分率 で、小数点以下 1 桁まで表示 するようにする
 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="")
行番号のないプログラム
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="")
修正箇所
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="")

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

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 メソッドの詳細について興味がある方は、下記のリンク先を参照して下さい。

なお、format メソッドより、f 文字列 のほうが 便利で分かりやすい ので、3.6 以降 のバージョンで format メソッド利用する必要全くありません

ai_match の改良 その 3(回数と比率の処理をまとめる)

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

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

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 行目countratio
  4. 9 行目ratio[key]書式指定.1%4 行目 には 存在しない

2 番目の違いの解消

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

 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()
修正箇所
    # 通算成績の回数の表示
    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()

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

繰り返し を使って 処理をまとめる ためには、異なる部分抜き出したデータ要素 として持つ 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 を記述 することにします。

 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()
修正箇所
    # 通算成績の回数の表示
    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()

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

行数 回数 比率
2、7 行目の文字列 "count" "ratio"
4、9 行目の変数 count ratio
4、9 行目の書式指定 d .1%

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

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

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

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

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

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

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

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

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

  • 2 行目diff_list から 順番要素を取り出し、取り出した tuple の 3 つの要素titledataformat3 つの変数代入 するという 繰り返し処理 を行う
  • 3 行目:元は "count""ratio"記述 されていた部分を title置き換える
  • 4 行目:元は countratio記述 されていた部分を data置き換える。元は d.1%記述 されていた部分を {format}置き換える
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()
行番号のないプログラム
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()
修正箇所

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

+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()

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

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

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

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

実行結果

50.0%
50.0%

ai_match の修正

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

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()
行番号のないプログラム
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()

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

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 のファイルを見ることができます。

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

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

次回の記事

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0