読む..

1 午後の C 言語
1. ケーブルテレビ局が提供するサービスの料金計算 (H25 春)
2. 辞書順での文字列の比較 (H25 秋)
3. テキストの編集 (H26 春)
4. 利用者 ID の管理状況の確認 (H26 秋)
5. 換字式暗号 (H27 春)
次の問 9 から問 13 までの 5 問については,この中から 1 問を選択し,選択した問題
については,解答用紙の選択欄の (選) をマークして解答してください。
なお,2 問以上マークした場合は,はじめの 1 問について採点します。
問 9 次の C プログラムの説明及びプログラムを読んで,設問 1,2 に答えよ。
〔プログラムの説明〕
ケーブルテレビ局を運営する U 社では,有線テレビ視聴サービスとインターネッ
ト接続サービスを提供している。
(1) 有線テレビ視聴サービスでは,有線テレビ基本視聴契約(以下,基本視聴契
約という)を結ぶと視聴できる基本放送チャンネル,及び基本視聴契約に加え
て有料放送視聴契約を結ぶと視聴できる有料放送チャンネルを提供している。
(2) 基本視聴契約にはプラン 1,プラン 2 の 2 種類があり,プラン 2 を選ぶと毎月
の基本視聴料金はプラン 1 よりも高くなるが,有料放送視聴料金は割安になる。
毎月の基本視聴料金は,表 1 のとおりである。
表 1 基本視聴料金表(月額)
基本視聴契約
基本視聴料金
プラン 1
3,000 円
プラン 2
5,000 円
(3) 毎月の有料放送視聴契約は,基本視聴契約のプランと視聴契約する有料放送
のチャンネル数によって決まる。
(4) 基本視聴料金と有料放送視聴料金の合計を,有線テレビ視聴料金という。
(5) インターネット接続サービスは,インターネット接続契約を結ぶと利用でき
る。インターネット接続サービスでは,低速回線と高速回線の 2 種類を提供し
ている。毎月のインターネット接続サービス利用料金は,基本視聴契約の有無,
プランの種類によって変わってくる。毎月のインターネット接続サービス利用
料金は,表 2 のとおりである。
表 2 インターネット接続サービス利用料金表(月額)
インターネット接続契約
基本視聴契約
低速回線
高速回線
なし
3,000 円
6,000 円
プラン 1
2,500 円
6,000 円
プラン 2
2,500 円
5,000 円
(6) 毎月の利用料金は,有線テレビ視聴料金とインターネット接続サービス利用
料金の合計である。
(7) 関数 calc service fee は,毎月の利用料金を求めるプログラムである。引
数及び返却値は,次のとおりである。引数に誤りはないものとする。
〔プログラム〕
(行番号)
1 ♯define BLKNUM 4
2 /*基本視聴料金,0 は契約なしの場合*/
3 const int basic charge[]={0,3000,5000}
4 channel block[BLKNUM]={1,3,6,10}
5 channel charge[2][BLKNUM]={{1500,1000,700,500},
6
{1000, 800,600,400}};
7 /*インターネット接続サービス利用料金,
8
0 は契約なしの場合*/
9 const int inet charge[]={0,2500,3000,5000,6000};
10 int calc service fee(int,int,int);
11 int calc service fee(int basic plan,int channel num,
12
int inet plan) {
13 int tv fee,inet fee;
14 int cnum,i;
15 /*有料テレビ視聴料金 tv fee を求める*/
16 tv fee=basic charge[basic plan];
17 if (basic plan > 0) {
18
for (i = BLKNUM - 1; i >= 0; i--) {
19
cnum = channel num - channel block[i] + 1;
20
if (cnum < 0) {
21
cnum = 0;
22
}
23
channel num -= cnum;
24
tv fee += cnum * channel charge[basic plan - 1][i];
25
}
26 }
27 /*インターネット接続サービス利用料金 inet fee を求める*/
28 if (inet plan == e1 ) {
29
inet fee = inet charge[0];
30 } else (inet plan == e2 ) {
31
if (basic plan != 0) {
32
inet fee = inet charge[1];
33
} else {
34
inet fee = inet charge[2];
35
}
36 } else {
37
if (basic plan f ) {
38
inet fee = inet charge[3];
39
} else {
40
inet fee = inet charge[4];
41
}
42 }
43 return tv fee + inet fee;
44 }
設問 1 有線テレビ視聴料金に関する次の記述中の
に入れる正しい答えを,
解答群の中から選べ。
(1) calc service fee(1, 6, 0) を実行した場合,行番号 23 が 2 回目に実行
されるときの変数 cnum の値は
a となり,3 回目に実行されるときの
変数 cnum の値は b となる。また,行番号 43 が実行されるときの変数
tv fee の値は c となる。
(2) 基本視聴契約がプラン 1 で有線放送 6 チャンネル分の視聴契約をする場合
の有線テレビ視聴料金と,基本視聴契約がプラン 2 で有料放送 6 チャンネル
分の視聴契約をする場合の有線テレビ視聴料金を比較した場合, d 。
a,b に対する解答群
ア 0
イ 1
ウ 2
エ 3
オ 4
カ 5
キ 6
ク 10
イ 7200
ウ 8000
エ 9700
c に対する解答群
ア 6600
d に対する解答群
ア どちらのプランでも有線テレビ視聴料金は等しい
イ プラン 1 での有線テレビ視聴料金の方が高い
ウ プラン 2 での有線テレビ視聴料金の方が高い
設問 2 プログラム中の
に入れる正しい答えを,解答群の中から選べ。ただ
し,e1 と e2 に入れる答えは,e に関する解答群の中から組合せとして正しいもの
を選ぶものとする。
e に対する解答群
e1
e2
ア
0
1
イ
0
2
ウ
1
0
エ
1
2
オ
2
0
カ
2
1
f に対する解答群
ア ==0
イ ==1
ウ ==2
エ !=0
オ !=1
カ !=2
出典 平成 25 年度 春期 FE 区分 午後 問 9
解説 関数 calc service fee は,毎月の利用料金を求めるプログラムです。毎月の利
用料金は,有線テレビ視聴料金とインターネット接続サービス利用料金の合計であ
り,有線テレビ視聴料金は,基本視聴料金と有料放送視聴料金の合計です。
有料放送視聴契約は,基本視聴契約のプランと視聴契約する有料放送のチャンネ
ル数によって決まるとしか説明にないため,その料金表はプログラムを解読して分
かることになります。プログラムを見ると,基本視聴契約のプラン 1,2 に関係す
ることと考え合わせて,channel charge が料金表に関係していそうです。
3 const int basic charge[]={0,3000,5000}
4
channel block[BLKNUM]={1,3,6,10}
5
channel charge[2][BLKNUM]={{1500,1000,700,500},
6
{1000, 800,600,400}};
表 2 のインターネット接続サービス利用料金表に対応していそうなのは行番号 9
です。金額種別が
っています。
9 const int inet charge[]={0,2500,3000,5000,6000};
行番号 17 は基本視聴契約あり/なしの判定をしています。行番号 24 で有料放送
視聴料金を求めています。
行番号 18 の for 文は,カウンタをカウントダウンしながら行番号 19 から 24 を 4
回繰り返します。
channel block を後ろから降順に参照するため,例えば有料放送のチャンネ
ル数 channel num が 10 であれば,行番号 19 の cnum は 1 となり(行番号 23 の
channel num は 9),行番号 24 で tv fee の 1 回目の計算をします。
2 回目,cnum は 4(channel num は 5),tv fee に加算
3 回目,cnum は 3(channel num は 2),tv fee に加算
4 回目,cnum は 2(channel num は 0),tv fee に加算
1 ♯define BLKNUM 4
15
/*有料テレビ視聴料金 tv fee を求める*/
16
tv fee = basic charge[basic plan];
17
if (basic plan > 0) {
for (i = BLKNUM - 1; i >= 0; i--) {
18
19
20
cnum = channel num - channel block[i] + 1;
if (cnum < 0) {
21
cnum = 0;
22
}
23
channel num -= cnum;
24
tv fee += cnum * channel charge[basic plan - 1][i];
}
25
26
}
有料放送のチャンネル数 channel num が 1 で渡されたときで確認してみます。
1 回目,行番号 19 の cnum は-8 だが行番号 21 で 0(channel num は 1),tv fee は 0
2 回目,cnum は 0(channel num は 1),tv fee は 0
3 回目,cnum は 0(channel num は 1),tv fee は 0
4 回目,cnum は 1(channel num は 0),tv fee を計算
チ ャ ン ネ ル 数 が 多 い と tv fee を 計 算 し て 加 算 す る 回 数 が 多 く な り ま す が ,
channel charge を見ると,後ろから降順に参照しており,金額は少ない額ら多い
額へとなるため,チャンネル数が多くても割安と分かります。プラン basic plan が 2
のときは,さらに割安になることが分かります。
channel block についてはチャンネル数を段階的にまとめるためのものではないかと
いう推測が立ちます。
なお,有料放送のチャンネル数に正比例して有料放送視聴料金が計算されると最初に
予想して進めて構わないのですが,本文の説明とプログラムロジックから予想を必要に
応じて修正しながら進めるようにしてください。本問は最初の予想と反した場合,思考
を硬直化させず柔軟に対処できるかどうかの観点も含んでいるように思われます。
設問 1(1) の解答
【解答】 a はイ,b はエ,c はエ,d はウ
行番号 23 での cnum と行番号 43 での tv fee の値が問われてます。そのため,ループ
をきちんと 4 回繰り返してみる必要があります。そこで行番号における変数値を表にし
ました。略名もありますが右下に行番号をつけています。
ch num19
6
6
5
2
cnum19
ch blk19
10
6
3
1
-3
1
3
2
ch num23
cnum23
0
1
3
2
6
5
2
0
tv fee24
ch chg24
500
700
1000
3000
3000
3700
6700
9700
以上から a,b,c は 1,3,9700 なので,答えはそれぞれ,イ,エ,エとなります。
次に,プログラムの説明 (2) ではプラン 2 を選ぶと有料放送視聴料金は安くなるとはあ
りますが基本視聴料金の差額を超えるかどうかは不明です。同様の表を作って確認しま
す。
ch num19
6
6
5
2
cnum19
ch blk19
10
6
3
1
-3
1
3
2
ch num23
cnum23
0
1
3
2
6
5
2
0
tv fee24
ch chg24
400
600
800
1000
5000
5600
8000
10000
プラン 2 では 10,000 円になるのでプラン1より高いことになります。よって d の答え
はウです。
設問 2 の解答
【解答】 e はア,f はウ
条件分けで inet charge から inet fee に代入しています。表 2 と配列 inet charge
を見比べると 0 を除いて 2,500,3000,5000,6,000 を使って inet fee を求めていま
す。これは 2500 と 6,000 を重複して使っていることになります。
つまり,インターネット接続契約が低速回線のときは基本視聴契約がプラン 1 でもプ
ラン 2 でも同じ 2,500 円で,高速回線のときは基本視聴契約がなしかプラン 1 のとき,
6,000 円となることを意味します。
プログラム変数に置き換えてみると,inet plan が 1 のとき,basic plan は 1 か 2 で
inet charge[1] が使われます。
inet plan が 2 のとき,basic plan は 0 か 1 で inet charge[4] が使われます。
そうすると inet charge[1] は行番号 32 で使われているので e2 は低速回線の 1 とな
ります。inet charge[4] は行番号 40 で使われているので f の basic plan はプラン 2
の 2 ということになります。
e1 は basic plan による条件分けがないことや行番号 8 のコメントから inet plan が
0 のケースと分かります。
以上から e はア,f はウです。
(解答終り)
次の問 9 から問 13 までの 5 問については,この中から 1 問を選択し,選択した問題
については,解答用紙の選択欄の (選) をマークして解答してください。
なお,2 問以上マークした場合は,はじめの 1 問について採点します。
問 9 次の C プログラムの説明及びプログラムを読んで,設問 1∼3 に答えよ。
二つの英単語を辞書順で比較する関数 diccmp を作成した。
一般に,英大文字,英小文字,記号文字を含む英単語を一定の順に並べる方法と
して,次の例のように,文字列中の文字コードを順に並べる方法の他に,英和辞典
や書籍の欧文索引のような順序で並べる方法(以下,辞書順という)がある。
例 4 個の単語“A.M.”,
“CPU”,
“all”及び“best”を並べる方法
文字コード順:“A.M.”<“CPU”<“all”<“best”
辞書順:
“all”<“A.M.”<“best”<“CPU”
検索などの場合は,英単語を辞書順に整列しておくと検索の効率がよく,精度も
向上する。
関数 diccmp は,英大文字,英小文字,
“-”及び“.”を含む二つの英単語の大小
を辞書順で比較する。
〔プログラムの説明〕
関数 diccmp は,二つの英単語の大小を辞書順で比較する。その引数と返却値は,
次のとおりである。
引数:
word1,word2 英単語を格納した文字列
返却値: 負の値
word1<word2(例:“map”<“May”)
0
word1=word2(例:“Mr.”=“Mr.”)
正の値
word1>word2(例:“U.S.”>“US”)
(1) 引数 word1 と word2 は,いずれも次の条件を満たしている。
① 各文字は,英大文字(
“A”∼“Z”
)
,英小文字(
“a”∼“z”
)
,
“-”又は“.”
のいずれかである。英大文字と英小文字を合わせて,英字という。
② 文字列の長さは,1 以上 30 以下である。
③ 文字列の先頭の文字は,英字である。
④ 文字列中の“-”及び“.”の直前の文字は,英字である。
(2) 辞書順での比較の方法は,次のとおりである。
① 各引数の文字列から,基本文字列と文字情報列を生成する。基本文字列と
は,引数の文字列から英字だけを順に取り出して,大文字を小文字に変換し
た文字列である。文字情報列とは,基本文字列中の各文字に対応する 6 種類
の文字情報(大文字,小文字,大文字“-”付き,小文字“-”付き,大文字
“.”付き,小文字“.”付き)を表す情報の列である。ここで,
“-”及び“.”
は,直前の英字に属する文字情報とみなす。
例 引数の文字列:
“Fri.”
→基本文字列:
F
r
i
文字情報列: 大文字 小文字 小文字 “.”付き
② 各引数の基本文字列同士を関数 strcmp で比較し,異なっていれば,その
返却値(負の値 又は 正の値)を返す。
③ 6 種類の文字情報について,その値の大小関係はプログラムの中で定めて
いる。
(3) プログラム中で使用しているライブラリ関数の概要は,次のとおりである。
isalpha(c):
c が英字のとき 0 以外の値を返し,それ以外のとき 0 を返す。
islower(c):
c が英小文字のとき 0 以外の値を返し,それ以外のとき 0 を返す。
strcmp(s1,s2): 文字列 s1 と s2 を比較し,s1<s2 のとき負の値を,s1=s2 のとき
0 を,s1>s2 のとき正の値を,それぞれ返す。
tolower(c):
c が英大文字のときその文字に対応する英小文字を返し,それ以
外のとき c を返す。
〔プログラム〕
♯ include <ctype.h>
♯ include <string.h>
int diccmp(char *, char *);
void diccnv(char *, char *, char *);
int diccmp(char *word1, char *word1){
int rc;
char char1[31], char2[31], attr1[31], attr2[31];
diccnv(word1, char1, attr1);
diccnv(word2, char2, attr2);
rc = strcmp(char1, char2);
if (rc == 0)
rc = strcmp(attr1, attr2);
return rc;
}
void diccnv(char *wordx, char *charx, *attrx) {
int ch, cpos, wpos;
cpos = 0
wpos = 0
while (wpos < 30 && (ch=wordx[wpos++])!=’/0’){
if (isalpha(ch)) {
if (islower(ch)) {
charx[cpos]=ch;
attrx[cpos]=’0’;
}
else {
charx[cpos]=tolower(ch);
attrx[cpos]=’4’;
}
ch=wordx[wpos];
if (ch == ’-’ || ch == ’.’){
if (ch == ’-’)
attrx[cpos] +=1;
else
attrx[cpos] +=2;
wpos++;
}
cpos++;
}
}
charx[cpos]=’/0’;
attrx[cpos]=’/0’;
}
設問 1 次の記述中の
に入れる正しい値を,解答群の中から選べ。
引数 word1 の内容を“A.D.”
,word2 の内容を“ad-”として,関数 diccmp を実行
した。関数 diccmp 中の return 文を実行する時点で,attr1 の内容は“
”,attr2 の内容は“ b ”,rc の内容は“
c ”となる。
a,b に関する解答群
ア 01
イ 02
ウ 11
エ 22
オ 41
カ 42
キ 55
ク 66
c に関する解答群
ア 0
イ 正の値
ウ 負の値
a 設問 2 次の記述中の
に入れる正しい値を,解答群の中から選べ。
関数 diccmp を用いて,4 個の単語“CO”,
“Co.”,
“co-”及び“co.”を相互に比
較したとき,その大小関係は, d となる。
d に関する解答群
ア “CO”<“Co.”<“co-”<“co.” イ “CO”<“Co.”<“co.”<“co-”
ウ “Co.”<“CO”<“co-”<“co.” エ “co-”<“co.”<“Co.”<“CO”
オ “co.”<“co-”<“CO”<“Co.” カ “co.”<“co-”<“Co.”<“CO”
設問 3 次の表 1 中の
に入れる正しい答えを,解答群の中から選べ。
このプログラムは,引数 word1 及び word2 がプログラムの説明中の (1) に示した
条件①∼④を満たしているものとして作成している。しかし,引数が条件を満たさ
ない場合のプログラムの動作についても確認しておきたい。そこで,引数が条件を
満たさない場合のプログラムの動作を,表 1 にまとめた。
なお,文字列中の文字は,全て 1 バイト文字とする。
表 1 引数が条件を満たさない場合のプログラムの動作
条件
①
条件を満たさない場合
プログラムの動作
文字列中に,英字,
“-”
,
“.”
以外の文字がある。
②
文字列の長さが 0 である。
e 。
文字列の長さが 31 以上で
f までを有効とし,
ある。
③
先頭の文字が英字でない。
④
“-”及び“.”の直前の文
後続の文字を無視する。
g 。
字が英字でない。
注記 網掛けの部分は表示していない。
e に関する解答群
ア 空文字列として扱い,プログラムは正常に終了する
イ 配列に何も値を設定せずに比較をするので,予期できない結果となる
ウ 配列の定義範囲外への書込みが発生するので,よきできない結果となる
エ プログラムが終了しない
f に関する解答群
ア 30 個目の文字
イ 30 個目の文字(直後の文字が“-”又は“.”の場合はその文字)
ウ 30 文字目
エ 30 文字目(30 文字目が英字で 31 文字目が“-”又は“.”の場合は 31 文字目)
g に関する解答群
ア その文字が“-”,“.”以外の場合はその文字を無視するが,“-”又は“.”の場
合は配列の定義範囲外への書込みが発生するので,予期できない結果となる。
イ その文字を無視する。
ウ 配列の定義範囲外への書込みが発生するので,予期できない結果となる
エ プログラムが終了しない
出典 平成 25 年度 秋期 FE 区分 午後 問 9
解説
与えられた 2 つの文字列を辞書順の比較をしてその結果を返す処理に関する問題
です。
比較のときに問題となる 6 種類の文字情報の大小関係はプログラムの中で決めら
れています。
diccmp では,与えられた文字列を,diccnv で変換して,基本文字列と文字情報
列にそれぞれに分けます。
文字列比較は高々二段階で行います。最初に基本文字列を strcmp で比較して大
小が分かれば①その結果を返します③。
rc = strcmp(char1, char2);
if (rc == 0)← ①
rc = strcmp(attr1, attr2);← ②
return rc;← ③
基本文字列が同じ場合①は,文字情報列を比較します。このとき strcmp の戻り
値をそのまま return rc している③ので,与えられた文字列が同一の結果 0 も返り
ます。
diccnv で は ,与 え ら れ た 文 字 列 を 基 本 文 字 列 と 文 字 情 報 列 に 変 換 し ま す 。
ch=wordx[wpos++] から分かるように与えられた文字列から 1 文字ずつ取り出して
処理します。文字数が 30 文字を超えるか途中でもヌル値 ′ /0′ を見つけたときに繰
返し処理を抜けます。
while (wpos < 30 && (ch=wordx[wpos++])!=’/0’){
if (isalpha(ch)) {← ④
···
}
}
charx[cpos]=’/0’;
attrx[cpos]=’/0’;
繰返し処理を抜けた時に基本文字列と文字情報列の配列にそれぞれの文字列の終
わりとしてヌル値’/0’ を追加しています。
wpos は与えられた文字列(入力文字列)の配列のポインタであり,cpos は基本
文字列と文字情報列(出力文字列)の配列のポインタです。
取出した 1 文字が小文字のとき⑤,基本文字列の配列に格納し,文字情報列の配
列に 0 を格納します⑥。
if (islower(ch)) {← ⑤
charx[cpos]=ch;
attrx[cpos]=’0’;← ⑥
}
else {
charx[cpos]=tolower(ch);← ⑦
attrx[cpos]=’4’;← ⑧
}
大文字だったときは小文字に変換します⑦。単純にアルファベット文字として比
較するためですが,同時に文字情報列の配列に 4 を格納します⑧。小文字<大文字
の関係を保持します。
次に ch=wordx[wpos] により次の 1 文字を取り出します。
ここでの処理は文字の後ろに-や. がついていたときの文字情報列の処理です。
ch = wordx[wpos];
if (ch == ’-’ || ch == ’.’) {
if (ch == ’-’ )
attrx[cpos] +=1;← ⑨
else
attrx[cpos] +=2;← ⑩
wpos++;
}
cpos++;
’-’ のときは元の値(⑥または⑧)に 1 を加算し⑨,’.’ のときは元の値(⑥また
は⑧)に 2 を加算します⑩。
以上から,6 種類の文字情報の大小関係は,例えば文字を m としてみると次のよ
うになります。
’m’ < ’m-’ < ’m.’ < ’M’ < ’M-’ < ’M.’
文字情報の値では次のとおり。
0 < 1 < 2 < 4 < 5 < 6
なお,⑨や⑩のように,元の値 (0,4) に,ずらして加算する (+1,+2) ことで変化
の幅を持たせる (0,1,2,4,5,6) 手法はよく使われます。
設問 1 の解答
【解答】 a はク,b はア,c はイ
“A.D.”の attr1 は,A を処理した時点で 4,次の. を処理した時点で 6 となりま
す。そうすると,“A.D.”を処理し終えた時点で attr1 は 66 です。
同様に,“ad-”の attr2 は a を処理した時点で 0,d を処理した時点で 00,次の
-を処理した時点で 01 となります。
また,attr1>attr2 であることから rc は正の値です。
以上から a,b,c は,順に,ク,ア,イです。
設問 2 の解答
【解答】 エ
“CO”
,
“Co.”
,
“co-”
,
“co.”は,基本文字列では“co”で同じなので,文字情報
列の大小関係で決めます。
そうすると,順に,
“44”,
“42”,
“01”
,
“02”となるため,大小関係は,
“co-”<
“co.”<“Co.”<“CO”となります。よって答えは,エです。
設問 3 の解答
【解答】 e はア,f はエ,g はイ
e に関して,②で文字列長の長さが 0 であるとき,文字列の先頭にヌル値があれ
ば,while (wpos < 30 && (ch=wordx[wpos++]) != ’/0’ ) から何もせずに処理
を抜けます。よって答えはアです。
f に関して,while (wpos < 30 && (ch=wordx[wpos++]) != ’/0’) から 30 文字
まで処理を行います。そのとき,-や. 付きの英字のときは,それまで処理をしま
す。よって答えはエが最も妥当です。
g に関して,
「文字列の先頭は,英字である」ということから,与えられた文字列
の先頭から 1 文字を無条件で取り出してアルファベット文字かチェックしています
④。英字以外の場合は無視されます。よって答えはイが妥当です。なお,アは’-’
や’.’ のときも無視するので誤りであり,ウは while 文の終了判定条件から定義範
囲外への書込みは発生せず,エも同様に終了しないことはありません。
while (wpos < 30 && (ch=wordx[wpos++]) != ’/0’) {
if (isalpha(ch)) {← ④
···
}
}
charx[cpos] = ’/0’;
attrx[cpos] = ’/0’;
(解答終り)
次の問 9 から問 13 までの 5 問については,この中から 1 問を選択し,選択した問題
については,解答用紙の選択欄の (選) をマークして解答してください。
なお,2 問以上マークした場合は,はじめの 1 問について採点します。
問 9 次の C プログラムの説明及びプログラムを読んで,設問 1,2 に答えよ。
〔プログラムの説明〕
関数 format text は,印字したときに単語が行末で切れないようにテキストを編
集してファイルに出力するプログラムである。
(1) テキストに含まれる文字は,次のものである。
① 英字 A∼Z,a∼z
② 数字 0∼9
③ 記号 !"#\%&'()*+,-./:;<=>?[]ˆ_{|}∼
④ 空白文字
⑤ 改行文字
(2) 単語は,空白文字及び改行文字を含まない文字列であり,空白文字又は改行
文字で区切られている。単語の文字数は 20 以下とする。図 1 に,テキストに含
まれる単語の例を示す。
テキスト
ItΔisΔimportant
ΔΔtoΔmaster
ΔΔaΔprogramming
Δlanguage.
単語
注記 “Δ”は空白文字を表す。
図 1 テキストに含まれる単語の例
(3) 改行文字を除き,1 行分として印字できる文字数(以下,最大文字数という)
をプログラムの引数で与えるが,その値は 40 以上とする。単語の途中で最大文
字数を超える場合は,単語の直前に改行文字を出力し,その単語が次の行の先
頭に印字されるようにする。
(4) テキスト中の空白文字及び改行文字は,そのまま出力する。ただし,連続す
る空白文字を出力している途中で最大文字数を超える場合は改行文字を出力し,
最大文字数を超える分は次の行の先頭から印字されるようにする。
(5) 関数 format text の引数は次のとおりである。ここで,引数の値に誤りはな
いものとする。
in file
編集前のテキスト(以下,入力テキストとい
う)が格納されているファイル名
out file 編集後のテキスト(以下,出力テキストとい
う)を格納するファイル名
width
最大文字数
(6) プログラムを実行したときは,図 2 に示すようになる。
(入力テキスト)
TheΔ
Δ
Δ
ΔInformationΔ
Δ
ΔTechnologyΔ
Δ
ΔEngineers↓
Δ
Δ
ΔExaminationΔ
Δ
ΔwasΔ
ΔfirstΔ
ΔadministeredΔ
ΔinΔ
Δ19669.ΔInΔ1970,↓
ΔitbecameΔ
ΔaΔnationalΔexamination.Δ
↓
Δ
Δ
Δ
Δ
Δ
Δ
ΔSinceΔitsΔcommencementΔ
ΔtheΔexaminationΔhasΔplayedΔ
Δ
Δ
↓
Δ
Δ
Δ
ΔanΔimportantΔroleΔ
Δ
ΔinΔtheΔdevelopmentΔofΔITΔ
Δ
Δ
Δengineers.Δ
↓
(出力テキスト)
TheΔ
Δ
Δ
ΔInformationΔ
Δ
ΔTecnologyΔ
Δ
ΔEngineers↓
Δ
Δ
ΔExaminationΔ
Δ
ΔwasΔ
ΔfirstΔ
ΔadministerdΔ
ΔinΔ
Δ
↓
1969.ΔInΔ1970,↓
ΔitΔbecameΔaΔnationalΔexamination.Δ
↓
Δ
Δ
Δ
Δ
Δ
Δ
ΔSinceΔitsΔcommencement,ΔtheΔexaminationΔhas↓
ΔpayedΔ
Δ
Δ
↓
Δ
Δ
Δ
ΔanΔimportantΔroleΔ
Δ
ΔinΔtheΔdevelopmentΔITΔ
Δ
↓
Δ
Δengineers.Δ
↓
---------+---------+---------+---------+---------+
10
20
30
40
50
1
印字したときの文字位置
注記 “Δ”は空白文字を,“↓”は改行文字を表す。
図 2 最大文字数を 50 とした場合の例
〔プログラム〕
(行番号)
1 ♯include <stdio.h>
2 ♯define WLEN MAX 20 /*単語の最大長*/
3 void format text(char *, char *, int);
4 void format text(char *in file, char *out file, int width) {
5
6
7
8
9
FILE *ifp, *ofp;
int ch,
/*入力した文字*/
lpos = 0, /*出力処理をしている行での出力済み文字数*/
sp = 0; /*str に格納されている文字数*/
char str[WLEN MAX+2]; /*単語を含む出力用文字列*/
10 ifp = fopen(in file,"r");
11 ofp = fopen(out file,"w");
12 while ((ch = fgetc(ifp)) != EOF) {
13
if (ch == ’\n’){
14
str[sp++]=ch;
15
str[sp] =’\0’;
16
fputs(str, ofp);
a ;
17
18
19
20
21
22
23
24
25
26
27
28
sp = 0;
} else if (ch==’ ’) {
lpos += sp;
if (lpos >= width) {
str[sp++] = b ;
lpos = 0;
}
str[sp] = ’\0’;
fputs(str, ofp);
fputc(ch, ofp);
lpos++;
c ;
29
30
31
32
33
34
35
} else {
if ( d ) {
fputc(’\n’,otp);
lpos = 0;
}
str[sp++] = ch;
36
}
37 }
38 str[sp]=’ \0’;
39 fputs(str,ofp);
40 fclose(ifp);
41 fclose(ofp);
42 }
設問 1 プログラム中の
に入れる正しい答えを,解答群の中から選べ。
a に関する解答群
ア lpos = 0
イ lpos = sp
エ lpos++
オ lpos += sp
ウ lpos = width
b に関する解答群
ア ’\0’
イ ’\n’
ウ ’ ’
エ ch
c に関する解答群
ア sp=0
イ sp=lpos
エ str[lpos]=ch
オ str[sp++]=ch
ウ sp++
d に関する解答群
ア lpos > 0
イ lpos >= width
ウ lpos > sp
エ sp > 0
オ (lpos + sp) > 0
カ (lpos + sp) >= width
設問 2 次の記述中の
に入れる正しい答えを,解答群の中から選べ。
テキストの編集処理のうち,行頭の空白文字に関する処理を変更することになっ
た。これに合わせて関数 format text に,処理を追加する。ここで,プログラム中
の
a ∼ d には正しい答えが入っているものとする。
(1) テキストの編集処理の変更内容は,次のとおりである。
① 入力テキスト中の 1 文字以上の連続する空白文字列で,印字したときに行
頭に来る部分は出力しない。
② 最初に出力する単語の場合,又は行の先頭に来る単語の場合で直前に出力
した単語の最後の文字が“.”であったときは,単語の直前に空白文字を一
つ出力する。
(2) 処理を変更したプログラムを実行したときは,図 3 に示すようになる。
(入力テキスト)
TheΔ
Δ
Δ
ΔInformationΔ
Δ
ΔTechnologyΔ
Δ
ΔEngineers↓
Δ
Δ
ΔExaminationΔ
Δ
ΔwasΔ
ΔfirstΔ
ΔadministeredΔ
ΔinΔ
Δ19669.ΔInΔ1970,↓
.
a
ona
on
ecame
exam
na
na
Δitb Δ
ΔΔ ti lΔ i ti Δ
↓
Δ
Δ
Δ
Δ
Δ
Δ
ΔSinceΔitsΔcommencementΔ
ΔtheΔexaminationΔhasΔplayedΔ
Δ
Δ
↓
Δ
Δ
Δ
ΔanΔimportantΔroleΔ
Δ
ΔinΔtheΔdevelopmentΔofΔITΔ
Δ
Δ
Δengineers.Δ
↓
(出力テキスト)
ΔTheΔ
Δ
Δ
ΔInformationΔ
Δ
ΔTecnologyΔ
Δ
ΔEngineers↓
ExaminationΔ
Δ
ΔwasΔ
ΔfirstΔ
ΔadministeredΔ
ΔinΔ
Δ1969.Δ
↓
n
ΔIΔ1970,,
itΔbecameΔaΔnationalΔexamination.Δ
↓
ΔSinceΔitsΔcommencement,ΔtheΔexaminationΔhasΔ
↓
playedΔ
Δ
Δ
↓
anΔimportantΔroleΔ
Δ
ΔinΔtheΔdevelopmentΔofΔITΔ
Δ
Δ
Δ
↓
engineers.Δ
↓
---------+---------+---------+---------+---------+
10
20
30
40
50
1
印字したときの文字位置
注記 “Δ”は空白文字を,“↓”は改行文字を表す。
図 3 処理を変更し,最大文字数を 50 とした場合の例
(3) 処理の変更に対応するために,プログラムを表 1 のとおりに変更する。
行番号
8
処置
行番号
間に追加
と 9 の 26
変更内容
int lch=’.’;
行番号
の間に追加
と 27 28
行番号
の間に追加
と 29 34
の間に追加
と 35
if ( e ) {
}
if ((lpos==0)&&(sp==0)&&(lch==’.’)){
fputc(’ ’,ofp);
lpos++;
}
f ;
e に関する解答群
ア lpos==0
イ lpos==sp
ウ lpos!=0
エ lpos!=sp
オ sp==0
カ sp!=0
f に関する解答群
ア lch =
イ lch =
.
エ str[sp++] =
オ str[sp++] = lch
ウ lch = ch
出典 平成 26 年度 春期 FE 区分 午後 問 9
解説
関数 format text は最大文字数を指定してテキストを編集するプログラムです。
扱うテキストは 1 バイト文字です。テキストが最大文字数を超えるときは改行文字を付
与します。
プログラムは入力テキストから 1 バイト読込んで処理します。ファイルの終わり(EOF)
を読み込んだときに,繰返し処理を抜けます。繰返し処理は,読み込んだ文字が,改行
文字か,空白文字か,それ以外の文字かで処理が分かれます。
12
13
while ((ch = fgetc(ifp)) != EOF) {
if (ch == ’\n’){
···
} else if (ch==’ ’) {
···
19
if (lpos >= width) {
21
···
}
24
···
} else {
if ( d ) {
···
}
30
31
34
···
36
37
}
}
設問 1 の a の解答
【解答】 ア
改行文字の場合,行番号 14 でそのまま,出力用文字列 str に格納して配列のカウンタ
を進め,行番号 16 で出力ファイルに書き込んでいます。
なお,行番号 15 で文字列の終わりを示すヌル値を設定するのは,関数 fputs(str,ofp)
で編集後テキストに書き込む文字列の終わりを示すためです。念のため。
13
14
15
16
if (ch == ’\n’){
str[sp++]=ch;
str[sp] =’\0’;
fputs(str, ofp);
a ;
17
18
19
sp = 0;
} else if (ch==’ ’) {
本プログラムは,編集前のテキストから,1 文字読み込んでは,str に格納していき,1
単語分まとまったら必要に応じて編集して,編集後のテキストにまとめて書き込むこと
を繰り返しています。この処理を,行番号 7,8 のコメントにあるように lpos と sp とい
うポインタで制御します。
sp は str のポインタなので編集が終われば行番号 18 にあるようにクリアします。そ
うすると,行番号 17 の a は lpos に関するもので,編集が終わればクリアする処
理だと気が付きます。そうするとアが入ります。
設問 1 の b の解答
【解答】 イ
読み込んだ文字が空白文字のときは,行番号 19 以下の処理に入ります。
19
20
21
22
} else if (ch==’ ’) {
lpos += sp;
if (lpos >= width) {
str[sp++] = b ;
23
24
25
26
27
28
lpos = 0;
}
str[sp] = ’\0’;
fputs(str, ofp);
fputc(ch, ofp);
lpos++;
c ;
29
30
} else {
行番号 20 は,a で解答したように,文字を処理したことになるので lpos を 1 つ進め
ています。行番号 22 は,最大文字数を超える場合なので,改行文字を出力する,つまり
str に加えているところです。そのため行番号 23 で lpos をクリアしています。以上か
ら行番号 22 の b はイが入ります。
設問 1 の c の解答
【解答】 ア
行番号 26 で,str を出力用テキストに書き込んでいることから,29 の c はア
が入ります。
【解答】 カ
設問 1 の d の解答
行番号 30 から 36 では,読み込んだ文字が,改行文字や空白文字以外の文字であるこ
とから単語を構成する文字を処理します。
行番号 35 で出力用文字列 str にその文字を格納して sp ポインタを 1 つ進めています
が,行番号 31 の条件式を満たすときは,行番号 32 で改行文字を出力用テキストに書き
込み,行番号 33 で出力済み文字数カウンタ lpos をクリアしています。つまり出力用テ
キストの 1 行分が改行されたことになります。
30
31
32
33
34
35
36
} else {
if ( d ) {
fputc(’\n’,otp);
lpos = 0;
}
str[sp++] = ch;
}
これは,〔プログラムの説明〕の(3)にある「単語の途中で最大文字数を超える場合
は,単語の直前に改行文字を出力し,その単語が次の行の先頭に印字されるようにする」
という個所に該当します。
このプログラムは 1 単語分まとまったら必要に応じて編集して,編集後のテキストに
まとめて書き込むことを繰り返しています。そのため,行番号 7 のコメントにある「出
力処理をしている行での出力済み文字数」である lpos と行番号 7 のコメントにある「str
に格納されている文字数」である lpos の合計が,1 行の最大文字数を超えないように制
御します。
以上から行番号 31 の d には,カが入ります。
【解答】 ウ
設問 2 の e の解答
設問 2 で追加されたテキストの編集処理 (1) の①では,「入力テキスト中の 1 文字以上
の連続する空白文字列で,印字したときに行頭に来る部分は出力しない」とあります。
読み込んだ文字が空白文字のとき,行番号 20 と行番号 25 から分かるように単語の終
わりと判断して一旦編集を終了して,行番号 26 で編集用テキストに書込み,読み込んで
いた空白文字は,行番号 27 の fputc(ch,ofp); で 1 文字だけ書き込んでいます。
確かに改行されても空白文字はそのまま保持されるのですが,行の先頭で空白文字が
目立つ原因にもなっています。そこで,デリミタ(区切り文字)として空白は残しつつ,
余分な空白は読み飛ばす,つまり編集しないということが考えられます。
19
20
21
22
23
24
25
26
27
28
} else if (ch==’ ’) {
lpos += sp;
if (lpos >= width) {
str[sp++] = b ;
lpos = 0;
}
str[sp] = ’\0’;
fputs(str, ofp);
fputc(ch, ofp);
lpos++;
c ;
29
30
} else {
そのため,行番号 21 から行番号 24 にかけて,最大文字数を超えて改行したときに,行
番号 27 で空白文字を書き込まなければよいとなります。そのとき,次に読み込んだ文
字も空白文字のときは,str に空白文字を書き込まなければよいのですから,lpos のポ
インタが進まなければよいことになります。
以上から,行番号 26 と 27 の間に追加する e の条件式には,ウが入ります。
設問 2 の f の解答
【解答】 ウ
設問 2 で追加されたテキストの編集処理 (1) の②では,「最初に出力する単語の場合,
又は行の先頭に来る単語の場合で直前に出力した単語の最後の文字が“.
”であったとき
は,単語の直前に空白文字を一つ出力する」とあります。
2 つの命題が or 条件であるのですが,行番号 34 と 35 の間に追加する条件分では,3 つ
の条件式の and 条件となっています。そうすると,
「最初に出力する単語の場合」に条件
文が成立するには,int lch=’.’; である必要があります。そのため,行番号 8 と 9 の間
に int lch=’.’; が追加されています。
そして以降,
「行の先頭に来る単語の場合で直前に出力した単語の最後の文字が“.
”で
あったとき」が成立するか判定するために,lch に「直前に出力した単語の最後の文字」
を保持しておく必要があります。
以上から f には,ウが入ります。
(解答終り)
次の問 9 から問 13 までの 5 問については,この中から 1 問を選択し,選択した問題
については,解答用紙の選択欄の (選) をマークして解答してください。
なお,2 問以上マークした場合は,はじめの 1 問について採点します。
問 9 次の C プログラムの説明及びプログラムを読んで,設問 1,2 に答えよ。
B 社では,セキュリティ管理のために,システムファイルから定期的に,システ
ムの運用・保守用の利用者 ID 一覧を作成し,ファイルで保管している。
これらの利用者 ID に付加できる特権には,システムファイルの更新などができ
るシステム特権(以下,特権 S という)及びバックアップ作業などのために全ての
ファイルを参照できるオペレーション特権(以下,特権 O という)の 2 種類がある。
B 社では,セキュリティ管理強化の一環で,利用者 ID の追加・削除や特権の付
加・解除が,申請に基づいて正しくシステムに反映されているかどうかを検証する
ことになった。そのために,最新及び 1 世代前の利用者 ID 一覧を比較して,この
間の利用者 ID 及び特権の登録内容の差異を印字するプログラムを作成する。
〔プログラムの説明〕
(1) 最新の利用者 ID 一覧をファイル NewFile から,1 世代前の利用者 ID 一覧を
ファイル OldFile から,それぞれ読み込む。レコードは利用者 ID の昇順に整
列されている。
(2) 1 レコードは,利用者 ID(1∼8 桁)
,利用者名(1∼10 桁),属性(1 桁)及び
最終使用日(8 桁)の 4 項目から成る。各項目は,空白文字で区切られている。
① 利用者 ID 及び利用者名は,英数字から成る文字列である。
② 属性は,次に示す内容の 8 ビット長のビット列 0 1 0 0 s o g r である。
上位 4 ビット:
固定 0100
ビット s:
特権 S が付加されていれば 1,
付加されていなければ 0
ビット o:
特権 O が付加されていれば 1,
付加されていなければ 0
ビット g 及び r:
その他の属性
③ 最終使用日は,数字から成る文字列で,その利用者 ID で最後にログイン
した年月日を表す。登録後,一度もログインしていない場合は,全桁が“0”
である。
(3) 利用者 ID 及び特権の登録内容の差異は,次のように印字する。
① NewFile 中にあって OldFile 中にない利用者 ID の場合
利用者 ID,利用者名の後に“利用者 ID 追加”と印字する。この利用者 ID
に特権 S が付加されていれば“特権 S 付加”
,特権 O が付加されていれば“特
権 O 付加”を追加印字する。
② OldFile 中にあって NewFile 中にない利用者 ID の場合
利用者 ID,利用者名の後に“利用者 ID 削除”と印字する。この利用者 ID
に特権 S が付加されていれば“特権 S 解除”
,特権 O が付加されていれば“特
権 O 解除”を追加印字する。
③ NewFile 及び OldFile の両方にあり,特権 S と特権 O の少なくとも一方
の付加状況が変わった利用者 ID の場合
利用者 ID,利用者名の後に“特権 S 付加”
,
“特権 S 解除”
,
“特権 O 付加”
,
“特権 O 解除”の該当する全てを印字する。
(4) 入力ファイル NewFile 及び OldFile のデータ例を図 1 に,図 1 のデータ例を
用いた実行結果を図 2 に,それぞれ示す。
ファイル NewFile のデータ例
ファイル OldFile のデータ例
利用者ID 利用者名 属性 最終使用日 利用者ID 利用者名 属性 最終使用日
AE001
AE002
AE003
AP005
AP006
UserE1
UserE2
UserE3
UserP5
UserP6
41
48
48
46
44
20140419
20141014
20140716
20141015
00000000
AE001 UserE1 40 20140419
AE002 UserE2 44 20140712
AE003 UserE3 4C 20140716
AP004 UserP4 45 20140715
AP005 UserP5 44 20140713
注記 1 属性の値(網掛け部分)は,16 進数で表示している。
注記 2 見出し行は,各ファイルには含まれないものとする。
図 1 入力ファイル NewFile 及び OldFile のデータ例
利用者 ID
利用者名
登録内容の差異
AE002
AE003
AP004
AP006
UserE2
UserE3
UserP4
UserP6
特権 S 付加 特権 O 解除
特権 O 解除
利用者 ID 削除 特権 O 解除
利用者 ID 追加 特権 O 付加
注記 見出し行は,事前に印字されているものとする。
図 2 図 1 のデータ例を用いた実行結果
(5) ライブラリ関数 strcmp(s1,s2) は,文字列 s1 と s2 を比較し,s1<s2 のとき
負の値を,s1=s2 のとき 0 を,s1>s2 のとき正の値を,それぞれ返す。
〔プログラム〕
♯include <stdio.h>
♯include <string.h>
♯define BitS 0x08
♯define BitO 0x04
♯define BitR 0x01
FILE
*NewFile, *OldFile;
int
NewEof,
OldEof;
char
NewID[9], OldID[9];
char
NewName[11],OldName[11];
unsigned char NewAttr,
OldAttr;
char
NewDate[9], OldDate[9];
void ReadNewRecord(){
fscanf(NewFile,"%s %s %c %s ",NewID,NewName,
&NewAttr,NewDate);
if (feof(NewFile) != 0) {
NewEof=EOF;
/* EOF:負の整数定数 */
strcpy(NewID,"\xFF");/*\xFF:8 ビット符号の最大値*/
}
}
void ReadOldRecord(){
fscanf(OldFile,"%s %s %c %s",OldID,OldName,
&OldAttr,OldDate);
if (feof(OldFile) != 0) {
OldEof=EOF;
strcpy(OldID,"\xFF");
}
}
void main(){
NewFile=fopen("NewFile","rb");
OldFile=fopen("OldFile","rb");
NewEof=0;
OldEof=0;
ReadNewRecord();
ReadOldRecord();
while ( a ) {
if (strcmp(NewID,OldID)==0) {
if ( b ) {
printf("\n%-8s %-10s",NewID,NewName);
if ((NewAttr & BitS)>(OldAttr & BitS))
printf(" 特権 S 付加");
if ((NewAttr & BitS)<(OldAttr & BitS))
printf(" 特権 S 解除");
if ((NewAttr & BitO)>(OldAttr & BitO))
printf(" 特権 O 付加");
if ((NewAttr & BitO)<(OldAttr & BitO))
printf(" 特権 O 解除");
}
c α }
else {
if ( d ) {
printf("\n%-8s %-10s 利用者 ID 追加",NewID,NewName);
if ((NewAttr & BitS)==BitS) printf(" 特権 S 付加");
if ((NewAttr & BitO)==BitO) printf(" 特権 O 付加");
ReadNewRecord();
}
else {
printf("\n%-8s %-10s 利用者 ID 削除",NewID,NewName);
if ((OldAttr & BitS)==BitS) printf(" 特権 S 削除");
if ((OldAttr & BitO)==BitO) printf(" 特権 O 削除");
ReadOldRecord();
}
}
}
fclose(OldFile);
fclose(NewFile);
}
設問 1 プログラム中の
に入れる正しい答えを,解答群の中から選べ。
a に関する解答群
ア (NewEof != EOF) && (OldEof != EOF)
イ (NewEof != EOF) || (OldEof != EOF)
ウ NewEof != OldEof
エ NewEof == OldEof
b に関する解答群
ア ((NewAttr & OldAtrr) & (BitS+BitO)) != 0x00
イ ((NewAttr | OldAtrr) & (BitS+BitO)) != 0x00
ウ (NewAttr & (BitS+BitO)) != (OldAtrr & (BitS+BitO))
エ (NewAttr | (BitS+BitO)) != (OldAtrr | (BitS+BitO))
c に関する解答群
ア ReadNewRecord();
イ ReadNewRecord();
ReadOldRecord();
ウ ReadOldRecord();
d に関する解答群
ア (NewAttr & (BitS+BitO)) != 0x00
イ NewAttr > OldAttr
ウ strcmp(NewID,OldID) < 0
エ strcmp(NewID,OldID) > 0
設問 2 次の記述中の
に入れる正しい答えを,解答群の中から選べ。
利用者 ID が有効に使用されているかどうかを検証するために,一定期間未使用,
又は現在使用不可の利用者 ID 一覧を印字するプログラムを作成する。
(1) 最新の利用者 ID 一覧をファイル NewFile から,一定期間前の世代の利用者
ID 一覧をファイル OldFile から,それぞれ読み込む。
(2) 次の①,②の少なくとも一方に該当する利用者 ID について,利用者 ID,利
用者名の後に,①に該当する場合は“現在使用不可”を,②に該当する場合は
“期間中未使用”を印字する。
① NewFile 中にあって,属性のビット r が 1 である利用者 ID
ここで,属性のビット r は,利用者 ID が使用可能なら 0,使用不可なら 1 で
ある。
② NewFile 及び OldFile の両方にあって,最終使用日の値が等しい利用者
ID(全桁が“0”同士で等しい場合を含む)
(3) 図 3 に,図 1 のデータ例を用いた実行結果を示す。
利用者 ID
利用者名
使用状況
AE001
AE003
UserE1
UserE3
現在使用不可 期間中未使用
期間中未使用
注記 見出し行は,事前に印字されているものとする
図 3 図 1 のデータ例を用いた使用状況の印字結果
この処理を実装するためには,プログラム中の while 文のブロック内(α で示し
た部分)を次のように変更すればよい。ここで,プログラム中の a には,正
しい答えが入っているものとする。
if (strcmp(NewID,OldID) <= 0) {
if (( e )||( f )) {
printf("\n%-8s %-10s",NewID,NewName);
if ( e )
printf(" 現在使用不可");
if ( f )
printf(" 期間中未使用");
}
ReadNewRecord()
}
else
ReadOldRecord()
e,f に関する解答群
ア
(NewAttr & BitR)!=(OldAttr & BitR)
イ
(NewAttr & BitR)==BitR
ウ
strcmp(NewDate,OldDate)==0
エ
strcmp(NewID,OldID)==0
オ
strcmp(NewID,OldID)==0 && (NewAttr & BitR)==BitR
カ
strcmp(NewID,OldID)==0 && strcmp(NewDate,OldDate)==0
出典 平成 26 年度 秋期 FE 区分 午後 問 9
解説
利用者 ID や特権の状況変化を差分として印字するプログラムです。差分は,最新
と 1 世代前の利用者 ID リストを比較しますが,特権は利用者 ID の状況に準じて処理し
ます。
① NewFile にあり OldFile になければ利用者 ID の追加,② OldFile にあり NewFile
になければ削除,③ NewFile と OldFile にあれば特権が変わっています。
main の制御は,if (strcmp(NewID,OldID)==0) で利用者 ID が同じなら,特権が変
わったとして if ( b ) 以下を行います。条件式を満たさないときは,利用者 ID
の追加または削除なので,if ( d ) で切り分けます。
void main(){
ReadNewRecord();
ReadOldRecord();
while ( a ) {
if (strcmp(NewID,OldID)==0) {
if ( b ) {
···
}
c }
else {
if ( d ) {
···
ReadNewRecord();
}
else {
···
ReadOldRecord();
}
}
}
if ((NewAttr & BitS)>(OldAttr & BitS)) の NewAttr & BitS では,属性 8 ビット
のうち下位から 4 ビット目の「ビット s」を抜き出すために BitS と and(論理積)をとっ
ています。そのために,♯define BitS 0x08 と定義していたのでした。
ここで NewAtt の「ビット s」が立っていれば NewAttr & BitS は 1 となります。一方,
OldAttr の「ビット s」が立っていなければ OldAttr & BitS は 0 となり,(NewAttr &
BitS)>(OldAttr & BitS) が成り立ちます。これは以前にはなかった特権 S が付加され
たことを意味します。なお,NewAttr & BitS と OldAttr & BitS の結果が一致すると
きは特権の状況に変化がないことを意味します。
if ( b ) {
printf("\n%-8s %-10s",NewID,NewName);
if ((NewAttr & BitS)>(OldAttr & BitS))
printf(" 特権 S 付加");
if ((NewAttr & BitS)<(OldAttr & BitS))
printf(" 特権 S 解除");
if ((NewAttr & BitO)>(OldAttr & BitO))
printf(" 特権 O 付加");
if ((NewAttr & BitO)<(OldAttr & BitO))
printf(" 特権 O 解除");
}
c if ((NewAttr & BitS)<(OldAttr & BitS)) では,NewAttr & BitS が 0 で OldAttr
& BitS が 1 なので特権 S が削除されたことが分かります。特権 O についても同様に判
定ができます。
そうすると,if ( b ) { では,NewAttr と OldAttr の比較をして属性の変化が
ないか判定していると推測できます。
if ( d ) { では,以下の処理で特権の付加または削除を行っており,OldID が
存在するかを判定する条件式があると推測できます。
ReadNewRecord() では,最新の利用者 ID リストを改行までの 1 行分を,fscanf(NewFile,"%s
%s %c %s ",NewID,NewName,&NewAttr,NewDate); で読み込んで,NewID,NewName,
NewAttr,NewDate に設定します。
そのとき,if (feof(NewFile) != 0) { によりファイルの終端を検出したときは,
NewEof に stdio.h で定義されている EOF を設定し,NewID には,“-1”を設定します。
ReadOldRecord() も同様です。
設問 1 の a の解答
【解答】 イ
利用者 ID リストを 1 レコード読み込んで利用者 ID を比較するのですが読込みの終了
は EOF を読み込んだときにする必要があります。EOF を読み込んで次に更に読み込むと
システムエラーとなるためです。
昇順に並んでいる利用者 ID をキーとしてそれぞれ比較しながらマージ処理を行って
います。片方の利用者 ID キーが先行しているときは遅れているほうのファイルからレ
コードを読込み,追い付く処理となります。同じタイミングで EOF を読み込むとは限ら
ないためどちらかが EOF を検出した時点で繰り返し処理を抜けます。逆にいえば,繰り
返し処理を抜ける時点では,未処理のレコードが残っていないようになっています。
設問 1 の b の解答
【解答】 ウ
同じ利用者 ID の最新の利用者 ID リストにおける属性と 1 世代前の利用者 ID リストに
おける属性に変化がないか調べる条件式を見つけます。
そうすると,特権に関するビット s とビット o を抜き出して互いに比較すればよいた
め,b にはウが入ります。
なお,念のためいうと,アとイは,NewAttr と OldAttr の&(論理積)または|(論理
和)を行った時点で元の属性が失われるので判定ができなくなり,エも,ビット s,ビッ
ト o との|(論理和)を取っているため元の属性が失われます。
設問 1 の c の解答
【解答】 イ
c の時点で処理しているのは,最新の利用者 ID リストと 1 世代前の利用者 ID リストか
ら読み込んだ同じ利用者 ID です。どちらの利用者 ID リストでも処理を終えているので
次のレコードを読み込む必要があります。
以上から,c にはイが入ります。
設問 1 の d の解答
【解答】 ウ
レコードは利用者 ID の昇順になっています。比較すべき利用者 ID があり,最新の利
用者 ID リストの利用者 ID が 1 世代前の利用者 ID リストの利用者 ID より大きいときは,
本来は 1 世代前の利用者 ID リストの利用者 ID と合致しなければいけないところから,1
世代前の利用者 ID リストから読み込んでいる利用者 ID はなくなっていることになりま
す。つまり当該利用者 ID は削除されたことになります。
一方,最新の利用者 ID リストの利用者 ID が 1 世代前の利用者 ID リストの利用者 ID
より小さいときは,本来は 1 世代前の利用者 ID リストの利用者 ID と合致しなければい
けないところから,1 世代前の利用者 ID リストには元々なかった利用者 ID を読み込ん
でいることになるため,当該利用者 ID は追加されていることになります。
ここで d の条件式が成立すると追加処理を行うため,d では最新の利用者 ID リストの
利用者 ID が 1 世代前の利用者 ID リストの利用者 ID より小さいかを判定していると考
えられます。
そうすると,ライブラリ関数 strcmp(s1,s2) は,文字列 s1 と s2 を比較し,s1<s2 の
とき負の値を返す仕様であるため,d にはウが入ります。
一定期間未使用又は現在使用不可の利用者 ID 一覧を印字するプログラムです。
設問 2
現在使用不可は,最新の利用者 ID リストにおける利用者 ID の属性のビット r が 1 か
どうかで判定します。
期間中未使用は,任意の世代前の利用者 ID リストにある利用者 ID が最新の利用者 ID
リストにも存在し,かつ,最終使用日が同一かどうかで判定します。始まりと終わりと
で最終使用日が変わらなければ,その間は使われていないことになります。
そうすると if ( e ) では,属性のビット r が 1 かどうかの判定を行うことにな
り,if ( f ) では,任意の世代前の利用者 ID リストにある利用者 ID が最新の利
用者 ID リストにも存在し,かつ,最終使用日が同一かどうかの判定を行うことになり
ます。
if (strcmp(NewID,OldID) <= 0) {
if (( e )||( f )) {
printf("\n%-8s %-10s",NewID,NewName);
if ( e )
printf(" 現在使用不可");
if ( f )
printf(" 期間中未使用");
}
ReadNewRecord()
}
else
ReadOldRecord()
設問 2 の e の解答
【解答】 イ
e では最新の利用者 ID リストから読み込んだ利用者 ID の属性のビット r が 1 かどうか
の判定を行っているため,イが入ります。
なお,アでは任意の世代前の利用者 ID の属性のビット r と比較をしているため,属性
のビット r が 1 であると現在使用不可にもかかわらず,印字されない不都合が生じます。
念のため。
設問 2 の f の解答
【解答】 カ
f では任意の世代前の利用者 ID リストにある利用者 ID が最新の利用者 ID リストにも
存在し,かつ,最終使用日が同一かどうかの判定を行っているため,カが入ります。
(解答終り)
次の問 9 から問 13 までの 5 問については,この中から 1 問を選択し,選択した問題
については,解答用紙の選択欄の (選) をマークして解答してください。
なお,2 問以上マークした場合は,はじめの 1 問について採点します。
問 9 次の C プログラムの説明及びプログラムを読んで,設問 1,2 に答えよ。
〔プログラムの説明〕
ひらぶん
かえじ
平文の文字列を暗号化する手段として,平文の文字を換字表に従って別の文字に
置き換える方法がある。関数 enc str は,与えられた平文を,換字表を用いて暗号
文に変換するプログラムである。
(1) 平文は,JIS X 0201(7 ビット及び 8 ビットの情報交換用符号化文字集合)の文
字で構成されている。
(2) 置換の対象となる文字(以下,置換対象文字という)は,英字(A∼Z,a∼z),
数字(0∼9),空白文字,
“.”及び“,”の 65 種類の文字であり,換字表に格納さ
れている。
(3) 換字表は 5 行 13 列の 2 次元文字型配列であり,全ての要素に異なる文字が格納
されている。
(4) 換字表による置換の方法について,図 1 に示す平文と暗号文の対応の例を用い
て説明する。平文の文字列の先頭から置換対象文字を 2 文字ずつ選び出して行い,
置換されたそれぞれの文字を暗号文の文字として,暗号文中で平文と同じ位置に
入れる。ここで,置換対象文字の組合せによって,置換後の文字の組合せが決ま
る。置換対象文字数が奇数の場合,最後の置換対象文字については 1 文字で置換
を行う。置換対象文字以外の文字については,平文中の文字をそのまま暗号文中
で平文と同じ位置に入れる。
(平文)
(暗号文)
F
E
-
E
X
A
M
.
-
注記 網掛けの部分には置換された文字が入る。
図 1 平文と暗号文の対応の例
(5) 関数 enc str の仕様は次のとおりである。ここで,引数の値に誤りはないもの
とする。
機能: 文字列 str を,換字表 xchg t を用いて平文
から暗号文に変換する。
引数: str
文字列
xchg t 換字表
〔プログラム〕
♯define RSIZ 5 /* 換字表の行列 */
♯define CSIZ 13 /* 換字表の行列 */
void enc str(char[],char[RSIZ][CSIZ]);
void enc str(char str[],char xchg t[RSIZ][CSIZ]) {
int cp[2],
/* 置換対象文字の換字表の列位置 */
rp[2],
/* 置換対象文字の換字表の行位置 */
pos[2], /* 置換対象文字の文字列中の位置 */
flg,i = 0,col,row,p = 0;
while (str[p] != ’\0’) {
flg = 0;
for (row = 0;row < RSIZ; row++) {
for (col = 0;col < CSIZ; col++) {
if (str[p] == xchg t[row][col]) {
flg=1;
break;
}
}
if (flg != 0) break;
}
if (flg != 0) {
α
cp[i] = col;
rp[i] = row;
pos[i++] = p;
if (i == 2) {
if (str[pos[0]] == str[pos[1]]) {
str[pos[0]] = str[pos[1]] =
xchg t[(rp[0]+1) % RSIZ]
[(cp[0]+1) % CSIZ];
} else {
if (rp[0]==rp[1]) {
str[pos[0]] = xchg t[rp[1]][cp[1]];
str[pos[1]] = xchg t[rp[0]][cp[0]];
} else if (cp[0] == cp[1]) {
str[pos[0]] = xchg t[rp[0]]
[(cp[0]+1) % CSIZ];
str[pos[1]] = xchg t[rp[1]]
[(cp[1]+1) % CSIZ];
} else {
str[pos[0]] = xchg t[rp[1]][cp[1]];
str[pos[1]] = xchg t[rp[0]][cp[1]];
}
}
i=0;
β
}
}
p++;
}
if (i != 0) {
str[pos[0]] = xchg t[RSIZ-1-rp[0]][CSIZ-1-cp[0]];
}
}
設問 1 図 2 の平文と換字表を引数として関数 enc str を実行したとき,プログラムの
動作に関する次の記述中の
に入れる正しい答えを,解答群の中から選べ。
文字列
[位置]
0
10
20 25
+---------+---------+----+
(平文) Function f(x, y) is given.
換字表 [行位置][列位置]
0
1
2
3
4
0
a
n
0
Z
M
1
b
o
1
Y
L
2
c
p
2
X
K
3
d
q
3
W
J
4
e
r
4
V
I
5
f
s
5
U
H
6
g
t
6
T
G
7
h
u
7
S
F
8
i
v
8
R
E
9 10 11 12
j
k l
m
w x y z
9 , .
Q P O N
D C B A
注記 “ ”は空白文字を表す。
図 2 平文と換字表の例
(1) プログラムの α の行が実行されるとき,変数 flg の値は 1,i の値は 0,col の値は
の値は
b a ,row
になっている。
(2) プログラムの β の行が最初に実行されるとき,引数 str,配列 cp,rp,pos に格納されている値は,図
3 に示すとおりである。
(添字)
0
(添字)
cp
rp
pos
1
c str
0
a b 0
2
’n’
···
···
1
7
1
1
注記 網掛けの部分は表示していない。
図 3 引数 str, 配列 cp,rp,pos に格納されている値
(3) プログラムの β の行が 5 回目に実行されるとき,pos[1] の値は 9,引数 str[9] の値は
d なっている。
(4) プログラムの β の行が 6 回目に実行されるとき,pos[1] の値は
e になっている。
に
a,b に関する解答群
ア 1
イ 2
ウ 3
エ 4
オ 5
カ 6
キ 7
ク 8
ケ 9
コ 10
c に関する解答群
ア ’F’
イ ’S’
エ ’v’
オ ’7’
ウ ’t’
d に関する解答群
ア ’n’
イ ’O’
エ ’Z’
オ 空白文字
ウ ’y’
e に関する解答群
ア 11
イ 12
ウ 13
エ 14
オ 15
設問 2 図 2 に示す換字表を使って平文“IPA”を暗号文に変換した結果として正しい答えを,解答群の中から
選べ。
解答群
ア CVa
イ iAP
ウ iCN
エ iNC
オ PIa
ウ VCa
出典 平成 27 年度 春期 FE 区分 午後 問 9
解説
enc str は,平文を,換字表を用いて暗号文に変換するプログラムです。2 文字ず
つ換字しますが,文字の並び位置は変わりません。1 文字残れば 1 文字で変換します。
while 文で,渡された文字列 str[] の終わりまで処理します。2 文字ずつ処理するとこ
ろから flg を使って前半の文字と後半の文字を処理します。if (flg != 0) { は 1 文字
だけを処理して抜けるためです。
while (str[p] != ’\0’) {
flg = 0;
for (row = 0;row < RSIZ; row++) {
for (col = 0;col < CSIZ; col++) {
if (str[p] == xchg t[row][col]) {
flg=1;
break;
}
}
if (flg != 0) break;
}
if (flg != 0) {
···
}
p++;
}
換字表の行 row を固定して,その行に属する列 col を先頭から最後まで回しなが
ら str[p] と textttxchg t[row][col] が一致するか探ります。見つかれば flg を 1 にして
break 文により内側のループを抜け,更に if (flg != 0) break; により外側のループ
を抜けます。
cp[0] には,2 文字の最初の文字に合致した列番号 col が入り,cp[1] には後の文字に
合致した列番号 col が入ります。同様に rp[0],rp[1] には行番号 row が入ります。
一方,rp[0] には,文字列 str[] から取り出した 2 文字の最初の文字の str[] の文字
位置が入り,rp[1] には,後の文字の str[] の文字位置が入ります。
while (str[p] != ’\0’) {
···
if (flg != 0) {
cp[i] = col;
rp[i] = row;
pos[i++] = p;
if (i == 2) {
if (str[pos[0]] == str[pos[1]]) {
···
} else {
···
}
i=0;
}
}
p++;
···
}
文字位置を pos[i++] = p; で代入した後で i をカウントアップしており,2 文字目を
処理したときに if (i == 2) { によりまとめて次の処理を行います。
if (str[pos[0]] == str[pos[1]]) { により,取り出した 2 文字の最初の文字と後
の文字が同じ文字の場合は,str[pos[0]],str[pos[1]] にそれぞれ同時に,換字表を
利用して新しく作成した文字を代入しています。
if (str[pos[0]] == str[pos[1]]) {
str[pos[0]] = str[pos[1]] =
xchg t[(rp[0]+1) % RSIZ]
[(cp[0]+1) % CSIZ];
} else {
···
}
i=0;
変換は,xchg t[(rp[0]+1) % RSIZ][(cp[0]+1) % CSIZ] において,行の要素
(rp[0]+1) % RSIZ で,2 文字の最初の文字に合致した換字表の行番号 rp[0] を使
い,それに 1 を加算したものを RSIZ,つまり 5 で割った余りを求めています。
同様に,列の要素 [(cp[0]+1) % CSIZ] で,2 文字の後の文字に合致した換字表の列番
号 cp[0] を使い,それに 1 を加算したものを CSIZ,つまり 13 で割った余りを求めてい
ます。
これらから,換字表のある要素から別の要素へシフトさせていることが分かります。
その上で,その別の要素にある換字表の文字を str[pos[0]] に str[pos[1]] 入れてい
ます。
これで,例えば,同じ 2 文字 aa が YY などのように変換されます。
次に,取り出した 2 文字が異なる場合ですが,if (rp[0]==rp[1]) { により,2 文字
の最初の文字に合致した換字表の行番号 rp[0] と後の文字に合致した換字表の行番号
rp[1] が同じ場合には,str[pos[0]] には,換字表の同じ行番号を持つ 2 文字の後の文
字に合致した換字表の列番号 cp[1] を使い,そこにある換字表の要素を入れています。
str[pos[1]] には,同様にして,換字表の同じ行番号を持つ 2 文字の最初の文字に合
致した換字表の列番号 cp[0] を使い,そこにある換字表の要素を入れています。
つまり,ここでは,取り出した 2 文字を入れ替えて格納し直しています。
if (rp[0]==rp[1]) {
str[pos[0]] = xchg t[rp[1]][cp[1]];
str[pos[1]] = xchg t[rp[0]][cp[0]];
} else if (cp[0] == cp[1]) {
str[pos[0]] = xchg t[rp[0]]
[(cp[0]+1) % CSIZ];
str[pos[1]] = xchg t[rp[1]]
[(cp[1]+1) % CSIZ];
} else {
str[pos[0]] = xchg t[rp[1]][cp[1]];
str[pos[1]] = xchg t[rp[0]][cp[1]];
}
2 文字の最初の文字に合致した換字表の列番号 cp[0] と後の文字に合致した換字表の
列番号 cp[1] が同じ場合には,2 文字の最初の文字に合致した換字表の行番号 rp[0]
と後の文字に合致した換字表の行番号 rp[1] は,それぞれ行番号の要素としてそのま
ま使いますが,2 文字の最初の文字に合致した換字表の列番号については,列の要素
[(cp[0]+1) % CSIZ] で,最初の文字に合致した換字表の列番号 cp[0] を使い,それに
1 を加算したものを CSIZ,つまり 5 で割った余りを求め,同様にして,2 文字の後の文字
に合致した換字表の列番号については,[(cp[1]+1) % CSIZ] で,後の文字に合致した
換字表の列番号 cp[1] を使い,それに 1 を加算したものを CSIZ,つまり 5 で割った余り
を求めています。
ここでは,文字に合致した換字表の行番号は変えないものの列番号をシフトさせるこ
とで別の文字に変換しています。
2 文字の最初の文字に合致した換字表の行番号と後の文字に合致した換字表の行番号
が異なり,かつ列番号も異なるときは,互いに行番号を入れ替えることで別の文字に変
換しています。
渡された文字列 str[] から 2 文字ずつ取り出して変換しますが,最後に 1 文字しか取
り出せなかったときは,上記のいずれの変換もされないため,if (i != 0) { 以下で変
換します。
while (str[p] != ’\0’) {
···
}
if (i != 0) {
str[pos[0]] = xchg t[RSIZ-1-rp[0]][CSIZ-1-cp[0]];
}
str[pos[0]] = xchg t[RSIZ-1-rp[0]][CSIZ-1-cp[0]] は,つまるところ換字表の
行番号の要素を RSIZ-1-rp[0]] により,ひっくり返しており,0 → 4,1 → 3,2 → 2,
3 → 1,4 → 0 となり,換字表の列番号の要素も同様に,交代します。
すぐに気づかれるように,行列要素が行数,列数とも奇数なので,3 行と 7 列が交差す
る要素が中心となるため,換字表のそこに定義されている文字,つまり 6 が,与えられ
る文字列の最後にくると同じ文字になります。
設問 1 の (1) の a,b
【解答】 a はキ,b はエ
最初の文字は,“F”なので換字表から探すと,行位置が 4,列位置が 7 です。よって,
a には列位置である 7 が入るため,キが入り,b には行位置ではある 4 が入るため,エが
入ります。
設問 1 の (2) の c
【解答】 c はエ
図 3 から cp[1] は 7 なので [(cp[1]+1) % CSIZ]= 8 % 13=8 となり,換字表では“u”
の右隣の“v”となります。
【解答】 d はエ
設問 1 の (3) の d
設問 1 にある平文を 2 文字ずつ処理して,5 回目となる β の個所では,
“ΔΔ”を換字し
たところです。それで pos[1] は 9 となっており,換字表から,“(添字)”は,cp が 12,
rp が,2 です。
同じ 2 文字を処理するところなので,行番号,列番号を 1 つずらすことになり,cp が
0,rp が 3 なので,str[9] には,
“Z”が格納されるため,d には,エが入ります。
設問 1 の (4) の e
【解答】 e はイ
設問 1 にある平文を 2 文字ずつ処理して,5 回目までは換字対象文字が換字表にありま
した。次の 2 文字を見ると“f(”なので,換字表にない“(”は,換字表にはないため,
換字されることなく“str[11]”にそのまま“(”として残ります。このとき,if (i ==
2) { の条件式が i=1 のままなので成立せず,β の個所は実行しません。
次の変換前の“str[12]”の文字“x”を読み込んだところで 2 文字をまとめて換字処
理になります。このとき,pos[1] には,12 が入っています。よって e には,イが入りま
す。
設問 2
【解答】 カ
“IPA”には,換字できない文字はありません。まず 2 文字を考えます。“IP”は,
(添字) 0
1
cp
4
10
rp
4
3
なので,換字ロジックにより行を入れ替えると,
(添字) 0
1
cp
4
10
rp
3
4
となるので,
“VC”となりこの時点で,該当する候補は“VCa”だけです。よって,カ
が入ります。
(解答終り)