
武蔵野個別指導塾の特徴
高校情報I モデルの妥当性—条件を変えて予測のずれと使える範囲を確かめる|武蔵野個別指導塾

到着後に受付する待ち列を計算し、未処理人数と開始者の待ち時間を使って条件変更によるずれを調べます。
よくあるのは、受付を表すモデルを試した高校生が、都合のよい条件の結果だけを採用する場面です。たまたま待ち人数が少なかった結果を見ても、混雑する条件で同じことが言えるとは限りません。到着のしかたや受付の能力を変え、何を予測でき、どこから先は言えないかを考えます。
ここでの受付と記録は、自分で決めた架空のものです。実際の受付を測定したデータではありません。モデルの妥当性とは、目的に照らして、そのモデルの前提と結果が使おうとする場面に十分合っているかという考え方です。プログラムが正しく動くことと、現実に役立つ予測であることは、別々に調べます。
どの問いに答えるモデルか
問われ方には、例えば次の3種類があります。1つ目は、到着時刻と受付の規則から、各時刻の待ち人数と受付開始時の待ち時間を求める問いです。2つ目は、到着や受付能力を変えたときに結果がどう変わるかを比較する問いです。3つ目は、予測と観測のずれを手がかりに、前提のどこを見直すかを説明する問いです。
このモデルの目的は、決めた時間までに何人がまだ受付を始めていないかと、受付を始めた人が何時刻分待ったかを調べることです。1人の受付にかかる時間の途中経過や、受付終了後の行動は表しません。統計的な推定も扱いません。結果は、入力した到着列と規則の下での予測です。
時刻は0から始まる整数です。到着列の添字が時刻で、各値はその時刻に到着した人数です。空の列も許します。各時刻に到着者を列の末尾へ加え、その後に先頭から最大capacity人の受付を開始します。同時に到着した人は同じ時刻に並ぶので、互いの順番は待ち時間の集計に影響しません。受付を開始した人は、その時刻の処理枠を使って列から出ます。次の時刻に処理枠は再びcapacity人分あります。
待ち時間は受付を開始した時刻から到着時刻を引いた整数です。最終時刻の受付後に列に残る人を未処理人数と呼び、待ち時間の一覧には入れません。途中で受付を始めた人の待ち時間と、まだ始めていない人の数を混ぜないためです。未処理の人は待ち時間が0なのではなく、受付開始時刻がまだ決まっていません。
照合する対象と前提
図1のように、入力条件からモデルの予測を作り、同じ条件で集めた観測記録と比べます。観測記録がなければ、モデルの内部で計算が合うかを調べられても、現実への妥当性は判断できません。予測と観測が一致しても、調べた条件の外まで一致すると証明したことにはなりません。

図1の照合では、まず定義をそろえます。例えば観測の「待ち人数」が受付中の人を含むなら、このモデルの未処理人数とそのまま比較できません。観測時刻が受付前か受付後かも合わせます。ずれは予測値から観測値を引いた符号付きの値で表せますが、符号だけで原因は決まりません。到着列の記録違い、受付能力の変動、処理順の違いなどを候補にして、追加の記録で区別します。
モデルの実装の正しさを調べることは、同じルールを手計算とプログラムでたどる作業です。一方、モデルの妥当性を調べるには、そのルールが対象の目的に合うかを問います。架空の記録に対する一致は、照合方法の練習になりますが、実際の受付を予測できる証拠にはなりません。たとえ一例が一致しても、別の到着の集中や受付能力の変化を試す必要があります。
基本例題:受付後の列を追う
時刻0、1、2の到着人数を順に[2, 0, 2]、各時刻の受付開始枠を1人とします。時刻2の受付後の未処理人数を整数で、受付を始めた人の待ち時間を開始順のリストで答えます。列には到着時刻を記録し、同じ時刻の人も別々に保持します。
時刻0は2人が到着し、1人がすぐ受付を始めます。待ち時間は0で、到着時刻0の人が1人残ります。時刻1は到着がなく、その1人が受付を始めるので待ち時間は1です。時刻2は2人が到着し、1人がすぐ受付を始めます。もう1人が残るため、答えは未処理1人、待ち時間[0, 1, 0]です。到着は計4人、受付開始は3人、残りは1人なので人数も一致します。

図2のように、時刻ごとの到着人数と受付後の待ち人数を分けて読みます。次の自作プログラムは、この規則を有限の到着列に適用します。入力は整数のリストと非負整数の受付枠です。負の人数、整数以外、リスト以外はエラーにします。真偽値はPythonでは整数として扱われることがありますが、人数としては受け付けません。
def simulate(arrivals, capacity):
if type(arrivals) is not list:
raise ValueError("arrivals")
if type(capacity) is not int or capacity < 0:
raise ValueError("capacity")
for count in arrivals:
if type(count) is not int or count < 0:
raise ValueError("arrival")
queue = []
waits = []
history = []
for time in range(len(arrivals)):
for person in range(arrivals[time]):
queue.append(time)
served = min(capacity, len(queue))
for person in range(served):
arrived = queue.pop(0)
waits.append(time - arrived)
history.append(len(queue))
return len(queue), waits, history
print(simulate([2, 0, 2], 1))
Python 3.14.8での入力は[2, 0, 2]と1、期待出力と実際の出力はいずれも (1, [0, 1, 0], [1, 0, 1]) です。最後のリストは各時刻の受付後の人数です。3つの返り値の順序は未処理人数、開始順の待ち時間、各時刻の受付後の人数です。
最初の2つのifは到着列の型と受付枠の型・範囲を確かめます。次のforは到着人数を1つずつ調べ、条件外なら処理を始める前にエラーにします。queueは待っている人の到着時刻、waitsは受付を始めた人の待ち時間、historyは各時刻の受付後の人数を保存します。いずれも最初は空です。
外側のforは時刻を0から列の長さの直前まで進めます。内側の最初のforは、その時刻に到着した人数分だけ時刻を末尾へ追加します。servedには受付枠と列の長さの小さい方を入れるので、存在しない人を取り出しません。次のforは先頭の到着時刻を取り出し、現在時刻との差をwaitsに加えます。受付後の列の長さをhistoryに記録し、最後に3つを返します。printは結果を表示する呼出しであり、関数の返り値そのものではありません。
各時刻の開始前には、列にはそれ以前に到着し、まだ受付を始めていない人が到着順に残っています。新しい人を末尾に置き、受付開始者を先頭から除くので、この条件は次の時刻にも保たれます。開始者は1回だけ列から出るため、到着人数の合計は開始者数と未処理人数の和に等しくなります。到着列と各人数は有限の整数なので、各forは有限回で終わります。この説明は規則についての根拠であり、数例の実行だけを一般的な証明にしていません。
条件を変えて比較する
標準例題では、同じ到着列[2, 0, 2]で受付枠を2人に変えます。答えの形は基本例題と同じです。時刻0に2人とも開始し、時刻1は誰もいません。時刻2にも2人とも開始します。未処理は0人、待ち時間は[0, 0, 0, 0]、各時刻の受付後の人数は[0, 0, 0]です。実際の出力も (0, [0, 0, 0, 0], [0, 0, 0]) でした。到着4人が全員開始し、その場で開始したため待ち時間は全員0です。
同じ受付枠1人で、到着列を[1, 1, 2]に変える試行もします。結果は (1, [0, 0, 0], [0, 0, 1]) です。最終的な未処理人数は基本例題と同じ1人でも、開始した人の待ち時間は異なります。未処理人数だけで比較すると、この差は見えません。2つの試行は乱数を用いず、条件を自分で変えた別々の試行です。同じ条件を繰り返すと同じ結果になる確定モデルなので、繰り返しの回数を増やしても到着の多様さは自動的には生まれません。
少し難しい例題では、基本例題と同じ条件で、架空の観測記録が「時刻2の受付後に2人未処理、受付開始者の待ち時間は[0, 1]」だったとします。予測は1人と[0, 1, 0]です。未処理人数の予測から観測を引いたずれは-1人、受付開始者数のずれは1人です。全到着4人について、観測側は開始2人と未処理2人に分かれるので人数は合っています。観測値は実測を装うものではなく、比較のための自作条件です。
このずれから、時刻2の受付枠が実際には0人だった可能性を考えられます。同じ到着列で時刻ごとに枠を[1, 1, 0]とすれば、予測は観測と一致します。しかし、到着時刻の記録や受付後の人数の数え方が異なっていた可能性も残ります。1つのずれから原因を断定せず、受付開始時刻や枠の記録を別に照合します。また、時刻ごとの枠を受け取る新しいモデルは、元の固定枠の関数とは別の仕様です。
その試行を実際に実行した自作コードは次のとおりです。入力は人数列と同じ長さの枠列で、どちらも非負整数のリストとします。値の検査は最初に行い、長さが違う場合もエラーにします。
def varying(arrivals, capacities):
if type(arrivals) is not list or type(capacities) is not list:
raise ValueError("lists")
if len(arrivals) != len(capacities):
raise ValueError("length")
for value in arrivals + capacities:
if type(value) is not int or value < 0:
raise ValueError("value")
queue = []
waits = []
history = []
for time in range(len(arrivals)):
for person in range(arrivals[time]):
queue.append(time)
served = min(capacities[time], len(queue))
for person in range(served):
arrived = queue.pop(0)
waits.append(time - arrived)
history.append(len(queue))
return len(queue), waits, history
print(varying([2, 0, 2], [1, 1, 0]))
Python 3.14.8での期待出力と実際の出力は (2, [0, 1], [1, 0, 2]) です。型と長さ、各値の検査後、3つの空リストを用意します。各時刻で到着者を追加し、その時刻の枠と待ち人数の小さい方だけ先頭から取り出して待ち時間を記録します。受付後の人数を記録し、最後に3つを返します。有限の列を順に処理するので終了し、待ち列に未開始者だけが残る条件も先ほどと同じです。空の2列なら (0, [], []) になります。
ずれを修正して再実行する
基本例題で、到着より先に受付を行う誤った規則を使ったと考えます。時刻0にはまだ誰も列にいないため、到着した2人は両方残ります。時刻1に1人が開始して待ち時間は1、時刻2に残りの1人が開始して待ち時間は2です。その後に時刻2の2人が到着するので、結果は未処理2人、待ち時間[1, 2]、履歴[2, 1, 2]です。到着4人と開始2人と残り2人の人数は合いますが、当日到着した人もその時刻に受付できるという仕様に反します。

図3のように、原因は計算式ではなく更新順です。修正では到着を先に追加し、その後で受付を開始します。基本例題の関数を再実行すると (1, [0, 1, 0], [1, 0, 1]) となり、手計算と一致します。誤答の人数保存が正しいからといって、順序まで正しいとは言えません。逆に、正しい順序の実装であっても、架空の規則が実際の受付に合うとは限りません。
成功と失敗の試験を設計する
結果を比べる前に、試験の入口と出口を決めます。下の正常な入力は、基本例題の関数に渡します。Python 3.14.8でそれぞれを実行し、期待値と実際の値が一致しました。空列は時刻がないので履歴も空、受付枠0は誰も開始しないという仕様です。
| 入力と条件 | 期待値と実際の返り値 | 場面での照合 |
|---|---|---|
| [], 1 | (0, [], []) | 到着0人、開始0人、未処理0人 |
| [1], 1 | (0, [0], [0]) | 1人が同時刻に開始 |
| [2], 1 | (1, [0], [1]) | 2人のうち1人が残る |
| [1, 1], 0 | (2, [], [1, 2]) | 2人とも開始せず残る |
| [0, 0], 2 | (0, [], [0, 0]) | 到着がない2時刻を記録 |
| [1, 1], 1 | (0, [0, 0], [0, 0]) | 同人数の到着が各時刻で処理される |
失敗側も同じ6条件で試験します。関数は最初に入力を検査するため、途中までの結果を成功値として返しません。下の4件は実際に呼び出してValueErrorを確認しました。残る2件は外の道具を利用する場合の設計上の試験で、この関数自体は外部通信を行いません。タイムアウトや道具の故障を実測したとは扱わず、失敗を注入する試験として期待する振る舞いを定めます。
| 失敗条件 | 期待する結果と扱い | 確認すること |
|---|---|---|
| 到着列に-1 | ValueError: arrival | 負の人数を結果に混ぜない |
| 到着列に文字 | ValueError: arrival | 壊れた要素を拒む |
| 到着列がリスト以外 | ValueError: arrivals | 列として扱わない |
| 受付枠が負 | ValueError: capacity | 不正な処理能力を拒む |
| 外部の記録取得が時間切れ | 失敗を通知し、予測と比較しない | 古い観測値を今回の値と誤認しない |
| 外部の道具がエラーを返す | 失敗を通知し、入力を再確認する | 欠けた記録を0人と見なさない |
時間切れの後に再試行する場合も、どの時刻の記録を取得したかを確かめ、結果を二重に数えない設計が必要です。例外や故障が起きたときに0人という値を代入すると、処理が成功したように見え、妥当性の評価まで誤ります。失敗した試行は理由と条件を残し、成功した試行と区別します。なお、外部取得の試験はここでは実装していないので、実環境での回復を保証するものではありません。
練習問題
次の5問は、基本例題の関数の規則を使います。各問の到着列は時刻0から始まり、各時刻は到着後に受付します。答えは未処理人数、開始順の待ち時間、受付後の人数の順の組で書きます。いずれも架空の場面です。
- 整理券の窓口で到着列が[3, 1]、受付枠が2人のときの組を求めます。
- 道具の貸出窓口で到着列が[0, 2, 1]、受付枠が1人のときの組を求めます。
- 展示受付で到着列が[2, 1, 0]、受付枠が0人のときの組を求めます。
- 資料の受渡窓口で到着列が[1, 0, 2]、受付枠が2人のときの組を求めます。
- 案内窓口で到着列が[4, 0, 0]、受付枠が1人のときの組を求めます。
練習の答えと照合
(0, [0, 0, 1, 0], [1, 0])です。時刻0に3人中2人が開始し1人残り、時刻1は残った人と新しい1人が開始します。到着4人、開始4人、残り0人です。(1, [0, 1], [0, 1, 1])です。時刻1に2人到着し1人が残ります。時刻2には先に待っていた人が開始し、新しい1人が残ります。到着3人のうち2人が開始し、1人が残ります。(3, [], [2, 3, 3])です。受付枠が0なので開始者はなく、到着3人が全員列に残ります。待ち時間の空リストは待ち時間が0という意味ではありません。(0, [0, 0, 0], [0, 0, 0])です。時刻0の1人と時刻2の2人は各時刻の枠に収まり、到着3人が全員すぐ開始します。(1, [0, 1, 2], [3, 2, 1])です。時刻0に4人が並び、3時刻で順に3人が開始します。残る1人の待ち時間はまだ確定していません。
Python 3.14.8で5問を順に実行し、上の各問の最終的な組と一致しました。待ち時間の各値は開始時刻から到着時刻を引いて確かめ、各問の到着人数を開始者数と残り人数に戻して照合しています。
覚えておくこと
- 前提と観測時点をそろえて予測を比較します。
- 未処理人数と、開始者の待ち時間を別々に記録します。
- 条件を変えた複数の試行で、使える範囲を探ります。
- 一致は調べた条件での根拠であり、すべての条件の保証ではありません。
- 失敗した入力や取得結果を成功値に混ぜません。
説明が止まったときの見直し
未処理人数と待ち時間を混同したときは、列に残った人に受付開始時刻があるかを尋ねます。時刻ごとに到着、受付開始、残りの3つを声に出し、各行の受付後の人数が次の行へ渡ることを確かめると、更新順の間違いが見えます。
個別指導では、まず本人に観測の人数が受付前と受付後のどちらかを説明してもらい、処理表の最初に食い違った時刻を一緒に探します。次に、入力条件を1つ変えた予測と、失敗したときの扱いを本人の言葉で説明できるか確かめます。条件とずれを結びつけて初めて、モデルの妥当性を判断できます。
読んで終わりにするか、ここから変えるか。
武蔵境駅徒歩30秒、講師1人に生徒1人の完全1対1。授業のあいだ、先生は隣を離れません。いまの詰まりを、無料相談でそのまま話してください。
まず15分だけ電話で話したい方は、無料電話相談(15分)から。塾からお電話を差し上げます。
本気になった日が、入塾の日。
お子様の教育、迷ったら「丸投げ」してください。必ず、期待以上の結果で返します。武蔵野で育む、武蔵境で学ぶ。
電話は年中無休。365日、24時間、お電話可能。




