プログラミング基礎WS問題 # 問題文(多重ループとポインタ) ## 問題 char型の文字列(文字数は1文字以上50文字未満)を2行読み取り、1行目の文字列を以下、①。2行目の文字列を以下、②とする。 そして、①・②の文字を一文字ずつ比べ、「①内の文字」が「②内の文字」に含まれている文字数(一致した回数)を出力例の形で出力するプログラムを書け。 この問題において、`char a[100];`のように領域を確保する目的にのみ、配列の宣言を行ってよい。ただし、**文字列内の文字を参照するときは、ポインタを使うこと**。 ※`a[x] == b[y]`のようなコードは禁止である。 また、読み取る文字列の文字はアルファベットのみとし、大文字と小文字は別の文字として扱うものとする。 ## 例1 ### 入力 ``` hakodate hokkaido ``` ### 出力 ``` (含まれていた文字数) = 8 ``` ### 説明 "hakodate"を①、"hokkaido"を②とすると、以下の8文字が一致してる。 1.①の1文字目のh と ②の1文字目のh 2.①の2文字目のa と ②の5文字目のa 3.①の3文字目のk と ②の3文字目のk 4.①の3文字目のk と ②の4文字目のk 5.①の4文字目のo と ②の2文字目のo 6.①の4文字目のo と ②の8文字目のo 7.①の5文字目のd と ②の7文字目のd 8.①の6文字目のa と ②の5文字目のa ## 例2 ### 入力 ``` Mirai MetaLab ``` ### 出力 ``` (含まれていた文字数) = 3 ``` ### 説明 "Mirai"を①、"MetaLab"を②とすると、以下の3文字が一致してる。 1.①の1文字目のM と ②の1文字目のM 2.①の4文字目のa と ②の4文字目のa 3.①の4文字目のa と ②の6文字目のa ## 例3 ### 入力 ``` abc abC ``` ### 出力 ``` (含まれていた文字数) = 2 ``` ### 説明 以下の2文字が一致してる。 1."abc"のa と "abC"のa 2."abc"のb と "abC"のb ※cとCは今回の問題では、「大文字と小文字は別の文字として扱う」としてるため、一致しない # 解答例(本番では見せない) ``` #include <stdio.h> #include <string.h> int main(void){ char a[50]; //①を読み込むための領域確保 char b[50]; //②を読み込むための領域確保 gets(a); //①を読み込む gets(b); //②を読み込む int count = 0;//含まれていた文字数(一致した回数)を保存する変数 for(int i = 0; i < strlen(a); i++){ //①で参照する文字を動かす for(int j = 0; j < strlen(b); j++){ //②で参照する文字を動かす if(*(a + i) == *(b + j)){ count++; //一致したら、countを1ふやす } } } printf(" (含まれていた文字数) = %d\n", count);//出力 return 0; } ``` ### 余裕のある人向け(追加問題まで終わった人の暇つぶし用) アルファベットの大文字と小文字を区別しないコードも書いてみよう! ### 入力 ``` abc abC ``` ### 出力 ``` (含まれていた文字数) = 3 ``` ### 余裕のある人向け問題の解答例(本番では見せない) アルファベットをすべて小文字(大文字)に変換して比較すればよいので ``` for(int s = 0; s < strlen(a); s++){ if('A' <= *(a + s) && *(a + s) <= 'Z'){ *(a + s) = *(a + s) - 'A' + 'a';//小文字に変換 } } for(int t = 0; t < strlen(b); t++){ if('A' <= *(b + t) && *(b + t) <= 'Z'){ *(b + t) = *(b + t) - 'A' + 'a';//小文字に変換 } } ``` これをcountを増やす二重for文の前に入れる。 または、上記のif文を二重for文の然るべきところに入れる。 `*(a + s) = *(a + s) - 'A' + 'a';`の意味が分からなければASCIIコードについて調べましょう! # プログラミング基礎WS 解説 ## 多重ループとポインタ ### 【細分化】 ![](https://i.imgur.com/ROBxD7J.png) ### 【手順】 <注意> 1. char型の文字列の文字数は1文字以上50文字未満と仮定する 2. `char a[100];`のように領域を確保する目的にのみ、配列の宣言を行ってよい 3. 文字列内の文字を参照するときは、ポインタを使わなくてはならない 4. 読み取る文字列の文字はアルファベットのみとし、大文字と小文字は別の文字として扱う ### 1.キーボードから文字列を読み込む * 1-1. char型で領域を宣言(50文字分の大きさ) * 1-2. gets関数で入力されたものを読み取る * 1-3. 1-1・1-2をもう一回行う ``` #include <stdio.h> int main(void){ char a[50]; //①を読み込むための領域確保 char b[50]; //②を読み込むための領域確保 gets(a); //①を読み込む gets(b); //②を読み込む return 0; } ``` #### 補足 ・今回は、1行目の文字列(以下、①)をa, 2行目の文字列(以下、②)をbとした ・<注意>より、1-1で領域を50文字分の大きさにした ・gets関数を使ったのは、一行分をまとめて読み込むため ### 2.「①内の文字」が「②内の文字」に含まれている文字数(一致した回数)を数える ☆この説明が難しいと感じたら、「3.おまけ」をご覧ください! #### 2-1.「含まれていた文字数(一致した回数)」をカウント変数を用意する * 2-1-1. int型の変数countを宣言する。 * 2-1-2. countを0で初期化する ``` int count = 0; ``` #### 2-2. 「①の文字列(今回はa)」のポインタの先頭から順番に、ループを使って値を参照する * 2-2-1. for文で 0番 から strlen(a)-1番目 まで繰り返す * 2-2-2. `*(a+i)`(iはforの繰り返した回数) によって、「①内の文字」を1文字だけ参照できる #### 2-3. 上記のループの中で、「②の文字列(今回はb)」のポインタの先頭から順番に、ループを使って値を参照し、2-2で参照している「①内の文字」と比較する * 2-3-1. 2-2-1のfor文内で、0番 から strlen(b)-1番目 までfor文で繰り返す * 2-3-2. `*(b+j)`(jはforの繰り返した回数) によって、「②内の文字」を1文字だけ参照できる * 2-3-3. `*(a+i) == *(b+j)`のとき、countを1増やす * 2-3-4. 「(含まれていた文字数) = n」(nは、countの値)の形で出力する ``` #include <stdio.h> #include <string.h> int main(void){ char a[50]; //①を読み込むための領域確保 char b[50]; //②を読み込むための領域確保 gets(a); //①を読み込む gets(b); //②を読み込む int count = 0;//含まれていた文字数(一致した回数)を保存する変数 for(int i = 0; i < strlen(a); i++){ //①で参照する文字を動かす for(int j = 0; j < strlen(b); j++){ //②で参照する文字を動かす if(*(a + i) == *(b + j)){ count++; //一致したら、countを1ふやす } } } printf(" (含まれていた文字数) = %d\n", count);//出力 return 0; } ``` となり、実装が完了した。 #### 補足 ・strlen(a)を使うには、#include <string.h>が必要である ・%dでprintfのなかにint型の変数の値をいれることができる ## 3.おまけ ~二重for文を実装するまでの詳しい解説~ ☆2-2, 2-3で難しいと感じた人や、解答のコードでうまく行くのは分かったが、なぜfor文のなかにfor文を入れるのか分からない・今後自分で実装できる自信がない人のために、考え方の流れを解説していきます! > 「文字列」と「1文字」が一致しているか確かめよ という問題を考えるとする。配列を使っていいなら、 ``` #include <stdio.h> #include <string.h> int main(void){ char a[5] = "ABC"; char b = 'A'; int count = 0; for(int i = 0; i < strlen(a); i++){ if(a[i] == b){ count++; } } printf("count = %d\n", count); return 0; } ``` となる。 これをgetsを使い文字を読み取り(同じくABCとA)、ポインタで値を参照すると、 ``` #include <stdio.h> #include <string.h> int main(void){ char a[50]; char b[50]; gets(a); gets(b); int count = 0; for(int i = 0; i < strlen(a); i++){ if(*(a+i) == *b){ count++; } } printf("count = %d\n", count); return 0; } ``` となる。 if文の`*(a+i)`によって、A→B→Cと動かしている。 そして、`if(*(a+i) == *b)`で、aのAとbのA・aのBとbのA・aのCとbのA というように 「文字列内の文字」と「文字」を1文字ずつ比べている。 これを踏まえて、本来の問題を見ると、 > ①(1行目の文字列)・②(2行目の文字列)の文字を一文字ずつ比べ... とある。 今回のような問題では、図のように「文字列aの0文字目と文字列bのすべての文字(0番, 1番, ... , strlen(b)-1番目)」, 「文字列aの1文字目と文字列bのすべての文字」, ... , 「文字列strlen(a)-1の文字目と文字列bのすべての文字」のようにすべての文字を一回ずつ比べる方法が最も基本的な書き方である。 それを図にしたのが、下である。 ![](https://i.imgur.com/DzsTewD.png) 図にすると、文字列aの0番と、文字列bのstrlen(b)-1番を比べたあと、0番に戻った文字列bと文字列aの1番を比べている*ことが確認できる。 この状況*をコードにすると、 ``` i = 0; //文字列aの0番の文字と文字列bの文字を0番からstrlen(b)-1番まで比べる for(int j = 0; j < strlen(b); j++){ if(*(a+i) == *(b+j)){ count++; } } i++; //文字列aの1番の文字と文字列bの文字を0番からstrlen(b)-1番まで比べる for(int j = 0; j < strlen(b); j++){ if(*(a+i) == *(b+j)){ count++; } } i++ //文字列aの2番と文字列bの文字を0番からstrlen(b)-1番まで比べる .... ``` このように動いている。 つまり、iが1増えるごとに ``` //文字列aのi番と文字列bの文字を0番からstrlen(b)-1番まで比べる for(int j = 0; j < strlen(b); j++){ if(*(a+i) == *(b+j)){ count++; } } ``` を実行することで図のように動くコードを実装できる。 これをstrlen(a)回繰り返すので、下記の「iが0~strlen(a)-1まで繰り返すコード」である ``` for(int i = 0; i < strlen(a); i++){ //☆ } ``` の☆に ``` //文字列aのi番と文字列bの文字を0番からstrlen(b)-1番まで比べる for(int j = 0; j < strlen(b); j++){ if(*(a+i) == *(b+j)){ count++; } } ``` をいれたコードである ``` #include <stdio.h> #include <string.h> int main(void){ char a[50]; //①を読み込むための領域確保 char b[50]; //②を読み込むための領域確保 gets(a); //①を読み込む gets(b); //②を読み込む int count = 0;//含まれていた文字数(一致した回数)を保存する変数 for(int i = 0; i < strlen(a); i++){ //①で参照する文字を動かす for(int j = 0; j < strlen(b); j++){ //②で参照する文字を動かす if(*(a + i) == *(b + j)){ count++; //一致したら、countを1ふやす } } } printf(" (含まれていた文字数) = %d\n", count);//出力 return 0; } ``` が図のように動くコードであり、2.で示した解答である。