【Python】2次元配列で隣接している座標を求める その1【NumPy】


Python 勉強中です

時間があるので Python の勉強をしています。NumPyってのが、いい感じにややこしくて、おもしろいなぁ。というレベル感の記事です。Google Colaboratory で動作させながら記事を書いてます。

数学的な知見があまりないので「Numpy 隣接」などで検索してもそれじゃない感じの概念の解説しかみつけられなかったので、自分で考えてみたので残しておこうと思います。
ゲーム系のプログラマーでもないし座標計算?な、ゆるい記事です。

まずは2次元配列を作成して表示する。

import numpy as np

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

これだけで簡単にできるのはなかなか嬉しい。Python, NumPy が人気なのも納得。

まずは6に隣接する1,5,7,11を求めていきます。
6の座標は(1,1)です。上下左右とはそれぞれ(-1,0)(+1,0)(0,-1)(0,+1)を座標と演算して(0,1)(2,1)(1,0)(1,2)ですね。
ファーストステップは点pから4つの座標がわかるところまで。可読性がいいかなと思い、座標はタプルを利用します。

p = (1,1)
print( l[p], l[(0,1)], l[(2,1)], l[(1,0)], l[(1,2)] )
# 6 1 11 5 7

最初の呪文「ブロードキャスト!」

ここで早速、NumPyさんの呪文「ブロードキャスト」が活躍しそうです。

細かい解説はしませんが、要素が一つのものと複数のものとを演算するといい感じにしてくれる呪文だと思っています。
そもそも(1,1)+(0,1)(1,2)を簡単に求めてくれるのも NumPyさんのおかげ。以下にコードを

print( p + (0,1) )
# (1, 1, 0, 1) # 通常タプル同士の + はただ連結される

print( p + np.array((0,1)) )
# [1 2] # どちらかがNumPy化?しているとそれぞれ足してくれる。不思議!

# Up Right Down Left # 時計回り
u_r_d_l = [(-1,0), (0,1), (1,0), (0,-1)]

print( p + np.array(u_r_d_l) )
# [[0 1]
#  [1 2]
#  [2 1]
#  [1 0]] # アメージング!

for文っぽいループなしに4つの座標の演算結果をゲットすることができました。コメントでNumPy化という謎の言葉を使いましたが、みんなどんな感じで呼んでますか?うちらはこうだな、などなどありましたらぜひコメントください。
おもしろいと感じたのは「+」の前後どちらかだけでもうまくいくところですね。

ちなみに座標部分はタプル(1,1)で記述していますが、リスト[1,1]でも問題ないはずです。座標を扱う際にタプル(a,b)に特別な機能があるわけではない(と思う)。同じように Python 勉強中の方がいたら混乱するかもしれないので、念のため言及しておきます。断言してないのはまだ僕も勉強中で書いている記事なので。

また演算結果はタプルではなくなってしまいますが[*map(tuple, xxx)]で、xxx に入れるとタプルを持ったリストに変換できます。わざわざタプルにする必要がない場合も、print()で表示したときに見やすいかもなって時などに、この方法を利用していきます。
map関数?はっ?て人も、このコードは[[],[],...][(),(),...]に変換してるんだなぐらいで考えてればOKです。(この記事では map関数の説明をする気がないです。。。ごめんなさい。ちなみにどなたか詳しい方でlist(map(...))[*map(...)]の違いってあるのか、ご教授いただけたら嬉しいです。)

# [[],[],...] → [(),(),...] へ変換
print( [*map(tuple, p + np.array(u_r_d_l)] )
# [(0, 1), (1, 2), (2, 1), (1, 0)]

これでかんせーい!ではないですね。

今は点p が中心あたりなので大丈夫ですが、これがはじっこにずれると範囲外の座標を示してしまいます。(-1,0)などのマイナス座標は Pythonはうまい具合に後ろから処理してくれますが、(3,0)(0,5)はエラーになってしまいます。
次の呪文で、この辺りのケアをしていきたいと思いますが、ここまでのコードをちょっと書き換えて一度、記述しておきます。まぁまぁなことやってるはずなのですが、短くてすごいですね。
2次元配列に壁の概念があって、点p にはじっこが入力されない場合はこれでおしまいですからね。

import numpy as np

l = np.arange(15).reshape(3,5)
p = (1,1)
u_r_d_l = np.array([(-1,0), (0,1), (1,0), (0,-1)])

print( [*map(tuple, p + u_r_d_l] )
# [(0, 1), (1, 2), (2, 1), (1, 0)]

思ってたよりこの後が長くなっているので、一度投稿しておきます。 つづく