0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Pythonで〇×ゲームのAIを一から作成する その28 list と文字列の変換

Last updated at Posted at 2023-11-16

目次と前回の記事

実装の進捗状況と前回までのおさらい

〇×ゲームの仕様と進捗状況

  1. 正方形で区切られた 3 x 3 の 2 次元のゲーム盤上でゲームを行う
  2. ゲーム開始時には、ゲーム盤のすべてのマスは空になっている
  3. 2 人のプレイヤーが遊ぶゲームであり、一人は 〇 を、もう一人は × のマークを受け持つ
  4. 2 人のプレイヤーは、交互に空いている好きなマスに自分のマークを 1 つ置く
  5. 先手は 〇 のプレイヤーである
  6. プレイヤーがマークを置いた結果、縦、横、斜めのいずれかの一直線の 3 マスに同じマークが並んだ場合、そのマークのプレイヤーの勝利とし、ゲームが終了する
  7. すべてのマスが埋まった時にゲームの決着がついていない場合は引き分けとする

仕様の進捗状況は、以下のように表記します。

  • 実装が完了した部分を 背景が灰色の長方形 で記述する
  • 実装の一部が完了した部分を、太字 で記述する

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

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

なお、前回の記事で採用しなかった関数は、test.py から削除してあります。

前回までのおさらい

前回の記事では、座標を表すデータ構造として、Excel 座標を紹介し、Excel 座標を xy 座標に変換する様々なアルゴリズムを紹介しました。

シーケンス型の [] の中の表記

最初に、今回の記事の説明で必要になる、python の シーケンス型[] の中の表記 について説明します。ここで説明する表記は 便利 なので、Python のプログラムでは 実際に良く使われます。今後の記事でも 頻繁に利用する ので、しっかりと 理解して下さい

シーケンス型とは何か

複数のデータを扱うことができる 文字列型listtuple のことを、シーケンス型 と呼び、以下のような 共通の性質 を持ちます。なお、最後の 2 つの、[] の中の記述 以外 の性質は、これまでの記事で 説明済み です。

  • 複数のデータまとめて扱う ことができる
  • 扱う 個別のデータ の事を、要素 と呼ぶ
  • 要素 は、0 以上の整数 で表される、インデックス によって 対応づけ られる
  • + 演算子 によって、前後のデータを 結合 した 新しいデータを作成 することができる
  • * 演算子 によって、同一 のデータを 複数回結合 した 新しいデータを作成 することができる
  • 反復可能オフジェクト なので、for 文の中で記述することで、先頭 の要素から 順番に取り出して 処理を行うことが出来る
  • [] の中インデックス を記述することで、対応する 要素を参照 することができる
  • [] の中 に、後述する スライス表記 を記述することで、対応する要素を持つ 新しいデータを作成 することができる

文字列型 のデータの場合は、1 つ 1 つの 文字 が、要素 に相当します。

また、list は ミュータブル、文字列型と tuple は イミュータブル なデータです。

シーケンス型のデータは、上記以外 にもいくつかの 共通する性質 を持ちますが、それらについては必要になった時点で紹介します。

シーケンス型の詳細については、下記のリンク先を参照して下さい。

dict は、整数以外キー を使って中の 1 つのデータを参照することができるので、シーケンス型 ではなくマッピング型分類 されます。

以下の説明は、シーケンス型の 共通の性質 の中の、[] の中の表記 についての説明です。

具体例を示したためにかなり長い内容になっていますが、覚えること はそれほど 多くは無い のでその点は安心して下さい。

0 以上の整数のインデックス

これまでのプログラムでは、シーケンス型 のデータの 直後の [] の中 に、インデックス を表す 0 以上の整数 を記述することで、指定した インデックス番要素を参照 しました。この記述方法は、他の多くのプログラム言語でも利用できる方法ですが、Python では、[] の中に、この後で説明する、それ以外の記述 を行うことができます1

この後の説明に対応する公式のドキュメントについては、下記のリンク先のすぐ下にある表と、注釈の (3)、(4)、(5) を参照して下さい。

負の整数のインデックス

[] の中に、負の整数 を記述することで、要素を 後ろから 数えて 指定 することができます。具体的には、-1後ろから 数えて 1 番目(最後)の要素、-2後ろから 数えて 2 番目 の要素のように指定します。

先頭 の要素のインデックスは 0 ですが、最後 の要素のインデックスは、-0 ではなく-1 である点に注意して下さい。そのことを 明確にする ために、正のインデックスの場合は 0 番、負のインデックスで後ろから数えた場合は、1 番目 のように表記します。

負の整数インデックス は、以下のような意味を持つ。

n を自然数(1 以上の整数の事)とした場合、-n は、要素を 後ろから 数えて -n 番目 の要素を表す。ただし、最後の要素-1 と表すものとする。

下記のプログラムの、それぞれの行の意味は以下の通りです。

  • 2 行目"ABCDEF"後ろから 1 番目(最後)の文字である "F" が表示される
  • 3 行目"ABCDEF"後ろから 3 番目 の文字である "D" が表示される
  • 4 行目"ABCDEF"後ろから 10 番目 の文字を表示しようとしているが、文字列の長さは 6 で、そのようなインデックスに 対応する 文字は 存在しない ので、「インデックスが範囲外である(index out of range)」という意味の エラーが発生 する
txt = "ABCDEF"
print(txt[-1])
print(txt[-3])
print(txt[-10])

実行結果

F
D
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\028\marubatsu.ipynb セル 1 line 4
      2 print(txt[-1])
      3 print(txt[-3])
----> 4 print(txt[-10])

IndexError: string index out of range

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

  • IndexError
    インデックス(index)に関するエラー
  • string index out of range
    文字列型(string)のデータのインデックス(index)が範囲外(out of range)である

スライス表記 その 1(start と end)

スライス表記 は、[] の中のインデックスの記述方法で、半角の :(コロン)で以下のように、2 つインデックス区切る ことで、start から end直前 までのインデックスの 要素 を持つ、同じデータ型新しいデータを作成 します。新しいデータには、end のインデックスの 要素入らない 点に注意して下さい。

また、この後で詳しく説明しますが、[] の中に 1 つのインデックスを記述して 要素を参照 する場合と 異なりスライス表記 では 新しいデータを作成 する点に注意して下さい。

スライス表記には、先ほど説明した 負の整数のインデックス を記述することもできます。

シーケンス型のデータ[start:end]

下記のプログラムの、それぞれの行の意味は以下の通りです。いずれも end で指定したインデックスの 文字表示されない 点に注意して下さい。

  • 2 行目"ABCDEF" の 2 番("C") から 5 番("F")の 直前 までの文字列が表示される
  • 3 行目"ABCDEF"最後から 5 番目"B")から 最後から 1 番目"F")の 直前 までの文字が表示される
txt = "ABCDEF"
print(txt[2:5])
print(txt[-5:-1])

実行結果

CDE
BCDE

list の場合に、下記のプログラムの 2 行目のように 特定の要素を参照 した場合と、3 行目のように [] の中に 1 行目と 同じ要素範囲 とする スライス表記 を記述した場合に、同一の処理が行われると 勘違い する人がいるかもしれません。

a = [1, 2, 3]
print(a[0])
print(a[0:1])

実行結果

1
[1]

しかし、実際には上記の実行結果のように、異なる処理 が行われます。2 行目の処理は、list の 要素を参照 するので、1 が表示されますが、スライス表記は、先ほど説明したように、スライス表記で指定された範囲の要素を持つ、同じ種類のデータ作成 されます。a は list なので、a[0:1] によって 作成されるデータ[1] のような list になります。この点は 勘違いしやすい ので注意が必要です。

上記は、tuple の場合も 同様 ですが、文字列型 のデータの場合は、下記のプログラムのように、a[0] によって参照されるデータは 1 文字の 文字列型のデータ になるので、2 行目と 3 行目では、同じ表示 が行われることになります。

a = "abc"
print(a[0])
print(a[0:1])

実行結果

a
a

スライス表記 その 2(範囲外の start と end)

スライス表記を 使わない 場合で、"ABC"[100] のように、対応 する要素が 存在しない インデックスを記述した場合は、エラーが発生 しますが、スライス表記 の場合は エラー発生せず に、下記のような処理が行われます。

  • start に、対応 する要素が 存在しない ような 負の整数記述 した場合は、先頭の文字 を表す 0 が記述 されたものと みなされ て新しいデータが作成される
  • end対応 する要素が 存在しない ような 正の整数 を記述した場合は、start から 最後の要素まで の要素を持つ新しいデータが作成される

下記のプログラムの、それぞれの行の意味は以下の通りです。

  • 2 行目最後から 100 番目 の文字は 存在しない ので、"ABCDEF"先頭"A") から 5 番("F")の直前までの文字列が表示される
  • 3 行目100 番 の文字は 存在しない ので、"ABCDEF" の 2 番("C")から 最後まで の文字が表示される
txt = "ABCDEF"
print(txt[-100:5])
print(txt[2:100])

実行結果

ABCDE
CDEF

スライス表記 その 3(start が end 以降の場合)

start を表す要素が、end を表す要素 以降 の場合は、下記のプログラムのように、要素を 1 つも持たない同じ種類新しいデータ(下記の場合は空文字)が作成されます。

print(txt[3:1])

実行結果


後述の step を記述 した場合は上記のようにならない場合があります。

スライス表記 その 4(start と end の省略)

スライス表記の、: の前後の startend は、それぞれ 省略 することができます。

  • start を省略 した場合は、先頭 の要素を表す 0 が記述 されたものとみなされる
  • end を省略 した場合は、start から 最後の要素まで を持つ新しいデータを作成する
  • 両方を省略 した場合は、すべての要素を持つ 新しいデータを作成する

下記のプログラムの、それぞれの行の意味は以下の通りです。

  • 2 行目先頭から 5 番("F")の直前までの文字列が表示される
  • 3 行目:2 番("C")から 最後まで の文字列が表示される
  • 4 行目:先頭から最後までの、すべて の文字列が表示される
txt = "ABCDEF"
print(txt[:5])
print(txt[2:])
print(txt[:])

実行結果

ABCDE
CDEF
ABCDEF

スライス表記によるデータの複製

スライス表記は、一見するデータの共有 を行っているように 見える かもしれませんが、実際には 指定した範囲を複製 した、新しいデータが作成 される点に注意して下さい。

例えば、下記のプログラムを実行した場合、2 行目で、list2 に代入されるデータは、3 行目の表示結果 から、以前の記事 で説明した list2 = list1 による 代入文 と同様に、データを共有するという処理が行われているように 見えるかもしれません

しかし、実際には list1list2 はデータを 共有していない ので、4 行目で list2 の 0 番の要素に 5 を代入しても、実行結果のように、list1 の値は 変化しません

1  list1 = [ 1, 2, 3 ]
2  list2 = list1[:]
3  print(list2)
4  list2[0] = 5
5  print(list1)
6  print(list2)
行番号のないプログラム
list1 = [ 1, 2, 3 ]
list2 = list1[:]
print(list2)
list2[0] = 5
print(list1)
print(list2)

実行結果

[1, 2, 3]
[1, 2, 3]
[5, 2, 3]

このような性質から、スライス表記 を使って シーケンス型 のデータの 一部 または 全部複製 することが出来ます。

スライス表記 によって行われる データの複製 は、浅いコピー と呼ばれる種類の複製で、完全な意味 でデータを 複製 する、深いコピー を行っている わけではない 点に注意が必要です。

ただし、今回の記事で紹介するプログラム では、浅いコピーと深いコピーの違いについて 理解する必要まだありません。浅いコピーと深いコピーの違いを区別することが 重要になる 場面があるので、必要になった時点で説明します。

スライス表記 その 5(step の指定)

頻繁に使われるわけではありませんが、スライス表記で、以下のように : を 2 つ記述 することで、start から end の直前までの要素を step おき に取り出した要素を持つ、同じ種類の新しいデータを作成します。

シーケンス型のデータ[start:end:step]

下記は、スライス表記によって、"ABCDEF" の 2 番("C") から 5 番("F")の直前までの文字列を 2 文字おき 取り出して表示するプログラムです。

print("ABCDEF"[2:5:2])

実行結果

CE

スライス表記 その 6(step の省略と 0 を指定した場合)

スライス表記の step の表記を 省略 した場合は、step1 を指定 したとみなされます。
従って、下記のプログラムの実行結果のように、[start:end:][start:end:1][start:end]全く同じ処理 が行われます。

txt = "ABCDEF"
print(txt[2:5:])
print(txt[2:5:1])
print(txt[2:5])

実行結果

CDE
CDE
CDE

スライス表記の step0 を指定すると、0 おきに要素を取り出すという、無限に 要素を取り出すという 意味になる ので、下記のプログラムのように、エラーが発生 します。

print("ABCDEF"[2:5:0])

実行結果

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\028\marubatsu.ipynb セル 11 line 1
----> 1 print("ABCDEF"[2:5:0])

ValueError: slice step cannot be zero

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

  • ValueError
    値(value)に関するエラー
  • slice step cannot be zero
    スライス表記(slice)の step は 0(zero)になってはいけない(can not be)

スライス表記 その 7(負の step を指定した場合)

スライス表記の step に負の整数 を指定した場合は、start から end直後 まで、-step おき に取り出した要素を持つ、新しいデータを作成するという意味になります。

また、startend に関して、以下のような処理が行われます。

  • start を省略 した場合は、最後の要素 を表す -1 が記述 されたとみなされる
  • end を省略 した場合は、start から 先頭の要素 までの 範囲を表す とみなされる
  • start の要素の方が、end の要素 以前 であった場合は、要素1 つも持たない、同じ種類のデータが作成される

別の言葉で説明すると、逆順で並んだ 要素を持つ新しいデータが作成されます。

下記のプログラムの、それぞれの行の意味は以下の通りです。この中で、すべての要素を逆順 にした新しいデータを 作成する 3 行目のプログラムは比較的良く使われます。

  • 2 行目:5 番("F")から 逆方向 に 2 番("C") の 直後 までの文字列が 2 文字おき で表示される
  • 3 行目最後から先頭 までの文字列が 逆順 で表示される
  • 4 行目:2 番の方が、5 番 以前 なので、空の文字列 が表示される
txt = "ABCDEF"
print(txt[5:2:-2])
print(txt[::-1])
print(txt[2:5:-1])

実行結果

FD
FEDCBA

list で複数の Excel 座標を表現するデータ構造

必要な説明が終わったので、前回の記事の続きを開始します。前回の記事では、下記のプログラムのように、テストケースで 複数の Excel 座標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
]

list と tuple の利点と欠点

複数のデータをまとめて扱うためのデータ構造として、listtuple が良く使われますが、list や tuple には、以下のような 利点と欠点 があります。

利点

  • インデックスを使って、指定した要素のデータを 簡潔な記述で参照 することが出来る
  • 反復可能オブジェクト なので、for 文に記述することで、先頭の要素から順番 に取り出して、繰り返しの処理を行う ことが出来る
  • list の様々な便利な メソッドを利用 することが出来る

欠点

  • データを プログラムに直接記述 する際に、[], などの 記号を記述 する必要があるため、記述が長くなる

上記のように、list は プログラムの中で処理を行う 際には 便利な性質 を持ちますが、プログラムで list を 直接記述する 場合は、記述が長くなる という 欠点 があります。

これは、前回の記事 で説明した、xy 座標の欠点同じ なので、前回の記事と 同様の方法 で、複数の Excel 座標を表す list を、簡潔に記述する ことが出来ます。その方法について少し考えてみて下さい。

複数の Excel 座標を表現するデータ構造 その 1

Excel 座標 は、x 座標 を表す文字列と、y 座標 を表す文字列を、連結する という __データ構造__でした。同様の方法 で、Excel 座標 を表す文字列を 連結した文字列 を使って、複数の Excel 座標 を表すという データ構造 を考えることが出来ます。

例えば、〇 の勝利のテストケースの着手の座標を表す [ "A1", "A2", "B1", "B2", "C1" ] は、それぞれの Excel 座標を連結 することで、"A1A2B1B2C1" のように、簡潔に記述 することが出来ます。下記は、このデータ構造でテストケースを記述したプログラムです。

testcases = [
    # 〇 の勝利のテストケース
    [                                  # 着手順とゲーム盤
        "A1A2B1B2C1",                  # 135  ooo
        Marubatsu.CIRCLE,              # 24.  xx. 
    ],                                 # ...  ...
    # × の勝利のテストケース
    [                                  # 着手順とゲーム盤
        "A2A1B2B1A3C1",                # 246  xxx 
        Marubatsu.CROSS,               # 13.  oo.
    ],                                 # 5..  ...
    # 引き分けのテストケース
    [                                  # 着手順とゲーム盤
        "A1A2B1B2C2C1A3B3C3",          # 136  oox 
        Marubatsu.DRAW,                # 245  xxo
    ],                                 # 789  oxo
]
修正箇所
testcases = [
    # 〇 の勝利のテストケース
    [                                  # 着手順とゲーム盤
-       [ "A1", "A2", "B1", "B2", "C1" ],  # 135  ooo
+       "A1A2B1B2C1",                  # 135  ooo
        Marubatsu.CIRCLE,              # 24.  xx. 
    ],                                 # ...  ...
    # × の勝利のテストケース
    [                                  # 着手順とゲーム盤
-       [ "A2", "A1", "B2", "B1", "A3", "C1" ],  # 246  xxx 
+       "A2A1B2B1A3C1",                # 246  xxx 
        Marubatsu.CROSS,               # 13.  oo.
    ],                                 # 5..  ...
    # 引き分けのテストケース
    [                                  # 着手順とゲーム盤
-       [ "A1", "A2", "B1", "B2", "C2", "C1", "A3", "B3", "C3" ],  # 136  oox 
+       "A1A2B1B2C2C1A3B3C3",          # 136  oox 
        Marubatsu.DRAW,                # 245  xxo
    ],                                 # 789  oxo
]

利点と欠点

このデータ構造の利点と欠点は、以下の通りです。

利点

  • list の場合と比較して、複数の Excel 座標を 短く記述 できる

欠点

  • Excel 座標を表す文字列が 直接隣り合っている ので、見た目が少し わかりづらい
  • それぞれの Excel 座標取り出して利用 する処理を、list の場合のように、for 文を使って 簡潔に記述 することが 出来ない

上記の利点は、xy 座標 に対する Excel 座標利点似ておりプログラム内で処理 を行う場合は、list を使ったほうが 便利 ですが、プログラム内に データを直接記述 する場合は、Excel 座標を連結 した データ構造 を使ったほうが 便利 です。

そこで、前回の記事 と同様に、Excel 座標を連結したデータを、list に変換 するための 関数を定義 することで、最小限の修正 でこの データ構造を利用できる ようにします。

list への変換を行う関数の仕様

そのような関数を定義するためには、関数の名前、入力、処理、出力 の 仕様を決める 必要があり、本記事では、それぞれを以下のように決めることにします。

  • 関数の名前複数Excel 座標list変換 するので、excels_to_list にする
  • 入力excels という名前の 仮引数 に、Excel 座標を連結 した文字列を 代入 する
  • 処理excels を、Excel 座標 を要素とする list に変換 する
  • 出力:返り値 として、Excel 座標 を要素とする list を返す

"A1A2B1B2C1" を、[ "A1", "A2", "B1", "B2", "C1" ] に変換するための アルゴリズム は、以下のようになります。

  1. 空の list作成 する
  2. 先頭 の文字から 順番 に、2 文字ずつ取り出す
  3. 取り出した 2 文字list に追加 し、手順 2 へ戻る

このアルゴリズムの 手順 2 は、繰り返し を行う 処理 なので、excels_to_list は、下記のようなプログラムで実装することができます。下記のプログラムの 3、4 行目の、日本語で記述されている部分 をどのように記述すれば良いかについて少し考えてみて下さい。

def excels_to_list(excels):
    excel_list = []
    for 文による繰り返し:
        excel = excels から 2 文字を取り出す処理
        excel_list.append(excel)
    return excel_list

excels_to_list の定義 その 1

手順 2 の実装方法が思いつかない場合は、具体的なデータ を使った場合に 行われる処理 について 考えてみる と良いでしょう。 "A1A2B1B2C1" の場合に手順 2 で取り出される文字を 順番に列挙 すると以下のようになります。下記から何らかの 法則見つけて下さい

  • 0、1 番の文字
  • 2、3 番の文字
  • 4、5 番の文字
  • 6、7 番の文字
  • 8、9 番の文字

上記から「繰り返しのたび に、取り出す文字の、先頭からの位置 が、0 から 始まり、10 になる まで2 ずつ 増えていく」ということがわかります。また、この 10 という数字は、 "A1A2B1B2C1"文字列の長さ を表す数字です。

range の使い方

上記の繰り返し は、for 文と組み込み型である range を使って記述することができます。

これまでの for 文の 反復可能オブジェクト に記述してきた range は、0 から 実引数 に記述した数値に なるまで1 ずつ増えていく ような繰り返しを行いましたが、range を使うことで、より柔軟な 繰り返し処理を行うことができます。

range には、下記のような 3 つの実引数 を記述することができ、start からはじまり、stop になるまで の間、step ずつ増減 していくような繰り返しで利用することが出来ます。なお、stepデフォルト引数 になっており、省略 すると 1 が代入 されます。

range(10) が、0 から 9 までの整数のように 10 を生成しない のと同様に、range(star, stop, step) は、stop を生成しない 点に 注意して下さい

range(start, stop, step=1)

range(x) は、range(0, x, 1) と同じ処理を行います。

下記は、for 文と range を使って、10 から 20 まで、5 おきに 数字を表示するプログラムです。20 は実行結果に 表示されない 点に注意して下さい。

for i in range(10, 20, 5):
    print(i)

実行結果

10
15

気づいている方が多いかもしれませんが、range3 つの実引数 は、スライス表記 で記述する 3 つの値 に良く 似ています。ただし、rangestartend負の整数 を記述した場合は、負のインデックスのような、後ろから数えるという特別な意味ではなく、文字通りの負の値 という 意味になる 点が 異なります

range についての詳細は、下記のリンク先を参照して下さい。

range に関する補足 1(整数以外の実引数)

本記事の内容とは関係ありませんが、range に関するいくつかの補足説明を行います。

range の実引数に、整数以外 の数値を記述して実行すると、以下のプログラムのように エラーが発生 します。これは、range3 つの実引数すべて に当てはまります。

for i in range(1.5):
    print(i)

実行結果

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\028\marubatsu.ipynb セル 16 line 1
----> 1 for i in range(1.5):
      2     print(i)

TypeError: 'float' object cannot be interpreted as an integer

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

  • TypeError
    データ型(type)に関するエラー
  • 'float' object cannot be interpreted as an integer
    浮動小数点数(float)のオブジェクト(object)は、整数(integer)として解釈(interpreted)できない(can not be)

今回の記事で具体例は紹介しませんが、整数以外 の数値に対する range と同じような 反復可能オブジェクト は、numpy というモジュールの arange というメソッドを使って利用することが出来ます。

range に関する補足 2(step0 を指定した場合)

rangestep に対応する実引数に、負の整数 を指定することは 可能 ですが、下記のプログラムのように、0 を指定 すると エラーが発生 します。

for i in range(0, 10, 0):
    print(i)

実行結果

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\028\marubatsu.ipynb セル 17 line 1
----> 1 for i in range(0, 10, 0):
      2     print(i)

ValueError: range() arg 3 must not be zero

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

  • ValueError
    値(Value)に関するエラー
  • range() arg 3 must not be zero
    range の 3 つ目の仮引数(arg)は、0(zero)であってはならない(must not be)

step0 を指定するということは、for 文で range から取り出される値が 常に start のまま になるということです。従って、いつまで繰り返しを行っても、取り出される値が stop になることはない ため、無限ループが発生 するので、0 を指定した場合はエラーが発生するようになっています。

len の使い方

文字列の長さは、len という組み込み関数を利用することで計算することが出来ます。

下記は、"A1A2B1B2C1" の長さを計算するプログラムです。

print(len("A1A2B1B2C1"))

実行結果

10

len は、シーケンス型 のデータの 要素の数を計算 する関数なので、listtuple に対しても 利用 することが できます。詳細は、下記のリンク先を参照して下さい。

関数の定義

下記は先ほどの、"A1A2B1B2C1" を、[ "A1", "A2", "B1", "B2", "C1" ] に変換するためのアルゴリズムを再掲したものです。

  1. 空の list作成 する
  2. 先頭 の文字から 順番 に、2 文字ずつ取り出す
  3. 取り出した 2 文字list に追加 し、手順 2 へ戻る

rangelen を使って、excels_to_list を下記のプログラムのように定義できます。下記のプログラムで行っている処理は以下の通りです。

  • 2 行目excel_list空の list代入 することで 手順 1 の処理を行う
  • 4 行目for i in range(0, len(excels), 2): を記述することで、手順 2 の「先頭の文字から順番に 2 文字ずつ取り出す」ために必要となる、取り出す 2 文字の先頭のインデックス を for 文の繰り返しのたびに取り出して i に代入 する
  • 6 行目取り出す 2 文字のインデックス である ii + 1 を使って 2 文字を参照 し、+ 演算子で 連結 することで、excel に取り出した 2 文字を代入 する
  • 7 行目excel_listexcel を追加 することで 手順 3 の処理を行う
  • 8 行目excelslist に変換 した excel_list返り値 として返す
1  def excels_to_list(excels):
2      excel_list = []
3      # excels の先頭から 2 文字ずつ文字を取り出す繰り返し処理
4      for i in range(0, len(excels), 2):
5          # i 文字目と、i + 1 文字目を連結した 2 文字を取り出す
6          excel = excels[i] + excels[i + 1]
7          excel_list.append(excel)
8      return excel_list
行番号のないプログラム
def excels_to_list(excels):
    excel_list = []
    # excels の先頭から 2 文字ずつ文字を取り出す繰り返し処理
    for i in range(0, len(excels), 2):
        # i 文字目と、i + 1 文字目を連結した 2 文字を取り出す
        excel = excels[i] + excels[i + 1]
        excel_list.append(excel)
    return excel_list

下記は、"A1A2B1B2C1"excels_to_list を使って変換する処理を行うプログラムです。実行結果から、正しく変換できることが確認できます。

print(excels_to_list("A1A2B1B2C1"))

実行結果

['A1', 'A2', 'B1', 'B2', 'C1']

test_judge の修正

複数の Excel 座標を表す データ構造が変化 したので、test_judge をそれに合わせて 修正 する必要があります。具体的には、前回の記事 と同様に、データ構造が変化したデータ代入 された 変数利用する場所 で、その変数を 実引数に記述 して excels_to_list を呼び出す ように修正するだけで済みます。test_judge の場合は、testdata にそのデータが代入されており、下記の 7 行目の for 文の反復可能オブジェクト の所でその変数が 利用 されています。具体的な修正箇所については、下記の修正箇所をクリックして下さい。

なお、test_judge の中で、前回の記事で定義 した excels_to_xy を、今回の記事の中で 初めて使っている ので、1 行目で、test モジュール から excel_to_xy をインポート する必要があります。

 1  from test import excel_to_xy
 2
 3  def test_judge(testcases):
 4      for testcase in testcases:
 5          testdata, winner = testcase
 6          mb = Marubatsu()
 7          for coord in excels_to_list(testdata):
 8              x, y = excel_to_xy(coord)            
 9              mb.move(x, y)
10          print(mb)
11
12          if mb.judge() == winner:
13              print("ok")
14          else:
15              print("error!")
行番号のないプログラム
def test_judge(testcases):
    for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
        for coord in excels_to_list(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 coord in testdata:
+       for coord in excels_to_list(testdata):
            x, y = excel_to_xy(coord)            
            mb.move(x, y)
        print(mb)

        if mb.judge() == winner:
            print("ok")
        else:
            print("error!")

下記のプログラムを実行することで、修正したデータ構造で、test_judge が正しく動作することが確認できます。

test_judge(testcases)

実行結果

Turn x
ooo
xx.
...

ok
Turn o
xxx
oo.
o..

ok
Turn x
oox
xxo
oxo

ok

excels_to_list の定義 その 2

上記では、for 文の中で、1 文字ずつ 取り出した 文字 に対して + 演算子で連結 することで、Excel 座標を表す 2 文字を取り出していましたが、この方法では、3 文字以上 の文字を取り出すのが 大変です。今回の記事の最初で説明した、スライス表記 を使うことで、文字列の中から 複数の文字 を、+ 演算子を使うより 簡潔な記述 で取り出すことが出来ます。i から 2 文字分取り出す 場合は、下記の 6 行目のように [i:i+2] を記述します。

1  def excels_to_list(excels):
2      excel_list = []
3      # excels の先頭から 2 文字ずつ文字を取り出す繰り返し処理
4      for i in range(0, len(excels), 2):
5          # i 文字目から 2 文字分を取り出す
6          excel = excels[i:i+2]
7          excel_list.append(excel)
8      return excel_list
行番号のないプログラム
def excels_to_list(excels):
    excel_list = []
    # excels の先頭から 2 文字ずつ文字を取り出す繰り返し処理
    for i in range(0, len(excels), 2):
        # i 文字目から 2 文字分を取り出す
        excel = excels[i:i+2]
        excel_list.append(excel)
    return excel_list
修正箇所

def excels_to_list(excels):
    excel_list = []
    # excels の先頭から 2 文字ずつ文字を取り出す繰り返し処理
    for i in range(0, len(excels), 2):
-       # i 文字目と、i + 1 文字目を連結した 2 文字を取り出す
+       # i 文字目から 2 文字分を取り出す
-       excel = excels[i] + excels[i + 1]
+       excel = excels[i:i+2]
        excel_list.append(excel)
    return excel_list

下記のプログラムを実行することで、修正したデータ構造で、test_judge が正しく動作することが確認できます。実行結果は前と同じなので省略します。

test_judge(testcases)

文字列の中から、複数の文字 を取り出す際に、スライス表記の代わりに、+ 演算子による連結を使う メリットはほとんどない ので、本記事ではスライス表記を使うことにします。

excels_to_list の定義 その 3

for 文append メソッド を使った list の作成 は、以前の記事 で説明した list 内包表記 を使うことで、簡潔に記述 することが出来ます。下記は、先程のプログラムを list 内包表記を使って修正したプログラムです。

def excels_to_list(excels):
    excel_list = [ excels[i:i+2] for i in range(0, len(excels), 2) ]
    return excel_list
修正箇所

def excels_to_list(excels):
-   excel_list = []
-   # excels の先頭から 2 文字ずつ文字を取り出す繰り返し処理
-   for i in range(0, len(excels), 2):
-       # i 文字目から 2 文字分を取り出す
-       excel = excels[i:i+2]
-       excel_list.append(excel)
+   excel_list = [ excels[i:i+2] for i in range(0, len(excels), 2) ]
    return excel_list

リスト内包表記 はプログラムを 短く記述 できますが、プログラムの意味が 分かりづらくなる という欠点があります。リスト内包表記に 慣れれば、上記のプログラムは充分 理解できる と思いますので、本記事では excels_to_list を上記のように定義する事にします。

これまでに定義した excels_to_list の問題点

これまでに定義した excels_to_list は、いずれも Excel 座標 を表す 文字列の長さ2 文字 であることが 前提 になっています。〇×ゲーム の場合はこの 前提は正しい ので問題ありませんが、"A19" のような、3 文字以上 の Excel 座標が 存在する場合 は、これまでに定義したどの excels_to_list でも 正しい処理 を行うことは できません

前回の記事のノートで紹介した、正規表現を使うことで、そのような場合にも対処することは可能ですが、「プログラムが複雑になる」、「〇×ゲームの場合は対処する必要がない」、「次に説明するデータ構造を使うことでこの問題点を簡単に解決することができる」などの理由から、本記事では具体的な方法については紹介しません。

〇×ゲームではこの問題は関係ありませんが、 excels_to_list には、上記の問題があるので、ゲーム盤のサイズ によっては 利用できない場合がある ことは認識して下さい。

複数の Excel 座標を表現するデータ構造 その 2

先程の複数の Excel 座標を表現するデータ構造は、"A1A2B1B2C1" のように、Excel 座標を表す文字列を 直接並べて記述 しているため、Excel 座標の 区切りがわかりづらい という欠点がありました。この問題を解決する方法として、下記のように、Excel 座標の 間に 半角の ","(カンマ) などの 区切りの文字挿入 して 連結する という方法があります。現実の世界でも、複数のデータを "," で区切ることがあるので、難しくはないと思います。

下記は、テストケースをこのデータ構造で記述したプログラムです。

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
]
修正箇所
testcases = [
    # 〇 の勝利のテストケース
    [                                  # 着手順とゲーム盤
-       "A1A2B1B2C1",                  # 135  ooo
+       "A1,A2,B1,B2,C1",              # 135  ooo
        Marubatsu.CIRCLE,              # 24.  xx. 
    ],                                 # ...  ...
    # × の勝利のテストケース
    [                                  # 着手順とゲーム盤
-       "A2A1B2B1A3C1",                # 246  xxx 
+       "A2,A1,B2,B1,A3,C1",           # 246  xxx 
        Marubatsu.CROSS,               # 13.  oo.
    ],                                 # 5..  ...
    # 引き分けのテストケース
    [                                  # 着手順とゲーム盤
-       "A1A2B1B2C2C1A3B3C3",          # 136  oox 
+       "A1,A2,B1,B2,C2,C1,A3,B3,C3",  # 136  oox 
        Marubatsu.DRAW,                # 245  xxo
    ],                                 # 789  oxo
]

利点と欠点

このデータ構造を、Excel 座標を直接並べた場合のデータ構造と 比較した場合 の利点と欠点は、以下の通りです。2 つ目の利点については、この後で説明します。

利点

  • Excel 座標の 区切りが明確 になるため、わかりやすくなる
  • list に変換 する 処理記述しやすい

欠点

  • "," を記述する分だけ、記述が長くなる

区切りの文字について

上記では区切りの文字として半角の "," を使いましたが、データの中使われていない文字 であれば、どのような文字 を、データを区切る文字として 使っても構いません

その理由は、例えば、"1,2""3" という 2 つのデータを、"1,2"中で使われている "," で区切って連結すると "1,2,3" になりますが、連結後"1,2,3" だけ を見た場合、データ を表す "," と、区切り を表す ","区別がつかなくなってしまう からです。

良く使われる 区切りの文字としては、Tab キーを押すことで入力できる Tab という文字、半角の空白 文字、改行を表す文字 などが使われます。

なお、Tab や、改行文字列のデータ として プログラムの中で記述 する場合は、以前の記事 で紹介した エスケープシーケンス という方法で記述する必要があります。具体的には、Tab\t改行\n のように記述します。下記は、1 行目は 半角の空白、2 行目は Tab、3 行目は 改行 で Excel 座標を区切って記述するプログラムです。

"A1 A2 B1 B2 C1"
"A1\tA2\tB1\tB2\tC1"
"A1\nA2\nB1\nB2\nC1"

本記事では 分かりやすさを重視 して、区切りの文字に 半角の "," を使うことにします。

半角の "," によって 区切られた 複数の データ の事を CSVTab によって 区切られた 複数の データ の事を TSV と呼びます。CSV は comma separated value の略で、カンマ(comma)によって区切られた(separated)値(value)の事を表します。TSV の T は Tab の略で、SV は CSV と同様です。

CSV や TSV は実際に 良く使われる ことが多く、例えば、Excel のファイルを保存 する際に、下図の ファイルの種類メニュー から「テキスト(タブ区切り)」や、「CSV(コンマ区切り)」を選択することで、CSV や TSV の 形式 で Excel のデータを ファイルに保存 することが出来ます。

今回の記事では具体的な方法については紹介しませんが、テストケース を表す データExcel に入力 して、CSV として保存 し、その ファイルを読み込む ことで 変数テストケース を表すデータを 代入 するという方法があります。そのようにすることで、データプログラム完全に分離 することができます。

特に、データの数が多くなる と、プログラム内に データを直接記述 するのが 現実的ではなくなる ので、データを ファイルに保存 するのが 一般的 です。

split メソッドによる list への変換

Python では、特定の文字列区切られた文字列 を、split メソッドを使って、list に変換 することが出来るので、上記のデータ構造を list に 簡単に変換 することが 出来ます

split は、実引数 に記述した 文字列 を、区切りの文字列 として 文字列を分割 し、分割したデータを 要素 とする list を作成 して 返す 関数です。下記は、"," によって 区切られた "A1,A2,B1,B2,C1" を、Excel 座標要素 として持つ list に変換 するプログラムです。

print("A1,A2,B1,B2,C1".split(","))

実行結果

['A1', 'A2', 'B1', 'B2', 'C1']

split メソッドの詳細については、下記のリンク先を参照して下さい。

半角の空白Tab改行 で区切られた場合は、それぞれ下記のように記述します。

print("A1 A2 B1 B2 C1".split(" "))
print("A1\tA2\tB1\tB2\tC1".split("\t"))
print("A1\nA2\nB1\nB2\nC1".split("\n"))

実行結果

['A1', 'A2', 'B1', 'B2', 'C1']
['A1', 'A2', 'B1', 'B2', 'C1']
['A1', 'A2', 'B1', 'B2', 'C1']

また、split は、下記のプログラムのように、2 文字以上 の文字列で 区切られた 文字列を、list に変換 することもできます。

print("A1::A2::B1::B2::C1".split("::"))

実行結果

['A1', 'A2', 'B1', 'B2', 'C1']

join メソッドによる文字列の変換

Python の 文字列 には、実引数 で記述した list の要素 を、その文字列 で間を 区切って連結 する join というメソッドがあります。別の言葉で説明すると、join メソッドは、split メソッドの 逆の処理 を行うメソッドで、split を使って変換したデータを join を使って 元に戻す ことや、その逆 を行うことが出来ます。

split メソッドと join メソッドが 用意 されているのは、list が表すデータを、特定の文字列区切って連結 した 文字列簡潔に記述する ことが 実際に良くある からです。

下記のプログラムは、['A1', 'A2', 'B1', 'B2', 'C1'] という list を、下記の方法でそれぞれ 文字列型 のデータに 変換 するプログラムです。5 行目のように、2 文字以上 の文字列で区切って連結することもできます。

  • 2 行目:要素を 直接連結 する
  • 3 行目:要素を 半角の ","区切って連結 する
  • 4 行目:要素を 半角の空白区切って連結 する
  • 5 行目:要素を 2 文字"::"区切って連結 する
coords = ['A1', 'A2', 'B1', 'B2', 'C1']
print("".join(coords))
print(",".join(coords))
print(" ".join(coords))
print("::".join(coords))

実行結果

A1A2B1B2C1
A1,A2,B1,B2,C1
A1 A2 B1 B2 C1
A1::A2::B1::B2::C1

要素を 直接連結 した場合を 除きjoin で文字列に 変換したデータ は、下記のプログラムのように、split を使って 元の list に戻す ことができます。

coords = ['A1', 'A2', 'B1', 'B2', 'C1']
print(",".join(coords).split(","))
print(" ".join(coords).split(" "))

実行結果

['A1', 'A2', 'B1', 'B2', 'C1']
['A1', 'A2', 'B1', 'B2', 'C1']

要素を 直接連結 した場合は、区切り の文字列が 存在しない ので、split メソッドを使って元の list に戻すことは できません

join メソッドの詳細については、下記のリンク先を参照して下さい。

list の 要素 に、文字列型以外 のデータが存在する場合に join メソッドを実行すると、下記のプログラムのように エラーが発生 します。

print(",".join([1, 2, 3]))

実行結果

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\028\marubatsu.ipynb セル 33 line 1
----> 1 print(",".join([1, 2, 3]))

TypeError: sequence item 0: expected str instance, int found

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

  • TypeError
    データ型(type)に関するエラー
  • sequence item 0: expected str instance, int found
    シーケンス型(sequence)の 0 番の要素(item)は文字列型(str)でなければならない(expected)が、整数型(int)が記述されている(found)

test_judge の修正

データ構造変わった ので、test_judge を以下のように 修正 します。

なお、文字列から list への 変換 は、1 行で簡潔に記述 できるので、excels_to_list のような 関数を定義せず に、test_judge の 5 行目に 直接 その処理を testdata.split(",") のように 記述 することにします。

 1  def test_judge(testcases):
 2      for testcase in testcases:
 3          testdata, winner = testcase
 4          mb = Marubatsu()
 5          for coord in testdata.split(","):
 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!")
行番号のないプログラム
def test_judge(testcases):
    for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
        for coord in testdata.split(","):
            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 coord in excels_to_list(testdata):
+       for coord in testdata.split(","):
            x, y = excel_to_xy(coord)
            mb.move(x, y)
        print(mb)

        if mb.judge() == winner:
            print("ok")
        else:
            print("error!")

下記のプログラムを実行することで、修正した excel_to_xy が正しく動作することが確認できます。実行結果は先程と同様になるので省略します。

test_judge(testcases)

どちらのデータ構造を使うべきか

間を区切らない "A1A2B1B2C1" と、間を "," で区切る "A1,A2,B1,B2,C1" のどちらのデータ構造を 使うべきか については、状況 に応じて 自分で判断 する必要があります。

今回の記事では、データ構造を選択する場合のこと説明していますが、データ構造が決まっている場合に アルゴリズムを選択 する場合や、アルゴリズムとデータ構造の 組み合わせを選択 する場合も 同様の方法 で行うことが出来ます。

判断を つけづらい 場合は、状況 からデータ構造に 求められる性質列挙 し、それらの性質を、候補 となるデータ構造が どれだけ満たす かを表す 表を作成する と良いでしょう。

今回の状況を具体例として説明します。

まず、今回の 状況を整理 します。今回は、test_judge のテストを行うために必要な、テストケース を表すデータを 記述する という状況です。その際に 求められる データ構造の 性質 を考えると以下のようになります。他にもあると思った人は追加して下さい。

  • 数多く のテストケースを 記述する必要 があるので、できるだけ 短く 記述したい
  • 同様の理由で、テストケースをできるだけ わかりやすく 記述したい
  • 記述したテストケースのデータが 間違っている場合 は、正しいテスト を行うことが できない ので、記述したテストケースが 正しいかどうかを目で確認 したい

次に、それぞれのデータ構造が上記の 性質 を、どれだけ 満たすか について 考察 します。

あたりまえのことですが、間を ","区切らない データ構造の方がデータを 短く記述 できます。ただし、区切った場合でも list で記述 する よりは かなり 短く記述 できます。

人によって違う可能性はあると思いますが、多くの人 にとっては、"A1A2B1B2C1" のような、間を区切らない 記述は わかりにくい ため、"A1,A2,B1,B2,C1" のように、間を "," で区切る記述よりも 記述しづらい のではないかと思います。

同様に、記述した内容が 正しいかどうか目で確認 する際も、間を "," で区切ったほうが 確認しやすい のではないでしょうか。つまり、短く記述できる という 利点 が、他の観点 から見た場合に 欠点 になる 場合がある ということです。

下記は、それぞれのデータ構造が上記の 性質をどれだけ満たすか を表にしたものです。いずれの性質も、それほど 大きな差 がある わけではない ので、劣る方に △ を記しました。

区切らないデータ構造 , で区切るデータ構造
記述の短さ
記述のわかりやすさ
確認のしやすさ

上記の表では、記号で評価 を行いましたが、5 点満点のような 数字で評価 を行うという方法も考えられます。数字で評価 を行った場合は、合計を計算 することにより 総合評価簡単に行う ことができるという 利点 が得られますが、それぞれの項目の 重要度が異なる 場合は、必ずしも 合計点を総合評価にすることが 良いとは限らない 点に注意が必要です。例えば、記述の短さが、他の性質 よりも はるかに重要 である場合などです。

そのような場合は、表に 重要度 を表す 数字 を記述する 列を加え、その数字を 評価の数字に乗算 したものの 合計を計算 することで、総合評価を求める という方法などが考えられるでしょう。重要度 を表す数字の事を 重み と呼びます。

次に、上記の表を使って、どちら のデータ構造を 選択すべき かを 考察 します。間を区切らないデータ構造の方が、"," を記述しない分だけ、数文字ほど 短く記述 できますが、その差 はあまり 大きくありません総合的に考える と、記述の長さの 欠点より、記述のわかりやすさと確認しやすさの 利点のほうが上回る と考えられることから、本記事では、"," で間を区切るデータ構造を採用 することにします。

なお、上記の 表の評価 や、それに基づいた 総合的な判断 は、筆者の主観 による評価なので、人によって評価や結論異なる かもしれません。例えば、記述のわかりやすさどちらも変わらない と思った方は、上記の表 をそのように 修正 して下さい。また、記述の短さ が、他の性質よりも はるかに重要 だと思った方は、総合的に考えて 区切らないデータ構造採用するべき でしょう。

残念ながら、データ構造を 決める前 に行った 考察 が、実際とは異なる ことは 良くあります。例えば、人によっては、テストケースを実際に記述する際に、"," を記述 する 手間 が、思いのほか 面倒 であったり、"," で区切らない記述を試してみると、思いのほか わかりくくないと感じる 場合などがあるでしょう。

現実の世界でも、綿密に立てた計画 が、想定外の理由うまくいかない ことが 良くあります。そのような場合は、その時点で 臨機応変データ構造を変更 すれば良いと思います。もちろん、データ構造を変更した結果、やっぱり元のデータ構造のほうが良かったということもありますが、そのような 経験を積む ことで、どのようなデータ構造が 自分にとって使いやすい かが わかるようになります失敗から学ぶ ことができれば、失敗は決して 無駄ではなく有益 だと考えて下さい。

今回の記事のまとめ

複数の Excel 座標 を、半角の ","区切って連結 した 文字列 で表現するという データ構造 を使うことで、下記のプログラムのように、前回の記事よりも、さらに 簡潔に テストケースを記述することが出来る。

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
]

split メソッドを使うことで、上記のデータ構造を、元の list変換 できる。

"A1,A2,B1,B2,C1".split(",")

test_judge の 5 行目の testdata を、testdata.split(",")修正するだけ で、他の部分を 一切変更することなく、テストを行うことができる。

 1  def test_judge(testcases):
 2      for testcase in testcases:
 3          testdata, winner = testcase
 4          mb = Marubatsu()
 5          for coord in testdata.split(","):
 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!")
行番号のないプログラム
def test_judge(testcases):
    for testcase in testcases:
        testdata, winner = testcase
        mb = Marubatsu()
        for coord in testdata.split(","):
            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 coord in testdata:
+       for coord in testdata.split(","):
            x, y = excel_to_xy(coord)
            mb.move(x, y)
        print(mb)

        if mb.judge() == winner:
            print("ok")
        else:
            print("error!")

長くなったので今回の記事はここまでにします。次回は、xy 座標を、Excel座標以外の方法で表現するデータ構造と、それを処理するアルゴリズムを紹介します。

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

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

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

以下のリンクは、今回の記事で作成した test.py です。前回の記事と同様に、今回の記事で採用しなかった test_judgeexcels_to_list を、プログラムの最後に記述しておくことにします。

次回の記事

  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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?