Ex09

From Prog0

Jump to: navigation, search

演習第9回

Contents

演習問題

主な内容

  • 2次元配列
  • マクロ

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

次のプログラムの複数の誤りを修正し、3×3行列に対して実行例のように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 (口頭試問)配列形状の変換

ファイル名: ex09a2.c

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

下線部を埋めて、mat1と配列内容を移し替えたmat2を表示させるプログラムを完成させなさい。


#include <stdio.h> 
 
int main()
{
 int i, j, m, n;
 int mat1[4][3]={{1,2,3},{4,5,6},{7,8,9},{10,11,12}};
 int mat2[_][_];
 
 printf("4x3行列の表示\n");
 for (i=0; ___; i++){
   for (j=0; ___; j++){
     printf("%2d ", mat1[i][j]); 
   }
   printf("\n");
 }
 
 /* 4x3行列から3x4行列への変換 */
 for (i=0; i<4; i++){
   for (j=0; j<3; j++){ 
     m = (3*i__)/4;
     n = _________;
     mat2[m][n] = mat1[i][j];
   } 
 }
 
 printf("配列形状変換後の3×4行列の表示\n");
 for (i=0; i<3; i++){
   for (j=0; j<4; j++){
     printf("%2d ", __________); 
   }
   ____________;
  }
 
return 0;
}


[実行例]

% ./a.out
4x3行列の表示
 1  2  3 
 4  5  6 
 7  8  9 
10 11 12 
配列形状変換後の3×4行列の表示
 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列に代入を行うことになる。

A-3 行列の積とマクロ

ファイル名: ex09a3.c

以下のプログラムの下線部を埋めて3 x 2行列Aと2 x 3行列Bの積を計算して表示するプログラムを完成させなさい。
ただし、行列A, Bの要素値はキーボードから整数値を入力するものとする。また、配列の要素数はROW, COLといったマクロで定義するものとして、配列の宣言やfor文の条件式に3や2などの定数値は書かないこととする。

なお、行列CをA, Bの積とし、A, B, Cをそれぞれ2次元配列a[i][j], b[i][j], c[i][j]で表した場合、c[i][j]の計算式は以下のようになる。 (ハンドアウトLec09-15, Lec09-16を参照)

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

[プログラム]

#include <stdio.h>
_______ ROW 3
_____________

int main()
{
  int i, j; 
  int A[ROW][___], B[___][___];

  /* 配列要素への入力 */
  printf("3 x 2行列Aを整数値で入力してください\n");
  for (i=0; i<ROW; i++){
    for (j=0; j<COL; j++){
      scanf("%d", ________);
    }
  }

  printf("2 x 3行列Bを整数値で入力してください\n");
  for (i=0; _____; i++){
    for (j=0; _____; j++){
      scanf("%d", &B[i][j]);
    }
  }

  /* 行列の積の計算と表示 */
  printf("行列の積:\n");
  for (i=0; _____; i++){
    for (j=0; _____; j++){
      printf("%3d ", (_________________________________));
    }
    printf(____);
  }
 
  return 0; 
}


[実行例]

% ./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 
%

B問題

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

ファイル名: ex09b1.c

実行例で示されるよう、3×3の2次元配列[matrix]を、1次元配列[array]に変換し、[matrix]と[array]の各配列を表示させるプログラムを完成させなさい。

[実行例]

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

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

キーボードからの入力に応じて、行列を縦(上下)または横(左右)に反転させて表示するプログラムを作成しなさい。
行列は5 x 5行列とし、2次元配列の宣言時に以下の要素値で初期化するものとする。


1 1 1 1 1
0 1 1 1 1
0 0 1 1 1
0 0 0 1 1
0 0 0 0 1

入力された数値が「0」の場合は縦(上下)に、「1」の場合は横(左右)に反転させて表示するようにせよ。
その他の数値が入力された場合は「不適切な数値が入力されました」というメッセージを出力する。

[実行例]

%./a.out
反転する方向を指定してください(縦:0、横:1) 0
0 0 0 0 1
0 0 0 1 1
0 0 1 1 1
0 1 1 1 1
1 1 1 1 1
%./a.out
反転する方向を指定してください(縦:0、横:1) 1
1 1 1 1 1
1 1 1 1 0
1 1 1 0 0
1 1 0 0 0
1 0 0 0 0
%./a.out
反転する方向を指定してください(縦:0、横:1) -1
不適切な数値が入力されました
%

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