
武蔵野個別指導塾の特徴
高校情報I プログラムの検証—期待値と実行値を比べて最初の食い違いを探す|武蔵野個別指導塾

上限に一致する値を集計する例で、期待値と途中状態の最初のずれから条件式の修正へ進みます。
よくあるのは、高校生が最後の出力だけを見て修正箇所を当てようとする場面です。答えが違ったとき、最後に表示した行を変えたくなるかもしれません。しかし、表示に使われた合計が、その前の反復ですでに違っていることもあります。期待した途中の値と実際の値を並べ、最初にずれた操作まで戻ると、調べる範囲を狭められます。
何を確かめる問題なのか
プログラムの検証には、少なくとも3つの問われ方があります。1つ目は、仕様から入力と期待される出力を決める問題です。正常な入力の代表例に加え、条件に一致する境界、要素がない場合、前提外の値を選びます。2つ目は、処理表の空欄を埋め、期待した状態と実行時の状態が初めて異なる箇所を見つける問題です。3つ目は、原因を直して同じ入力と別の入力で再実行し、修正によって別の条件を壊していないかを調べる問題です。
以下では、非負の整数で表す作業時間の一覧から、指定した上限以下の値の合計と件数を返します。入力の順番は変えず、同じ値が複数あればそれぞれ数えます。上限と各値は非負の整数とし、真偽値は整数として受け付けません。空の一覧は有効で、結果は合計0、件数0です。負の値や整数以外は前提外とし、エラーとして扱います。返り値は合計と件数の順の組です。平均は件数0で割れないので、この処理では返しません。
この条件は単なる注意書きではありません。たとえば空の一覧に対して「0件」と返す設計と「入力不足」としてエラーにする設計では、正しい結果が違います。試験する前に、入力の範囲と出力の形を定めなければ、実行結果が正しいかを判定できません。境界値は、上限の直前、一致、直後を別々に選ぶと、等号の抜けを見つけやすくなります。
期待値と実行値の距離を縮める
期待値はプログラムを動かす前に、仕様を使って求める結果です。実行値は実際に動かして得た結果です。両者が一致すればその入力については期待どおりですが、まだ試していない入力も正しいとは限りません。逆に不一致なら、実装の誤りだけでなく、期待値の計算や入力の転記にも原因がありえます。
図1のように、仕様から期待値を作る道と、入力をプログラムに渡して実行値を得る道を分けます。両者を比較し、違った場合は途中状態まで戻ります。期待値を実行結果に合わせて書き直すと、誤りを見落とします。

図1の比較では、数字だけでなく返り値の形やエラーの種類も対象です。たとえば合計と件数がともに0でも、空の組や文字列を返せば、呼出し側が合計と件数を取り出せません。例外を返す契約なら、途中の数値を出して処理を続けることも期待どおりではありません。
処理表には各反復の直前と直後を記録します。直前の合計と件数が正しいのに、ある値を扱った直後から違えば、その反復の条件判定や更新を調べます。最初の食い違いは原因の候補を狭めますが、それ自体が原因の証明ではありません。表の記録方法が誤っていないか、同じ入力を本当に渡したかも確認します。
基本例題:上限に一致する時間を含める
作業時間 [2, 5, 7] と上限 5 を渡します。上限以下の値の合計と件数を組で答え、各反復の状態を示してください。5は上限に一致するため含め、7は除きます。手で求めた期待値は (7, 2) です。元の一覧へ戻すと、採用した2と5の合計は7で、採用した要素は2件です。
def collect(times, limit):
if type(limit) is not int or limit < 0:
raise ValueError("invalid limit")
for value in times:
if type(value) is not int or value < 0:
raise ValueError("invalid time")
total = 0
count = 0
for value in times:
if value <= limit:
total = total + value
count = count + 1
return total, count
print(collect([2, 5, 7], 5))
def は一覧と上限を受け取る処理を定義します。最初の if は上限の型と範囲を調べ、前提外なら ValueError を送出して処理を止めます。次の反復では一覧の各値を検査し、不正な値を見つけた時点で同じ種類のエラーを送出します。ここではリストを入力とし、入力検査を集計より先に終えるため、不正な途中結果を返しません。
total と count を0で初期化し、次の反復で一覧の先頭から順に値を調べます。条件が真ならその値を合計に加え、件数を1増やします。偽ならどちらも更新しません。最後の return は組を呼出し側へ返し、print はその組を表示します。入力検査と集計はそれぞれ有限のリストを1回ずつ順に通るので、各反復は終わります。
対象 条件 合計 件数
開始 未判定 0 0
2 真 2 1
5 真 7 2
7 偽 7 2
図2のように、2と5で合計と件数が一緒に変化し、7では両方がそのままです。Python 3.14.8で上のコードを実行した出力は (7, 2) で、手で求めた期待値と一致します。

図2の最後の状態と返り値が同じであることまで確かめてください。途中表だけ正しくても、返す順番を逆にすると、利用する側には別の意味で伝わります。
標準例題:境界と空の一覧を試す
同じ処理に [4, 4, 6]、上限4を渡したとします。期待する組を答えてください。一致した4は2要素あるため、手計算では合計8、件数2です。6は上限より大きいので数えません。実行結果も (8, 2) です。元の一覧で採用された位置は先頭とその次であり、同じ値だからといって1件へまとめていません。
次に空の一覧と上限4を渡します。検査の反復と集計の反復はともに0回で、初期値のまま (0, 0) を返します。実行結果も (0, 0) です。これはデータが存在するのに条件に合う値がない場合と同じ出力ですが、入力の意味は異なります。どちらも試せば、空の一覧で要素を取り出そうとする誤りと、条件に合わない値を誤って足す誤りを別々に調べられます。
正しさを説明するときは、入力が仕様を満たすことを前提にします。集計の各反復が終わった時点で、処理済みの部分にある上限以下の値の合計と件数が total と count に入っている、と考えます。開始時は処理済み部分が空なので両方0です。次の値が条件を満たせば合計と件数を更新し、満たさなければ変えないため、この関係は保たれます。有限の全要素を処理した終了時には、処理済み部分が一覧全体です。これが返り値の理由であり、数例の成功だけから一般化しているわけではありません。
発展例題:不正な入力と外の処理を分ける
[3, -1] と上限3を渡す場合、期待する結果は数値の組ではなく ValueError です。検査は3を通過し、次の負の値で停止します。Pythonで実行すると実際に ValueError が送出されます。上限が負、上限が文字列、一覧に文字列や真偽値がある場合も同じエラーです。type を調べているのは、真偽値を数として扱わないという今回の契約を守るためです。
この関数はリストを前提とします。リスト以外の容器や、読み込みの途中で失敗する外の道具をそのまま渡す場合の動作は、この関数の契約には含めていません。外の道具から一覧を受け取る仕組みを作るなら、読み込みと集計を別の段階にし、読み込みの失敗を空の一覧と取り違えないようにします。たとえば、読み込みに成功して空だった場合は (0, 0)、読み込みが失敗した場合は失敗を呼出し側へ知らせる、と先に決めます。
時間切れも成功と区別します。外の道具が返答する前に制限時間を超えた場合、期待する状態は「時間切れ」であり、0件という集計結果ではありません。試験では、時間内に値を返す代役と、時間切れを知らせる代役を用意し、それぞれ呼出し側の表示や再試行の有無を確認します。再試行するなら回数を有限に定め、失敗が続いたときに停止することも試します。実際に待ち続けて確かめる必要はありません。
壊れた入力の試験では、読み込みそのものは成功しても内容が不正な場合を区別します。たとえば一覧の途中が文字列なら、集計を開始する前の検査でエラーになります。外の道具の失敗、内容の不正、時間切れは発生箇所が異なるため、同じ空の結果に置き換えると原因を追えません。例外の種類やどこまで処理したかを記録し、入力を勝手に修正して成功扱いにしない設計が必要です。
誤答から修正までの経路
次は、基本例題と同じ入力に対する誤った集計部分です。入力検査は正しい処理と同じとして、条件式だけを取り出します。
def wrong_collect(times, limit):
total = 0
count = 0
for value in times:
if value < limit:
total = total + value
count = count + 1
return total, count
print(wrong_collect([2, 5, 7], 5))
各行の役割は、合計と件数の初期化、一覧の走査、上限との比較、合計と件数の更新、組の返却、呼出しと表示です。実行値は (2, 1) で、期待値 (7, 2) と異なります。2の処理後はどちらも合計2、件数1です。5の処理後、期待する状態は合計7、件数2ですが、誤った処理では合計2、件数1のままです。ここが最初の食い違いです。
図3のように、上限と一致した5で判定が偽になる原因は < にあります。仕様の「以下」には一致を含むので、<= に直します。修正後の基本例題は (7, 2)、一致値が重複する標準例題は (8, 2)、空の一覧は (0, 0) と再確認しました。表示行を変更するだけでは、途中の合計と件数は直りません。

原因を小さく確かめるには、上限5に対する一覧を [5] に縮められます。期待値は (5, 1)、誤った処理は (0, 0) です。最小入力は差を見せやすくしますが、元の複数要素の入力も修正後に再実行します。短い入力だけでは、反復の途中で状態を上書きする別の誤りを見逃すことがあるからです。
自分で試す5問
各問は上で定めた collect の契約を使います。数値の結果は (合計, 件数) の組、前提外の入力は例外の種類で答えてください。各値は作問のためのもので、実際の記録ではありません。
- 作業時間
[1, 3, 8]、上限3。期待する組と、3を処理した直後の合計・件数を求めてください。 - 作業時間
[9]、上限2。期待する組と、条件に合う要素の個数を求めてください。 - 作業時間
[0, 0, 2]、上限0。重複を別々に数えた組を求めてください。 - 作業時間
[2, "3"]、上限4。期待する結果と、不正を見つける段階を答えてください。 - 外の道具で一覧を読み込む設計で、成功して空の一覧が届いた場合と、読み込みが時間切れになった場合を区別します。それぞれ呼出し側へ渡す結果の形を答えてください。時間切れには集計を行わず、時間切れを知らせるものとします。
答えと照合
(4, 2)です。1の後は合計1、件数1、3の後は合計4、件数2です。8は対象外なので最後も同じです。元の一覧の1と3を足すと4で、選ばれた要素は2個です。実行値も(4, 2)でした。(0, 0)です。9は2以下ではなく、採用される要素は0個です。単一要素の一覧でも、必ず1件になるわけではありません。実行値も(0, 0)です。(0, 2)です。2つの0はどちらも条件を満たし、合計は0のまま件数だけ2になります。2は除きます。実行値も(0, 2)で、合計0と件数0を同一視しないことを確認できます。ValueErrorです。最初の2は検査を通りますが、次の文字列は整数ではありません。集計前の入力検査で止まるため、合計2や件数1の組を返しません。実際にもValueErrorが送出されました。- 成功して空の一覧が届けば
(0, 0)です。時間切れなら数値の組を作らず、時間切れを呼出し側へ知らせます。前者は0要素を処理した正常な結果で、後者は一覧を取得できていない状態です。代役を使う試験では、両状態が区別され、時間切れ時に集計が呼ばれないことを確認します。
手元で押さえる要点
仕様から期待値を先に作り、出力の形も比較します。 境界、空、重複、前提外を選び、失敗時の結果も定めます。 途中表の最初の食い違いから原因を絞ります。 修正後は元の入力と別の入力を再実行します。
説明が止まった場所を手掛かりにする
期待値と実行値が違う理由を説明できないときは、まず入力を書き写し、条件に合う要素に印を付けます。それから処理表を1行ずつ埋め、判定の真偽と更新後の値を声に出して説明します。境界で止まるなら「以下」に等しい値が入るか、件数で止まるなら同じ値が何個あるかを確認します。
個別指導で確かめるときも、最後の数値を言えるかだけで判断しません。どの入力を失敗とするか、最初の不一致はどこか、変更後に何を再試験するかを本人の言葉でたどります。失敗を記録し直せる処理表が、次の修正の出発点になります。
読んで終わりにするか、ここから変えるか。
武蔵境駅徒歩30秒、講師1人に生徒1人の完全1対1。授業のあいだ、先生は隣を離れません。いまの詰まりを、無料相談でそのまま話してください。
まず15分だけ電話で話したい方は、無料電話相談(15分)から。塾からお電話を差し上げます。
本気になった日が、入塾の日。
お子様の教育、迷ったら「丸投げ」してください。必ず、期待以上の結果で返します。武蔵野で育む、武蔵境で学ぶ。
電話は年中無休。365日、24時間、お電話可能。


