プログラミングC
第2回 演習問題

この回で使用する外部変数(グローバル変数)は、プログラム中の どこからでもアクセスできるため、慎重に取り扱わないと予想外の 動作を引き起こす原因になります。よって、プログラミングCの 演習や試験では、問題文中で外部変数を使うよう指示があった場合のみ 使用するようにして下さい。

A問題(50点)

今回から一人ひとりの授業理解度をチェックする目的で、A問題では口頭試問形式の採点を行います。
採点対象の問題が解けたら手を挙げてTA/SAまたは教員を呼び、答えを口頭で説明してください。
その際、正しく説明できたら点数がつけられ、間違っている場合はやり直してもらいます(間違っても減点はしません)。
その他の問題はこれまでと同様に提出コマンドでファイルを提出してください。
なお、採点の関係上、口頭採点のファイルも提出コマンドで提出してください。

演習問題1

通常、平均値を求める場合は、全ての要素(数値)を足してから、その個数で除算するが、プログラムの実行中にデータが次々と入力されるとき、入力毎に平均をとることを逐次平均と呼んでいる。
この処理は以下のような式で計算できる。
平均=(前回までの平均*前回までのデータの個数+今回のデータ)/今回までのデータの個数
前回までの平均値とデータの個数の値を静的変数として関数内に持つ逐次平均のための関数 double seqavg(double) (引数は新規入力された値、戻り値は逐次平均の計算法で求めた新しい平均値とする)を作成/使用し、実行例のように逐次的に平均を求めるプログラムを作成しなさい。(なお、データの入力、平均出力等は全て main にて行う。) ただし、データ入力時に ctrl+d でプログラムが終了するものとする。
(提出ファイル名: prog01.c)
実行例
% ./a.out
データを入力して下さい: 2.0
データの個数 = 1,ここまでの平均 = 2.000000
データを入力して下さい: 4.0
データの個数 = 2,ここまでの平均 = 3.000000
データを入力して下さい: 6.0
データの個数 = 3,ここまでの平均 = 4.000000
データを入力して下さい: 8.0
データの個数 = 4,ここまでの平均 = 5.000000
データを入力して下さい: ^D
最終的な平均値は5.000000です。
% ./a.out
データを入力して下さい: 2.2
データの個数 = 1,ここまでの平均 = 2.200000
データを入力して下さい: 3.6
データの個数 = 2,ここまでの平均 = 2.900000
データを入力して下さい: 0.6
データの個数 = 3,ここまでの平均 = 2.133333
データを入力して下さい: 7.9
データの個数 = 4,ここまでの平均 = 3.575000
データを入力して下さい: 7.7
データの個数 = 5,ここまでの平均 = 4.400000
データを入力して下さい: 4.9
データの個数 = 6,ここまでの平均 = 4.483333
データを入力して下さい: ^D
最終的な平均値は4.483333です。
%

B問題(50点)

演習問題2

与えられた 2 次元配列の要素の中から、入力された数値と一致するものを リニアサーチにより探し出すプログラムを作成する。 必要に応じて以下を参考にしながら、プログラムを完成させなさい。

演習問題3

机の上に本を何冊か積み上げ、そこから本を取る場合、 一番最後に積んだ本を最初に取り出すことになり、 一番最初に積んだ本は最後に取り出されることになる。 このように 最後に入れたデータを最初に取り出す形式(後入れ、先出し、Last In, First Out: LIFO)のデータ構造は スタック(stack)と呼ばれ、コンピュータの基本的なデータ構造の1つである。 以下にスタックに関する基本的な用語を記す。 プログラミング入門ハンドアウトの第13回も参照のこと。
以下の仕様を満たす関数 func を用いてスタックを実現するプログラムを作成する。 関数の中身を埋めて、プログラムを完成させなさい。
「関数 func の仕様」
(提出ファイル名: prog03.c)
注)通常、スタックの実装においては、データの格納と取り出しのために それぞれ push, pop という 2 つの関数(あるいは命令)を用意する (プログラミング入門ハンドアウト第13回で紹介している例もこれに沿っている)が、 この問題ではプログラミングの練習のために、1 つの関数に格納/取出(/表示)の機能を盛り込んでいる。
#include <stdio.h>
#define N 5
#define EMPTY -1
#define FULL  -2

int func(int);

int main()
{
  int n, r;

  printf("スタックを実現するプログラム\n");
  printf("  正の整数値:入力値をスタックに格納する(Push)\n");
  printf("  負の整数値:スタックからデータを取り出す(Pop)\n");
  printf("  0:終了\n");
  while (1) {
    printf("整数値を入力 (正:格納,負:取出,0:終了): ");
    scanf("%d", &n);
    if (n == 0) break; /* 終了 */
    r = func(n); /* 格納または取出 */
    func(0); /* 表示 */
    if (r > 0) printf("取出データ: %d\n", r); /* 取得データの表示 */
    else if (r == EMPTY) printf("エラー(スタックが空です)\n"); 
    else if (r == FULL ) printf("エラー(スタックが満杯です)\n"); 
  }
  return 0;
}

/* 
[引数]
正の整数: 格納,負の整数: 取出,0: 表示
[戻り値]
格納の場合) スタックが満杯: マクロ定数 FULL,それ以外: 0
取出の場合) スタックが空: マクロ定数 EMPTY,それ以外: 取り出した値
表示の場合) 0
*/
int func(int x)
{
  static int stack[N]; /* データを格納する配列 */
  static int size = 0; /* データ数 */
  /* 

    ここを作成

  */
}
実行例
% ./a.out
スタックを実現するプログラム
  正の整数値:入力値をスタックに格納する(Push)
  負の整数値:スタックからデータを取り出す(Pop)
整数値を入力 (正:格納,負:取出,0:終了): 31
size = 1 [31 ]
整数値を入力 (正:格納,負:取出,0:終了): 41
size = 2 [31 41 ]
整数値を入力 (正:格納,負:取出,0:終了): 59
size = 3 [31 41 59 ]
整数値を入力 (正:格納,負:取出,0:終了): -1
size = 2 [31 41 ]
取出データ = 59
整数値を入力 (正:格納,負:取出,0:終了): 26
size = 3 [31 41 26 ]
整数値を入力 (正:格納,負:取出,0:終了): 53
size = 4 [31 41 26 53 ]
整数値を入力 (正:格納,負:取出,0:終了): 58
size = 5 [31 41 26 53 58 ]
整数値を入力 (正:格納,負:取出,0:終了): -1
size = 4 [31 41 26 53 ]
取出データ = 58
整数値を入力 (正:格納,負:取出,0:終了): -1
size = 3 [31 41 26 ]
取出データ = 53
整数値を入力 (正:格納,負:取出,0:終了): -1
size = 2 [31 41 ]
取出データ = 26
整数値を入力 (正:格納,負:取出,0:終了): 97
size = 3 [31 41 97 ]
整数値を入力 (正:格納,負:取出,0:終了): -1
size = 2 [31 41 ]
取出データ = 97
整数値を入力 (正:格納,負:取出,0:終了): -1
size = 1 [31 ]
取出データ = 41
整数値を入力 (正:格納,負:取出,0:終了): -1
size = 0 []
取出データ = 31
整数値を入力 (正:格納,負:取出,0:終了): 0
% ./a.out
スタックを実現するプログラム
  正の整数値:入力値をスタックに格納する(Push)
  負の整数値:スタックからデータを取り出す(Pop)
整数値を入力 (正:格納,負:取出,0:終了): 1
size = 1 [1 ]
整数値を入力 (正:格納,負:取出,0:終了): 2
size = 2 [1 2 ]
整数値を入力 (正:格納,負:取出,0:終了): 3
size = 3 [1 2 3 ]
整数値を入力 (正:格納,負:取出,0:終了): 4
size = 4 [1 2 3 4 ]
整数値を入力 (正:格納,負:取出,0:終了): 5
size = 5 [1 2 3 4 5 ]
整数値を入力 (正:格納,負:取出,0:終了): 6
size = 5 [1 2 3 4 5 ]
エラー(スタックが満杯です)
整数値を入力 (正:格納,負:取出,0:終了): -1
size = 4 [1 2 3 4 ]
取出データ = 5
整数値を入力 (正:格納,負:取出,0:終了): -1
size = 3 [1 2 3 ]
取出データ = 4
整数値を入力 (正:格納,負:取出,0:終了): -1
size = 2 [1 2 ]
取出データ = 3
整数値を入力 (正:格納,負:取出,0:終了): -1
size = 1 [1 ]
取出データ = 2
整数値を入力 (正:格納,負:取出,0:終了): -1
size = 0 []
取出データ = 1
整数値を入力 (正:格納,負:取出,0:終了): -1
size = 0 []
エラー(スタックが空です)
整数値を入力 (正:格納,負:取出,0:終了): 0
%

Extra問題

演習問題4

バス停で列を作ってバスを待つ場合、一番最初に並んだ人が最初にバスに乗り、 一番最後に並んだ人は最後にバスに乗ることになる。 このように最初に入れたデータを最初に取り出す(先入れ、先出し、First In, First Out: FIFO)形式のデータ構造は キュー(queue)と呼ばれ、スタック(stack)と共にコンピュータの基本的なデータ構造の1つである。 以下にキューに関する基本的な用語を記す。
以下の仕様を満たす関数 func を用いてキューを実現するプログラムを作成する。 関数の中身を埋めて、プログラムを完成させなさい。
「関数 func の仕様」
(提出ファイル名: prog04.c)
注)本来、キューの実装においては、データの格納と取り出しのために それぞれ enqueue, dequeue という 2 つの関数(あるいは命令)を用意する (プログラミング入門第13回ハンドアウト参照)が、 この問題ではプログラミングの演習のために、1 つの関数に格納/取出(/表示)の機能を盛り込んでいる。 また、変数 head, tail に加えて size を用いることによって、 head=tail のときに空なのか満杯なのか区別できるようになり、 配列のすべての要素にデータを格納できるようになっている。
#include <stdio.h>
#define N 5
#define EMPTY -1
#define FULL  -2

int func(int);

int main()
{
  int n, r;

  printf("キューを実現するプログラム\n");
  printf("  正の整数値:入力値をキューに格納する(Enqueue)\n");
  printf("  負の整数値:キューからデータを取り出す(Dequeue)\n");
  printf("  0:終了\n");
  while (1) {
    printf("整数値を入力 (正:格納,負:取出,0:終了): ");
    scanf("%d", &n);
    if (n == 0) break; /* 終了 */
    r = func(n); /* 格納または取出 */
    func(0); /* 表示 */
    if (r > 0) printf("取出データ: %d\n", r); /* 取得データの表示 */
    else if (r == EMPTY) printf("エラー(キューが空です)\n"); 
    else if (r == FULL ) printf("エラー(キューが満杯です)\n"); 
  }
  return 0;
}

/* 
[引数]
正の整数: 格納,負の整数: 取出,0: 表示
[戻り値]
格納の場合) キューが満杯: マクロ定数 FULL,それ以外: 0
取出の場合) キューが空: マクロ定数 EMPTY,それ以外: 取り出した値
表示の場合) 0
*/
int func(int x)
{
  static int queue[N]; /* データを格納する配列 */
  static int head = 0, tail = 0, size = 0; /* 先頭,末尾,データ数 */
  /* 

    ここを作成

  */
}
実行例
% ./a.out
キューを実現するプログラム
  正の整数値:入力値をキューに格納する(Enqueue)
  負の整数値:キューからデータを取り出す(Dequeue)
  0:終了
整数値を入力 (正:格納,負:取出,0:終了): 31
head = 0, tail = 1, size = 1 [31 ]
整数値を入力 (正:格納,負:取出,0:終了): 41
head = 0, tail = 2, size = 2 [31 41 ]
整数値を入力 (正:格納,負:取出,0:終了): 59
head = 0, tail = 3, size = 3 [31 41 59 ]
整数値を入力 (正:格納,負:取出,0:終了): -1
head = 1, tail = 3, size = 2 [41 59 ]
取出データ = 31
整数値を入力 (正:格納,負:取出,0:終了): 26
head = 1, tail = 4, size = 3 [41 59 26 ]
整数値を入力 (正:格納,負:取出,0:終了): 53
head = 1, tail = 0, size = 4 [41 59 26 53 ]
整数値を入力 (正:格納,負:取出,0:終了): 58
head = 1, tail = 1, size = 5 [41 59 26 53 58 ]
整数値を入力 (正:格納,負:取出,0:終了): -1
head = 2, tail = 1, size = 4 [59 26 53 58 ]
取出データ = 41
整数値を入力 (正:格納,負:取出,0:終了): -1
head = 3, tail = 1, size = 3 [26 53 58 ]
取出データ = 59
整数値を入力 (正:格納,負:取出,0:終了): -1
head = 4, tail = 1, size = 2 [53 58 ]
取出データ = 26
整数値を入力 (正:格納,負:取出,0:終了): 97
head = 4, tail = 2, size = 3 [53 58 97 ]
整数値を入力 (正:格納,負:取出,0:終了): -1
head = 0, tail = 2, size = 2 [58 97 ]
取出データ = 53
整数値を入力 (正:格納,負:取出,0:終了): -1
head = 1, tail = 2, size = 1 [97 ]
取出データ = 58
整数値を入力 (正:格納,負:取出,0:終了): -1
head = 2, tail = 2, size = 0 []
取出データ = 97
整数値を入力 (正:格納,負:取出,0:終了): 0
% ./a.out
キューを実現するプログラム
  正の整数値:入力値をキューに格納する(Enqueue)
  負の整数値:キューからデータを取り出す(Dequeue)
  0:終了
整数値を入力 (正:格納,負:取出,0:終了): 1
head = 0, tail = 1, size = 1 [1 ]
整数値を入力 (正:格納,負:取出,0:終了): 2
head = 0, tail = 2, size = 2 [1 2 ]
整数値を入力 (正:格納,負:取出,0:終了): 3
head = 0, tail = 3, size = 3 [1 2 3 ]
整数値を入力 (正:格納,負:取出,0:終了): 4
head = 0, tail = 4, size = 4 [1 2 3 4 ]
整数値を入力 (正:格納,負:取出,0:終了): 5
head = 0, tail = 0, size = 5 [1 2 3 4 5 ]
整数値を入力 (正:格納,負:取出,0:終了): 6
head = 0, tail = 0, size = 5 [1 2 3 4 5 ]
エラー(キューが満杯です)
整数値を入力 (正:格納,負:取出,0:終了): -1
head = 1, tail = 0, size = 4 [2 3 4 5 ]
取出データ = 1
整数値を入力 (正:格納,負:取出,0:終了): -1
head = 2, tail = 0, size = 3 [3 4 5 ]
取出データ = 2
整数値を入力 (正:格納,負:取出,0:終了): -1
head = 3, tail = 0, size = 2 [4 5 ]
取出データ = 3
整数値を入力 (正:格納,負:取出,0:終了): -1
head = 4, tail = 0, size = 1 [5 ]
取出データ = 4
整数値を入力 (正:格納,負:取出,0:終了): -1
head = 0, tail = 0, size = 0 []
取出データ = 5
整数値を入力 (正:格納,負:取出,0:終了): -1
head = 0, tail = 0, size = 0 []
エラー(キューが空です)
整数値を入力 (正:格納,負:取出,0:終了): 0
%

演習問題5

外部変数を用いて、配列を操作するプログラムを書く。
(提出ファイル名: prog05.c)

会津大学の試験の点数と成績評価の規定は以下のようになっている。

 しかし、今回の試験の点数は配点の都合により110点満点になったとする。 最終的な点数は100点満点にする必要があるので、そのための補正を行う。 つまり、素点に対して100点/110点を掛ける操作を行う。この計算では小数点以下の値が生じるので、小数点第1位で四捨五入することとし、この補正処理のためにcalib関数を作成、使用する。
 また、実行例のように、統計データとして、補正前後の平均点や評価分布も求め、 表示するものとする。

プログラムには以下の外部変数を用いる。
#define MAX_ST_NUM 50                   /* 学生数の最大値 */
#define MAX_SCORE 110			/* 110点満点 */
int student_id[MAX_ST_NUM];             /* 学籍番号  */
int abs_score[MAX_ST_NUM];              /* 点数 (補正前) */
char abs_grade[MAX_ST_NUM];             /* 補正前の評価 (A - F) */
int rel_score[MAX_ST_NUM];              /* 点数 (補正後) */
char rel_grade[MAX_ST_NUM];             /* 補正後の評価 (A - F) */
int grade_dist[2][5];                   /* 評価の分布 [0]:補正前, [1] 補正後 */
                                        /* 例 grade_dist[0][0] 補正前の Fの人数 */
char cgrade[5]={'F','D','C','B','A'};   /* 評価文字(評価点数と対応している) */
int num_student;                        /* 実際に成績を読み込んだ学生の数 */
double abs_ave=0.0;                     /* 平均点(補正前) */
double rel_ave=0.0;                     /* 平均点(補正後) */
main 関数および その他の関数のおおまかな構成と動作を以下に示す。
また、第1回演習問題3の関数 grade_char を、 (必要なら char_point も、一部修正した上で)使用しても構わない。
int calib(int); /* 満点の補正を行う。内部では実数で計算し、小数点第1位で四捨五入した整数を返す */

int main()
{
  /* 標準入力から学籍番号と点数を読みこむコードをここに書く。 */

  adjust_score();  /* 点数を補正し、補正後の点数の配列に書きこむ。*/
  		   /* 補正前・補正後の各学生の評価(A~F)を決定し、それぞれ
                     平均点と評価分布を求める*/
  print_grade();   /* 補正前と後の点数と評価を表示。実行例参照 */
  print_stat();    /* 統計と評価分布を表示。フォーマットは実行例参照 */
  return 0;
}

実行例

サンプル入力ファイル(score_data)はこちら
% ./a.out < score_data
 ID   点数 評価 点数 評価
      (補正前)  (補正後)
-------------------------
 10001    67 B    61 C
 10002   110 A   100 A
...
(略)
 10016    64 C    58 C
-------------------------
統計
学生数 16人 補正前平均点 57.9点 補正後平均点 52.8点
評価分布
補正前 A 3 B 3 C 3 D 4 F 3 
補正後 A 2 B 3 C 4 D 2 F 5 
%

演習問題6

問題5について、入力データをリダイレクションを使わず、直接ファイル score_dataを開いて読み込み処理するプログラムに変更せよ。 さらに、評価分布表示の後に、キーボード入力で指定した補正後の評価 の該当者一覧を表示するように変更せよ。 その際、補正後の評価に対して、引数で指定した評価 (A, B, ..., F)に該当するデータを表示する関数 void grade_search(char) を追加し、使用すること。 評価該当者一覧表示のためのキーボード入力は、繰り返し入力できるようにしておき、 EOFが入力された場合、プログラムを終了するものとする。
ヒント:次回のハンドアウトを参照してください。
(提出ファイル名: prog06.c)

実行例 (評価分布以降、それ以前は前問と同じ)

% ./a.out
(略)
評価分布
補正前 A 3 B 3 C 3 D 4 F 3 
補正後 A 2 B 3 C 4 D 2 F 5 
表示したい評価を大文字で入れてください(A-D,F): A
Aの一覧
 10002  100 A
 10014   84 A
表示したい評価を大文字で入れてください(A-D,F): C
Cの一覧
 10001   61 C
 10005   57 C
 10011   53 C
 10016   58 C
表示したい評価を大文字で入れてください(A-D,F): ^D
%