C言語のきほん|2次元配列のデータを条件で処理する

条件を組み合わせれば、2次元配列はもっと便利になる。
表のようなデータを、思いどおりに見つけて処理しよう。

2次元配列は、行と列でデータを整理できるので、表のような情報を扱うときにとても便利です。
たとえば、売上表、出席表、座席表、気温の記録、テスト結果など、現実のデータには「縦と横のまとまり」がよく出てきます。

前の学習では、for文の二重ループを使って、2次元配列のすべての要素を順番に取り出す方法を学びました。
ここでは、その一歩先へ進んで、条件分岐を組み合わせた処理を考えていきます。

2次元配列を扱うときは、ただ全部を表示するだけではなく、

  • ある条件に当てはまるデータだけを数える
  • 一番大きい値や一番小さい値を探す
  • 合計や平均を求める
  • 条件ごとに処理を分ける

といった場面がたくさんあります。

このような処理では、for文で配列全体を調べながら、if文や if ~ else if ~ else を使って判定していくのが基本になります。
つまり、2次元配列の学習では、繰り返しと条件分岐を組み合わせることがとても大切です。

今回は、2次元配列の中から条件に合うデータを取り出したり、最大値や最小値を求めたりする考え方を、やさしく整理しながら見ていきます。
さらに後半では、実践問題として、九九表の作成、平均点と評価、合計点と順位、そして配列の並び替えや変換に近い応用問題まで扱います。

「2次元配列は見た目が少し難しそう」と感じる人もいるかもしれませんが、考え方はとても素直です。
外側のループで行を進め、内側のループで列を進める。そこで条件を判定する。
この流れがつかめれば、かなり多くの問題に対応できるようになります。

2次元配列と条件分岐を組み合わせる考え方

2次元配列は、次のように「行」と「列」でデータを持っています。

たとえ方内容
何番目のまとまりか
そのまとまりの中の何番目か
配列名[i][j]i行j列の値

たとえば、3人分の4日間の歩数データがあるとします。

1日目2日目3日目4日目
1人目5200810076009000
2人目4300650072008800
3人目9100840078006900

このような表は、C言語では2次元配列で表現できます。
そして、たとえば次のような処理ができます。

  • 8000歩以上の日数を数える
  • 全体の中で最大の歩数を探す
  • 全体の中で最小の歩数を探す

ここで大事なのは、配列の中のすべての値を1つずつ見ていく必要があることです。
そのために、for文の二重ループを使います。

基本の形は次のとおりです。

for (int i = 0; i < 行数; i++) {
    for (int j = 0; j < 列数; j++) {
        /* 配列[i][j] に対する処理 */
    }
}

この中で if文を使えば、条件に合うデータだけを数えたり、値を更新したりできます。

サンプルプログラムの例

題材は、3人分の4日間の歩数データです。

このプログラムでは、次の2つを行います。

  • 8000歩以上の日数を数える
  • 全体の中で最大歩数と最小歩数を求める

サンプルプログラム

ファイル名:10_10_1.c

#include <stdio.h>

#define PERSON_COUNT 3      /* 人数 */
#define DAY_COUNT 4         /* 日数 */
#define TARGET_STEPS 8000   /* 目標歩数 */

int main(void)
{
    /* 歩数データを2次元配列で管理する */
    int steps[PERSON_COUNT][DAY_COUNT] = {
        {5200, 8100, 7600, 9000},
        {4300, 6500, 7200, 8800},
        {9100, 8400, 7800, 6900}
    };

    /* 目標歩数以上の日数を数える */
    int count_target_days = 0;

    for (int i = 0; i < PERSON_COUNT; i++) {
        for (int j = 0; j < DAY_COUNT; j++) {
            if (steps[i][j] >= TARGET_STEPS) {
                count_target_days++;
            }
        }
    }

    printf("%d歩以上だった日数は %d 日です。\n", TARGET_STEPS, count_target_days);

    /* 最大歩数と最小歩数を求める */
    int max_steps = steps[0][0];
    int min_steps = steps[0][0];

    for (int i = 0; i < PERSON_COUNT; i++) {
        for (int j = 0; j < DAY_COUNT; j++) {
            if (steps[i][j] > max_steps) {
                max_steps = steps[i][j];
            }

            if (steps[i][j] < min_steps) {
                min_steps = steps[i][j];
            }
        }
    }

    printf("最大歩数は %d 歩です。\n", max_steps);
    printf("最小歩数は %d 歩です。\n", min_steps);

    return 0;
}

実行結果例

8000歩以上だった日数は 5 日です。
最大歩数は 9100 歩です。
最小歩数は 4300 歩です。

このプログラムで学べること

このサンプルには、2次元配列を条件付きで処理するための大事な考え方が、ぎゅっと詰まっています。

条件に合うデータだけを数える

まず前半では、8000歩以上の日数を数えています。

考え方はとてもシンプルです。

  1. count_target_days を 0 で初期化する
  2. 配列全体を二重ループで順番に見る
  3. もし 8000 以上なら count_target_days を 1 増やす

この流れを表にすると、次のようになります。

手順内容
1カウンタ変数を0で用意する
2for文の二重ループで全要素を見る
3if文で条件に合うか判定する
4条件に合えばカウンタを増やす

この方法は、歩数だけでなく、点数、売上、在庫数、温度など、いろいろな場面で使えます。
たとえば「100以上のデータはいくつあるか」「0のデータはいくつあるか」「偶数はいくつあるか」などにもそのまま応用できます。

最大値と最小値を求める

後半では、最大歩数と最小歩数を求めています。
ここで大事なのは、比較のための初期値をどう決めるかです。

このプログラムでは、最初の要素である steps[0][0] を初期値にしています。

int max_steps = steps[0][0];
int min_steps = steps[0][0];

このようにしておけば、配列の中に実際に存在する値を基準に比較できるので安心です。

その後、配列全体を調べながら、

  • 今見ている値が max_steps より大きければ更新
  • 今見ている値が min_steps より小さければ更新

という処理を繰り返します。

この流れを表にすると、次のようになります。

求めたいもの判定条件更新内容
最大値配列の値 > 現在の最大値最大値を入れ替える
最小値配列の値 < 現在の最小値最小値を入れ替える

配列の動きをイメージしてみよう

今回の配列は次のような並びです。

1日目2日目3日目4日目
1人目5200810076009000
2人目4300650072008800
3人目9100840078006900

二重ループでは、次の順に見ていきます。

steps[0][0] → steps[0][1] → steps[0][2] → steps[0][3]
steps[1][0] → steps[1][1] → steps[1][2] → steps[1][3]
steps[2][0] → steps[2][1] → steps[2][2] → steps[2][3]

つまり、1行目を左から右へ見終わったら、次の行に進むという形です。
この順番を頭の中でイメージできるようになると、2次元配列の理解がぐっと深まります。

if文を二重ループの中に入れる意味

2次元配列の条件付き処理では、if文を二重ループの中に入れることが多いです。
なぜなら、配列の要素は1つずつ取り出して判定する必要があるからです。

たとえば、次のような考え方です。

やりたいこと二重ループの中で行う処理
条件に合う個数を数えるif文で判定してカウンタを増やす
最大値を探すif文で比較して更新する
最小値を探すif文で比較して更新する
評価を決めるif ~ else if ~ else で判定する

このように、2次元配列の処理では、繰り返しの中で条件分岐を行うという形が基本になります。

よくあるつまずきポイント

2次元配列の問題では、いくつかつまずきやすいポイントがあります。
ここは早めに意識しておくと安心です。

添字の範囲を間違える

配列の添字は 0 から始まります。
たとえば 3行4列の配列なら、行は 0 ~ 2、列は 0 ~ 3 です。

項目正しい範囲
3行の行番号0, 1, 2
4列の列番号0, 1, 2, 3

ここを 1 から始めてしまうと、意図しない場所を参照したり、配列の範囲外に出たりする原因になります。

最大値・最小値の初期値を適当に決める

最大値を 0 にしたり、最小値を 9999 にしたりしても動く場合はありますが、データによっては正しくないことがあります。
そのため、配列の最初の要素を初期値にする方法が基本です。

行数と列数を取り違える

外側が行、内側が列という形を意識しておきましょう。
慣れないうちは、i は行、j は列、と決めて考えると整理しやすいです。

実践問題

9行9列の int型の配列に掛け算九九の結果を代入するプログラムを作成してください。
配列の内容は表示して確認してください。

解答例

ファイル名:10_10_2.c

#include <stdio.h>

#define SIZE 9

int main(void)
{
    int kuku[SIZE][SIZE];

    /* 九九の結果を配列に代入する */
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            kuku[i][j] = (i + 1) * (j + 1);
        }
    }

    /* 配列の内容を表示する */
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            printf("%2d ", kuku[i][j]);
        }
        printf("\n");
    }

    return 0;
}

実行結果例

 1  2  3  4  5  6  7  8  9 
 2  4  6  8 10 12 14 16 18 
 3  6  9 12 15 18 21 24 27 
 4  8 12 16 20 24 28 32 36 
 5 10 15 20 25 30 35 40 45 
 6 12 18 24 30 36 42 48 54 
 7 14 21 28 35 42 49 56 63 
 8 16 24 32 40 48 56 64 72 
 9 18 27 36 45 54 63 72 81 

解説

この問題のポイントは、添字は0から始まるのに、九九は1から始まるというところです。

たとえば、

  • 1の段は i = 0
  • 2の段は i = 1
  • 9の段は i = 8

となります。
そのため、計算するときは i や j をそのまま使うのではなく、i + 1j + 1 にして掛け算しています。

kuku[i][j] = (i + 1) * (j + 1);

ここがこの問題のいちばん大事な部分です。

対応関係を表にすると、次のようになります。

添字実際の段や数
01
12
23
......
89

表示では printf("%2d ", kuku[i][j]); を使って、桁がそろいやすいようにしています。
%2d は「少なくとも2桁分の幅で整数を表示する」という意味なので、表のように見やすくなります。

実践問題

以下の2次元配列で表される5人分の作業時間データがあります。
各人の平均作業時間と判定を求めて表示するプログラムを作成してください。

作業時間データ

番号1日目2日目3日目4日目
16787
29878
35676
44556
58989

判定基準

  • 8時間以上:A
  • 7時間以上:B
  • 6時間以上:C
  • 6時間未満:D

解答例

ファイル名:10_10_3.c

#include <stdio.h>

#define MEMBER_COUNT 5
#define DAY_COUNT 4

int main(void)
{
    int work_hours[MEMBER_COUNT][DAY_COUNT] = {
        {6, 7, 8, 7},
        {9, 8, 7, 8},
        {5, 6, 7, 6},
        {4, 5, 5, 6},
        {8, 9, 8, 9}
    };

    double average;
    char rank;

    printf("番号  1日目 2日目 3日目 4日目 平均  判定\n");

    for (int i = 0; i < MEMBER_COUNT; i++) {
        int sum = 0;

        for (int j = 0; j < DAY_COUNT; j++) {
            sum += work_hours[i][j];
        }

        average = (double)sum / DAY_COUNT;

        if (average >= 8.0) {
            rank = 'A';
        } else if (average >= 7.0) {
            rank = 'B';
        } else if (average >= 6.0) {
            rank = 'C';
        } else {
            rank = 'D';
        }

        printf("%d      %d    %d    %d    %d   %.2f   %c\n",
               i + 1,
               work_hours[i][0], work_hours[i][1],
               work_hours[i][2], work_hours[i][3],
               average, rank);
    }

    return 0;
}

実行結果例

番号  1日目 2日目 3日目 4日目 平均  判定
1      6    7    8    7   7.00   B
2      9    8    7    8   8.00   A
3      5    6    7    6   6.00   C
4      4    5    5    6   5.00   D
5      8    9    8    9   8.50   A

解説

この問題では、1人ごとのデータを処理するのがポイントです。
外側のループが1人分、内側のループが4日分のデータを処理しています。

流れは次のとおりです。

手順内容
11人分の合計を求める
2平均を計算する
3平均に応じて判定を決める
4結果を表示する

特に大事なのは、sum を外側ループの中で毎回 0 に戻していることです。

int sum = 0;

これを外側ループの外に置いてしまうと、前の人の合計が残ってしまうので注意が必要です。

また、平均を小数で求めたいので、次のように double へ型変換しています。

average = (double)sum / DAY_COUNT;

もし型変換をしないと、整数同士の割り算になり、小数部分が切り捨てられてしまいます。

実践問題

元の「合計点と順位」の問題に近い内容として、次のような問題が考えられます。

以下の2次元配列で表される5人分の月間販売数データがあります。
各人の合計販売数と順位を求めて表示するプログラムを作成してください。

販売数データ

番号商品1商品2商品3商品4
118252120
230221924
316182017
428272529
520192321

順位は、最初に1位を仮定して、自分より合計販売数が多い人がいれば順位を1つ下げるものとします。

解答例

ファイル名:10_10_4.c

#include <stdio.h>

#define STAFF_COUNT 5
#define PRODUCT_COUNT 4

int main(void)
{
    int sales[STAFF_COUNT][PRODUCT_COUNT] = {
        {18, 25, 21, 20},
        {30, 22, 19, 24},
        {16, 18, 20, 17},
        {28, 27, 25, 29},
        {20, 19, 23, 21}
    };

    int total[STAFF_COUNT];
    int rank[STAFF_COUNT];

    /* 合計販売数を求める */
    for (int i = 0; i < STAFF_COUNT; i++) {
        total[i] = 0;
        for (int j = 0; j < PRODUCT_COUNT; j++) {
            total[i] += sales[i][j];
        }
    }

    /* 順位を求める */
    for (int i = 0; i < STAFF_COUNT; i++) {
        rank[i] = 1;
        for (int j = 0; j < STAFF_COUNT; j++) {
            if (total[j] > total[i]) {
                rank[i]++;
            }
        }
    }

    printf("番号  商品1 商品2 商品3 商品4 合計  順位\n");

    for (int i = 0; i < STAFF_COUNT; i++) {
        printf("%d     %d    %d    %d    %d   %d   %d\n",
               i + 1,
               sales[i][0], sales[i][1], sales[i][2], sales[i][3],
               total[i], rank[i]);
    }

    return 0;
}

実行結果例

番号  商品1 商品2 商品3 商品4 合計  順位
1     18    25    21    20   84   3
2     30    22    19    24   95   2
3     16    18    20    17   71   5
4     28    27    25    29   109   1
5     20    19    23    21   83   4

解説

この問題では、処理が2段階になっています。

まずは各人の合計を求めます。
これはこれまでと同じように、二重ループで1行分を足していけば大丈夫です。

次に順位を求めます。
ここが少しおもしろいところです。

自分の順位を最初は1位と考えておいて、
自分より合計が大きい人が1人見つかるたびに順位を1つ増やす
という考え方で求めています。

たとえば、ある人の合計が 84 で、それより大きい人が2人いれば、その人の順位は3位です。

この考え方を表にすると、次のようになります。

自分より大きい合計の人数順位
0人1位
1人2位
2人3位
3人4位

この問題は、二重ループを使って人同士を比較する処理の練習になります。
2次元配列そのものだけでなく、1次元配列と組み合わせて使う練習としてもとても良い問題です。

実践問題

6×6 の整数型配列1があります。
この配列に入っている 0 と 1 の並びを使って図形を表しています。
モード番号に応じて、配列1の内容を別の配列2へ変換して格納し、結果を表示するプログラムを作成してください。

変換モード

  1. 右へ90度回転
  2. 左右反転
  3. 上下反転

元の配列1

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

表示は見やすいように、1 を ■、0 を □ で表示してください。

解答例

ファイル名:10_10_5.c

#include <stdio.h>

#define SIZE 6

void print_array(int data[SIZE][SIZE]);

int main(void)
{
    int src[SIZE][SIZE] = {
        {0, 1, 1, 1, 1, 0},
        {0, 1, 0, 0, 0, 0},
        {0, 1, 0, 0, 0, 0},
        {0, 1, 1, 1, 0, 0},
        {0, 1, 0, 0, 0, 0},
        {0, 1, 0, 0, 0, 0}
    };

    int dst[SIZE][SIZE];
    int mode;

    printf("元の配列の表示\n");
    print_array(src);

    printf("\n");
    printf("1: 右へ90度回転\n");
    printf("2: 左右反転\n");
    printf("3: 上下反転\n");
    printf("変換モードを指定してください > ");
    scanf("%d", &mode);

    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            if (mode == 1) {
                dst[j][SIZE - 1 - i] = src[i][j];
            } else if (mode == 2) {
                dst[i][SIZE - 1 - j] = src[i][j];
            } else if (mode == 3) {
                dst[SIZE - 1 - i][j] = src[i][j];
            }
        }
    }

    printf("\n結果の配列の表示\n");
    print_array(dst);

    return 0;
}

void print_array(int data[SIZE][SIZE])
{
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            if (data[i][j] == 1) {
                printf("■ ");
            } else {
                printf("□ ");
            }
        }
        printf("\n");
    }
}

実行結果例

元の配列の表示
□ ■ ■ ■ ■ □
□ ■ □ □ □ □
□ ■ □ □ □ □
□ ■ ■ ■ □ □
□ ■ □ □ □ □
□ ■ □ □ □ □

1: 右へ90度回転
2: 左右反転
3: 上下反転
変換モードを指定してください > 2

結果の配列の表示
□ ■ ■ ■ ■ □
□ □ □ □ ■ □
□ □ □ □ ■ □
□ □ ■ ■ ■ □
□ □ □ □ ■ □
□ □ □ □ ■ □

モード 2 を選んだ場合の例です。

解説

この問題では、元の配列 src のどの位置の値を、変換後の配列 dst のどこへ入れるかを考えるのがポイントです。

変換ごとの対応は次のようになります。

変換代入先
右へ90度回転dst[j][SIZE - 1 - i] = src[i][j]
左右反転dst[i][SIZE - 1 - j] = src[i][j]
上下反転dst[SIZE - 1 - i][j] = src[i][j]

最初は少し難しく見えますが、これは「行番号と列番号をどう入れ替えるか」という話です。

たとえば左右反転なら、行はそのままで、列だけ反対側へ移します。
そのため、

  • 行番号 i はそのまま
  • 列番号 j は SIZE - 1 - j に変わる

という形になります。

この問題は、2次元配列の値そのものを処理するだけでなく、位置を操作する練習になります。
応用問題としてとてもよく、画像処理やゲームのマップ操作にもつながる考え方です。

2次元配列の条件処理で意識したいこと

ここまでの内容を通して、特に意識したいポイントを整理しておきます。

ポイント内容
配列全体を見るfor文の二重ループを使う
条件を付けるif文や if ~ else if ~ else を使う
個数を数えるカウンタ変数を用意する
最大・最小を求める最初の要素を初期値にして比較する
行ごとの集計外側のループごとに合計を初期化する
添字に注意する0から始まることを忘れない
配列の位置を変えるi と j の対応関係を丁寧に考える

2次元配列は、最初は少し難しそうに見えますが、
実際には「1つずつ見て、条件に応じて処理する」の繰り返しです。

とくに大事なのは、外側が行、内側が列という流れをしっかり意識することです。
ここが自然に分かるようになると、平均、順位、判定、変換といったいろいろな処理が書けるようになります。