Ex09

From Prog0

Jump to: navigation, search

演習第9回

Contents

演習問題

主な内容

  • 2次元配列
  • マクロ

以下の問題を解いて期限内に解答を提出してください。

出席確認

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

予備知識

今回の演習の実行例では、以下のように配列の要素などを入力する際に配列への格納後の状態に対応するように、途中に改行を入れながら入力する様子が示されている。

1 2 3
4 5 6

ハンドアウトp47、Lec09-8に示す通り、このような二次元配列への要素入力はscanf("%d", &data[i][j])のように ループの中のscanfで一つづつ値を読み込み、順次配列の要素として格納するような書き方をすることが多い。 この時、ハンドアウトLec09-8の実行例の通り、キーボードから空白で区切られた値を連続して入力しても、scanfは順次一つづつ値を受け取り、 配列の要素として格納できる。つまり、行単位で複数の値を受け取るようなscanfの使い方をする必要はない。 さらに、scanfは空白文字も改行文字も同じように区切り文字として扱うので、入力の際に途中に改行を入れても問題ない。 これもハンドアウトLec09-8の実行例の通り、改行をどのように入れるか、また入れないか(行単位で区切るように入れる、毎回入れる、全く入れない) にも影響されない。

ハンドアウトの例題

問題に取り掛かる前にハンドアウトの例題 lec09-4.c を以下のようにコピーし、2次元配列の初期化の方法を確認した上でコンパイル・実行を試すこと。

% cd ~/Prog0/Ex09
% cp /home/course/prog0/public_html/2024/lec/source/lec09-4.c .


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次元配列とマクロ

ファイル名: ex09a2.c

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

#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("%d ", mat3[i][j]);
    }
    printf("\n");
  }

  return 0;
}

[実行例]

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

A-3 配列形状の変換

ファイル名: ex09a3.c

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

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

#include <stdio.h>

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

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

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

実行例

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

B問題

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

ファイル名: ex09b1.c

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

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


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

[実行例]

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

B-2 2次元配列の一部の利用と行列のスカラー倍

ファイル名: ex09b2.c

n x m行列の各要素に、ある整数値を掛けて表示するプログラムを作成しなさい。
ただし、プログラム中ではSIZE x SIZE (SIZEはマクロで定義し、値は10とする)の2次元配列を用意し、 そのn行m列目までを使って n x m行列の要素を格納し、処理を行うようにすること。 行列サイズ n, m と要素値はキーボードから入力する。

[実行例]

%./a.out
行列の大きさを入力して下さい(最大10 x 10) :3 3
3 x 3行列を整数値で入力してください
1 2 3
4 5 6
7 8 9
行列を何倍にしますか? 2
 2  4  6
 8 10 12
14 16 18

%./a.out
行列の大きさを入力して下さい(最大10 x 10) :3 4
3 x 4行列を整数値で入力してください
1 2 3 4
4 5 6 7
5 6 7 8
行列を何倍にしますか? 3
 3  6  9 12
12 15 18 21
15 18 21 24
%

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