見出し画像

センター試験「情報関係基礎」2018年の迷路をPythonで書く

 タイトル画像のような迷路を解く。「解く」というのは,スタート(S)からゴール(G)まで,戻らずにいく経路を求めるという意味だ。
 白が道。たとえば (X,Y) が(2,2) のマスは行き止まりのなので,(3,4) から左にいく経路は進めない。このような,袋小路になるマスを調べて塗っていくと,右のように経路が判明する。

画像1

この問題をCindyscriptで書いたのが前回のnote。
問題文などはこちらを参照していただきたい。
この note では,これをPython で書いたときにどんなところで難航したか,を書いておく。

袋小路を塗る手続きはほとんど問題がない

問題のコードは次にようになっている。

画像2

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 を両方表示してしまえば次のようになる。

画像4

いずれにしても,いまのところ,「塗ったマスを灰色で表示」することはできていない。できたのはずっとあと。
 また,表示の具合がいまひとつだ。縦横の罫線の幅が異なる。テキストの表示ではどうしてもこうなる。となれば,グラフィックで長方形を描く方法を考えるしかない。しかし,それがわからない。
 ここでしばらく止まっていた。その間に,本を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は配列のインデックスで意味は全く違う。

かくして,数日間の格闘の末の完成品がこちら。問題の形と比べてみよう。

画像6

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()

画像5


matplotlib.patches を使わず,matplotlibでやるには,
plt.text(x + 0.3,tate - 1 - y + 0.4, wd,fontsize=30)
に,color オプションで色をつける方法もわかったので,いずれの方法でもできたことにはなる。 表示としてはmatplotlib.patches を使った方がいい。

 しかし,これで「めでたし,めでたし」とはいかないのだ。
なぜなら,でき上がった表示のためのコードは,センター試験の問題のコードとは似ても似つかぬものになっているではないか。これでは授業で「問題を見て書きなさい」とはいえない。できた状態で,袋小路を塗る部分だけを書かせるしかない。
Cindyscriptなら簡単で同じ形をしているから,表示のコードもところどころを空欄にして書かせることができる。Pythonではそうはいかないわけだ。