joho1-2017の日記

joho1-2017の日記

情報処理実習1の解説ブログです.皆さんが課題を解く時の助けになれば幸いです.

ポインタ2

今週は先週に引き続き,ポインタについて学習しました.
そして今回新たに扱うのがファイルポインタです.
今まで,画面の入出力で扱ってきた,printfやscanfなどは,ファイル名が省略されていたもので,ファイル名を指定して使うのがfprintf, fscanfなどと考えると,使い方(引数や返り値の型)を覚えやすいと思います.
以下課題の解説です.

5組
1.このファイル document.txt に書かれた文章を,まずそのまま画面に表示し, その後に,同文章を構成するアルファベット,数字,記号の個数を表示せよ. なお,英文字は大文字と小文字は区別せずにカウントすること.

//実行例
Ubuntu is a Debian-based Linux operating system,
with Unity as its default desktop environment.
It is based on free software and named after
the Southern African philosophy of ubuntu (literally, "human-ness"),
which often is translated as "humanity towards others" or
"the belief in a universal bond of sharing that connects all humanity".
...........

(数値が正しいとは限らない)

a は 153 個ありました.
b は   0 個ありました.
c は  76 個ありました.
d は  44 個ありました.
e は 217 個ありました.
...

1 は 217 個ありました.
2 は 217 個ありました.
...
" は   6 個ありました.
! は   1 個ありました.
? は   2 個ありました.
...

この課題は,問題文を読んだだけでも,

ファイルの中身を画面に表示
文章を構成するアルファベット,数字,記号の個数を表示

の2つがやるべき事としてありますね.こういった場合,まずはファイルを読み込んで表示するところまでコードを書き終えたら,コンパイルをして実行してみます.ここまでうまく実行できている事が確認できれば,もし後半の個数を表示するところまで書き終えてコンパイル・実行した時にうまくいかなくても,前半部分がうまく機能している事は確認できているので,ダメな部分は後半書いたコードのどこかに絞れるわけです.このようにこまめにコンパイル・実行をする事で,一見無駄に時間をかけているように思えても,結果的には効率的にプログラムコーディングが出来るのです.騙されたと思って,ぜひ一度試してみてください!

 では,課題のヒントに入ります.

課題1の問題文は上で記述しているので,省略します.この課題のポイントは,
 ①ファイルの中身を読み込んだ際の処理
 ②アルファベット,数字,記号のカウント方法
です.①に関しては,もし表示するだけならfgetc( )やfgets( ),fscanf( )をそのままprintf関数で表示するだけでいいですが,今回は文章中の文字についてカウントする処理が②にあります.なので,文字列(char型の配列)に代入しておくのが良いでしょう.
②に関しては,①で文章を格納した文字列に対して,繰り返し処理でアルファベット,数字,記号のそれぞれどれに属するのかを1文字ずつ条件判定させていき,個数をカウントする変数をprintf関数で表示させると,この問題は解決です.

2.以下のリンクからテキストファイルinputdata.txtをダウンロードせよ.
このファイルを読み込み,一行ごとの数値(5個のデータ)の平均値を行の最後に追加し,別のファイルに書き込むプログラムを作成せよ.
出力するファイル名は,outputdata.txt とする.

【ヒント】上記ファイルには,1行に5個の数値データが格納されている.今回は,データ個数が事前に分かっているとしてよい.

【ヒント】最初に画面に表示するようにプログラムをつくり,うまく出来るようになったら同じ内容をファイルに書くようにすれば比較的短時間でできる.

この課題のポイントは,
 ①ファイルの中身を読み込んだ際の処理
 ②代入する配列のサイズ
です.課題1と同様,読み込んだファイルの中身をただ表示するわけではないので,①に関して,配列に代入した方が良いでしょう.
②に関して,用意する配列の要素数はどうするべきでしょう?ヒントの通り,1行5個のデータ個数だから,要素数も5個で良いでしょうか?問題文を見ると,「一行ごとの数値の平均値を行の最後に追加」とありますね.それなら,初めから6個分(読み込むデータ個数+1個)の要素数の配列を宣言するのが良いかと思います.

3.このファイルは test_result.csv 内には,各行に生徒の名前と3科目のテストの点数がカンマ( , )区切りで記述されている.
このファイルの中身を読み取り,各生徒の合計点数を算出してその値が大きい順に並べてファイル test_rank.csv に出力するプログラムを作成せよ.
ヒント:1 行分のデータを読み込むには,ファイル内のデータ形式に合わせて書式指定文字列に,fscanf(fp, "%s, %d, %d, %d", ...); を使用すればよい. (test_rank.csvの各行に,名前と合計点をカンマ区切りで記し,最後の行に平均点を追加すること.)

//出力ファイルの中身
Yuma,285
Alicia,276
Clarice,274
Jean,273
《中略》
Flora,214
Elena,206
Kate,165
average,247.13

この課題のポイントは,
 ①fscanf関数を使う際の代入する配列
 ②合計点数の大きい順に並び替え
です.課題1,2でも処理したように,この問題でもやはりファイルの中身を読み込む時に代入する配列を用意するのが適当です.しかし,今回は1列目が文字列,2列目以降は整数と,型が違うため,配列1つでは足りません.それぞれに対応した配列を用意しましょう.②に関して,これまでも並び替えを行うswap関数を自作したと思います.そのため,詳しい処理は省きますが,並び変えるのは名前の配列と合計点の配列の2つがあるので注意しましょう.

4.ファイル lyrics.txt を読み込み,"*iller"が含まれる行全体だけを抜き出して表示するプログラムを作成せよ(*は任意の文字).

//出力の例
'Cause this is thriller, thriller night
You know it's thriller, thriller night
You're fighting for your life inside a killer, thriller tonight
・・・

この課題のポイントは,
 ①"*iller"を含む行の判定
です.この課題は行単位で判断していけばいいので,読み込みにはfgets( )を使えば良いと思います.そして①に関して,1文字ずつ判定するのも悪くはないのですが,どうせなら文字列処理関数の中の検索できる関数を使いましょう!どんな関数があるのかは,「C言語 文字列処理関数」で検索してみよう.

6組

1. Quiz 6を参考にして,ポインタの配列を用いて, 月の英語名称のデータテーブルを作り, 月の番号(1,2,・・・,12)を入れると英名が, または英名を入れると月番号が表示されるようにせよ. また,存在しない月番号や英名を入力すると,誤りを指摘するようにエラー処理を行え.
まず,月の番号を引数として,月名を返す関数については簡単に作れると思います.受け取った数値をそのまま月名の配列に渡してあげればいいですね.月名から番号を返す方は,strcmp関数を使うのが簡単かと思います.月名の配列をループで見ていき,受け取った月名と一致したところでbreakする.といった形で実装できるかと思います.ループが最後まで回れば,一致する文字列がなかった,すなわち入力が誤りだったということですね.
あとは入力が数値なのか,文字列なのかを判断する必要がありますが,今回atoi関数を使うのがシンプルです.数値に変換できなかった場合は0を返すので,atoiの返り値が1~12の範囲であれば,数値として扱い,それ以外であれば文字列として扱えばよいかと思います.ただしatoiは先頭が数値であれば変換できてしまうので,余裕がある人はそこの部分のエラー処理についても考えてみてください.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char* month_db[] = {
	"January", ...};

int name2num(char* name)
{
	//...
}

const char* num2name(int num)
{
	//...
}

void print(char strin[])
{
	//...
	if(...){
		printf("%s\n", num2name(num));
	}else{
		num = name2num(strin);
		if(...){
			printf("%d\n", num);
		}else{
			puts("入力が誤っています.");
		}
	}
}

int main()
{
	char strin[128];

	printf("input: ");
	scanf("%s", strin);
	print(strin);

	return 0;
}

2. このファイルは【as_test_result.csv】 内には各行に生徒の名前と3科目のテストの点数がカンマ( , )区切りで記述されている(並びは名前順). このファイルの中身を読み取り,各生徒の合計点数を算出してその値が大きい順に並べて ファイルに出力するプログラムを作成せよ. なお,出力ファイル名はtest_rank.csvとし,各行に名前と合計点をカンマ区切りで記し, 最後の行に平均点を追加すること.
今週の3つの課題中で,この課題2が一番むずかしいのではないでしょうか.csvファイルから読み込む部分は,授業中の演習課題を参考にすれば書けるかと思います.もしもfscanfを使いたい場合は"%s"としてしまうとカンマも読み込んでしまうので,気をつけてください.カンマまでを読み込みたい場合はスキャン集合指定子(%[^,])を使えば良さそうですね.
この課題はcsvファイルから読み込んだ値を変数に格納する部分が少し難しいかもしれませんが,ここができればあとは,今まで一度は書いたことがあるような処理ばかりかと思います.

#include <stdio.h>
#include <stdlib.h>

void load(char name[][128], ...)
{
	//...
	for(; fscanf(fp, "%[^,],%d,%d,%d\n", ...
	//...
}

void calc_sum(int score[][3], ...)
{
	//...
}

void write_csv(char name[][128], ...)
{
	//...
	for(...){
	    //...
	}
	fprintf(fp, "average,%.2f\n", ...);
}

void sort(char name[][128], ...)
{
	//...
}

int main()
{
	char name[512][128];
	int score[512][3];
        //...

	load(name, score);
	calc_sum(score, sum);
	sort(name, sum);
	write_csv(name, sum);

	return 0;
}

3. 行列データをメモリイメージそのままバイナリファイル 【as_matrix_image.bin】 に保存した. このデータを上記の行列変数に読み込み,画面に表示するプログラムを作れ.

この課題はバイナリファイルを扱うことに,少し抵抗を感じるかもしれませんが,おそらくプログラム自体は今回の課題の中で最も簡単に書けるかと思います.読み込み部分はfread関数を使えばいいですね.授業ページを参考にしてください.このとき何バイトのデータをいくつ読み込むのかの部分は,サイズがdoubleで,個数は4x4の行列なので16となります.バッファ変数はMatrix型のアドレスを渡せばいいですね.

#include <stdio.h>
#include <stdlib.h>

typedef double Matrix[4][4];

void load(...)
{
	// ...
}

int main()
{
	Matrix m;
	load(m);
	for(int i=0; i<4; i++){
		for(int j=0; j<4; j++){
			printf("%.1f, ", m[i][j]);
		}
		puts("");
	}
	return 0;
}




ポインタの習得は難しいかと思いますが,とにかく手を動かしてみることが最も近道だと思います.コンパイル -> 実行 -> うまくいかなければprintfで途中の値を出力してみて...といういつもの手順を繰り返してみてください.
ポインタをしっかり扱えるようになると,C言語初級者から中級者にランクアップしたと言えるかと思います.難しい場所ではありますが時間をかけて頑張ってください.

Remove all ads