基本情報技術者 2018年 春期 午後 問09
次のCプログラムの説明及びプログラムを読んで、設問1,2に答えよ。
簡易集計プログラムである。このプログラムを用いると、例えば、図1の入力ファイルから、品番ごとのレコード件数と金額の合計を求めて、図2の集計ファイルを得ることができる。
プログラムは、キー項目及び数値項目の、開始桁位置及び桁数を引数で受け取り、キー項目で整列済みのレコードを入力ファイルから読み込み、キー項目の値ごとに、その件数と数値項目の値の合計を求め、集計ファイルにレコードとして書き出す。ここで、数値項目には整数の値が入る。

〔プログラム1の説明〕
(1) 入力ファイルは、固定長レコードの並びから成る。レコードは、1,000文字以下の1バイト文字の並びであり、最後の文字の後には改行文字が付いている。ファイル名は、引数dataFileで指定する。
(2) レコード中のキー項目の開始桁位置及び桁数は、それぞれ引数keyPos及びkeyLenで指定する。また、数値項目の開始桁位置及び桁数は、それぞれ引数valuePos及びvalueLenで指定する。開始桁位置は、レコードの先頭文字の桁位置を0として数える。キー項目及び数値項目の桁数は、いずれも9桁以下とする。
(3) 入力レコードは、キー項目の昇順に整列されている。
(4) 集計ファイルにレコードとして、キー項目の値、キー項目ごとの件数及び数値項目の値の合計を各9桁分の領域に右詰めで出力し、各項目の直前に1個の空白文字を出力する。レコードの最後の文字の後には改行文字を付ける。ファイル名は、引数listFileで指定する。
(5) 引数及び入力レコードの内容に誤りはないものとする。また、数値項目の値の合計は9桁以下であり、算術演算であふれは起きないものとする。
(6) プログラム中で使用しているライブラリ関数(一部)の概要は、次のとおりである。
・atol(s):文字列sが表す数値をlong型の表現に変換した値を返す。
・fgets(s, m, f):ストリームfから文字の列(改行文字まで、最大m−1文字)を読み取り、配列sに格納し、sを返す。ストリームの終わりに達した場合はNULLを返す。
・strcmp(s1, s2):文字列s1とs2を比較し、s1 < s2のとき負の値を、s1 = s2のとき0を、s1 > s2のとき正の値を、それぞれ返す。
・strcpy(s1, s2):文字列s2を文字列s1に複写する。
・strncpy(s1, s2, n):文字列s2の先頭からn個の文字を文字列s1に複写し、文字列s1の値を返す。

設問1:
問題文を見る プログラム1中のに入れる適切な答えを、解答群の中から選べ。
aに関する解答群
ア:char
イ:FILE
ウ:file
エ:int
b, cに関する解答群
ア:fprintf(outFile, format, inKey, count, inValue);
イ:fprintf(outFile, format, key, count, value);
ウ:count++;
value += inValue;
エ:count++;
value += inValue;
fprintf(outFile, format, key, count, value);
オ:count--;
fprintf(outFile, format, inKey, count, inValue);
模範解答
a:イ、b:ウ、c:イ
解説
解答の導き方
このプログラムは、キー項目が同じレコードをひとまとめにし、その件数と数値項目の合計を出力します。まず、空欄ごとに何をする場所かを確認します。
a:ファイルを扱う型
宣言は、入力用のinFileと出力用のoutFileを同じ型で用意する形です。どちらもfopenで開いたファイルを扱います。
ファイルポインタ(ファイルを読み書きするための情報を扱う変数)の型はFILEです。FILEは、プログラムの先頭で読み込んでいるstdio.hで使える型です。したがって、aにはFILEが入ります。
b:同じキーのレコードを読み込んだときの処理
最初のレコードを読むと、keyにはそのキー項目、countには1、valueには数値項目の値が入ります。その後、1レコード読むたびに、次のように処理します。
プログラムでは、strcmp(key, inKey) != 0の場合に、キーが異なると判断しています。この場合は、それまでの集計結果を出力してから、新しいキーの集計を始めます。
一方、elseに入るのはキーが同じ場合です。このときは、件数を1増やし、数値項目の値を合計に加えます。よって、bには次の2行が入ります。
count++;
value += inValue;
これは解答群のウです。同じキーの途中では、まだ出力せず、集計値だけを更新します。
c:最後のキーの集計結果を出力する処理
入力レコードは「キー項目の昇順に整列されている」ため、同じキーのレコードは続けて現れます。キーが変わったときには、それまでの集計結果を出力できます。
ただし、最後のキーについては、その後に別のキーが現れません。そのため、while文を抜けた後に出力しないと、最後の集計結果が集計ファイルに書き出されません。
ここでは、現在集計しているキーであるkey、件数であるcount、合計であるvalueを出力します。したがって、cには次の文が入り、解答群ではイです。
fprintf(outFile, format, key, count, value);
誤りやすいポイント
- キーが変わったときに出力するのは、新しく読み込んだinKeyではなく、それまで集計していたkeyです。inKeyは次の集計対象のキーです。
- 同じキーのレコードを読んだときは、件数と合計を更新します。そこで出力してしまうと、キーごとの集計結果ではなく、途中の値まで書き出してしまいます。
- キーが変わったときだけ出力して終わりにすると、最後のキーの集計結果が出力されません。while文の後の出力も必要です。
- 件数だけ、または合計だけを更新するのでは不十分です。同じキーのレコードを1件追加したときは、countとvalueの両方を更新します。
FAQ
Q: なぜキーが変わったときに、すぐ新しいキーの集計結果を出力しないのですか?
A: 新しいキーのレコードはまだ1件しか読み込んでいないためです。キーが変わった時点で出力するのは、直前まで集計していたキーの結果です。
A: 新しいキーのレコードはまだ1件しか読み込んでいないためです。キーが変わった時点で出力するのは、直前まで集計していたキーの結果です。
Q: 入力ファイルがキー順に整列されている必要があるのはなぜですか?
A: 同じキーのレコードが続いて現れるため、キーが変わった時点で、そのキーの集計が終わったと判断できるからです。
A: 同じキーのレコードが続いて現れるため、キーが変わった時点で、そのキーの集計が終わったと判断できるからです。
Q: 入力ファイルにレコードがない場合、集計結果は出力されますか?
A: 最初のfgetsがNULLを返すため、集計処理には入りません。したがって、集計結果のレコードは出力されません。
A: 最初のfgetsがNULLを返すため、集計処理には入りません。したがって、集計結果のレコードは出力されません。
関連キーワード: C言語、ファイルポインタ、文字列比較、逐次処理、集計処理
設問2:
問題文を見る 次の記述中のに入れる適切な答えを、解答群の中から選べ。ここで、プログラム2中のaには、設問1の正しい答えが入っているものとする。
図3に示す、時分秒のうちの時をキー項目として昇順に整列済みのレコードを入力ファイルから読み込み、時間帯(0時台、1時台、…、23時台)ごとの件数と金額の合計(合計金額)を求め、時、件数、合計金額と合計金額を表す棒グラフを印字する。この処理を、次の手順で行う。
(1) プログラム1を利用して、図3の入力ファイルから、図4の集計ファイルを得る。
(2) プログラム2を利用して、図4の集計ファイルから、図5の印字結果を得る。


〔プログラム2の説明〕
(1) プログラム1で書き出した集計ファイルからレコードを読み込む。ファイル名は、引数listFileで指定する。
(2) 読み込んだ各レコードに、そのレコードの合計金額の値valueに応じた長さのグラフを追加して印字する。集計ファイル中の合計金額の値の最大値valueMaxを25個の“*”で表し、他の値は、25 × value ÷ valueMax個(小数点以下切捨て)の“*”で表す。ここで、集計ファイル中の合計金額の値の最大値は正であり、最小値は0以上であるものとする。

プログラム2は、作成途中である。図4の集計ファイルを用いると図5の印字結果が得られるが、集計ファイル中の合計金額の値によっては、コメント/* α */を付した印字処理の実行時に問題が発生する場合がある。
〔プログラム2の説明〕の(2)にある前提“集計ファイル中の合計金額の値の最大値は正であり、最小値は0以上である”が満たされない場合も考慮に含め、コメント/* α */を付した印字処理の実行時に発生し得る事象として、①算術演算であふれが発生、②算術演算でゼロ除算が発生、③配列の定義外の要素位置を参照、がある。これらの事象が発生し得るvalueとvalueMaxの値の例を、表1にまとめた。ここで、long型の数値の範囲は、〜( = 2,147,483,648)とする。

d〜fに関する解答群


模範解答
d:カ、e:ア、f:イ
解説
解答の導き方
プログラム2では、最初に集計ファイルを読み込み、合計金額の最大値をvalueMaxに保存します。次に各レコードの合計金額をvalueに読み込み、次の計算でグラフを表示する位置を決めます。
ここで、整数除算(割り算の結果の小数部分を切り捨てる計算)が使われています。求めた位置からgraphの文字列を表示するため、位置が大きいほど表示される「*」は少なくなります。
配列(同じ種類の値を順番に格納する領域)の位置は、先頭を0として数えます。位置が配列の範囲を外れると、定義外の要素位置を参照することになります。
表の選択肢をこの計算に当てはめます。
アではゼロ除算が発生します。 分母であるvalueMaxが0だからです。掛け算の結果は-25,000,000で、long型の範囲内ですが、その後の割り算で0を割ることになります。
イでは配列の範囲外を参照します。 掛け算の結果-250,000,000はlong型で表せます。割り算の商は-25なので、参照位置はです。graphの配列の範囲を超えています。valueが負のとき、引き算で負の商を引くため、参照位置が大きくなるのがポイントです。
カでは算術演算であふれが発生します。 オーバーフロー(型で表せる数値の範囲を超えること)は、valueやvalueMaxが範囲内かどうかだけでなく、途中の計算結果も範囲内かどうかを確認する必要があります。valueとvalueMaxの100,000,000はlong型で表せますが、先に行う掛け算の結果は2,500,000,000です。long型の上限である2,147,483,647を超えるため、掛け算であふれが発生します。後でvalueMaxで割ると数学的には25になりますが、掛け算の時点で範囲を超えています。
誤りやすいポイント
-
最後に割るから、掛け算の途中であふれないと思うこと
計算は掛け算をしてから割り算をします。途中の結果であるがlong型の範囲を超えるかを確認します。 -
valueMaxが正なら、ゼロ除算は起きないと決めつけること
表のアではvalueMaxが0です。前提が満たされない場合も考える設問なので、0で割るケースも確認します。 -
負の値を使うと参照位置が小さくなると思うこと
イでは商が-25になり、の結果は50です。負の値を引くため、参照位置は大きくなります。 -
小数の計算として扱うこと
エではの小数部分が切り捨てられ、商は0です。整数除算では、小数部分は残りません。
FAQ
Q: カはvalueもvalueMaxもlong型の範囲内なのに、なぜ問題が起きますか?
A: 途中で計算するが2,500,000,000になり、long型の上限を超えるためです。計算に使う値だけでなく、途中結果も確認します。
A: 途中で計算するが2,500,000,000になり、long型の上限を超えるためです。計算に使う値だけでなく、途中結果も確認します。
Q: イはvalueMaxが0ではないのに、なぜ問題が起きますか?
A: 割り算自体はできますが、負のvalueから負の商が得られます。その商を25から引くと参照位置が50になり、配列の範囲を外れます。
A: 割り算自体はできますが、負のvalueから負の商が得られます。その商を25から引くと参照位置が50になり、配列の範囲を外れます。
Q: エの割り算で、なぜ商が0になるのですか?
A: 整数除算では小数部分が切り捨てられます。は1未満なので、商は0です。
A: 整数除算では小数部分が切り捨てられます。は1未満なので、商は0です。
関連キーワード: 整数演算、整数除算、ゼロ除算、オーバーフロー、配列



