目次と前回の記事
これまでに作成したモジュール
以下のリンクから、これまでに作成したモジュールを見ることができます。
これまでに作成した 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_num
が 567
のような、割り算 を 計算しづらい 数値の場合は、〇 の勝率などを 暗算 で 計算 することは 困難 です。
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 行目:それぞれの 比率 を 計算 して 記録 するための 空の dict を
ratio
とという名前の変数に 代入 する。なお、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)を表す list をorder_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
の 実引数 に、""
、key
、data[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 が指定されたものとみなされます。
また、指数表記 で 数値 を 文字列に変換 する際に、指数部 が 正の整数 の場合は、正であること を 明確にする ために、+
が記述 されます。
下記は、10000000000
と 0.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 行目の違いは以下の通りです。
- 2、7 行目 の 表示内容
-
5 行目 の
print()
が 9 行目の後 に 存在しない -
4、9 行目 の
count
とratio
-
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 を作成 することにします。異なる部分 のデータを 集めたデータ の データ構造 としては、list、tuple、dict などを利用する方法が考えられますが、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 つの要素 をtitle
、data
、format
の 3 つの変数 に 代入 するという 繰り返し処理 を行う -
3 行目:元は
"count"
や"ratio"
が 記述 されていた部分をtitle
に 置き換える -
4 行目:元は
count
やratio
が 記述 されていた部分を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 です。
次回の記事
-
指数を使った数値の表記については、今回の記事の「書式の指定」の所で説明します ↩