センター試験「情報関係基礎」2018年の迷路をPythonで書く
タイトル画像のような迷路を解く。「解く」というのは,スタート(S)からゴール(G)まで,戻らずにいく経路を求めるという意味だ。
白が道。たとえば (X,Y) が(2,2) のマスは行き止まりのなので,(3,4) から左にいく経路は進めない。このような,袋小路になるマスを調べて塗っていくと,右のように経路が判明する。
この問題をCindyscriptで書いたのが前回のnote。
問題文などはこちらを参照していただきたい。
この note では,これをPython で書いたときにどんなところで難航したか,を書いておく。
袋小路を塗る手続きはほとんど問題がない
問題のコードは次にようになっている。
Cindyscript ではこのように書いた
nutta=1;
while(nutta>0,
nutta=0;
repeat(tate-2,y,start->2,
repeat(yoko-2,x,start->2,
s=Masu_(x-1)_y+Masu_(x+1)_y+Masu_x_(y-1)+Masu_x_(y+1);
if(Masu_x_y==0 & s==3,
Masu_x_y=1;
nutta=1;
Color_x_y=0.3;
);
);
);
);
ここで,リスト Color は,塗ったマスを灰色で表示するためのものであり,元の問題にはない。
Python ではこうなる
nutta = 1
while nutta > 0:
nutta = 0
for y in range(1,tate-1):
for x in range(1,yoko-1):
s=Masu[x-1][y]+Masu[x+1][y]+Masu[x][y-1]+Masu[x][y+1]
if Masu[x][y] == 0 and s==3 :
Masu[x][y] = 1
nutta = 1
Color[x][y] = 0.3
書法以外はほぼ同じだ。
動作を確かめるために,ひとまず,print でコンソールに表示してみた。灰色表示はできないがとりあえず。
def dispmasutext(mat):
for y in range(tate):
for x in range(yoko):
if mat[x][y] != 0:
wd = "■"
else :
wd = "□"
print(wd,end=" ")
print(" ")
を関数として定義しておき
dispmasutext(Color0)
dispmasutext(Color)
で表示する。
ところが同じものしか表示されない。
Color0 と Color をそのままprint で表示してみるとColor0が Colorと同じものになっている。
解決するのに時間を要した。
Web で調べていて,わかったのは「Python では,Color0 = Masu では配列のコピーができない」ということだった。
Color0 = Masu では,Masu に加えた変更が,そのまま Color0 にも反映されてしまうのだ。「オブジェクト」としているためらしい。(現時点ではらしい,としか言えない。オブジェクトについての知識が少なく,それ以上の説明ができないからだ。いずれは調べざるを得ないだろう)
では,Color0 も全部要素を書かなくてはだめか。そこで見つかったのが,copy。さらに,2次元配列の場合は deepcopy。
もうひとつ,ここまで言及はしなかったが,初期設定で,2次元配列(行列)を転置するには,Python では Tメソッドを使う。
かくして,初期設定はこのように変わった。
import copy
Masu=np.array([
[1,1,1,1,1,1,1,1,1,1,1],
[1,0,1,0,1,0,0,1,0,0,1],
[1,0,1,0,1,1,0,1,0,1,1],
[1,0,0,0,0,1,0,0,0,0,1],
[1,1,0,1,1,1,1,0,1,0,1],
[1,0,0,0,0,0,0,0,1,0,1],
[1,0,1,1,1,1,1,1,1,0,1],
[1,0,0,0,0,1,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1,1]
])
Start = 3 # スタート位置−1 (配列インデックスが0からなので)
Goal = 7
yoko = len(Masu[0])
tate = len(Masu)
Masu[0][Start] = 0 # 表示のため,9ではなく0にする
Masu[tate-1][Goal] = 0
Masu = Masu.T
Color0 = copy.deepcopy(Masu)
Color = copy.deepcopy(Masu)
これで袋小路を塗る手続きは正しく動いた。
次は,塗ったマスを灰色で塗るために,matplotlib を使って表示してみる。matplotlib を使う方法は,2007年のブロック落としの図でやったので,同じようにやってみた。
def dispmasu2(mat):
for y in range(tate):
for x in range(yoko):
if mat[x][y] > 0:
wd = "■"
else:
wd = "□"
plt.text(x + 0.3,tate - 1 - y + 0.4, wd,fontsize=30)
plt.text(Start+ 0.5,tate - 0.6, "S",fontsize=20)
plt.text(Goal+ 0.5,0.4, "G",fontsize=20)
plt.xlim(0,yoko)
plt.ylim(0,tate)
plt.show()
ブロック落としの時のように,引数に表示位置をいれて,横に並べてみたが,うまくいかない。引数をなしにして dispmasu2() の中でColor0 と Color を両方表示してしまえば次のようになる。
いずれにしても,いまのところ,「塗ったマスを灰色で表示」することはできていない。できたのはずっとあと。
また,表示の具合がいまひとつだ。縦横の罫線の幅が異なる。テキストの表示ではどうしてもこうなる。となれば,グラフィックで長方形を描く方法を考えるしかない。しかし,それがわからない。
ここでしばらく止まっていた。その間に,本を1冊買った。それを読んでもわからない。
Webで探して,matplotlib.patches を使う方法を見つけた。
サンプルで,長方形や円を表示しているページがあるので,それを参考にする。
長方形を描くのに
rect = patch.Rectangle(xy=(3,2), width=1, height=1, facecolor="red",fill=True)
のようにすることはわかった。
しかし,これを for ループのなかでやってみるとうまくいかない。常に rect = なのだから,長方形はひとつしか描かれないのだろう。(だろう,とはつまりわかっていない状態)
変数(オブジェクトか)に代入する形だから,そのような変数は複数必要。となれば配列か。
ためしに
rect = list(range(5))
でリストを作って,テストコードを書いてみたらうまくいった。しかし,実際は2次元配列だ。2次元配列の rect をどう作っておくか。調べると,リスト内包表記を使えばよいらしい。サンプルコードを真似してやってみたが,うまくいかない。ひとまず,1次元の配列を使って表示できるところまではいった。
その後,2次元配列でもできたのだが,なかなか簡単にはいかなかったわけだ。
なお,色指定については,black などの色名を使うようなので,色名リストを作っておいてそれを利用するようにした。
Fillcolor=["white","black","gray"]
したがって,袋小路だったときの塗り色を0.3 ではなく2に変更。0.3はRGBの値,2は配列のインデックスで意味は全く違う。
かくして,数日間の格闘の末の完成品がこちら。問題の形と比べてみよう。
def dispmasu(mat):
fig, ax = plt.subplots()
rect = [[""] * tate for i in range(yoko)]
n=0;
for y in range(tate):
for x in range(yoko):
fcol = mat[x][y]
if mat[x][y] > 0:
rect[x][y] = patch.Rectangle(xy=(x, tate-y-1), width=1, height=1, facecolor=Fillcolor[fcol],fill=True)
else:
rect[x][y] = patch.Rectangle(xy=(x, tate-y-1), width=1, height=1, fill=False)
ax.add_patch(rect[x][y])
ax.set_xlim(0, yoko)
ax.set_ylim(0, tate)
ax.set_aspect('equal')
plt.text(Start+ 0.3,tate - 0.8, "S",fontsize=20)
plt.text(Goal+ 0.3,0.2, "G",fontsize=20)
plt.show()
matplotlib.patches を使わず,matplotlibでやるには,
plt.text(x + 0.3,tate - 1 - y + 0.4, wd,fontsize=30)
に,color オプションで色をつける方法もわかったので,いずれの方法でもできたことにはなる。 表示としてはmatplotlib.patches を使った方がいい。
しかし,これで「めでたし,めでたし」とはいかないのだ。
なぜなら,でき上がった表示のためのコードは,センター試験の問題のコードとは似ても似つかぬものになっているではないか。これでは授業で「問題を見て書きなさい」とはいえない。できた状態で,袋小路を塗る部分だけを書かせるしかない。
Cindyscriptなら簡単で同じ形をしているから,表示のコードもところどころを空欄にして書かせることができる。Pythonではそうはいかないわけだ。