情報Ⅰ 共通テスト試作問題の第4問をPythonで書く
交通渋滞のシミュレーションである。
前回,愛知県の小牧高校の井出先生の取り組みについて書いた。第4問の交通渋滞のシミュレーションをExcel で作り,授業2コマを使って実習したという報告である。その前後で試作問題を解かせて解答状況の変化を調べている。
そこで,これをPythonで書いてみた。授業はやらないが,授業を想定するなら生徒に何をやらせるか,ということについても書いていく。
まず,設計をする。目標は,図3のグラフを描くことだ
こちらが問題に載っている図。
問題の分析と設計
問題の条件を整理し,どんな変数が必要かを決めていく。井出先生のExcelの表も参考にする。
条件と,それをあらわす変数をリストアップしていく。
・時刻単位は10秒とする。
・青信号のとき国道は20台,県道は10台が通過できる。
→Passtime_s = 20 , Passtime_p = 10 定数なので先頭を大文字にしておく
・国道の青信号の時間は60秒,赤信号は30秒
→Signal_b = 60 , Signal_r = 30
・10秒間に到着する車は国道が8〜12台,県道は3〜4台
→arrive_s = randint(5)+8 でランダムに発生
→arrive_p = randint(3)+2
・国道側の信号の状態(青か否か)
→ signal = True / False
・渋滞している車の台数
→ wait_s 国道
→ wait_p 県道
次に,状況の変化を表にする。井出先生の表にあるように,信号の色も必要になりそうだ。しかし,欄の数はもっと少なくても済む。
授業では,まず条件を読ませた後,この表を作らせる。ゼロから作らせるのは無理なので,いくつかを空欄にしたものを渡して考えさせるのがよいだろう。
アルゴリズムを考える
表ができたということは,アルゴリズムの作成に近づけたということだ。
・国道の渋滞台数は,時刻60まで0だ,そのあとは到着台数で加算されていく。
・県道の渋滞台数は,時刻60まで到着台数で加算されていく。そのあとは計算で求められる。ここが大切。表を作ることによって計算の仕方がわかる。ただし,生徒は,表は作れるが計算式はわからないかもしれない。そのあたりが,授業のもっていきどころだ。国道渋滞台数は次の計算で求められる。
信号が青ならば,現在の渋滞台数+到着台数ー20 ただし,結果が負ならば0
これは, 現在の渋滞台数+到着台数ー20 と 0 の大きい方 と考えられる。
信号が赤ならば 現在の渋滞台数+到着台数
県道渋滞台数は 信号の青/赤を入れ替えて同様に計算できる
これがわかればコーディングができる。
乱数を発生し,変数の値を決めていく
表の1行分をリストにし,そのリストを要素とするリストを時間進行にしたがって増やしていく。なお,Numpy を import しておく。
Mat = [[0,arrive_s,wait_s,arrive_p,wait_p]] # 状態リスト
for t in range(1,Totaltime):
arrive_s = np.random.randint(5) + 8
arrive_p = np.random.randint(2) + 3
mod = (t-1) % (Signal_b + Signal_r) + 1
signal = (mod <= Signal_b)
if signal: # 青信号ならば
wait_s = max([wait_s + arrive_s - Passtime_s,0])
wait_p = wait_p + arrive_p
else:
wait_s = wait_s + arrive_s
wait_p = max([wait_p + arrive_p - Passtime_p,0])
Mat.append([t*10,arrive_s,wait_s,arrive_p,wait_p])
実際にはゼロから書かせるのは難しいだろうから,何箇所かを空欄にしたテキストを渡して考えさせるのがよい。打ち込みは全部でもよいし,空欄の箇所だけでもよい。生徒の状況に応じて臨機応変に。
なお,生徒に考えさせるときちょっと難しいのが,2行目の for 文だ。range でどこからどこまで変化させればよいか。初めに作った表で考えさせる。
次の arrive_s を決めるランダムの発生の式も,生徒には難しめだろう。
np.random.randint(n) で,0以上 n未満の整数ができる。では5から8(5以上8以下)にするには? これがわからない生徒が意外に多い。
信号の青/赤 の切り替えも生徒には難しい。「余りを使う」ことがわかっても,% で余りを求めるとき,表を見て,「9までがワンセット」と考えるならば,まず1を引いておいて余りを求め,それに1を加える,のだが,それが思いつきにくい。次が自然数の列と,9で割った余りを % で求めた値,実際に欲しい値の関係だ。
1 , 2 , 3 , 4 , 5 , 6 ,7 , 8 , 9 , 10 , 11
% 9 の値 1 , 2 , 3 , 4 , 5 , 6 ,7 , 8 , 0 , 1 , 2
欲しい値 1 , 2 , 3 , 4 , 5 , 6 ,7 , 8 , 9 , 1 , 2
さらに,
wait_s = max([wait_s + arrive_s - Passtime_s,0])
はリスト処理を使っているが,生徒実習では条件分岐で
if wait_s + arrive_s - Passtime_s > 0 :
wait_s = wait_s + arrive_s - Passtime_s
else:
wait_s = 0
とする方がよいかもしれない。
わずかこれだけのコードでも,生徒には難しいところがあるから,教材化と授業はそのつもりで進めないといけない。
結果を表示して確かめる
結果を表示してみよう。はじめは Totaltime を少なくしておく。20くらいでよい。
for i in range(Totaltime):
print(Mat[I])
値を順に見ていって,正しい進行になっているかを確認する。デバッグのためにこの確認作業は大切だ。
グラフを描く
値に間違いがないなら,次にこれをグラフにする。
描画面の設定から始めるのは生徒には難しく,時間もかかるので,そこは教員が作っておく。必要なのは,x の値の列を作り,Matrix から,対応する渋滞台数を取り出していくことだ。国道と県道の2つがある。
x = list(np.arange(0,Totaltime*10,10))
y_s = [] // 国道の渋滞数リスト
y_p = [] // 県道の渋滞数リスト
for i in range(Totaltime):
y_s.append(Mat[i][2])
y_p.append(Mat[i][4])
plt.plot(x,y_s,'r') // 赤で折れ線グラフ
plt.plot(x,y_s,'or') // 赤い点を打つ
plt.plot(x,y_p,'b') // 青で表示
plt.plot(x,y_p,'ob')
plt.show()
次のようなグラフができる。
このグラフの妥当性も確かめよう。問題の図と似ている,というだけではダメだ。
左下を拡大すると次のようになっている。
国道のラインは,信号が切り替わる 60 秒までが0である。
信号がまた青になる90秒,180秒のところが,赤の山と青の谷になっている。ここに着目することが,問題(ア)を解く鍵であった。もしこれがずれていたらどこかにバグがあることになる。
ということは,このように実行結果を確認させることで,グラフのどこに着目すべきなのかがわかるということだ。ただ問題のグラフを読むのと,こうしてプログラムを作ってみて確認のためにグラフを読んでいくのとでは理解の度合いが違うだろう。
条件を変えてシミュレーション
問題にあるのは次の4つだ。
Signal_b と Signal_r を書き換えて実行すると,順に次のようになる。
これも,左下に着目して,赤の山と青の谷になっているところの時刻を見ていく。4番目,すなわち3の条件が,問題の図4に合うことがわかる。
最後に,プログラム全体を載せておこう。
import numpy as np
import matplotlib.pyplot as plt
Totaltime = 100 # 観測時間(10秒単位)
plt.figure(figsize=(20,7))
plt.axis([0,Totaltime*10,0,70])
plt.xticks([x*30 for x in range(Totaltime // 3)])
plt.yticks([y*10 for y in range(7)])
plt.grid(axis = 'y')
Passtime_s=20 #国道の通過可能台数
Passtime_p=10 #県道の通過可能台数
Signal_b = 5 # 青信号の時間:単位10秒
Signal_r = 4 # 赤信号の時間
signal = True # 青信号か
arrive_s = 0 # 国道の車の到着台数
arrive_p = 0 # 県道 到着台数
wait_s = 0 # 国道渋滞台数
wait_p = 0 # 県道渋滞台数
Mat = [[0,arrive_s,wait_s,arrive_p,wait_p]] # 状態リスト
for t in range(1,Totaltime):
arrive_s = np.random.randint(5) + 8
arrive_p = np.random.randint(2) + 3
mod = (t-1) % (Signal_b + Signal_r) + 1
signal = (mod <= Signal_b)
if signal: # 青信号ならば
if wait_s + arrive_s - Passtime_s > 0 :
wait_s = wait_s + arrive_s - Passtime_s
else:
wait_s = 0
wait_p = wait_p + arrive_p
else:
wait_s = wait_s + arrive_s
wait_p = max([wait_p + arrive_p - Passtime_p,0]) # リスト処理を使う
Mat.append([t*10,arrive_s,wait_s,arrive_p,wait_p])
#for i in range(Totaltime):
# print(Mat[i])
x = list(np.arange(0,Totaltime*10,10))
y_s = []
y_p = []
for i in range(Totaltime):
y_s.append(Mat[i][2])
y_p.append(Mat[i][4])
plt.plot(x,y_s,'r')
plt.plot(x,y_s,'or')
plt.plot(x,y_p,'b')
plt.plot(x,y_p,'ob')
plt.show()