Pythonの二次元配列がこんなに美しいとは知りませんでした

288, 2021-07-13

目次

Pythonの二次元配列がこんなに美しいとは知りませんでした

配列の中にさらに配列がある構造のことを二次元配列と言います。
Pythonでも二次元配列を扱うことができます。

結論から言うとPythonによる二次元配列、つまり二次元リストは↓のように使います。

# 二次元配列
twoarr = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
]
print(twoarr[0][1])  # 1
print(twoarr[1][2])  # 5

美しい

この記事ではPythonによる二次元配列の扱い方を解説します。
具体的には↓を見ていきます。

  • 二次元配列とは?

  • Pythonのリストと二次元配列

  • 二次元配列の作り方

  • 二次元配列の要素へのアクセス

  • 二次元配列の出力

  • 二次元配列の伸縮

  • numpyによる二次元配列

二次元配列とは?

二次元配列とは二次元の配列ですが、この二次元とは「配列の中に配列がある」という状態を指して二次元と言います。
また二次元的に要素にアクセスできるという特徴も持っています。
そのため行列などのデータ構造の定義に使われることがあります。

一次元配列は普通の配列でよく使われますが、プログラミングでは二次元配列も比較的によく使われます。
たとえばHTMLのテーブル構造を表現したりしたいときは、二次元配列で行列を作ります。
それからゲームのフィールドを表現するデータ構造として、二次元配列が使われることもあります。
二次元配列の各要素には、X座標とY座標でアクセスできる特徴があるため、こういった二次元のゲームと相性がいいわけです。

二次元配列を扱えるようになると、そういった少し高度なプログラミングを行うことが可能です。
二次元配列は慣れてしまうとわりと簡単に扱うことが出来るデータ構造の1つです。
もっとも、行列の演算などになるとそれなりに難しいのですが。

そのため様々なシーンでよく使われる人気のデータ構造と言えます。
この二次元配列を学ぶためには一次元配列の知識が前提として必要です。
これに新たに加えて、二次元的なデータの扱いを覚える必要があります。

この記事ではそういった二次元配列の特徴的な操作方法も詳しく解説します。

Pythonのリストと二次元配列

「配列」というとPythonを扱う人は「?」と言う感じですが、これはPythonで言う「リスト」のことを言います。
厳密には配列とリストのデータ構造は違うものなんですが、Pythonではリストが配列です。
というのも、CPythonなどの実装ではリストは配列として実装されているからです。

そのため「配列 == Pythonではリスト」という式が成り立ちます。
今回扱う二次元配列もリストを使って解説します。
ちなみにリストだけでなくタプルでも二次元配列は表現することが出来ます。

二次元配列の作り方

最初におさらいとして普通の配列、リストの作り方です。
これは↓のようにします。

arr = [0, 1, 2]
print(arr)
# [0, 1, 2]

↑のように配列は角カッコ([])で作ります。角カッコの中に要素を配置します。
0, 1, 2というカンマで区切られた整数がそれぞれ1つずつリストの要素です。
↑の場合、配列内の要素はぜんぶで3つです。

二次元配列ではこの要素を配列に変更します。
↓のようにです。

arr = [[0, 1, 2]]
print(arr)
# [[0, 1, 2]]

↑の場合、外側の角カッコが外側(1次元目)の配列で、中側の角カッコが内側(2次元目)の配列になります。
↑の場合、外側の配列内の要素数は1つ、内側の配列内の要素数は3つになります。

外側の配列内の要素は増やすことが出来るので、↓のようにも二次元配列を作ることが出来ます。

arr = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
]
print(arr)
# [[0, 1, 2], [3, 4, 5], [6, 7, 8]]

↑の場合、外側の配列の要素数は3つで、中側の配列の要素数もそれぞれ3つずつになります。
この状態の二次元配列を行列(正方行列)と表現することもあります。

もちろん内側の配列の要素数は同じでなくても大丈夫です。

arr = [
    [0, 1],
    [2, 3, 4],
    [5],
]
print(arr)
# [[0, 1], [2, 3, 4], [5]]

二次元配列の要素へのアクセス

二次元配列の要素にアクセスするには二次元的なインデックスの指定が必要です。
たとえば↓のようにです。

arr[外側の配列のインデックス][内側の配列のインデックス]

これは↓のように座標で表現することも出来ます。

arr[Y座標][X座標]

座標の場合は(X, Y)ではなくて(Y, X)になっている点に注意が必要です。

たとえば↓のような二次元配列の要素にアクセスするには↓のようにします。

arr = [[0, 1, 2]]
print(arr[0][0])  # 0
print(arr[0][1])  # 1
print(arr[0][2])  # 2

↑のコードをクローズアップしてよく見てみます。
たとえば↓のコードです。

arr[0][1]

↑の角カッコによるアクセスはこういう意味になります。
「arrの外側の配列の0番目の要素にアクセス。そしてその要素(配列)の1番目の要素にアクセス」

もう少し分解してみましょう。
たとえば↓のコードです。

elem = arr[0]
print(elem)
# [0, 1, 2]

↑のコードではarr[0]で単純に普通の配列として要素を取得しています。
その結果は↑のように[0, 1, 2]という配列が返ってきます。
つまり先ほどのarr[0][1]というコードは↓のコードと同じ意味になります。

elem = arr[0]
print(elem[1])
# 1

一次元目の配列を取得して、その配列の要素にアクセスしてるだけということですね。

二次元配列の出力

二次元配列の中身をfor文で出力したいとします。
その場合は↓のようなコードを書きます。

twoarr = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
]

for arr in twoarr:
    for el in arr:
        print(el)
# 0
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8

二次元配列をfor文で回す時は、↑のようにfor文も入れ子にします。
まず外側のfor文で外側の配列の要素(arr)を取得します。

for arr in twoarr:

↑のarrというのは[0, 1, 2][3, 4, 5]などの配列のことです。
そして内側のfor文で取り出した配列の要素を取得します。

    for el in arr:

elelement(要素)の略です。ちなみにarrarray(配列)の略で、twoarrtwo dimensional array(二次元配列)の略です。

twoarrはあまり一般的な表記じゃないね

この辺は好きに書いてね

二次元配列をfor文とインデックスで回すには↓のようにします。

twoarr = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
]

for i in range(len(twoarr)):
    for j in range(len(twoarr[i])):
        print(twoarr[i][j])
# 0
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8

↑のfor文の場合、特徴的なのは添え字ijの生成です。
まず外側のfor文でtwoarrの要素数だけ回します。これのカウント変数をiとしています。

for i in range(len(twoarr)):

↑のコードのtwoarrは外側の配列の要素数が3なので、i0から2まで回ることになります。
そして内側のfor文でtwoarr[i]の要素(配列)の長さだけ回します。

    for j in range(len(twoarr[i]))

↑の場合、twoarr[i]i0のときは[0, 1, 2]という配列の長さが取得されます。
そしてi1のときは[3, 4, 5]の配列の長さです。
jにはそれらの配列の長さのカウント変数が保存されます。

どういうことかコードで見てみましょう。
↓のようにijを出力します。

twoarr = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
]

for i in range(len(twoarr)):
    for j in range(len(twoarr[i])):
        print(f'i = {i}, j = {j}')

結果は↓のようになります。

i = 0, j = 0
i = 0, j = 1
i = 0, j = 2
i = 1, j = 0
i = 1, j = 1
i = 1, j = 2
i = 2, j = 0
i = 2, j = 1
i = 2, j = 2

↑の出力を見るとi0のときj0から2の間で回っています。
そしてi1に増えて再びj0から2の間で回ります。
このようにfor文を回すことで二次元配列の各要素の添え字(ij)を網羅的に生成することが可能です。

この基本的な添え字を使ったfor文を改造すると、行列を回転させたりあるいは反転させたりなどといった処理も書くことが出来ます。

二次元配列の伸縮

二次元配列はただの配列、リストです。
そのため配列のメソッドを使うことが出来ます。

関連記事
Pythonでリスト(配列)に普通に要素を追加する方法

参考
5. データ構造 — Python 3.9.4 ドキュメント

二次元配列に新しく配列を追加するには↓のようにコードを書きます。

twoarr = [
    [0, 1, 2],
    [3, 4, 5],
]

twoarr.append([6, 7, 8])

print(twoarr)
# [[0, 1, 2], [3, 4, 5], [6, 7, 8]]

↑は配列のメソッドappend()を使って、配列の末尾に配列を追加しています。
また内側の配列(二次元目)に要素を追加したい場合は↓のようにします。

twoarr = [
    [0, 1, 2],
    [3, 4, 5],
]

twoarr[0].append(6)

print(twoarr)
# [[0, 1, 2, 6], [3, 4, 5]]

↑の場合はtwoarr[0]twoarr0番目の要素([0, 1, 2])の末尾に6という整数を追加しています。

配列の先頭に配列を挿入したい場合はinsert()を使います。

twoarr = [
    [0, 1, 2],
    [3, 4, 5],
]

twoarr.insert(0, [6, 7, 8])

print(twoarr)
# [[6, 7, 8], [0, 1, 2], [3, 4, 5]]

insert()の第1引数には第2引数を挿入したいポジションをインデックスで指定します。
↑の場合0を指定しているので、配列の先頭に新しい要素が挿入されます。

insert()の場合も内側の配列に要素を挿入する場合は↓のようにします。

twoarr = [
    [0, 1, 2],
    [3, 4, 5],
]

twoarr[1].insert(0, 6)

print(twoarr)
# [[0, 1, 2], [6, 3, 4, 5]]

↑の場合はtwoarr[1]twoarr1番目の要素([3, 4, 5])の先頭に整数6を挿入しています。

numpyによる二次元配列

人気の外部ライブラリ、numpyで二次元配列を作る方法も解説します。
numpyでは↓のように二次元配列を作ります。

import numpy as np


twoarr = np.array([
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
])
print(twoarr)
# [[0 1 2]
#  [3 4 5]
#  [6 7 8]]

二次元配列の要素の取得方法は普通の配列と同じです。

print(twoarr[0][1])  # 1
print(twoarr[1][2])  # 5

また、arange()reshape()を組み合わせて二次元配列を作成することも出来ます。

twoarr = np.arange(12).reshape(3, 4)
print(twoarr)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

↑の場合、arange()には二次元配列全体の要素数を指定します。
そしてreshape()には次元数をそれぞれ(y, x) == (行, 列)で渡します。

おわりに

今回はPythonの二次元配列を見てみました。
二次元配列、もとい行列は美しいですね。

ほれぼれするね

ふつくしい