基本情報技術者 2012年 秋期 午後 問09
次のCプログラムの説明及びプログラムを読んで、設問1,2に答えよ。
〔プログラムの説明〕
関数check_winning_lotは、複数本のくじのくじ番号を当選番号と比較し、当たりを確認するプログラムである。
(1) くじ番号は、6桁の数字から成る文字列である。数字だけから成る文字列を数字列という。
(2) くじの当たりは1〜6等の6等級から成り、図1に示すように各等級には一つ以上の当選番号がある。各等級での当選番号の桁数と個数は異なる。

(3) 当選番号は6桁以下の数字列であり、くじ番号の最下位桁から当選番号の桁数だけ取り出した数字列が当選番号と一致したくじが当たりとなる。なお、くじ番号によっては、複数の等級の当たりとなる場合もある。図1の例では、くじ番号“223692”のくじは、1等と6等の当たりとなる。
(4) 関数check_winning_lotの引数は次のとおりである。ここで、引数の値に誤りはないものとする。
lots 確認するくじのくじ番号表
win_list 当選番号表
winner 当選本数表
(5) 確認するくじのくじ番号表lotsの要素lots[i]には、くじ番号の数字列へのポインタが格納されている。最後の要素lots[Nl](Nlは、確認するくじの本数)には、空ポインタ定数(NULL)が格納されている。
(6) 当選番号表win_listの要素win_list[i]には、i+1等の番号表へのポインタが格納されている。win_list[i]が指すi+1等の番号表の要素win_list[i][j]には、当選番号の数字列へのポインタが格納されている。最後の要素win_list[i][Ni](Niは、i+1等の当選番号の個数)には、図2に示すように空ポインタ定数が格納されている。

(7) 当選本数表winnerの要素winner[i]には、i+1等の当たりのくじの本数が格納される。
(8) 関数check_winning_lotで使用している関数check_lotとstrlenの仕様は次のとおりである。
int check_lot(char *lot, char *win);
機能: くじ番号と当選番号を、最下位桁から1桁ずつ順に当選番号の桁数分だけ比較し、一致するかどうかを調べる。
引数: lot くじ番号
win 当選番号
返却値: 確認結果(0:一致しない、1:一致する)
unsigned int strlen(const char *s);
機能: 文字列の長さを求める。
引数: s 文字列
返却値: 終端を示すナル文字に先行する文字の個数

設問1:
問題文を見る プログラム中のに入れる正しい答えを、解答群の中から選べ。
aに関する解答群
ア:k < LOT_DNUM
イ:k <= LOT_DNUM
ウ:win_list[i][k] == NULL
エ:win_list[i][k] != NULL
オ:*win_list[i][k] == NULL
カ:*win_list[i][k] != NULL
bに関する解答群
ア:= lenw
イ:= LOT_DNUM
ウ:+= lenw
エ:+= LOT_DNUM
cに関する解答群
ア:(i < lenw) && (result == 0)
イ:(i < lenw) && (result == 1)
ウ:(i < LOT_DNUM) && (result == 0)
エ:(i < LOT_DNUM) && (result == 1)
オ:result == 0
カ:result == 1
模範解答
a:エ、b:エ、c:イ
解説
解答の導き方
このプログラムは、くじ番号を当選番号と比べて、等級ごとの当たりの本数を数えます。空欄は、当選番号表をどこまで調べるか、また番号をどのように比較するかを考えると決められます。
まず、aは等級ごとの当選番号表を調べる繰返し条件です。
問題文では、各等級の番号表の最後に空ポインタ定数が格納されると説明されています。ポインタは、文字列などのデータが置かれている場所を示す値です。NULLは、データの場所を示していないことを表します。
したがって、win_list[i][k]がNULLではない間、つまり当選番号が格納されている間は調べ続けます。空ポインタ定数が見つかったら繰返しを終えます。この条件に当たるのは、win_list[i][k] != NULLです。
ここで、win_list[i][k]は当選番号の文字列そのものではなく、その文字列を指すポインタです。*win_list[i][k]とすると文字列の先頭の1文字を表すため、最後の要素が空ポインタであることの確認には使いません。
次に、bはくじ番号のポインタlotをどこへ進めるかを考えます。
くじ番号は6桁で、check_lotは最下位桁から比較します。文字列の先頭から数えて6文字進めると、ポインタは最後の数字の次、つまり文字列の終端を示すナル文字の位置に来ます。ナル文字は、文字列の終わりを示す特別な文字です。その位置から1文字戻ると、くじ番号の最下位桁を指せます。
lotを6文字分進める式は、lot += LOT_DNUMです。lot = LOT_DNUMでは、ポインタを進めるのではなく、lotに数値を代入してしまいます。そのため、bにはlot += LOT_DNUMが入ります。
同じ考え方で、check_lotでは当選番号側のポインタwinも、当選番号の文字数分だけ進めています。23行目でstrlen(win)から当選番号の長さを求め、24行目でwinをその長さ分だけ進めています。これで、lotとwinはそれぞれの文字列の終端を指します。
最後に、cは何桁比較するかと、比較をいつ止めるかを考えます。
当選番号は「6桁以下」で、くじ番号の最下位桁から当選番号の桁数分だけ比較します。したがって、比較する回数はくじ番号の桁数であるLOT_DNUMではなく、当選番号の桁数であるlenwに合わせます。
26行目の繰返しでは、iが0から始まり、1回比較するごとに1増えます。比較を続ける条件は、次の2つです。
- i < lenw:当選番号の桁数分だけ比較が終わっていない
- result == 1:これまでの比較で不一致が見つかっていない
27~29行目では、比較した数字が違う場合にresultを0にします。すると次の繰返し条件でresult == 1が成り立たなくなり、残りの桁を調べずに終了できます。以上から、cには(i < lenw) && (result == 1)が入ります。
例えば、くじ番号223692と当選番号92を比べる場合、lenwは2です。lotとwinをそれぞれ文字列の終端まで進めた後、末尾から1文字ずつ比べます。
2回とも一致し、当選番号の桁数分の比較も終わるので、check_lotは1を返します。一方、途中で数字が一致しなければresultが0になり、そこで比較を打ち切ります。
誤りやすいポイント
- win_list[i][k]と*win_list[i][k]を混同しないようにします。前者は当選番号へのポインタ、後者は当選番号の先頭の1文字です。NULLかどうかを調べるのはポインタです。
- 当選番号表の最後にNULLがあるため、aの条件は、当選番号がある間は続ける条件になります。NULLが見つかったら続けるのではありません。
- lot += LOT_DNUMは、lotを6文字分進める処理です。lotに数値を代入する処理ではありません。
- 比較回数は、くじ番号の桁数ではなく当選番号の桁数です。くじ番号は6桁ですが、当選番号は等級によって桁数が異なります。
- 不一致が分かった後は比較を続ける必要がありません。resultが1である間だけ繰り返す条件にします。
FAQ
Q: なぜlotとwinを文字列の終端まで進めてから比べるのですか?
A: 末尾から比べるためです。ポインタを終端まで進めておくと、比較のたびに1文字戻るだけで、最下位桁から順に確認できます。
A: 末尾から比べるためです。ポインタを終端まで進めておくと、比較のたびに1文字戻るだけで、最下位桁から順に確認できます。
Q: cの条件にLOT_DNUMではなくlenwを使うのはなぜですか?
A: 比較する桁数は当選番号の桁数だからです。例えば、当選番号が2桁なら、くじ番号の末尾2桁だけを比べます。
A: 比較する桁数は当選番号の桁数だからです。例えば、当選番号が2桁なら、くじ番号の末尾2桁だけを比べます。
Q: resultが0になった後も、残りの桁を調べてもよいのですか?
A: 調べても一致という結果には戻りません。1桁でも違えば当選番号と一致しないため、resultが0になった時点で比較を終えると効率的です。
A: 調べても一致という結果には戻りません。1桁でも違えば当選番号と一致しないため、resultが0になった時点で比較を終えると効率的です。
関連キーワード: C言語のポインタ、NULLポインタ、文字列、番兵法、条件式
設問2:
問題文を見る 次の記述中のに入れる正しい答えを、解答群の中から選べ。ただし、f1とf2に入れる答えは、fに関する解答群の中から組合せとして正しいものを選ぶものとする。
くじの当たりを増やすために、6桁の当選番号に対して前後賞が用意されることになった。これに合わせて関数check_winning_lotに、前後賞の当選結果を自動判定する処理を追加する。ここで、プログラム中のa〜cには正しい答えが入っているものとする。
(1) 関数check_winning_lotの引数を次のとおりに変更する。ここで、引数の値に誤りはないものとする。
lots 確認するくじのくじ番号表
win_list 当選番号表
winner 当選本数表
special 前後賞の当選本数表
(2) 前後賞は、6桁の当選番号に対して用意される。例えば、当選番号“123456”に対する前後賞の番号は、“123455”と“123457”となる。当選番号“000000”に対する前後賞の番号は“999999”と“000001”、当選番号“999999”に対する前後賞の番号は“999998”と“000000”とする。
(3) 前後賞の当選結果の確認は、確認するくじのくじ番号を数値とみなし、それよりも1だけ少ない数値と多い数値を表す数字列を作成し、その数字列を当選番号と比較することによって行う。このとき、くじ番号“000000”よりも1だけ少ない数値を表す数字列は“999999”に、くじ番号“999999”よりも1だけ多い数値を表す数字列は“000000”にする。
(4) 前後賞の当選本数表の要素special[i]にはi+1等の前後賞の当たりのくじの本数が格納される。
(5) 処理の追加に対応するために、プログラムを表1のとおりに変更する。

dに関する解答群
ア:i
イ:j
ウ:LOT_DNUM
エ:LOT_DNUM + 1
オ:LOT_DNUM + i
カ:LOT_DNUM + j
キ:LOT_DNUM - 1
ク:LOT_DNUM - i
ケ:LOT_DNUM - j
eに関する解答群
ア:*lots[j]
イ:*lots[j] + m
ウ:*lots[j] + m + 1
エ:*(lots[j] + m)
オ:*(lots[j] + m + 1)
fに関する解答群


模範解答
d:キ、e:エ、f:イ
解説
解答の導き方
この設問では、くじ番号から「1だけ小さい数」と「1だけ大きい数」を表す数字列を作ります。そのために、数字列のどの位置から処理するか、各位置の数字をどう取り出すか、桁が繰り下がる・繰り上がるときにどうするかを順に考えます。
d:右端の桁から処理します
くじ番号は6桁で、文字列の位置は左端を0として数えます。したがって、6桁の数字列の各位置は0〜5です。右端の位置は、6から1を引いた位置になります。
そこで、ループを右端の桁から左端の桁へ進めるには、開始位置を「LOT_DNUM - 1」にします。LOT_DNUMは6なので、mは5から始まり、0まで減っていきます。選択肢はキです。
右端から処理するのは、1を足すときの繰り上がりも、1を引くときの繰り下がりも、まず右端の桁で起きるからです。なお、位置6には文字列の終わりを示すナル文字が入るため、そこから始めてはいけません。
e:くじ番号のm番目の文字を取り出します
lots[j]は、確認中のくじ番号を表す文字列を指しています。文字列の位置mにある文字は、lots[j]にmを加えてその位置を指し、先頭の「*」でその位置の文字を取り出します。
そのため、式は「*(lots[j] + m)」となり、選択肢はエです。
「*lots[j] + m」では、先頭の文字を取り出してからmを加えることになります。これではm番目の文字を選べません。
f:0から引くときは9にし、9に足すときは0にします
pnumは、くじ番号より1だけ小さい数を作るための数字列です。右端から1を引くとき、対象の数字が「0」なら、その桁は「9」に変わり、さらに左の桁から1を引く必要があります。よって、pnum[m]が比較する値f1は「'0'」、置き換える値f2は「'9'」です。
nnumは、くじ番号より1だけ大きい数を作るための数字列です。右端から1を足すとき、対象の数字が「9」なら、その桁は「0」に変わり、さらに左の桁に1を足す必要があります。この処理も、f1が「'0'」、f2が「'9'」の組合せと一致します。
したがって、f1が「'0'」、f2が「'9'」となる組合せを選びます。これは選択肢イです。ここで扱っているのは数字の文字なので、数値の0や9ではなく、文字を表す「'0'」や「'9'」を使います。
フラグによって、繰り下がり・繰り上がりを止めます
pnumとnnumは、各桁を元のくじ番号の文字で初期化します。pflgとnflgは、それぞれ1を引く処理、1を足す処理が済んだかどうかを示します。
例えば、くじ番号が「120000」の場合、1だけ小さい数を作る処理では、右端から続く「0」を「9」に変えていきます。「2」の桁で「1」を引いたら、左側の桁は変える必要がないため、pflgを1にして繰り下がりを止めます。結果は「119999」です。
1だけ大きい数を作る処理では、右端の「0」を「1」に変えた時点で足し算が済みます。nflgを1にして繰り上がりを止めるため、結果は「120001」です。
同じ処理で、くじ番号が「000000」なら1だけ小さい数は「999999」に、くじ番号が「999999」なら1だけ大きい数は「000000」になります。先頭の0を残すため、結果は6桁の数字列として扱います。
誤りやすいポイント
-
ループの開始位置をLOT_DNUMにする
LOT_DNUMは6ですが、数字列の位置は0〜5です。右端の位置はLOT_DNUM - 1です。 -
文字列の位置を選ぶ式を取り違える
*(lots[j] + m)は、lots[j]が指す文字列のm番目の文字を取り出します。先頭の文字にmを加えるだけでは、m番目の文字を取り出したことにはなりません。 -
繰り下がりと繰り上がりの境界を逆にする
1を引く処理では「0」を「9」に変えます。1を足す処理では「9」を「0」に変えます。 -
前後賞の数字列に先頭の0を残さない
「000001」のような数字列も6桁のまま扱います。数値として表示し直して先頭の0を消す処理ではありません。
FAQ
Q: なぜ右端から左へ処理するのですか?
A: 1を足すときの繰り上がりも、1を引くときの繰り下がりも、まず右端の桁で起きるためです。繰り上がりや繰り下がりが続く場合に、そのまま左の桁へ処理を進められます。
A: 1を足すときの繰り上がりも、1を引くときの繰り下がりも、まず右端の桁で起きるためです。繰り上がりや繰り下がりが続く場合に、そのまま左の桁へ処理を進められます。
Q: なぜ前後賞の確認では、当選番号が6桁かどうかを調べるのですか?
A: 前後賞は6桁の当選番号に対して用意されるためです。6桁でない当選番号は、前後賞の確認対象にしません。
A: 前後賞は6桁の当選番号に対して用意されるためです。6桁でない当選番号は、前後賞の確認対象にしません。
Q: pnumやnnumの最後にナル文字を入れるのはなぜですか?
A: C言語では、文字列の末尾にナル文字を置いて文字列の終わりを示します。pnumとnnumを文字列として扱えるように、数字を入れた後で末尾にナル文字を設定します。
A: C言語では、文字列の末尾にナル文字を置いて文字列の終わりを示します。pnumとnnumを文字列として扱えるように、数字を入れた後で末尾にナル文字を設定します。
関連キーワード: 文字列、ポインタ、配列、桁上がり、桁下がり



