目次と前回の記事
実装の進捗状況と前回までのおさらい
〇×ゲームの仕様と進捗状況
正方形で区切られた 3 x 3 の 2 次元のゲーム盤上でゲームを行う
ゲーム開始時には、ゲーム盤のすべてのマスは空になっている
2 人のプレイヤーが遊ぶゲームであり、一人は 〇 を、もう一人は × のマークを受け持つ
2 人のプレイヤーは、交互に空いている好きなマスに自分のマークを 1 つ置く
先手は 〇 のプレイヤーである
- プレイヤーがマークを置いた結果、縦、横、斜めのいずれかの一直線の 3 マスに同じマークが並んだ場合、そのマークのプレイヤーの勝利とし、ゲームが終了する
- すべてのマスが埋まった時にゲームの決着がついていない場合は引き分けとする
仕様の進捗状況は、以下のように表記します。
- 実装が完了した部分を
背景が灰色の長方形
で記述する - 実装の一部が完了した部分を、太字 で記述する
これまでに作成したモジュール
以下のリンクから、これまでに作成したモジュールを見ることができます。
前回までのおさらい
前回の記事では、judge
メソッドのテストを行う test_judge
を実装し、命令網羅(C0) と分岐網羅(C1) の条件を満たすテストを行いました。
実装がなかなか進まない点について
テストの話が始まってから、〇×ゲームの実装が全然進まないのでじれったいと思っている人が多いかもしれません。筆者もテストの話は、2、3 回くらいで終わるつもりだったのですが、執筆してみると説明したほうが良いと思えることが増えてきて、記事がどんどん長くなってしまいました。
今回の記事で取り上げる、座標を表すデータ構造についても、1 回の記事で終わらせるつもりが、次回に続くことになってしまいました。ただし、様々なデータ構造 と、そのデータ構造を扱う アルゴリズム を 体験 しておくことは、これから自力でプログラムを記述しようと思っている人にとっては必ず将来の役に立つと思います。
また、今回の記事で取り上げる内容は、関数のテストだけでなく、今後の 〇×ゲームや、AI を実装する際に、実際に使用する ことになります。はやく実装を進めたい人にとっては退屈な記事だと思う人がいるかもしれませんが、もうしばらくテストに関する話が続きますのでお付き合いください。
座標を表すデータ構造に求められる性質
前回の記事では、テストケースを表すデータを、下記のように記述しました。
もちろん、今後のテストで テストケースを増やす 場合に、下記のデータ構造でテストケースを記述することは可能ですが、着手の座標を表すデータを 2 つの要素を持つ list で 記述するのが大変 だと思いませんか?
testcases = [
# 〇 の勝利のテストケース
[
[
[ 0, 0 ], # 着手順とゲーム盤
[ 0, 1 ], # 135 ooo
[ 1, 0 ], # 24. xx.
[ 1, 1 ], # ... ...
[ 2, 0 ],
],
Marubatsu.CIRCLE, # 期待される judge メソッドの返り値
],
# × の勝利のテストケース
[
[
[ 0, 1 ], # 着手順とゲーム盤
[ 0, 0 ], # 246 xxx
[ 1, 1 ], # 13. oo.
[ 1, 0 ], # 5.. o..
[ 0, 2 ],
[ 2, 0 ],
],
Marubatsu.CROSS, # 期待される judge メソッドの返り値
],
# 引き分けのテストケース
[
[
[ 0, 0 ], # 着手順とゲーム盤
[ 0, 1 ], # 136 oox
[ 1, 0 ], # 245 xxo
[ 1, 1 ], # 789 oxo
[ 2, 1 ],
[ 2, 0 ],
[ 0, 2 ],
[ 1, 2 ],
[ 2, 2 ],
],
Marubatsu.DRAW, # 期待される judge メソッドの返り値
],
]
これまでは、着手を行うマスの座標を、x 座標 と y 座標の値を要素とする list というデータ構造で表現してきましたが、ゲーム盤の 異なるマス を、異なるデータ で 表現 することができれば、他のデータ構造 で座標を表すデータを 表現してもかまいません。別の言葉でいうと、異なるマスを区別 することができるようなデータ構造であれば、どのようなデータ構造 でも 構わない ということです。
着手を行う 座標を表すデータ を、別の、より 簡潔な記述 を行うことが出来る データに置き換える ことで、簡潔にデータを記述 することができるようになります。
異なるマスを区別 できれば良いので、座標を表すデータ構造は、無限に考える ことができます。ただし、記述のしやすさ や、扱いやすさ などの 差は生まれます。
様々な データ構造を学ぶ ことで、状況 に応じた 適切なデータ構造 を 選択 することが できる ようになり、その結果として、効率良く、バグが発生しにくい プログラムを 記述 することが できるようになります。
本記事では、様々なデータ構造を紹介する際に、それぞれの 性質、利点、欠点 などを合わせて紹介しますので、そのことを感じ取ってください。
これから、座標を表す データ構造 を いくつか紹介 することになるので、それぞれの データ構造 に 名前を付けて区別 することにします。
これまでに利用してきた、「x 座標 と y 座標を要素に持つ list」 で座標を表現するデータ構造の事を、xy 座標 と表記することにします。
xy 座標の利点と欠点
xy 座標の主な利点と欠点は以下の通りです。
利点
- 「数値で表現された x 座標、y 座標」という、数学 で使われ、多くの人が 慣れ親しんでいる 2 次元の座標と同じ方式で、座標を表現するため、直観的に分かりやすい
- list を使って 2 つのデータ を 別々に表現 しているため、プログラムでそれぞれの座標を 区別して扱うことが出来る
欠点
- テストケースのように、xy 座標を 直接プログラム内に記述する 場合は、
[]
や,
などの 記号を記述する必要 があるため、表記が長くなる - list の 0 番のインデックスの要素を x 座標に、1 番のインデックスの要素を y 座標に 対応させている が、そのことは 説明がなければ分かりづらい
- 複数の xy 座標 を表すデータは、list の中に、xy 座標を表す list を 入れ子 にするという、2 次元配列を表す list で表現 する必要があるが、2 次元配列を表す list の データの記述 は一般的に 長く、見た目が わかりづらくなる という 欠点 がある
2 番目の欠点がわかりづらいと思いますので、具体例を挙げて説明します。何の説明もなし に、[0, 1]
という list だけ を見て、これが座標を表すデータであることを 理解することは難しい と思いますが、dict を使った { "x": 0, "y": 1 }
のようなデータであれば、キーの値 から、座標を表すデータであることが、説明がなくても 直観的に 理解する ことができる人は多いでしょう。ただし、この問題は、x, y = coord
のように、分かりやすい名前 の変数に、list の要素を 代入する ことで 要素の数が少ない場合 は 改善 することが出来ます。
上記の dict を使って座標を表現する データ構造 は、データの記述 が xy 座標よりも さらに長くなってしまう という 欠点 があります。また、xy 座標の場合は、数学の座標を (0, 1) のように表記するので、[0, 1]
が座標を表すデータであることを 直観的に理解 することは 困難ではない でしょう。
今回の記事では、テストケースを 簡潔に記述 することができるデータ構造を 紹介する ことが 目的 なので、dict を使って座標を表現する具体例は紹介しません。
なお、dict を使ってデータを表現する具体例については、今回の記事の「excel_to_xy
の定義その 2」の所で紹介します。
xy 座標は、test_judge
のブロックの中の下記のプログラムのように、xy 座標 の中の 2 つの x 座標と y 座標を表すデータを、簡単に 異なる 変数に代入 して 扱うことができる ので、既に変数に代入 されている xy 座標 のデータを 処理 する際には 便利です。
for x, y in testcase:
mb.move(x, y)
一方、テストケースのように、複数の xy 座標を表すデータをプログラム内に 直接記述 する場合は、記述が長く、見た目が わかりづらい という 大きな欠点 があります。
座標を表すデータ構造その 1(Excel 座標)
Excel という表計算ソフトでは、表のセル(マスのこと)を A1 のように、列 を表す アルファベット と、行 を表す 整数 を 並べる ことで表記します。そこで、座標を表す下記のようなデータ構造を考え、Excel 座標1 と表記することにします。
Excel 座標 の データ構造 を以下のように 定義 する。
-
x 座標 を、x 番目 の 大文字のアルファベット で表現する。ただし、一番左の列 に対応するアルファベットを
"A"
とする -
y 座標 を 整数 を表す 文字列 で表現する。ただし、一番上の行 を
"1"
とする - x 座標 を表す アルファベット と、y 座標 を表す 整数 を 並べた文字列 を座標を表すデータとする
Excel 座標では、y 座標 を 0 からではなく、1 から数える点 に注意して下さい。
本記事では扱いませんが、xy 座標のように、y 座標を 0 から数える ような座標を使っても 構いません。また、x 座標を 小文字 で表現したり、ひらがななど で表現しても 構いません。興味と余裕がある方はチャレンジしてみて下さい。
チェス でも同様の方法でマスの座標を表現しますが、x 座標 を 小文字で表現 する点と、y 座標 を、一番下 の行を 1 とし、上に向かって 数字を 増やしていく という点が異なるようです。詳しくは下記のリンク先を参照して下さい。
オセロ(リバーシ)の場合は、x 座標 を 小文字で表現 する点を除けば、Excel 座標と同じ方法 でマスの座標を表現するようです。詳しくは下記のリンク先を参照して下さい。
Excel 座標で、〇× ゲームのゲーム盤の 9 つのマスの座標は、下図のように表記します。
下記は、テストケースの中の、xy 座標を表すデータを、Excel 座標に 置き換えた プログラムです。元のプログラムと比較して、簡潔 に、わかりやすく 記述することが出来るようになります。また、複数の座標 を表すデータの データ構造 が、元は 2 次元配列 を表す list であったのが、1 次元配列 の list になっており、簡潔 になっています。
from marubatsu import Marubatsu
testcases = [
# 〇 の勝利のテストケース
[ # 着手順とゲーム盤
[ "A1", "A2", "B1", "B2", "C1" ], # 135 ooo
Marubatsu.CIRCLE, # 24. xx.
], # ... ...
# × の勝利のテストケース
[ # 着手順とゲーム盤
[ "A2", "A1", "B2", "B1", "A3", "C1" ], # 246 xxx
Marubatsu.CROSS, # 13. oo.
], # 5.. ...
# 引き分けのテストケース
[ # 着手順とゲーム盤
[ "A1", "A2", "B1", "B2", "C2", "C1", "A3", "B3", "C3" ], # 136 oox
Marubatsu.DRAW, # 245 xxo
], # 789 oxo
]
Excel 座標の利点と欠点
Excel 座標の主な利点と欠点は以下の通りです。
利点
- 〇×ゲームの場合は、2 文字の文字列で 簡潔に表記できる
- アルファベットと数値という 異なる方法 で x 座標と y 座標を 記述 しているので、どの文字が x 座標と y 座標を表しているかを 一目で区別 できる
- 同様の理由で、x 座標と y 座標を表す文字列を、数学の座標の
(0, 1)
のように、,
などの 記号で区切らず に、直接並べて短く記述 することが出来る。 - Excel 座標の表記に慣れれば、直観的でわかりやすい
- 複数の Excel 座標を表すデータを 1 次元配列 を表す list で表現 できる
欠点
- x 座標と y 座標を表すデータを 1 つの文字列で表現 しているので、
move
メソッドの仮引数のように、x 座標と y 座標を 別々に扱う必要がある 際に 利用しづらい - 慣れるまでは、アルファベットで記述された x 座標 の 意味 が 理解しづらい
- x 座標 を アルファベットで表現 しているので、元の整数 の x 座標を 利用したい 場合は、アルファベットを整数の座標に 変換 する必要がある
上記のように、Excel 座標は、データの記述が簡潔 であるという利点がある反面、そのデータをプログラムで利用する際に 何らかの処理が必要 になります。
データの変換
Excel 座標で記述したテストケースでは、座標を表す データ構造が変わった ので、それに合わせて プログラムを修正 する必要があります。
修正する方法として、Marubatsu
クラスの move
メソッドや place_mark
メソッドなど、xy 座標 を使って 処理 が行われている 部分をすべて、Excel 座標 使って 処理を行う ように 修正 するという方法が考えられます。しかし、先程の利点と欠点の所で説明したように、Excel 座標は、データをプログラムに直接 記述する際は便利 ですが、Excel 座標のデータをプログラム内で 利用する際 は、xy 座標と比較 すると 不便 です。
他の修正方法としては、Excel 座標 を、xy 座標 に 変換 する 関数を定義 して利用するという方法があります。こちらの方法であれば、これまでに記述した xy 座標で処理を行う部分のプログラムを 修正 する 必要はありません。別の言葉で説明すると、データの記述 は Excel 座標 で 簡潔に記述 し、データの処理 は xy 座標 を使って行うという、両者の 良い所取り をするという方法です。本記事では、こちらの方法を採用することにします。
関数の仕様
そのような関数を定義するためには、関数の名前、入力、処理、出力 の 仕様を決める 必要があり、本記事では、それぞれを以下のように決めることにします。
-
関数の名前:Excel 座標を xy 座標に変換するので、
excel_to_xy
にする -
入力:座標 を表す coordinates という英単語を略した、
coord
という名前の 仮引数 に、Excel 座標を代入 する - 処理:Excel 座標を xy 座標に 変換 する
- 出力:返り値 として、xy 座標を返す
具体的には、下記のような関数を定義することにします。
def excel_to_xy(coord):
coord を xy 座標に変換する処理
return [x, y]
excel_to_xy
の実装が 完了した後 で、test_judge
メソッド内の、xy 座標 を使ってマスに マークを配置 する、下記の部分の 処理を修正 する必要があります。excel_to_xy
を使って、どのように修正すれば良いかについて少し考えてみて下さい。
for x, y in testdata:
mb.move(x, y)
testdata
には、[ "A1", "A2", "B1", "B2", "C1" ]
のような、Excel 座標を要素 として持つ list が代入 されています。従って、上記の for 文を for coord in testdata:
のように 修正 することで、先頭の要素 から 順番 に、coord
に Excel 座標を代入 して 繰り返し処理 を行うことができます。
for 文のブロックの中では、先ほど定義した、excel_to_xy
の実引数に coord
を記述して 呼び出す ことで、Excel 座標 を xy 座標 に 変換 することができます。
下記のプログラムは、そのように修正したプログラムです。excel_to_xy
を定義して利用 することで、下記以外の 修正 を一切行う 必要がなくなります。
for coord in testdata:
x, y = excel_to_xy(coord)
mb.move(x, y)
修正箇所
- for x, y in testdata:
+ for coord in testdata:
+ x, y = excel_to_xy(coord)
mb.move(x, y)
なお、x, y = excel_to_xy(coord)
の部分は、excel_to_xy
の 返り値 が、list でも tuple でも 同じ処理 が行われます。そのため、excel_to_xy
の return
文を、return x, y
のように記述して、tuple を返す2ように 変更 しても 同一の処理 が行われます。こちらの場合は、[]
を記述する必要がない点と、実際に良く記述される ので、本記事でも下記のように、関数の返り値を return x, y
のように記述することにします。
def excel_to_xy(coord):
coord を xy 座標に変換する処理
return x, y
修正箇所
def excel_to_xy(coord):
coord を xy 座標に変換する処理
- return [ x, y ]
+ return x, y
次に、excel_to_xy
の定義を記述します。この関数の定義のブロックに、どのようなプログラムを記述すれば良いかについて少し考えてみて下さい。
アルゴリズムとは何か
アルゴリズムとは、何らかの処理 を行うための 手順 のことを表します。同じ処理 に対して、様々なアルゴリズム を考えることが出来ますが、アルゴリズムの 種類 によって、処理の記述のしやすさ、処理にかかる時間などの 性質が異なります。
現実の例で例えると、東京から大阪まで旅行する場合の事を考えてみて下さい。旅行するための 方法 のが アルゴリズムに相当 します。旅行する方法としては、「飛行機で行く」、「新幹線で行く」、「長距離バスで行く」、「車で行く」、「バイクで行く」、「自転車で行く」、「歩いていく」など、様々な方法 が考えられます。また、同じ「車で行く」の場合でも、どの道で行くかによって、様々な方法 が考えられます。それぞれの方法によって、「費用」、「時間」、「疲れ」などの 性質が異なる ので、旅行を行う際には、それらの方法の 性質 と、旅行を行う際の 自分の都合 を 考慮 して、最もふさわしい と 自分で考えた方法 を 選択する ことになるはずです。
プログラミングを学ぶ際に、アルゴリズムを学ぶことが重要 であると良く言われるのは、様々なアルゴリズムを学ぶ ことで、自分が行いたい処理 を 実装する ために ふさわしい アルゴリズムを、自分で選択 してプログラミングを行うことが 出来るようになる からです。例えば、上記の旅行の例で、お金をかけずに1日以内 で旅行したい場合、格安の長距離バスがあることを 知っていれば 、それを 選択して 安く旅行することが できます が、そのことを 知らなければ、別の より高い方法 しか選択することはできません。
本記事では、同じ処理 に対して 様々な アルゴリズムが 存在する例 として、excel_to_xy
を様々なアルゴリズムで実装し、それらの 利点 と 欠点 について説明します。
アルゴリズムとデータ構造
同じ処理であっても、データを表す データ構造 が異なれば、アルゴリズム も 異なります。例えば、xy 座標から、x 座標と y 座標を取り出して利用する場合は、下記のプログラムのような代入文を使ったアルゴリズムを利用することが出来ます。
coord = [0, 1]
x, y = coord
また、下記のプログラムのように、それぞれの要素を 個別に変数に代入する というアルゴリズムも考えられるでしょう。
coord = [0, 1]
x = coord[0]
y = coord[1]
一方、Excel 座標の場合は、上記のアルゴリズムは いずれも 利用することは できません。
このように、アルゴリズムとデータ構造 は、セットになる概念 です。従って、プログラミングを学ぶ際には、アルゴリズムとデータ構造の 両方を学ぶ ことが 重要 です。
今回の記事では、Excel 座標というデータ構造に対するアルゴリズムを紹介します。
excel_to_xy
の定義その 1(if 文を使う方法)
最も 単純 な方法として、下記のような if 文 を使ったアルゴリズムが考えられます。
def excel_to_xy(coord):
if coord == "A1":
return 0, 0
elif coord == "A2":
return 0, 1
elif coord == "A3":
return 0, 2
elif coord == "B1":
return 1, 0
elif coord == "B2":
return 1, 1
elif coord == "B3":
return 1, 2
elif coord == "C1":
return 2, 0
elif coord == "C2":
return 2, 1
elif coord == "C3":
return 2, 2
else:
print("invalid coord", coord)
上記のプログラムは、if 文 を使って、coord
が 〇×ゲームのそれぞれのマスの Excel 座標と等しい かどうかを チェック し、等しければ 対応する xy 座標 を return
文で返しています。また、〇×ゲームの どのマス の Excel 座標 とも 等しくなければ、不正な(invalid)座標であることを表す メッセージを表示 しています。
下記のプログラムは、Excel 座標の "A1"
を excel_to_xy
を使って xy 座標に変換 するプログラムです。実行結果から正しく変換できていることが確認できます。
x, y = excel_to_xy("A1")
print(x, y)
実行結果
0, 0
なお、下記のプログラムのように、〇×ゲームのゲーム盤に 存在しない "E5"
のような Excel 座標を、excel_to_xy
を使って xy 座標に変換しようとすると、if
文の else
の次の行で invalid coord E5
が 表示された後 で、エラーが発生 します。
x, y = excel_to_xy("E5")
print(x, y)
実行結果
invalid coord E5
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\027\marubatsu.ipynb セル 4 line 1
----> 1 x, y = excel_to_xy("E5")
2 print(x, y)
TypeError: cannot unpack non-iterable NoneType object
上記のエラーメッセージは、以下のような意味を持ちます。
-
TypeError
データタイプ(type)に関するエラー -
cannot unpack non-iterable NoneType object
反復可能オブジェクト(iterable)でない(non)None(NoneType のこと)は、展開(unpack)することはできない(cannot)
このエラーは、下記の else
の行が実行された後で、return
文 を 実行せず に、excel_to_xy
の ブロックの処理が終了 したことが原因です。
else:
print("invalid coord", coord)
以前の記事 で説明したように、return
文を実行せず に 関数 のブロックの 処理が終了 すると、返り値 として None
が 返ります。従って、x, y = excel_to_xy("E5")
を呼び出すと、x, y = None
が 実行される ことになります。
反復可能オブジェクトの展開
これまで説明していませんでしたが、x, y = 式
のような 代入文 は、式の値 が list や tuple、文字列型のような、反復可能オブジェクト の場合に利用できます。このような、反復オブジェクト の中のデータの 一部を別々に取り出して利用 することを 展開(unpack) と呼びます。None
は 反復可能オブジェクトではない ため、展開できない(cannot unpack)というメッセージが表示されるエラーが発生します。
なお、else
のブロックの中に、下記のような return 文を記述して tuple を返す3 ようにプログラムを 修正 することで、エラーが発生しない ようにすることもできますが、excel_to_xy
の場合は、そのような 修正 を 行う必要はない と思います。
else:
print("invalid coord", coord)
return None, None
その理由は、テストケースを表すデータの中の座標に、"E5"
のような、〇×ゲームのゲーム盤に 存在しない座標 が 記述 されていた場合は、データが間違っている ので、そのテストケースでは 正しいテスト を行うことが 出来ません。そのため、そのようなテストケースを使ってテストを行った際に、エラーが発生 して プログラムが停止 したほうが、良い からです。逆に、正しいテスト を行うことが 出来ない のに、エラーが発生せず、最後まで プログラムが 実行される ことのほうが 問題がある でしょう。
動作の確認
下記のプログラムは、test_judge
を修正したプログラムです。
def test_judge(testcases):
for testcase in testcases:
testdata, winner = testcase
mb = Marubatsu()
for coord in testdata:
x, y = excel_to_xy(coord)
mb.move(x, y)
print(mb)
if mb.judge() == winner:
print("ok")
else:
print("error!")
修正箇所
def test_judge(testcases):
for testcase in testcases:
testdata, winner = testcase
mb = Marubatsu()
- for x, y in testdata:
+ for coord in testdata:
+ x, y = excel_to_xy(coord)
mb.move(x, y)
print(mb)
if mb.judge() == winner:
print("ok")
else:
print("error!")
下記は、test_judge
の実引数に、Excel 座標 で座標を 記述 した、先程の命令網羅(C0) の テストケース を記述して テストを行う プログラムです。実行結果から、Excel 座標を使ったテストケースに対して、正しくテストが行われることが確認できます。
test_judge(testcases)
実行結果
Turn x
ooo
xx.
...
ok
Turn o
xxx
oo.
o..
ok
Turn x
oox
xxo
oxo
ok
excel_to_xy
に対するテストの必要性
ところで、excel_to_xy
に対して、テストを行う必要があるのでは と思った人はいないでしょうか?excel_to_xy
の場合は、test_judge
に対して現在行っているようなテストを 行う必要はない と思います。その理由は、testcases
の中の、引き分けを表すテストケース には、〇×ゲームの すべてのマスの座標 が 含まれる ので、test_judge
を実行し、表示される テストケースの ゲーム盤 を 目で見て確認 することで、実質的 に、excel_to_xy
の 全数テスト を行っていると みなすことが出来る からです。
従って、上記のプログラムの実行は、test_judge
だけでなく、excel_to_xy
が正しく動作 するかどうかの 確認 を 兼ねています。ok
が表示 されることを確認する だけでなく、それぞれのテストケース で 正しいマス に マークが配置 されているかどうかを 目で確認する ことを 忘れない で下さい。
このように、定義した すべての関数 に対して 詳細なテスト を行うのは 手間がかかりすぎる ので、テストを行うまでもないような 単純な関数 や、上記のような 他の手段 で テストを代替 できる場合などでは、テストを省略 することが良くあります。
利点と欠点
このアルゴリズムの主な利点と欠点は以下の通りです。
利点
- 行っている処理の 意図 が わかりやすい
欠点
- プログラムの 記述が長く、大変
- すべての座標 に 対応 する 条件式 を 記述 する必要がある
- 行っている処理の意図はわかりやすいが、具体的 に行われる 処理を理解 するために、if 文のそれぞれの条件文とブロックの処理を見る必要がある点では わかりづらい
excel_to_xy
の定義その 2(テーブルを使う方法)
if 文 を使った excel_to_xy
の アルゴリズム は、行っている処理の意図が わかりやすい という 利点 がありますが、記述が長くなる という 欠点 があります。
そこで、if 文 より 簡潔に記述 できる アルゴリズム をいくつか紹介します。
excel_to_xy
が行う処理は、Excel 座標 から xy 座標 への 対応づけ です。これは、以前の記事 で説明した、名前からオブジェクト への 対応づけ を行う、名前空間 と 似ている と思いませんか?また、以前の記事 で行った、名前空間 は、dict を使って 実現されている という説明を思い出してください。
Excel 座標 が 文字列で表現 されることと、dict の キー に 文字列を利用できる ことから、dict を使う ことで、Excel 座標 から xy 座標 への 対応づけ を 表現 することが できます。具体的には、dict の キー に Excel 座標 を、その キーの値 に対応する xy 座標 を 代入 することで、下記のプログラムのように、Excel 座標 と xy 座標 を 対応付け ます。
excel_to_xy_table = {
"A1": [0, 0],
"A2": [0, 1],
"A3": [0, 2],
"B1": [1, 0],
"B2": [1, 1],
"B3": [1, 2],
"C1": [2, 0],
"C2": [2, 1],
"C3": [2, 2],
}
このような対応づけは、下記のような 表(table)を 作っている ようなものなので、この対応づけを表す dict を代入する 変数の名前 は、excel_to_xy_table
という名前にしました。また、このような データ から 別のデータ への 対応づけ を目的とする データ の事を、テーブル と呼ぶことがあるので、本記事でもそのように表記します。
excel 座標 | xy 座標 |
---|---|
"A1" |
[0, 0] |
"A2" |
[0, 1] |
略 | 略 |
"C3" |
[2, 2] |
このデータを使って excel_to_xy
を下記のプログラムのように定義することが出来ます。なお、先ほどの excel_to_xy_table
は、この関数の ローカル変数 としています。
def excel_to_xy(coord):
excel_to_xy_table = {
"A1": [0, 0],
"A2": [0, 1],
"A3": [0, 2],
"B1": [1, 0],
"B2": [1, 1],
"B3": [1, 2],
"C1": [2, 0],
"C2": [2, 1],
"C3": [2, 2],
}
return excel_to_xy_table[coord]
下記のプログラムを実行することで、修正した excel_to_xy
が正しく動作することが確認できます。その際に、テストケース の データ構造 は 変更されていない ので、test_judge
を 変更 する必要は ありません。なお、実行結果は先ほどと 同じ内容 になるので 省略 します(github の方では、下記のプログラムを実際に実行します)。
test_judge(testcases)
このアルゴリズムの場合も、〇×ゲームのゲーム盤に 存在しない "E5"
のような Excel 座標を、excel_to_xy
を使って xy 座標に変換しようとすると、下記のプログラムのように、"E5"
というキーが dict に存在しないという エラーが発生 しますが、先程と同じ理由でエラーが発生しないようにする修正は行いません。
print(excel_to_xy("E5"))
実行結果
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\027\marubatsu.ipynb セル 9 line 1
----> 1 print(excel_to_xy("E5"))
c:\Users\ys\ai\marubatsu\027\marubatsu.ipynb セル 9 line 1
1 def excel_to_xy(coord):
2 excel_to_xy_table = {
3 "A1": [0, 0],
4 "A2": [0, 1],
(...)
11 "C3": [2, 2],
12 }
---> 13 return excel_to_xy_table[coord]
KeyError: 'E5'
下記のプログラムのように、テーブルを表すデータを変数に代入せず、直接 return 文に記述 することもできます。分かりづらい と思いますので初心者の方には お勧めしません が、実際に このような 記述が行われる 場合があるので紹介しました。なお、確認を行うプログラムとその実行結果は省略します。
def excel_to_xy(coord):
return {
"A1": [0, 0],
"A2": [0, 1],
"A3": [0, 2],
"B1": [1, 0],
"B2": [1, 1],
"B3": [1, 2],
"C1": [2, 0],
"C2": [2, 1],
"C3": [2, 2],
}[coord]
利点と欠点
このアルゴリズムの主な利点と欠点は以下の通りです。
利点
- if 文を使ったアルゴリズムより 記述が短い
- 慣れれば Excel 座標と xy 座標の 対応 が わかりやすい
欠点
- すべての座標 に 対応 する キーと値 を 記述 する必要がある
- 慣れるまで は、if 文を使ったアルゴリズムより 意図がわかりづらい
excel_to_xy
の定義その 3(x、y 座標を分ける)
if 文を使った場合と、テーブルを使った場合は、いずれも 〇×ゲームの 全て のマスの 座標 に対応する 記述を行う 必要があります。〇×ゲームの場合は、9 マスしかないのでそれほど大変ではありませんが、例えば $9 * 9 = 81$ マスもある将棋のようなゲーム盤の場合は、81 もの記述を行う必要があるので 大変 です。
この問題は、x 座標 と、y 座標 のデータを 分けて処理 を行うことで 改善 することが出来ます。文字列型 のデータは、反復可能オブジェクト なので、先程説明したように、下記のプログラムのように記述することで、展開 によって それぞれの文字 を 個別の変数 に 代入 することが出来ます。
x, y = "A1"
print(x, y)
実行結果
A 1
上記のように記述することで、Excel 座標 の x 座標 と y 座標 を表す文字列を、個別 に xy 座標 に 変換する ことができるようになります。先ほどの テーブル を使う 方法 を利用する場合、下記のプログラムのように、2 ~ 6 行目で Excel 座標 の x 座標 を 変換するテーブル と、7 ~ 11 行目で y 座標 を 変換するテーブル という、2 つのテーブル を用意します。13 行目で、x
と y
に、coord
の 1 文字目 と 2 文字目 を 代入 し、14 行目で、2 つのテーブル を使って、xy 座標 を 計算 して返しています。
1 def excel_to_xy(coord):
2 excel_to_x_table = {
3 "A": 0,
4 "B": 1,
5 "C": 2,
6 }
7 excel_to_y_table = {
8 "1": 0,
9 "2": 1,
10 "3": 2,
11 }
12
13 x, y = coord
14 return excel_to_x_table[x], excel_to_y_table[y]
行番号のないプログラム
def excel_to_xy(coord):
excel_to_x_table = {
"A": 0,
"B": 1,
"C": 2,
}
excel_to_y_table = {
"1": 0,
"2": 1,
"3": 2,
}
x, y = coord
return excel_to_x_table[x], excel_to_y_table[y]
テーブルの項目数の比較
上記のように、x 座標と y 座標の 2 つのテーブルに 分離 することで、テーブルの中の 項目数 を 9 個から、$3 + 3 = 6$ 個に 減らす ことが出来ます。
ゲーム盤の 縦横のサイズ を $n$ とした場合、分離しない場合 のテーブル内のデータの数は $n * n$ になりますが、分離した場合 は $n * 2$ となり、下の表のように、$n$ が 大きく なればなるほど、その差 は 大きく なります。
n | 分離しない場合 | 分離する場合 | ゲームの例 |
---|---|---|---|
3 | 9 | 6 | 〇×ゲーム |
8 | 64 | 16 | オセロ |
9 | 81 | 18 | 将棋 |
19 | 381 | 38 | 囲碁 |
100 | 10000 | 200 |
下記のプログラムを実行することで、修正した excel_to_xy
が正しく動作することが確認できます。実行結果は先程と同様になるので省略します。
test_judge(testcases)
利点と欠点
このアルゴリズムの主な利点と欠点は以下の通りです。
利点
- 1 つのテーブルを使ったアルゴリズムよりは 記述が短い
- テーブルの項目数の合計が少なくなる
欠点
- 慣れるまで は、1 つのテーブルを使ったアルゴリズムより 意図がわかりづらい
excel_to_xy
の定義その 4(y 座標を計算で変換する)
その 3 の excel_to_xy
定義で、y 座標 を テーブル を使って 変換 するのが 馬鹿らしいと 思った人はいませんか?具体的には、Excel 座標 の y 座標を表す 2 文字目 に対応する xy 座標 の y 座標 は、いずれも 値が 1 つ小さくなる ので、excel_to_xy
の return 文 を、下記のように 修正 すれば良いと思った人がいるのではないでしょうか?。
return excel_to_x_table[x], y - 1
実際に、上記のように excel_to_xy
の定義を修正して、下記のプログラムを実行すると、実行結果のような エラーが発生 します。なお、今後は、エラーメッセージが長い 場合は、下記の実行結果のように、一部を省略 することにします。
test_judge(testcases)
実行結果
略
c:\Users\ys\ai\marubatsu\027\marubatsu.ipynb セル 16 line 9
2 excel_to_x_table = {
3 "A": 0,
4 "B": 1,
5 "C": 2,
6 }
8 x, y = coord
----> 9 return excel_to_x_table[x], y - 1
TypeError: unsupported operand type(s) for -: 'str' and 'int'
上記のエラーメッセージは、以下のような意味を持ちます。
-
TypeError
データ型(type)に関するエラー -
unsupported operand type(s) for -: 'str' and 'int'
文字列型(str)と(and)整数型(int)に対する(for)-
演算子(operand)による演算を行うことはできない(unsupported)
このエラーは、y
に代入されている 文字列型 のデータから、1
という 整数型 のデータを -
演算子 で 減算 しようとしている点が原因です。別の言葉で説明すると、Python では、文字列 と 数値 を -
演算子 で減算 できない ということです。
明示的な型変換と、暗黙の型変換
このエラーを修正するためには、文字列型 の y
を、数値型 のデータに 変換 する必要があります。整数型 へのデータの 変換 は、以前の記事 で説明したように、int
という組み込み関数を使って行うことが出来るので、下記のプログラムのように excel_to_xy
の return 文で y
を int(y)
のように修正することでエラーが発生しなくなります。なお、必要が無くなった ので、excel_to_y_table
は 削除 しました。
def excel_to_xy(coord):
excel_to_x_table = {
"A": 0,
"B": 1,
"C": 2,
}
x, y = coord
return excel_to_x_table[x], int(y) - 1
修正箇所
def excel_to_xy(coord):
excel_to_x_table = {
"A": 0,
"B": 1,
"C": 2,
}
- excel_to_y_table = {
- "1": 0,
- "2": 1,
- "3": 2,
- }
x, y = coord
- return excel_to_x_table[x], y - 1
+ return excel_to_x_table[x], int(y) - 1
int
などを使って、意図的 にデータの型変換を行うことを 明示的な型変換 と呼びます。
一方、JavaScript など、プログラム言語によっては、数値型のデータと文字列型のデータを -
演算子で 減算できる 場合があります。そのような場合は、どちらかのデータ型が、もう片方のデータ型に 自動的に変換 されて計算が行われます。
異なるデータ型 に対して 演算を行う 際に、自動的 に行われる データ型の変換 のことを、暗黙の型変換 と呼びます。
必要になった時点で詳しく説明しますが、Python でも、整数型 と 浮動小数点数型 のデータを演算する場合などで、暗黙の型変換が行われる 場合があります。
下記のプログラムを実行することで、修正した excel_to_xy
が正しく動作することが確認できます。実行結果は先程と同様になるので省略します。
test_judge(testcases)
利点と欠点
このアルゴリズムの主な利点と欠点は以下の通りです。
利点
- y 座標 に関する テーブル が 必要なくなり、プログラムを 短く記述 できる
欠点
- y 座標の値を計算する際の、
1
を減算 する処理の 意図がわかりづらい
x, y = coord
によって、Excel 座標 の x 座標と、y 座標を表す文字列を、x
と y
に 代入できる のは、〇×ゲーム では、Excel 座標の x 座標 と、y 座標 を表す文字列が、必ず 1 文字 であるからです。例えば、囲碁のような、ゲーム盤のサイズが 19 もあるようなゲーム盤の場合は、Excel 座標が "A19"
のように 3 文字以上 になってしまう場合があます。実際に x, y = "A19"
を実行すると、文字数が 3 であるのに対して、代入 する 変数の数 が 2 つしかない ため、下記のプログラムの実行結果のように エラーが発生 します。
x, y = "A19"
実行結果
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\027\marubatsu.ipynb セル 19 line 1
----> 1 x, y = "A19"
ValueError: too many values to unpack (expected 2)
上記のエラーメッセージは、以下のような意味を持ちます。
-
ValueError
値(文法のこと)(syntax)に関するエラー -
too many values to unpack (expected 2)
展開(unpack)するデータの値の数(values)が、期待された 2(expected 2)より多い(too many)
そのような場合は、正規表現などの、別の方法を使う 必要がある点に注意が必要です。長くなるので今回の記事ではプログラムの意味は説明しませんが、正規表現を使う場合は、下記のプログラムのように記述します。
import re # 正規表現を利用するためのモジュールをインポートする
def excel_to_xy(coord):
excel_to_x_table = {
"A": 0,
"B": 1,
"C": 2,
}
# 正規表現を使って、Excel 座標のアルファベットの部分と整数の部分を分離する
m = re.fullmatch("([A-Z]+)(\d+)", coord)
# 分離したデータを x と y に代入する
x = m.group(1)
y = m.group(2)
return excel_to_x_table[x], int(y) - 1
下記のプログラムは、上記の excel_to_xy
を使って、"A19"
を xy 座標に変換しています。実行結果から正しく変換できることが確認できます。
print(excel_to_xy("A19"))
実行結果
(0, 18)
正規表現 は文字列の中から、特定のパターン を持つ文字列を 検索 したり、置換 したりすることが出来る 非常に強力 で 便利 な 機能 ですが、使いこなす にはある程度の 勉強が必要 です。本記事では必要になった時点で紹介するかもしれませんが、自分で勉強したい方は下記のリンク先などを参照して下さい。
excel_to_xy
の定義その 5(文字列のテーブルを利用する)
実は、Excel 座標の x 座標 のほうも、テーブルを使わず に xy 座標に 変換 することが出来ます。そのうちの一つの方法は、文字列型 のデータに対して利用できる、index
メソッド を利用する方法です。index
メソッドは、実引数 に記述した 文字列 が、インスタンスの 何番の文字列 であるかを計算して 返す メソッドです。ただし、その際に 先頭の文字 を 0 番 と 数える 点に注意して下さい。例えば、下記のプログラムは、"A"
という文字列が、"ABC"
という文字列の何番に存在するかを計算しますが、"A"
は "ABC"
の 先頭の 0 番 に存在するので、実行結果に 0
が表示されます。
"ABC".index("A")
実行結果
0
文字列の中に、index
メソッドの実引数に記述された文字が 複数存在 した場合は、先頭から 探して 最初に見つかった文字 の番号が返されます。
Excel 座標 の x 座標の "A"
、"B"
、"C"
は、xy 座標 の x 座標の 0
、1
、2
に 対応する ので、"ABC"
に対して index
メソッド を使って "ABC".index(coord)
のように記述することで、「Excel 座標 の x 座標」を「xy 座標 の x 座標」に 変換 することが出来ます。下記のプログラムは、そのように excel_to_xy
メソッドを修正したプログラムです。なお、必要が無くなったので、excel_to_x_table
は削除しました。
def excel_to_xy(coord):
x, y = coord
return "ABC".index(x), int(y) - 1
修正箇所
def excel_to_xy(coord):
- excel_to_x_table = {
- "A": 0,
- "B": 1,
- "C": 2,
- }
x, y = coord
- return excel_to_x_table[x], int(y) - 1
+ return "ABC".index(x), int(y) - 1
そのように見えないかもしれませんが、この方法も "ABC"
という、一種の テーブル を使って、Excel 座標の x 座標から、xy 座標の x 座標を 対応づけ ています。
利点と欠点
このアルゴリズムの主な利点と欠点は以下の通りです。
利点
- dict の テーブル が 必要なくなり、プログラムを 短く記述 できる
欠点
- x 座標の値を計算する処理の 意図がわかりづらい
-
"ABC"
のような、文字列のテーブルが必要 なので、x 座標の数が多い、将棋のようなゲーム盤の場合は、"ABCDEFGHI".index(coord)
のような 長い記述 を行う必要がある
簡潔な記述の欠点
この修正で、excel_to_xy
を たった 2 行 でかなり 簡潔に記述 することが出来るようになりましたが、簡潔に記述 するとプログラムが わかりにくくなる 場合が多いという 欠点 があります。例えば、if 文 で記述した excel_to_xy
は、プログラムは長いですが、初心者でも 何を行うプログラムであるかを 理解することは難しくないでしょう。一方、上記の 2 行で記述した excel_to_xy
が何を行っているかを、一目で理解する ことはそれほど 簡単ではない と思います。
例えば、excel_to_xy
を下記のプログラムのように 1 行 で まとめる ことも可能ですが、さらにわかりづらい プログラムになっていると思いませんか?下記のプログラムはわかりづらいので、本記事では採用しないことにします。
def excel_to_xy(coord):
return "ABC".index(coord[0]), int(coord[1]) - 1
excel_to_xy
の定義その 6(ord
を利用する)
初心者 がこの方法を 思いつく ことは 困難 だと思いますが、文字コード表の知識 と、組み込み関数 ord
を知っていれば、"ABC"
のような文字列のテーブルを 使わず に Excel 座標 の x 座標を、xy 座標 の x 座標に 変換 することが出来ます。
コンピューターでは、一つ一つの 文字 を 整数 で表現しています。文字と整数の 対応づけ は、文字コード表 という表を使って行われ、文字に対応する整数の事を 文字コード と呼びます。文字の中で 半角の文字 に対しては、ASCII 文字コード表 が使われます。
ASCII 文字コード表では、下記のリンク先の表からわかるように、半角の大文字のアルファベットの A
を 65
という 整数 に 対応づけ ています。また、B
は 66
、C
は 67
のように A
から Z
までのアルファベットは、65
から の整数に 順番に 対応しています。
組み込み関数 ord
は、実引数 に記述した 文字列 に対応する 整数の文字コード を 返す 関数で、下記のプログラムのように、"A"
を実引数として ord
を呼び出すと、65
が返ります。ord
の詳細は下記のリンク先を参照して下さい。
print(ord("A"))
実行結果
65
ASCII 文字コード表 の 性質 と、ord
を 組み合わせる ことで、A
を 0
、B
を 1
、C
を 2
に 計算 で 変換 することが出来ます。具体的には、下記のプログラムの ord(x) - 65
のように、文字 に対応づけられた 整数 の 文字コード から、A
に対応づけられた 65
を 減算 します。具体的な計算については、下記の表を見て下さい。
x |
ord(x) |
`ord(x) - 65' |
---|---|---|
"A" |
65 |
0 |
"B" |
66 |
1 |
"C" |
67 |
2 |
def excel_to_xy(coord):
x, y = coord
return ord(x) - 65, int(y) - 1
テーブルを使わない事の影響
テーブルを使わないようにプログラムを修正したことによって、"E5"
のような、〇×ゲームのゲーム盤に 存在しない Excel 座標を excel_to_xy
で xy 座標に変換しようとした際に、下記のプログラムのように エラーが発生しなくなります。
説明していませんでしたが、定義その 4 から y 座標 に関して テーブルを使わなくなった ので、"A5"
のような、〇×ゲームに 存在しない y 座標 が記述された Excel 座標を excel_to_xy
変換しても、エラーが 発生しなくなっていました。
print(excel_to_xy("E5"))
実行結果
(4, 4)
これは、ort
や int
を使って座標を変換することによって、Excel 座標の x 座標に 任意のアルファベット を記述した場合と、y 座標に 任意の 1 桁の整数 を記述した場合でも、座標の変換 の計算を行うことが 出来てしまう ためです。
このように、計算 を使って変換を行う方法は、テーブル を使って変換を行う方法と比較して、汎用性が高くなる という性質がありますが、〇×ゲームのゲーム盤に存在しない座標のような、想定外のデータ を変換しようとした場合の事を 考慮する 必要が生じます。
〇×ゲームに存在しない座標を変換しようとした際に、エラーが発生するように excel_to_xy
を修正することもできますが、excel_to_xy
でエラーが発生しなくとも、変換後の xy 座標 を使って、move
メソッドを 実行 すると、下記のプログラムのように エラーが発生 するので、今回の記事では excel_to_xy
の修正は行いません。その方法については、今後の記事で、同様の処理を行う必要が出てきた場合に紹介します。
なお、下記のプログラムでは x
と y
に 4
が代入された結果、place_mark
のブロックの中で、self.board[x][y]
、すなわち self.board[4][4]
という、範囲外のインデックス を使って list の要素を参照することになる、IndexError が発生します。
mb = Marubatsu()
x, y = excel_to_xy("E5")
mb.move(x, y)
実行結果
略
File c:\Users\ys\ai\marubatsu\027\marubatsu.py:69, in Marubatsu.place_mark(self, x, y, mark)
50 def place_mark(self, x: int, y: int, mark: str) -> bool:
51 """ ゲーム盤の指定したマスに指定したマークを配置する.
52
53 (x, y) のマスに mark で指定したマークを配置する.
(...)
66 マークを配置できた場合は True、配置できなかった場合は False
67 """
---> 69 if self.board[x][y] == Marubatsu.EMPTY:
70 self.board[x][y] = mark
71 return True
IndexError: list index out of range
上記のエラーメッセージは、以下のような意味を持ちます。
-
IndexError
インデックス(index)に関するエラー -
list index out of range
list のインデックス(index)が範囲外(out of range)である
利点と欠点
このアルゴリズムの主な利点と欠点は以下の通りです。
利点
- テーブル が 完全に必要なくなり、プログラムを 最も短く記述 できる
- アルファベットと整数を並べた座標であれば変換できるので、汎用性が高くなる
欠点
- プログラムを記述するために、文字コード と
ord
の 知識が必要 になる - プログラムの 意図 が最も わかりづらい
- 想定しない Excel 座標に対する 対処が必要 になる場合がある
どのアルゴリズムを選ぶべきか
今回の記事で紹介した どの excel_to_xy
を 使っても 正しい 計算を行う ことが 可能 なので、どれを選んでも構いません。ただし、慣れないうちに、自分が理解できない ようなプログラムを記述すると、後でバグが発生 した時に 手に負えなくなる 可能性が高いので、自分の力だけで 一からプログラムを記述する場合は、慣れないうちは、自分が理解できる ようなレベルのプログラムを 記述する ことをお勧めします。
ただし、いつまでも 最初の if 文 を使った excel_to_xy
のようなアルゴリズムを 記述する のは 効率が悪い ので、理解できるようになったら、短くプログラムをまとめて記述できるようなアルゴリズムに チャレンジしてみて下さい。
初心者の方で、文字コードの知識がある人はあまり多くないと思いますので、本記事では、excel_to_xy
の 定義その 5 を採用することにします。
勘違いする人がいるかもしれれないので補足しますが、if 文を使ったアルゴリズムが常に効率が悪いわけでは ありません。if 文を使ったアルゴリズムが 最も効率が良い 場合が実際に 良くあります。適切 なアルゴリズムを 選択する能力 を身に付けるためには、様々なアルゴリズム でプログラムを 実際に記述 し、それぞれの性質の違いを 実際に体験 して 学ぶ必要がある ということです。
小文字のアルファベットに対応する方法
本記事のプログラムでは採用しませんが、"a5"
のような、x 座標を表すアルファベットに 小文字を記述 できるようなプログラムを記述する方法について補足します。
文字列型のデータには、upper
という、小文字のアルファベット を 大文字に変換 した文字列を 作成して返す メソッドがあります。なお、upper
は、下記のプログラムの実行結果のように、小文字のアルファベット 以外の文字 は 変換しません。
print("a1".upper())
print("B2".upper())
実行結果
A1
B2
upper
を使って、下記のプログラムのように excel_to_xy
を修正することで、Excel 座標に小文字のアルファベットを記述できるようになります。
def excel_to_xy(coord):
x, y = coord.upper()
return "ABC".index(x), int(y) - 1
修正箇所
def excel_to_xy(coord):
- x, y = coord
+ x, y = coord.upper()
return "ABC".index(x), int(y) - 1
index
メソッドに関する補足
index
メソッド は、文字列型だけでなく、list、tuple に対しても 利用 することが 出来ます。list、tuple に対して index
メソッドを呼び出した場合、下記のプログラムのように、実引数 に記述したデータが代入されている 要素のインデックス(index)を返すという処理が行われます。これが、このメソッドの 名前が index
である 理由 です。
print([1, 2, 3].index(2)) # 1 番の要素に 2 が代入されているので、1 が返る
print((1, 2, 3).index(3)) # 2 番の要素に 3 が代入されているので、2 が返る
実行結果
1
2
このことは、文字列型 のデータでも 同様 です。その理由は、以前の記事 で説明したように、文字列型のデータは 先頭の文字 から 順番 に、0 から始まる整数 の インデックス が割り当てられており、list や tuple と同様に、[]
を使って、下記のプログラムのように、インデックス番 の 文字を参照 することが出来るからです。
print("ABC"[1]) # 1 番のインデックスのデータである "B" が表示される
実行結果
B
下記のプログラムのように、文字列の中に 存在しない文字列 を、index
メソッドの実引数に記述して呼び出すと、エラーが発生 します。具体例は示しませんが、list や tuple に対して要素に 存在しない値 をindex
メソッドの実引数に記述して呼び出した場合も 同様に エラーが発生します。
"ABC".index("D")
実行結果
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\027\marubatsu.ipynb セル 36 line 1
----> 1 "ABC".index("D")
ValueError: substring not found
上記のエラーメッセージは、以下のような意味を持ちます。
-
ValueError
値(value)に関するエラー -
substring not found
substring([]
の中に記述するインデックスの事)が見つからなかった(not found)
今回の記事のまとめ
Excel 座標を利用することで、下記のように、xy 座標を利用した場合と比較して、テストケース を 簡潔に、分かりやすく 記述できる。
testcases = [
# 〇 の勝利のテストケース
[ # 着手順とゲーム盤
[ "A1", "A2", "B1", "B2", "C1" ], # 135 ooo
Marubatsu.CIRCLE, # 24. xx.
], # ... ...
# × の勝利のテストケース
[ # 着手順とゲーム盤
[ "A2", "A1", "B2", "B1", "A3", "C1" ], # 246 xxx
Marubatsu.CROSS, # 13. oo.
], # 5.. ...
# 引き分けのテストケース
[ # 着手順とゲーム盤
[ "A1", "A2", "B1", "B2", "C2", "C1", "A3", "B3", "C3" ], # 136 oox
Marubatsu.DRAW, # 245 xxo
], # 789 oxo
]
Excel 座標を利用する場合は、後述する Excel 座標 を xy 座標 に 変換 する 関数 excel_to_xy
を定義 し、下記のプログラムのように test_judge
の 6 行目に x, y = excel_to_xy()
を追加するだけ で、他の部分を 一切変更することなく テストを行うことが出来る。
1 def test_judge(testcases):
2 for testcase in testcases:
3 testdata, winner = testcase
4 mb = Marubatsu()
5 for coord in testdata:
6 x, y = excel_to_xy(coord) # この行を追加するだけで済む
7 mb.move(x, y)
8 print(mb)
9
10 if mb.judge() == winner:
11 print("ok")
12 else:
13 print("error!")
excel_to_xy
は様々な方法で定義することが出来るが、工夫する ことで、下記のプログラムのように 簡潔に定義 することが出来る。
def excel_to_xy(coord):
x, y = coord
return "ABC".index(x), int(y) - 1
長くなったので今回の記事はここまでにします。次回は、複数の Excel座標を短く記述できるデータ構造などの、他のデータ構造とそれを処理するアルゴリズムを紹介します。
本記事で入力したプログラム
以下のリンクから、本記事で入力して実行した JupyterLab のファイルを見ることができます。
今回の記事では、marubatsu.py は更新していないので、marubatsu_new.py はありません。
以下のリンクは、今回の記事で作成した test.py です。excel_to_xy
は定義その 5 のものを記述しますが、参考までに定義 1 ~ 4、6 は、`excel_to_xy_1`` のような名前で記述しておくことにします。
次回の記事