宮商定時制計算部

基本情報技術者試験
平成22年秋午後問9 C言語

はじめに

各回の過去問を解いていく中で、この回の問題は設問の意図をうまくつかめず、実際の試験が終わったあともどこか引っかかる部分があったので、今回プログラムを改めて検討し、納得できる形で残そうと解説を作った次第です。

説明文の整理

設問に入る前に、使用する引数・変数の役割やプログラムの大まかな流れを理解しておきましょう。

まずは関数update_wait_timeの引数から見ていきます。
引数bus_idとbusstopは、説明文(1)より、それぞれ「バスの車体番号」、「バス停の番号」と説明されています。説明文(7)より、「バスがバス停0~8を発車するとき又はバス停9に到着したときに呼び出され、bus_idで識別されるバスの走行位置を更新」とあるので、bus_idの「バスの車体番号」はバス停を発車した(ことにより関数update_wait_timeを呼び出す)バスであり、図2のbusstopの値から、busstopはbus_idが発車したバス停のことだとわかります。
言い換えれば、”bus_id号車のバスがbusstop駅を発車するときに、update_wait_timeが呼び出される”ということになります。
引数routeとbusは、バス停ごとの待ち時間や各バスの走行位置などを記録している構造体で、説明文(4)、(5)より、プログラムによってこれらの値が変更されることがわかります。routeではバス停の番号と配列の添字が対応関係になっているのに対し、busはidというメンバを持っており、バスの車体番号と配列の添字が一致するとは限らないことに注意してください。

次は変数を見ていきます。説明文(6)を見てください。
precedingとsucceedingは、バスの走行位置(区間番号)を記憶する変数であり、precedingはbus_idのバスに一台先行しているバスの位置を、succeedingはbus_idのバスの一台後を走行しているバスの位置を記憶しているようです。説明文(7)より、「busstopが示すバス停からprecedingが示す区間の始まりとなるバス停までの、各バス停に表示する到着待ち時間を計算する」とあるので、precedingは到着待ち時間が更新されるバス停の範囲を決定する際に使用されていると考えることができます。succeedingの用途に関しては説明文に具体的な記述がないので、設問を解く際に考えることにしましょう。

以上の点をふまえて、全体の流れを図にまとめると以下のようになります。

走行位置を記録し、IDで一意に識別されたバスが始点(バス停0を発車していない)・各区間・終点(バス停9に到着)のいずれかに位置しています。
各バス停間の標準所要時間が予め決められており、その数値を元に到着待ち時間が計算されることになります。

あるバスがバス停を発車した時点でプログラムが呼び出され、バスbus_idの走行区間(bus[n].cur_pos)とバス停bus_id〜precedingの待ち時間(busstop[n].wait_time)を更新します。
(上図のバス停1は次に発車するバスがバス停0を発車していないため、待ち時間は0になります。)

設問1

プログラム中の穴埋めを行う問題です。

空欄aのif文では、条件が真の場合にバスの現在位置を更新しています。
外側のfor文ではマクロで指定されたバスの台数分(BUSNUM)だけループを回しているので、添字[j]で示されるバスのidを確認し、一致した場合にバスの走行位置を更新すればよさそうです。 idが一致している必要があるので、選択肢からイとエは除外できます。 ウに関しては、バスのidが0から始まり、かつ連続した値であった場合には成立してしまいますが[1]、idの命名規則に関する明確な記述はなく、idの整合を確認しているとも言えないため適当な選択肢ではないでしょう。
よって、残るア:bus_id == bus[j].idが正解となります。

空欄bの代入文は、複数のif,else文の入れ子の中で実行されています。 succeedingの部分を見てみましょう。
bus[j].cur_posの値がbusstopよりも小さい場合、 そのバスは引数で渡されたバス停よりも前に位置していることになります。 説明文より、succeedingには次に到着するバスの走行位置が入っていれば良いので、各条件を満たしたバスのうち、最もbusstopに近いバスの現在位置で更新すれば良いことがわかります。(precedingも位置関係が逆になるだけで同じです。)

選択肢の中でバスの現在位置を指しているのはウ,エ,オですが、値を前後させる必要はないので、ウ:bus[j].cur_posが適切な答えです。

空欄cの条件を満たした場合、待ち時間に0を代入していますが、説明文に、「次に到着するバスがバス停を発車していないとき、そのバス停iにおける待ち時間route[i].wait_timeには0を格納する」、「バス停0からbusstopが示すバス停までの区間に走行中のバスがない場合は-1とする[2]」とあるので、次のバスが発車していない == succeedingが-1の場合に0を代入すれば良いことがわかります。合致する選択肢はエ:succeeding == -1です。

空欄dのあるfor文では、プログラム中のコメントのとおり、次のバス停(busstop + 1)からprecedingまでの待ち時間を計算しています。 空欄cのelse文の代入処理からもわかるとおり、バス停busstopの到着待ち時間(wait_time)は、次に到着するバスが通過する各バス停の標準所要時間の合計なので、

route[busstop].std_time + route[busstop - 1].wait_time

で表すことができます[3]

41行目でstd_timeの代入が行われているので、空欄dではその前のバス停のwait_timeを足せば良いですね。 wait_timeを足しているイ:+= route[i - 1].wait_timeが答えです。

設問2

プログラムの変更に関する問題です。

先程のupdate_wait_timeに、遅延時間の計算に必要な変数・処理を追加しています。

(実際に試験用紙に追記する際はプログラムの間に書くわけにはいきませんので、横に簡略化して書きました。)

空欄eでは、IDが引数と同一であった場合(空欄aのif文内)に実行されています。
代入先のbus[j].cur_delayは、引数で渡されたバスの運行遅延時間ですから、空欄eには遅延時間を計算する式が入っていれば良いことになります。解答群を見てみましょう。
イやオでは一つ前のIDのバスの遅延時間を代入してしまっているので、”このバスの”遅延時間の計算としてはふさわしくありません。 一方ウやカは引数で指定されたバス(bus_id)が発車したバス停(busstop)が表示していた”次に到着するバスの”遅延時間を代入していますから、こちらも適切ではありません。
アやエでは、プログラムの変更によって追加された引数act_timeからroute[busstop].std_timeを引いています。act_timeは前のバス停からこのバス停(busstop)までの実所要時間が入っているので、ここから標準所要時間を引けば前のバス停から出発したバス停までの遅延時間が計算されることがわかります。
問題なのは代入(=)するべきか加算代入(+=)すべきかというところですが、プログラムの呼び出しがバスの発車単位で行われていることを考えると、バスの遅延時間は蓄積されていく必要がある[4]ので、値を足し込んでいるエ:+= act_time - route[busstop].std_timeが正解になります。

空欄fの代入は新たに追加された変数delay_precedingとdelay_succeedingに対して行われています。
delay_precedingの代入先を見てみると、44行〜45行間に追加された処理では次のバス停からprecedingまでのバス停に値を代入しています。各バス停に表示される遅延時間には、”次に到着するバスの遅延時間”が入っていれば良いので、このroute[i].delay_timeに入っているべき値は、引数で渡されたバス(bus_id)の運行遅延時間です。 delay_precedingへの代入処理はIDが引数bus_idと同一であった場合(空欄aのif文内)に実行されていますから、空欄fにはbus[j].cur_delayの代入が入っていれば良いことになります。また、(空欄eに正しい答えが入っていることが前提ですが)プログラム中でcur_delayが一度も使われていないことからも、bus[j].cur_delayを使用することがわかります。 ここでもイとオで単純な代入(=)と加算代入(+=)の二択になりました。delay_succeedingへの代入部分を考えてみましょう。
delay_precedingではバスのIDが一致したときのみの一回限りの代入でしたが、delay_succeedingでは、succeeding同様もっともバス停(busstop)に近いバスの遅延時間を入れるために繰り返し代入しているので、ループのたびに値を「更新」する必要があります[5]。このことから新たに値を書き換える単純な代入(=)処理、選択肢ではイ:= bus[j].cur_delayが正しい答えであることがわかります。

あとがき

初めて解いたときは随分と思い悩んだものですが、時間をかけてじっくり考えてみるとプログラム自体は意外と単純な構造で、解説を作る段階では鮮明にその内容をイメージすることができました。
ただ、改めて問題を振り返ってみると、プログラムの説明が長めであること、変数・構造体に類似の名前が多く紛らわしい(目が滑りやすい)こと、プログラムの変更箇所が局所的かつ多いこと、設問2ではプログラムの流れを理解した上で既存の空欄全てに正解が入っていないと推理が難しいことなど、国語の問題と呼ばれている言語問題の中でも特に読解に重きを置いている問題で、やはり難しめの問題だと感じます(筆者がこの手の問題を苦手としているというのもありますが…)。
いずれにせよ、個人的に相性の悪い問題でしたので、なんとか理解できるところまで持っていけたことを嬉しく思います。
読みにくい部分等々あったかと思いますが、問題を理解する際の参考になれば幸いです。


脚注

[1] for文のカウンタ変数jが0から始まっているため、バスのIDも0始まりであれば互いに対応関係になり、プログラムが正しく動いてしまう。

[2] succeedingの初期化に-1を使用しているので、走行中のバスがない == 値が更新されなければ-1のままになる。

[3] ただし、対象のバスが次に到着するバス停(busstop + 1)に限っては、到着するバスが違うため、それより前のバス停の待ち時間を足すわけにはいかないので、(if文内の処理が実行されず)std_timeのみが代入される。

[4] バスがバス停を発車するごとにプログラムが何度も呼び出されることになるので、その都度各バス停間を移動した際の遅延時間を足していく必要がある。

[5]delay_precedingではbus_idのバスの遅延時間を求めたいため、ループ中のbus[j].idがbus_idと一致しているかが(一度だけ)判ればよかったのに対し、delay_succeedingではbus_idのバスの次に到着するバスの遅延時間が必要になるため、succeedingと同じような手順で求めていく必要がある。

作成日:2018/07/28