Ex09

From Prog0

Jump to: navigation, search

演習第9回

Contents

演習問題

主な内容

  • 2次元配列
  • マクロ

以下の問題を解いて期限内に解答を提出してください。 なお、一人ひとりの授業理解度を確認することを目的として、口頭試問形式による採点を行います。
採点対象の問題が解けたら手を挙げ、教員またはTA/SAを呼んでください。口頭で解答の内容を説明してもらいます。
正しく説明できた場合は点数を付け、説明が不十分な場合はやり直してもらいます(間違えても減点はしません)。
今回の口頭採点の対象は A-3 の1問のみです。 (口頭採点の対象も、その他の問題と同様、menuコマンドで提出してください。)

出席確認

演習時間中に出席確認をLMS上の「演習出欠」で行ってください。出席確認用のパスワードは演習時間のどこかのタイミングで提示されます。


予備知識

ハンドアウトp47、Lec09-8で示されているサンプルプログラム (lec09-1.c)では、ループの中に

scanf("%d", &data[i][j])

と書き、 ループが回るごとに一つずつ数値をキーボード入力により読み込み、順次2次元配列の要素として格納するようになっている。 このサンプルプログラムではint data[3][5]という3行5列の2次元配列(要素数15)に値を格納しているが、 一つのscanfで1行ずつ5つの値の数値を読み込むことを3行分繰り返したり、全ての要素(15個分)の数値を一つのscanfで読み込むよりも 簡単かつ柔軟な書き方になっている。

また、このように書かれたプログラムを実行する場合、キーボード入力をどう区切るかはハンドアウトp47、Lec09-8の実行例の各パターンに示されている通り、 任意にできる。これは、scanfは空白文字も改行文字も同じように区切り文字として扱うためである。 今回の演習の実行例では、多くの場合Lec09-8のパターン1のように配列への格納後の状態に対応するような改行を入れているが、 実際には改行をどのように入れるか、また入れないか(行単位で区切るように入れる、毎回入れる、全く入れない)は自由であり、 scanfの動作やプログラムの実行結果には影響しない。 また、区切り文字は最低一つ入っていればよく、それより多く入っていてもscanfの動作には影響しない。

なお、2次元配列の要素入力は必ずこのようにscanfで一つずつ入力するように書くべき、というわけではない。 他の方法(一つのscanfで1行ずつ読み込む)を用いるほうがわかりやすいプログラムになる場合もあるので、状況に応じて判断すること。

同様のことはハンドアウトp49、Lec09-13に示されている配列の初期化の方法についても言える。 状況に応じて適切な方法を取ればよい。


A問題

A-1 2次元配列(行列)の主対角線にある要素値

ファイル名: ex09a1.c

次のプログラムの誤りを修正し、実行例のように2次元配列(行列)の主対角線にある要素値を表示するプログラムを作成しなさい。

#include <stdio.h>

int main()
{
  int i, j, mat[][3]={1 2 3 4 5 6 7 8 9};
  
  for (i=0; i<3; i++){
    for (j=0; j<=3; j++){
      printf("%d ", mat[j][i]);
    }
    printf("\n");  
  }
  printf("行列の主対角線にある要素値\n");
  for (i=0; i<3; i++){
    printf("%d ", mat[i]);
  }
  printf("\n");

  return 0;
}

[実行例]

%./a.out
1 2 3 
4 5 6 
7 8 9 
行列の主対角線にある要素値
1 5 9 
%

A-2 2次元配列から1次元配列への変換

ファイル名: ex09a2.c

以下は、2次元配列の内容を1次元の配列に移し替えるプログラムである。実行例を参考に、下線部を補い、プログラムを完成させなさい。

#include <stdio.h>

int main()
{
  int i, j;
  /* 以下の形式で初期化された2次元配列の行数がどうなるかはハンドアウトLec09-13を参照すること */
  int matrix[ ][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
  int array[_____];
  
  printf("2次元配列[matrix]の内容:\n"); /* 1次元配列への移し替えも行う */
  for(i = 0; i < _____; i++){
    for(j = 0; j < 3; j++){
      printf("%d ", matrix[i][j]);
      array[_____] = matrix[i][j];
  }
    printf("\n");
  }

  printf("配列[array]の内容:\n");
  for(i = 0; i < _____; i++){
      printf("%d ", array[i]);
  }
  printf("\n");
 
  return 0;
}

[実行例]

% ./a.out
2次元配列[matrix]の内容:
1 2 3 
4 5 6 
7 8 9 
配列[array]の内容:
1 2 3 4 5 6 7 8 9 
%

A-3 (口頭試問) 行列とマクロ

ファイル名: ex09a3a.c, ex09a3b.c

以下はキーボードから入力する2つの3 x 2 行列の和を計算して表示するプログラムである。実行例を参考に、下線部および指示された箇所を補い、プログラムをex09a3a.cのファイル名で完成させなさい。なお、行列の要素を格納する配列の行数と列数はそれぞれROW、COLというマクロで定義するものとし、配列の宣言やfor文の条件式に3や2などの定数値は書かないこととする。

#include <stdio.h>
________________
________________

int main()
{
  int i, j;
  int mat1________________,mat2________________,mat3________________;
  
  printf("%d x %d 行列[mat1]の要素を入力してください:\n", ROW, COL);
  for(________________)
    for(________________)
      scanf("%d", &mat1[i][j]);
      
  printf("%d x %d 行列[mat2]の要素を入力してください:\n", ROW, COL);
  for(________________)
    for(________________)
      scanf("%d", &mat2[i][j]);
      
  /* この部分を補う */
  
  printf("[mat1]と[mat2]の和は:\n");
  for(________________){
    for(________________){
      printf("%2d ", mat3[i][j]);
    }
    printf("\n");
  }

  return 0;
}

[実行例 (ex09a3a.c)]

% ./a.out
3 x 2 行列[mat1]の要素を入力してください:
1 2 
3 4
5 6
3 x 2 行列[mat2]の要素を入力してください:
1 1
2 2
3 3
[mat1]と[mat2]の和は:
 2  3 
 5  6 
 8  9 
%

次にこのプログラムのマクロ定義だけを書き換えて、2つの4 x 3 行列の和を計算して表示するプログラムとしなさい。ファイル名はex09a3b.cとする。

[実行例 (ex09a3b.c)]

% ./a.out
4 x 3 行列[mat1]の要素を入力してください:
1 2 3
4 5 6
7 8 9
10 11 12
4 x 3 行列[mat2]の要素を入力してください:
0 0 0
1 1 1
0 0 0
1 1 1
[mat1]と[mat2]の和は:
 1  2  3 
 5  6  7 
 7  8  9 
11 12 13 
%

B問題

B-1 配列形状の変換

ファイル名: ex09b1.c

以下は、要素数12の1次元配列 inarray から4x3の2次元配列 mat1 へ、また4x3の2次元配列 mat1 から3x4の2次元配列 mat2 へ配列内容を移し替えることで配列の形状を変換するプログラムである。

/*この部分を補う*/と書かれた空欄2箇所を埋めて、プログラムを完成させよ。なお、2つ目の空欄(4x3の2次元配列 mat1 から3x4の2次元配列 mat2 への変換を行う)の中ではmat2[m][n] = mat1[i][j];という代入文を用いること。

#include <stdio.h>

int main()
{
  int i, j, m, n;
  int inarray[12]={1,2,3,4,5,6,7,8,9,10,11,12};
  int mat1[4][3], mat2[3][4];
   
  /* 1次元配列から4x3配列への変換 */
  /*この部分を補う*/
   
  printf("1次元配列から4x3配列への変換結果\n");
  for (i=0; i<4; i++){
    for (j=0; j<3; j++){
      printf("%2d ", mat1[i][j]); 
    }
    printf("\n");
  } 

  /* 4x3配列から3x4配列への変換 */
  /* この部分を補う その際  mat2[m][n] = mat1[i][j]; を使うこと */

  printf("4x3配列から3x4配列への変換結果\n");
  for (i=0; i<3; i++){
    for (j=0; j<4; j++){
      printf("%2d ", mat2[i][j]); 
    }
    printf("\n");
   }

 return 0;
}

実行例

% ./a.out
1次元配列から4x3配列への変換結果
 1  2  3 
 4  5  6 
 7  8  9 
10 11 12 
4x3配列から3x4配列への変換結果
 1  2  3  4 
 5  6  7  8 
 9 10 11 12 
%

なお、4x3配列から3x4配列への変換を行う際はmat2[m][n] = mat1[i][j];によって4x3配列mat1のi行j列の値を3x4配列mat2のm行n列に代入を行うことになる。i, jとm, nの関係を知るためには、2次元配列の各要素が1次元配列の何番目の要素であったのかを考えるとわかりやすい。

B-2 行列要素の抜き出し

ファイル名: ex09b2.c

キーボードからの入力に応じて行列の行要素または列要素を表示するプログラムを、実行例を参考にして作成しなさい。行列は 4 x 3 の行列とし、2次元配列の宣言時に以下の要素値で初期化するものとする。

 1  2  3 
 4  5  6 
 7  8  9 
10 11 12


最初に入力された数値が「0」の場合はその後指定された行要素を、「1」の場合は列要素を同様に表示する。その他の数値が入力された場合は行列全体を表示するものとする。 行番号・列番号は配列の要素番号そのままの0始まりで数えるものとする。また、指定した行番号、または列番号が範囲外の場合は、下記の実行例に示すエラーを出力するようにすること。

[実行例]

% ./a.out
行要素と列要素のどちらを表示したいか選択して下さい
(行要素:0, 列要素:1)  0
表示したい行番号を入力して下さい :2
 7  8  9 
% ./a.out
行要素と列要素のどちらを表示したいか選択して下さい
(行要素:0, 列要素:1)  1
表示したい列番号を入力して下さい :0
 1
 4
 7
10
% ./a.out
行要素と列要素のどちらを表示したいか選択して下さい
(行要素:0, 列要素:1)  -1
 1  2  3 
 4  5  6 
 7  8  9 
10 11 12 
% ./a.out
行要素と列要素のどちらを表示したいか選択して下さい
(行要素:0, 列要素:1)  0
表示したい行番号を入力して下さい :4
行の範囲外です
% ./a.out
行要素と列要素のどちらを表示したいか選択して下さい
(行要素:0, 列要素:1)  1
表示したい列番号を入力して下さい :-2
列の範囲外です
%

B-3 行列の積

ファイル名: ex09b3.c

3 x 2行列Aと2 x 3行列Bの積を計算して表示するプログラムを作成しなさい。
ただし、行列A, Bの要素値はキーボードから入力するものとする。

行列CをA, Bの積とし、A, B, Cをそれぞれ2次元配列a[i][j], b[i][j], c[i][j]で表した場合、c[i][j]の計算式は以下のようになる。

c[i][j] = a[i][0]*b[0][j] + a[i][1]*b[1][j] (i, j = 0, 1, 2)


[実行例]

% ./a.out
3 x 2行列Aを整数値で入力してください
1 2
3 4
5 6

2 x 3行列Bを整数値で入力してください
1 2 3
4 5 6
 
行列の積:
  9  12  15 
 19  26  33 
 29  40  51 
%

Extra問題

E-1 〇×三目並べゲーム

ファイル名: ex09e1.c

以下のプログラムは3x3のマス上で遊ぶ〇×の三目並べゲームであるが、不完全な部分がある。正しく動作するように改良しなさい。

プログラムの仕様:

  • マスとなる配列の要素はあらかじめ「0」に初期化しておく。
  • 何も置かれていないところの要素は「0」、〇が置かれたところは「1」、×が置かれたところは「-1」になるとする。
  • マスの表示は要素値によって該当する文字(〇か×)を表示する(盤が見にくくなるが座標も一緒に表示する)。
  • 座標の入力はflagが「0」のとき〇の番、「1」のとき×の番になるようにして、それぞれの番が終ったときに相手の番になるように、flagの値を反転させる。


#include <stdio.h>
#define NUM 3

int main()
{
  int map[NUM][NUM];
  int i, j, m, n;
  int flag = 0;

  /* 配列の初期化 */
  for (i=0; i<NUM; i++)
    for (j=0; j<NUM; j++)
      map[i][j] = 0;

  while (1) {
  /* 盤の表示 */
    for (i=0; i<NUM; i++) {
      for (j=0; j<NUM; j++) {
        if (map[i][j]==1) printf("(%d %d) 〇 ", i, j);
        else if (map[i][j]==-1) printf("(%d %d) × ", i, j);
        else printf("(%d %d)   ", i, j);
      }
      printf("\n");
    }

    /* 座標の入力 */
    if (flag==0) {
      printf("〇の番です\n座標を行・列の順で入力してください:");
      scanf("%d%d", &m, &n);
      map[m][n] = 1;
      flag = 1;
    } else {
      printf("×の番です\n座標を行・列の順で入力してください:");
      scanf("%d%d", &m, &n);
      map[m][n] = -1;
      flag = 0;
    }
  }
  
  return 0;
}

改良すべき問題点:

  • 座標入力の際に、盤面上(配列の範囲内)であるかチェックしていない。
  • 既に〇×が置かれていても再度置けてしまう。
  • 終了判定(勝ち、負け、引き分け)がない。

なお、終了判定がない状態でプログラムを実行し、それを強制的に停止させたい場合は「Ctrl + c」を押すこと。

終了判定のヒント:

  • 〇の勝ち:縦、横、斜め、それぞれの合計が何処か1箇所でもNUMになったとき
  • ×の勝ち:縦、横、斜め、それぞれの合計が何処か1箇所でも-NUMになったとき
  • 引き分け:配列の中に「0」の要素がなくなったとき

[実行例]

% ./a.out
(0 0)   (0 1)   (0 2)   
(1 0)   (1 1)   (1 2)   
(2 0)   (2 1)   (2 2)   
〇の番です
座標を行・列の順で入力してください:0 0
(0 0) 〇 (0 1)   (0 2)   
(1 0)   (1 1)   (1 2)   
(2 0)   (2 1)   (2 2)   
×の番です
座標を行・列の順で入力してください:0 1
(0 0) 〇 (0 1) × (0 2)   
(1 0)   (1 1)   (1 2)   
(2 0)   (2 1)   (2 2)   
〇の番です
座標を行・列の順で入力してください:1 1
(0 0) 〇 (0 1) × (0 2)   
(1 0)   (1 1) 〇 (1 2)   
(2 0)   (2 1)   (2 2)   
×の番です
座標を行・列の順で入力してください:1 1
そこには打てません!
(0 0) 〇 (0 1) × (0 2)   
(1 0)   (1 1) 〇 (1 2)   
(2 0)   (2 1)   (2 2)   
×の番です
座標を行・列の順で入力してください:0 2
(0 0) 〇(0 1) × (0 2) × 
(1 0)   (1 1) 〇 (1 2)   
(2 0)   (2 1)   (2 2)   
〇の番です
座標を行・列の順で入力してください:3 2
盤の範囲外です。
(0 0) 〇 (0 1) × (0 2) × 
(1 0)   (1 1) 〇 (1 2)   
(2 0)   (2 1)   (2 2) 
〇の番です
座標を行・列の順で入力してください:2 2
(0 0) 〇 (0 1) × (0 2) × 
(1 0)   (1 1) 〇 (1 2)   
(2 0)   (2 1)   (2 2) 〇 
〇の勝ち!!
%

課題提出上の注意事項

解答ファイルはmenuコマンドを使って提出してください。以下のようにmenuコマンドを実行し、表示されるメッセージに沿って操作すること。

% ~prog0/bin/menu

menuコマンドは、解答ファイルが ~/Prog0/Ex## のディレクトリに指定されたファイル名で置かれているものとして処理します。正常に提出された場合は ○ が、何らかのエラーが生じた場合は × が表示されます。

解答の提出期間は以下のとおりです。

問題提出受付開始提出〆切
A問題 演習日の6日前の午後9時演習終了時刻
B, Extra問題 演習日の6日前の午後9時演習日の6日後の午後9時

提出は〆切前であれば何度でもやり直すことができます。再提出すると、前に提出したファイルは新しい内容で上書きされます。

Personal tools