今までに学習した事に基づき、 起動時に与えられたファイルに含まれる文字の出現回数を数えるプログラムを書いてみる。 ただし、起動時にファイルが一つも与えられなければ、画面に質問を表示し、ユーザからの 指示をうけるものとする。
日本語をプログラムのコメント以外にいれる場合には、「ファイル(F)」→「エンコードの種類(D)」で、 あらかじめ「Unicode(UTF-8)」を選択しておくこと。
/* count.c */ /* * 起動時に与えられたファイルに含まれる文字を調べ、 * それぞれの文字ごとに現れた数を表示する */ #include <stdio.h> #include <strings.h> /* * 実際に文字を解析する関数 * 引数はファイル名とする * 引数が NULL の場合は標準入力を処理する */ int parse_file(char *); int main(int argc, char *argv[]) { FILE *fp; char filename[80]; int type; argc --; /* argc, argvには、実行ファイル名の情報も含まれているので * 1 減らすことで、引数の数となる。 */ switch(argc) { case 0: printf("どのような処理をしますか ? (番号で選択してください)\n"); printf("1: 標準入力の処理\n"); printf("2: ファイル名を指定する\n"); scanf("%d", &type); /* 標準入力から整数を読み込む */ if (type == 1) { parse_file(NULL); } else { printf("ファイル名 -> "); scanf("%s", filename); /* 標準入力から文字列を読み込む */ parse_file(filename); } break; default: while (argc) { argv++; parse_file(*argv); argc--; } } return 0; } /* * ファイルに含まれる「文字」を調べ、 * 種類ごとに出現回数を表示する */ int parse_file(char *name) { int ch, i; int counter[256] = { 0 }; /* char 型は 0-255 の 256 通りの可能性がある */ int total = 0; FILE *fp; if (name != NULL) { if(NULL == (fp = fopen(name, "r"))) { fprintf(stderr, "ファイル %s を開くことができません。\n", name); return -1; } } else { fp = stdin; } while(EOF != (ch = fgetc(fp))) counter[ch]++; /* 該当する配列要素の中身をインクリメントする */ fclose(fp); for(i = 0; i < 256; ++i) { if (counter[i] > 0) { total += counter[i]; printf("%#04x: %5d\n", i, counter[i]); } } printf("\ntotal: %5d\n", total); return 0; }
scanf
関数
キーボードから整数を読み込んだり、ファイル名を読み込んだりするのに、新しく
scanf関数を用いている。
scanf関数は、printf
関数と同様な書式を用いて、
与えられた内容を変換して変数に格納する。scanf
の書式は次の通りである。
#include <stdio.h> int scanf(書式文字列, 格納先変数のポインタ, ...);
上の例では、最初の scanf は、入力を "%d"、すなわち10進数としての整数として
変換し、int 型の変数 type に格納している。
前回、学習したように、関数(この場合は scanf
)内部での変更を
呼び出し元へ伝搬させるために、格納先は「ポインタ」で指定する必要がある。
そのため、通常変数である type に格納するためには "&"演算子を適用して変数のアドレスを求め、引数としている。
2番目のファイル名を入力している scanf
では、変換方法を "%s"、
すなわち文字列を、格納先には配列 filename を指定している。
scanf
は入力が標準入力に固定されているが、FILE 型のポインタで示される
ストリームから入力を行う関数として fscanf
がある。(prinf と fprintf の関係と同様である。)
fscanf は
#include <stdio.h> int fscanf(ストリーム, 書式文字列, 格納先変数のポインタ, ...);
という形式で使用する。
scanf関数は、書式で指定された内容と異なる入力が与えられた場合、 変換を行わないなど期待にそぐわないの結果を与えたることもある上、 格納先の変数の大きさの確認を行わない(特に文字列の場合)ので、 使用時には十分注意を払うこと。
count.c
をコンパイル後、実行してみると
たとえば、次のような出力を得るはずである。
[user99@proge1]~/kadai10% ./count どのような処理をしますか ? (番号で選択してください) 1: 標準入力の処理 2: ファイル名を指定する 2 ファイル名 -> count.c 0x09: 92 0x0a: 83 0x20: 137 0x21: 2 0x22: 20 ... (途中略) ... 0xe3: 167 0xe4: 2 0xe5: 41 0xe6: 29 0xe7: 10 0xe8: 15 0xe9: 7 total: 1913
さて、これで、文字の出現回数は数えられたが、 どの文字が一番多く出現したのかという事に着目したときに、 必ずしも判りやすいとは言えない。 しかし、
[user99@proge1]~/kadai10% ./count2 count.c 09: 92|************************************** 0a: 83|********************************** 20: 137|********************************************************* 21: 2| 22: 20|******** ... (略) ... e3: 167|********************************************************************** e4: 2| e5: 41|***************** e6: 29|************ e7: 10|**** e8: 15|****** e9: 7|** total: 1913
などと表示されると、視覚的に捉えやすい。
そこで、count.c
をもとに簡単なグラフが出るようにプログラムを改造してみよ。
条件は次の通りとする。
プログラムするためのヒント
count.c
と同様、1文字ごとに読み込み、
対応する配列の要素をインクリメントすることで各文字の出現回数を配列に格納する
すべての入力を読み込み終わったら、配列の要素をしらべて出現回数が最大のものを探す。 最大のものを変数に格納する。
各文字ごとに出力を行う。 グラフを表示するための「*」の数は
で与えられるはずである。
count.c
を count2.c
という名前でコピーした後、
上のヒントにしたがってグラフ出力ができるように修正せよ。
8日目 | 表紙 | 10日目 |