LoginSignup
2
0

Pythonで〇×ゲームのAIを一から作成する その4 配列 と list 型のデータ

Last updated at Posted at 2023-09-19

目次と前回の記事

前回のおさらい

前回の記事では、以下のような〇×ゲームの仕様を決め、仕様 1 を最初に実装することに決めました。

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

仕様 1. を実装するためには、3 x 3 の 2 次元のゲーム盤 を表すデータを プログラムで記述 する必要があります。そのためには、配列 と Python の list 型のデータ について学ぶ必要があります。

今回の記事はこの 2 つについて詳しく説明します。

配列 と list 型のデータ

ほとんどのプログラム言語には、配列 という 複数のデータをまとめて扱う ことができるデータ型が用意されています。〇×ゲームのゲーム盤のように、2 次元の座標 で表現されるデータは、2 次元配列 を利用するのが直観的でわかりやすい方法でしょう。

配列や 2 次元配列という用語の説明をいきなりしてもわかりにくいと思いますので、先に配列の性質を持つ Python のデータ型の 1 つである list について説明します。

「list 型のデータ」のように記述すると文章が長くなるので、そのことを強調したい場合でない限り、以降は「list 型のデータ」のことを単に「list」と記述します。

はじめに、Python の list の基本的な性質と用語についてまとめ、その後でそれぞれの性質や用語について説明します。なお、Python の list には下記以外にも様々な性質がありますが、それらについては必要に応じて説明する予定です。

Python の list の基本的な性質と用語

  • list は 複数のデータ順番に まとめて扱うことができる Python の データ型 である
  • list は、半角の [] の間に、list に格納する個別のデータを半角の , で区切って 順番に 記述する
  • list に格納された個別のデータの事を list の 要素 と呼ぶ
  • list の要素には、先頭の要素から順番0 からはじまる整数の番号 が付けられており、その番号を使って list 内の個別の要素を参照することができる
  • list の要素を参照するための番号のことを list の インデックス 1と呼ぶ
  • 上記の性質から、list の要素は 0 以上の整数 のインデックスを使って、順序付けられている
  • list の要素の参照は、半角の [] を使って、listのデータ[インデックス] のように記述する
  • list の要素は 変数と同様の性質 を持ち、Python の 任意のデータ型のデータ を代入したり、式の中で記述して要素に代入されたデータを参照することができる

「データ型のデータ」という表現は、同じ「データ」という用語が並んでいてわかりづらいので、以降は「データ型のデータ」のことを、単に「データ型」と表記します。

参照の意味

上記の説明の中の「参照」という用語がわかりづらいかもしれないので以下に補足します。下記の 参照識別子(または id)という用語は、良く使われる 重要なプログラミング用語 なので必ず意味を理解してください。

list のインデックスのように、番号や住所などの 識別子 を使って 特定のものを指定 することを 参照 と呼びます。識別子の英語は identifier で、よく先頭の 2 文字をとって id と表記されます。

識別子に求められる 重要な性質 として、異なるものを参照する識別子には、必ず異なる識別子が付けられる というものがあります。

例えば「住所」、「電話番号」、「マイナンバー」などは識別子ですが、人を参照する際に良く使われる「氏名」は、同姓同名の可能性がある ため、氏名だけでは厳密な意味では識別子とは言えません。

list のインデックスが list の要素を参照する以外でも、プログラムでは、様々な識別子 をって、様々なものを 参照 します。例えば、「変数名」は、変数に 代入された値 を参照する識別子です。従って、「変数の値」を、「変数名が参照する値」のように言い換えることができます。

複数の識別子 を使って 参照 が行われる場合もあります。例えば、list を変数に代入した場合は、その「変数名」と、「インデックス」の 2 つの識別子 を使って、a[1] のように list の要素の値を参照します。a[1] の意味についてはこの後で説明します。

list の記述方法

Python の list は、半角の [] の間に、list に格納する個別のデータを半角の , で区切って 順番に 記述します。下記のプログラムは、100、200、300 という 3 つのデータを格納した list を a という変数に代入しています。

print を使って list を表示すると、下記の実行結果のように、その list をプログラムで記述した場合と同じ内容が表示されます。

a = [ 100, 200, 300 ]
print(a)

実行結果

[100, 200, 300]

本記事が利用している Qiita では、上記のようにプログラムの文字が、Python の構文(syntax)(文法のこと)規則によって 色分け されて表示されます。このような表示の事を シンタックスハイライト (syntax highlight)と呼びます。

どのように色分けが行われるかは、ソフトによって異なります。Qiita では、数値型のデータが紫色、文字列型のデータが水色、print などの組み込み関数は緑色、などのように色分けが行われます。色分けの種類はかなり多いので、本記事では具体的には紹介しませんが、慣れてくればどの色が何を表すのかがわかるのようなるのではないかと思います。

プログラムを入力する際に、シンタックスハイライトは 入力ミスを見つける 大きな 手掛かり となります。入力したプログラムの文字の色がいつもと違うと思ったら、何らかの入力ミスがある可能性が高いと考えたほうが良いでしょう。

VSCode で入力されたプログラムもシンタックスハイライトが行われますが、色分けのルールは Qiita とは異なっている点に注意して下さい。また、VSCode では、設定を行うことによってシンタックスハイライトの色を自由に変更することもできるようです。

なお、本記事のプログラムの 実行結果 には、構文などはありませんので、色分けは行いません

本記事では、プログラムに記述する内容を本文中に表記する場合は print のように、灰色の背景で表記します。

list の要素とインデックス

list に関連する重要な用語に 要素インデックス があります。それぞれ以下のような意味を持つので必ず理解して下さい。

  • list に格納された個別のデータの事を list の 要素 と呼ぶ
  • list の要素には、先頭の要素から順番0 からはじまる整数の番号 が付けられており、その番号を使って list 内の個別の要素を参照することができる
  • list の要素を参照するための番号のことを list の インデックス と呼ぶ

先程のプログラムで記述した [100, 200, 300] という list の、100、200、300 の 3 つの要素のインデックスは、それぞれ 0、1、2 に対応しています。

プログラム言語では、list のインデックスの数字を 1 からではなく、0 から数える 点に注意して下さい。なお、プログラムではインデックスに限らず、数字を 0 から数えるのが 一般的 です。

list の要素への代入と参照

list の要素は、インデックスと半角の [] を使って、listのデータ[インデックス] のように記述して参照します。例えば a に list が代入されていた場合、a の 1 番のインデックスの要素は、a[1] のように記述します。

a の 1 番のインデックスの要素」のように記述すると表記が長くなってしまうので、以後は「a の 1 番の要素」のように、「インデックス」を省略 して表記します。

list の要素は 変数と同様の性質を持つ ので、list の要素に対して代入文を記述して 値を代入 したり、list の要素を式の中で記述することで 代入された値を取り出して 計算を行うことができます。

下記のプログラムは、以下の処理を行います。

  • 1 行目で a[100, 200, 300] という list を代入する
  • 2 行目で a の 1 番の要素 + 50 を計算してを表示する
  • 3 行目で a の 2 番の要素に 500 を代入する
  • 4 行目で a の値を表示する

2 行目を実行すると list の 1 番の要素の値である 20050 が加算されて 250 が表示されます。この a[1] + 50代入文ではない ので、この計算が行われても a[1] の内容は 変化しません。この点を 勘違い しないように注意して下さい。

3 行目で list の 2 番の要素に 500 を代入しているので、 4 行目の表示では list の 2 番の要素の値が 500 に変化しています。

a = [100, 200, 300]
print(a[1] + 50)
a[2] = 500
print(a)

実行結果

250
[100, 200, 500]

慣れないうちは勘違いしがち ですが、「list の 1 番の要素」を、list の先頭の要素であると勘違いしないように気を付けて下さい。先ほど説明したように、インデックスは 0 から数える ので、「list の 1 番の要素」は、先頭の要素を 1 番目 として数えると 2 番目 の要素になります。

本記事ではこのことを区別するために、先頭の要素を 1 番目として数えた場合は、「〇 番目 の要素」のように、番目 という用語を太字で記述して区別できるようにすることにします。なお、そのように区別しても紛らわしいことには変わりはないので、基本的には「〇 番目 の要素」という表現はできるだけしないようにするつもりです。

配列と list の意味の違い

まず、配列という用語の意味を説明します。

0 以上の整数識別子 として、複数のデータを 順番にまとめて 扱えるようなデータのことを 配列 と呼びます。

Python の list は上記の 配列の性質を持つ データ型なので、Python の list のことを配列と表現することが良くありますが、配列という用語と list という用語は 同じ意味を持つわけではありません

配列は データが持つ性質 の事を表し、多くのプログラム言語では配列の性質を持つデータを 様々な方法で扱う ことが可能です。Python の list は、Python が扱うことができる、配列の性質を持つデータを表現する方法の 中の 1 つ にすぎません。現実の世界で例えると、配列は「スポーツ」のような 分類 を表し、list は「サッカー」のようなスポーツに 分類されるもの のうちの 1 つに相当します。

実際に Python には、list 以外にも、array という配列の性質を持つデータ型があります。ただし、array と list は多くの点で似てはいますが、いくつか異なる性質を持っています2

他にも Python には numpy という非常に良く使われるモジュールがあり、numpy を利用することで Python の list や array とは異なる配列の性質を持つデータを扱うことができます3

Python の list は 配列の性質を持つので、list の事を配列と表記しても間違いではありませんが、Python では配列の性質を持つデータを 複数の方法 で扱うことができるので、単に配列と記述するとそれらのうちのどれ表しているかが 不明確 になります。そこで、本記事では配列と list という用語を以下のように 使い分ける ことにします。

本記事では「list」と「配列」という用語を以下のように使い分けます。

  • Python のデータ型である list を指す場合は配列ではなく、list と表記します。
  • 「0 以上の整数を識別子として、複数のデータを順番にまとめて扱えるようなデータ」という、配列の 性質 を表したい場合は 配列 と表記します。

本記事で、list を「リスト」のようなカタカナではなく、アルファベットで表記しているのは、データの性質 を表す「リスト」という プログラミングの用語がある からです。本記事は日本語で書かれているので、list のようにアルファベットで表記することで、list が Python という 特定の プログラミング言語のデータ型であるということを表現しました。

複合データ型の定義

list は、その要素の中に Python の 任意のデータ型 を代入できます。プログラミングの用語では、このように、複数のデータ型を 組み合わせて表現 されるデータ型のことを、複合データ型 (または、複合型のデータ型)と呼びます。

プログラム言語によって、どのようなデータ型を複合データ型と分類するかは異なります。また、色々と調べてみたのですが、Python では、データ型を複合データ型という性質で分類することはあまりないように思えました。

そこで、本記事では 複合データ型 という データ型を分類 する用語を、以下のように 定義 します。他の Python の記事ではこの用語が 別の意味 で使用される可能性がある点に注意して下さい。

Python の 任意のデータ型 を、複数組み合わせて データを表現するデータ型

Python では、list、dict、tuple などが複合データ型に分類されます。dict、tuple に関しては、必要になった時点で説明します。

Python では、list、dict、tuple などは、コンテナ(container)に 分類 されます。コンテナは、文字通り、複数のデータ格納(contain)できるデータのことですが、Python のコンテナに分類される 文字列型 のデータは、その中に 文字だけしか格納 することが できません。そこで、本記事では 複合データ型 という用語を Python の 任意のデータ型を格納 できるという意味で、コンテナという用語と 区別して使う ことにします。

1 次元配列と 2 次元配列

Python の list は、インデックスを 1 つ指定 して特定の要素を参照します。このように、1 つの識別子 を使って複数のデータをまとめて扱うような配列のことを 1 次元配列 と呼びます。

しかし、3 x 3 のマスで構成される〇×ゲームのようなゲーム盤のマスは、x 座標と y 座標の 2 つの座標 で参照するのが一般的です。そのため、1 次元配列はゲーム盤のデータを表現するにはあまり向いているとは言えません。

そのような場合は、2 つの識別子 を使って複数のデータをまとめて扱うことができる 2 次元配列 4 を使うのが一般的と言えるでしょう。

Python では list の要素に list を 入れ子で代入する ことで 2 次元配列を表現できます。
このことは、初心者にはわかりづらい部分があると思いますので、具体例を挙げながら順を追って説明します。

複数の識別子を使って、複数のデータの中から 1 つのデータを参照する場合、1 つ 1 つの識別子の事を 次元(dimension)と呼びます。また、識別子の数が n 個の場合は、そのデータを参照する識別子の事を n 次元のデータ と呼びます。

数学で使われる 2 次元平面の座標は、「x 座標」と「y 座標」の 2 次元で平面上の 1 つの点を参照するので 2 次元のデータです。我々が生きている 3 次元空間の座標は一般的に、「x 座標」、「y 座標」、「z 座標」の 3 次元で空間上の 1 つの点を参照するので 3 次元のデータです。

3 次元までのデータであれば、その分布を紙に書いたり、頭の中で思い浮かべたりすることができます。4 次元以上のデータの分布を紙に書いたり、頭の中に思い浮かべることは容易ではありませんが、次元の数に 上限はありません。例えば、ある高校で行われた期末テストの生徒の点数を、「学年」、「クラス」、「出席番号」、「科目」の 4 つの次元の識別子で参照する場合、それらの識別子は 4 次元のデータです。

2 次元配列を表す list の記述方法

下のような 3 x 3 の表で、セルの中に左上から順に 1 から 9 までのデータが格納された表を、Python の list で表現することを考えてみます。なお、この表は以下のような意味を持つものとします。

  • 左右方向を x 座標、上下方向を y 座標で表現する
  • 図の太字の部分は x、y 座標の番号を表す
  • 表のセルを x 座標と y 座標の 2 次元のデータ の座標で参照する
  • 表のセルの座標を、x 座標と y 座標の番号を使って、(1, 2) のように表現する
  • この表の場合、(1, 2) のセルには 8 が格納されている

以降は 〇×ゲームのゲーム盤の座標 もこの座標で表記することにします。

0 1 2
0 1 2 3
1 4 5 6
2 7 8 9

表計算ソフト Excel のセルの行の座標や、将棋盤のマスの座標などでは、座標は 1 から数えますが、プログラムでは 0 から数えるのが一般的なので、上記の表でも座標を 0 から数えています。

配列のインデックスの性質から、上記の表のデータのように 0 から始まる整数の識別子 を使って複数のデータの中から 1 つのデータを参照するようなデータは 配列が適しています

Python の list は、そのままでは 1 つのインデックスで要素を参照するので、上記のような 2 次元の表のデータを直接表現することはできません。しかし、この表を 列ごとに分けて 考えると、各列のデータは 1 つの インデックスに、012 という y 座標を表す数値を対応させた list で表現することができます。そこで、この表のデータを列ごとに list で表現することにします。

この表の 0、1、2 列のデータを list で表現すると、下記のプログラムのようになります。下記のプログラムでは、col0col1col25 という変数にそれぞれ 0、1、2 列を表す list を代入しています。

col0 = [1, 4, 7]
col1 = [2, 5, 8]
col2 = [3, 6, 9]

列は 縦方向のデータ なので、col0 = [1, 2, 3] ではない 点に注意して下さい。

なお、列ではなく、行ごとに list でデータを記述することもできます。その場合の表のデータの記述方法と、列ごとに記述した場合の違いなどについては後述します。

例えば、1 列のデータは下記のように print(col1) を実行することで表示することができます。

print(col1)

実行結果

[2, 5, 8]

繰り返しになりますが、list ではインデックスは 1 ではなく 0 から数字を数えます。従って「1列のデータ」は一番左の列のデータではなく、左から 2 番目 の列のデータを表す点に注意して下さい。

1 列の 2 行、すなわち (1, 2) のセルのデータは col1[2] に代入されているので、下記のプログラムで表示することができます。

print(col1[2])

実行結果

8

Python では 2 次元配列を表すデータを、list の要素に list を代入する という方法で記述することができます。具体的には下記のプログラムのように、list が代入されている col0col1col2 を要素として持つ list を記述します。

下記のプログラムでは、table6 という名前の変数にそのデータを代入しています。

table = [ col0, col1, col2 ]

table に代入された list の要素は順番に、0 列のデータ、1 列のデータ、2 列のデータを表します。従って、下記のようにprint(table[1])を実行することで、先ほどのprint(col1)と同様に、1 列のデータを表す list を表示することができます。

print(table[1])

実行結果

[2, 5, 8]

table[1]col1 と同様に、1 列のデータを表すことがわかったので、(1, 2) のセルの要素は col1[2]col1 の部分を table[1]置き換えた table[1][2] のように記述することができます。そのことは下記のプログラムを実行すると、実行結果のように (1, 2) のセルの中身である 8 が表示されることで確認できます。

print(table[1][2])

実行結果

8

このようにして記述された 2 次元配列を表す list は、上記のように 2 つのインデックス を指定することで、1 つの要素を参照することができます。上記の場合は、(x, y) のマスを表す要素を table[x][y] のように記述します。

2 次元配列を表す list を直接記述する方法

先程のプログラムでは、列のデータを表す list を 一旦変数に代入してから 2 次元配列を表す list を記述していましたが、下記のプログラムのように、list の要素に 変数を記述して代入していた 部分に list のデータを直接記述 するができます。

下記のプログラムでは、先ほどのプログラムの table と区別するために、別の table2 という名前の変数に表のデータを代入しています。

table2 に代入した表のデータは、先ほどのプログラムの記述の仕方は異なりますが、table に代入した表のデータと 全く同じもの を表すので、下記の 2 行目を実行すると (1, 2) のセルの内容が正しく表示されます。

table2 = [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
print(table2[1][2])

実行結果

8

上記のプログラムからわかるように、Python では、list の中に list を 入れ子で記述する ことで、2 次元配列のデータを記述することができます。

分かりづらいと思った方は、下記の先ほどのプログラムと見比べて下さい。まず、下記のプログラムの 4 行目の col0 を、1 行目で col0 に代入した [1, 4, 7] に置き換えて下さい。4 行目の col1col2 も同様の方法で置き換えると上記のプログラムのようになります。

col0 = [1, 4, 7]
col1 = [2, 5, 8]
col2 = [3, 6, 9]
table = [col0, col1, col2]

2 次元配列を表す list を print で表示した場合は、下記のプログラムの実行結果のように、list をすべて 直接記述した場合と同じ内容 が表示されます。tabletable2 の内容の表示結果が同じになることを確認して下さい。

print(table)
print(table2)

実行結果

[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]

1 行 で 2次元配列のデータを記述するのがわかりづらいと思った方は、下記のように列のデータごとに改行して記述すると良いでしょう。

ただし、実行結果からわかるように、このように記述しても print でその内容を表示すると 1 行でまとめて 表示が行われます。

table3 = [
    [1, 4, 7],
    [2, 5, 8],
    [3, 6, 9]
]
print(table3[1][2])
print(table3)

実行結果

8
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]

今回の記事では具体例は示しませんが 3 次元以上の配列のデータも同様に、次元の数だけ list の中に list を 入れ子 にして記述します。3 次元以上の配列のデータの具体例については、本記事で必要になった時に紹介します。

Python のコメント

Python では、半角の # を記述することで、その行のそれ以降 にコメント(注釈)を記述することができます。コメントとして記述した内容は、プログラムの内容とはみなされず 実行されることはありません

本記事 で表示するプログラムのコメントは 灰色の文字 で表示されます。また、VSCode ではコメントの部分は 緑色の文字 で表示されるので一目で区別がつくようになっています。

下記のプログラムは、上記のノートのプログラムにコメントをつけ加えたものです。table3 に代入する list の 3 つの 要素が、それぞれ何列のデータを表すかをコメントで記述しています。

table3 = [
    [1, 4, 7], # 0 列のデータ
    [2, 5, 8], # 1 列のデータ
    [3, 6, 9]  # 2 列のデータ
]

このように、適切なコメントを記述することで、プログラムの意味が 分かりやすくなります
ただし、コメントを書きすぎると 逆にプログラムがわかりにくくなる こともあります。コメントは、分かりづらいと思ったところなどに ピンポイント で記述することをお勧めします。

2 次元配列のインデックスの順番

これまでのプログラムでは、2 次元配列の 2 つのインデックスは順に「x座標」、「y座標」という意味を持ちましたが、その順番を逆にして「y座標」、「x座標」の順にしてデータを記述することもできます。その場合は、表のデータを下記のプログラムのように記述し、(x, y) のマスを table4[y][x] のようにインデックスに 「y座標」、「x 座標」の順 で値を指定して参照します。

インデックスの意味の順番が逆になったため、下記のプログラムの print(table4[2][1]) では、(2, 1) ではなく、(1, 2) のセルの中身を表示している点に注意して下さい。同様の理由で、print(table4) で表示される内容も変化します。

table4 = [
    [1, 2, 3], # 0 行のデータ
    [4, 5, 6], # 1 行のデータ
    [7, 8, 9]  # 2 行のデータ
]
print(table4[2][1])
print(table4)

実行結果

8
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

どちらの順番を採用しても 正しくデータを表現 できますが、それぞれ以下のように利点と欠点があります。どちらを採用するかはプログラムの作成者が 自分で決める 必要がありますが、その順番を後で忘れないように注意して下さい。順番を勘違いしてプログラムを記述すると、思わぬ エラーの原因 になってしまうからです。

2 次元以上の配列を表す list をプログラムで記述する場合は、インデックスの順番と意味意識しながら プログラムを記述することが重要です。

インデックスの順番と意味が分かりづらいと思った場合は、プログラムの分かりやすい場所などにコメントで list のインデックスの意味と順番を書いておくと良いでしょう。

利点 欠点
x、yの順 座標の順番が数学で使われる (x, y) の記法と同じなので直観的で わかりやすく、間違えにくい table3 のようにデータを記述した場合、列と行のデータが逆になっているように見えるので直観的にわかりづらい7
y、xの順 table4 のようにデータを記述した場合、見た目が直観的にわかりやすい 座標の順番が数学の記法と逆なので わかりづらく、間違えやすい

本記事では数学の記法に従ったほうがわかりやすいと思いましたので、「x座標」、「y座標」の順でインデックスの意味を表すことにします。

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

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

次回の記事

  1. 「要素番号」、「添え字」、「オフセット(offset)」と呼ぶ場合もあります

  2. 本記事では array を利用しないので、array の説明は割愛します

  3. numpy は非常に便利なので、今後の記事で実際に利用します

  4. 3 つの識別子を使う配列を 3 次元配列と呼びます。同様に 4 次元配列や 5 次元配列など、任意の次元の配列があります

  5. col は「列」を表す column の略です

  6. table は、英語で表の事を表します

  7. 実際には table3 のような形で、2 次元配列のデータをすべてプログラムで直接記述することはあまりないと思いますので、この欠点はあまり気にする必要はないかもしれません

2
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
2
0