Programmers Room Tips集 
情報処理試験関連 更新[2000.02.14]

Contents

1999年秋 問11の解説 [2000.02.14]

以下は、2000年2月14日発行の「実戦・C/C++言語」の増刊号に掲載された、1999年秋情報処理2種の問題の一つで、私なりの解説を加えてあります。

間11 次のCプログラムの説明及びプログラムを読んで,設問1,2に答えよ。

[プログラムの説明]

関数wordwrapは,単語が行末で切れないように英文を出力する。

  1.  関数wordwrapの引数は,英文が格納された入力文字列の配列str[]と, 出力する1行の最大文字数max(ただし,改行文字を含まない)である。
  2. 入力文字列中の単語は,1文字の空白文字で区切られている。入力文字列 の先頭と最後に空白文字はない。
  3. 単語の途中で1行の最大文字数を超える場合は,その単語が次の行の先頭 になるように出力する。
  4. 単語の最大長をwx,入力文字列の長さをsxとすると,0<wxく16≦max<sx<1024の関係が成り立つものとする。

入力文字列:

The△CPU△executes△programs△which△tell
△the△computer△what△to△do\0
(△:空白文字)

1行の最大文字数: 30

出力:

The△CPU△executes△programs
whlch△tell△the△computer△what
to△do

[プログラム]

(行番号)
01 #include <stdio.h>
02 #include <string.h>
03 #define BUFFMAX 1024
04 #define WORDMAX 16
05
06 void wordrap(char str[], int max)
07 {
08     char word[BUFFMAX / 2][WORDMAX], buff[BUFFMAX];
09     int leng[BUFFMAX / 2], i, idx, cnt, pos;
10                                 /*** 文字列を単語に分解する ***/
11     i = idx = cnt = 0;
12     while (str[i] != '\0') {
13         if (str[i] == ' ' ) {
14             word[idx][cnt] = '\0';
15             leng[idx] = cnt;
16             idx++;
17             cnt = 0;
18         }
19         else {
20             word[idx][cnt] = str[i];
21             cnt++;
22         }
23         i++;
24     }
25     word[idx][cnt] = str[i];
26     leng[idx] = cnt;
27                                                       /*** 出力する ***/
28     i= pos = O;
29     while (i <= idx) {
30         if ((pos + leng[i]) > max) {
31             buff[pos-1] = '\0';
32             printf( "%s\n", buff);
33             pos = 0;
34         }
35         strcpy(&buff[pos], word[i]);
36         pos += leng[i];
37         buff[pos] = ' ';
38         pos++;
39         i++;
40     }
41     buff[pos-1] = '\0';
42     printf("%s\n", buff):
43 }

設問1 次の記述中の【 】に入れる正しし答えを,解答群の中から選べ。

入力文字列と1行の最大文字数が次の値のとき,21行目は【 a 】回実行
される。また,プログラムが終了したときの変数posの値は【 b 】である。

入力文字列   [AAAA△BBBBB△CCC△DDDD△EEE△FFF\0]

1行の文字数: 18

解答群
    ア 5    イ 7    ウ 8   エ 12  オ 13
    力 15  キ 16  ク 22  ケ 27

設問2 次の記述中の【 】に入れる正しい答えを,解答群の中から選べ。

入力文字列の先頭の空白文字はそのまま出力するように,最初の単語に先
頭の空白文字列を含め,また単語間の空白文字が何文字あっても,1文字に
するようにプログラムを修正する。
そのためには,αを12行目の直前に,βを18行目の直前に挿入すればよい。

α while (str[i] == ' '){
        word[idx][cnt] = str[i];
        【 c 】
        【 d 】
    }

β  while(str[ 【 e 】] == ' ')
        i++;

解答群
    ア cnt++  イ cnt--    ウ i        エ i++     オ i--
    力 i+1     キ i-1       ク idx++   ケ idx--

 


※ ここでの記述上の注意。


○ 問題をさっと読む。

参考書などには、まず「設問」を読みなさいと書いてあることが多いで
す。確かに、問題部分には解答に必要の無いことが書いてある場合があります。
もっともなのですが、とりあえず一通り読んで、全体的にどうなっているのか概要をつかむようにしています。
あまり問題とプログラムの理解に時間を取られると、時間内に全部解けなくなります。
軽く読んで、大体の内容を掴むのは、慣れない人には大変かもしれません。その対策は問題集で、数をこなすしか無いでしょう。

○ 設問をじっくり読む

大体の概要を掴んだら設問をじっくり読みます。
特にしっかりプログラムの内容を掴んでいなくても、問題を読むだけで何とか解けてしまう場合もあります。基本的には設問の内容にそってじっくりとコードを追って解答していきます。

○ 実際の解答

設問1
入力文字列と1行の最大文字数が次の値のとき,21行目は【 a 】回実行される。また,プログラムが終了したときの変数posの値は【 b 】である。

入力文字列 [AAAA△BBBBB△CCC△DDDD△EEE△FFF\0]
1行の文字数: 18

まずaの方。21行目は何回実行されるのでしょうか?
21行目はelseブロックの中にあり、その上に対応するif文がありそれを、whileで囲んでいるだけの構造です。
このwhile文は何かを考えると、str[i] != '\0' で、iはwhileに入る手前で、0になっていて、while文の最後でiを加算していて、その他でiの値をいじっていないわけですから、入力文字列を先頭0番目から\0を発見する最後まで回るわけです。
よってwhileは入力文字数回まわるのです。
if文を見てみると、ブランクの場合に入るわけですから、elseブロックには「ブランクではない文字数(\0はループから抜けるので含まない)」回入るわけです。
さぁ上記入力文字列のブランク以外を数えましょう。22回ですね。
aの答えは22 (ク)です。

次にbの方。
posのプログラム終了時の値を答えるのですが、このプログラムの処理は大まかに27行目で分断されています。
具体的には前半で入力文字列を単語単位に2次元配列wordにつめておき、後半で、wordを使って、仕様通りに最大文字数以内で単語を出力させているわけです。
まずwhileを見ると、(i<=idx)となっています。
iの値はこの問題の解答であるposと一緒にwhileの前でクリアされていますので、とりあえず置いておいて、idxは何かを考えると、上のwhileに入る直前でクリアし、上のifでインクリメント(加算)しています。
ということは、ブランクの数になるわけで、数えると5個あり、意味的には「単語の数の最大インデックス(個数-1)」ということになります。
だから下のwhileは単語の数分回るのです。
さてaの時と同様、「posが何か」を考えましょう。なんと上のif文の中でクリアしています。
このif文はなんでしょうか?

if ((pos + leng[i]) > max) {

posにleng[i]を足した値がmaxより大きい場合?!!
maxは最大文字数なので、18が指定されています。lengはintの配列で、上のループを見ると、どうやらブランクが発見された場合に、単語の長さを代入しているようなので、どうやら「各単語の長さ」が入っているようです。初めてこのif文が実行されるときを「シミュレート」してみると、
if (( 最初は0 + 最初の単語AAAAの長さ4 ) > 18 )
でこのif文には入らないことが分かります。
このようにして、どんどんシミュレートしていけばいつか答えが出るのでしょうが、それでは芸が無いし、問題の主旨にも反しているので、ifの次を見ましょう。
buffのposの位置にi番目の単語をコピーしています。(35行)
posにコピーした単語の長さを加えています。(36行)
buffのposの位置にブランクを代入しています。(37行)
i++だから単語のインデックスをインクリメントです。37行のブランク分
進めたわけです。(38行)

ここでbuffのpos位置(buff[pos])がでてきたので、buffが何か考えます。31と32、41と42行目で表示しています。ということは、buffは出力文字列1行分であることがわかります。ということは、posは、「出力用の文字列に値を詰めていくためのポジション」であることが分かってきます。

ということは、最後にposが出てくる41行目を見てみると、
buff[pos-1] = '\0';
「最後の出力行の終端文字の位置+1」が答えです。

さぁまたここで数えます。最後の文字列はどうなっているのでしょう。
ここでもう一度問題を読み返します。問題文を読んで設問の入力文字列から出力文字列を考えれば、

AAAA△BBBBB△CCC\0
DDDD△EEE△FFF\0

となるはずです。

最後の行(2行目)の\0の位置(インデックス)はインデックスは0から始まるので12です。
pos - 1 = 12 だから pos=13 よってbの答えはオです。

設問2
「先頭の文字はそのまま出力」したいわけで、上のループに入ってしまうと、スペースは区切りとして処理されるわけですから、まずいわけです。
そこで上のループの前に以下の文が入るわけです。
α while (str[i] == ' '){
       word[idx][cnt] = str[i];
       【 c 】
       【 d 】
   }

strがブランクの間だけ回るようになっているのだから、先頭の単語(word[idx])に入力文字列の先頭のブランクを詰めることになります。
Cとdに入るものは、おのずと分かりますね。何、わからない?

それでは強引に解いてしまいましょう。
wordは下のwhileで出力文字を作成するための、単語です。
最初はi = idx = cnt = 0;ですから、
    word[0][0] = str[0];
日本語で書くと

最初の単語の0番目の文字 = 入力文字列の0番目の文字

となります。仮にブランクが5つ入っていたとします
    △△△△△AAAA△BB....
word[0]に1文字ずつstrの先頭のブランクを代入することをループを使わないで考えると
    word[idx][0] = str[0];
    word[idx][1] = str[1];
    word[idx][2] = str[2];
    word[idx][3] = str[3];
    word[idx][4] = str[4];
word[idx]とstrの添え字が一つずつ加算されてますね。
これをループで実現するわけですから、cntとiをそれぞれ1ずつインクリメントしてやればよい訳です。
よって答えはアとエです。

(添え字:配列のインデックスのこと。str[i]の添え字はi。)

  --------------------------------------------
余談ですが、αの部分は
for(i=0,cnt=0;str[i]==' ';i++,cnt++)
    word[idx][cnt] = str[i];
と書く方が私は好きです。これでは試験の問題になり難いのでしょうけど。
  --------------------------------------------

つぎに、途中のブランクが複数あったら一つにまとめる話。
β while(str[ 【 e 】] == ' ')
     i++;
これを18行目に挿入するわけです。
該当位置が含まれるifブロックは何の処理でしたっけ?
そうです。「区切りを見つけた場合にいままで値を代入した単語(word)の
代入を終わらせて、次の単語の代入準備をする」です。

出力文字列の単語と単語の間にブランクを入れる処理は下のループ以降で
やっていますので、上のループでは途中に出てきたブランクは無視する(
wordに代入しない)ようにしなくてはなりません。
βの部分が無い元の状態では、ブランクが2個続いていたとき、if文で次
のwordの準備をした後、ループの先頭に戻って、次のブランクでまたif文
に入ってしまい、まだword[idx]に何も代入されていないのに、次のword
の準備をしていしまったら、空のword[idx]が作られてしまいます。(そ
のままだと、下のループでブランクが2つ出力されます)

よって、βの役割は、ブランクが複数続いた場合に無視するようにすれば
よいわけです。これで分かったことと思います

が、やはり分からなかった人のために、もう少し説明すると、
iは入力文字列の位置を示す値ですから、wordに代入しないでi++とするこ
とでその位置のstrの値を無視することになります。だからβのwhileの中
身のi++は、ブランクを無視しているのです。さてどういうときに無視す
るのでしょう。
2つ目以降のブランクを無視するわけで、今現在の文字str[i]にはif文に
入って来たのだから、絶対にブランクが入っているわけです。
・今ブランク
・ブランクが続く => 今ブランクで、次もブランク
ということですから、
while(次もブランクだったら)
while(str[ 【 e 】] == ' ')は、次の文字をチェックしてやればよい
のです。
だから答えは i+1 カです。


[TOPへもどる] [Soft Page TOPへもどる] [ProgramerS Room Topへ] [このページのTop]