C言語の基本|2次元配列の条件処理

二重ループで全体を見渡し、if文で必要な値を選び出す。2次元配列のデータを条件に合わせて活用しよう。

2次元配列は、行と列を使って表形式のデータを管理できる便利な仕組みです。設備ごとの測定値、担当者ごとの作業時間、店舗ごとの販売数など、縦と横の関係を持つデータをすっきり整理できます。

しかし、実際のプログラムでは、配列の内容を表示するだけでなく、次のような処理も必要になります。

  • 基準値以上のデータを数える
  • 最大値や最小値を探す
  • 平均値から評価を決める
  • 合計値を比較して順位を求める
  • 添字を変換して図形の向きを変える

このような処理では、for文の二重ループとif文を組み合わせます。

外側のfor文で行を進め、内側のfor文で列を進めながら、取り出した要素をif文で判定するのが基本です。この流れを理解すれば、2次元配列から必要な情報を見つけたり、条件に応じて処理を変えたりできるようになります。

2次元配列と条件分岐の組み合わせ

2次元配列は、次の形で要素を指定します。

配列名[行の添字][列の添字]

たとえば、3台の設備について4回分の振動値を記録する場合、次のような表として考えられます。

設備\測定回1回目2回目3回目4回目
設備128453752
設備231624855
設備326416739

C言語では、次の2次元配列で表現できます。

int vibrations[3][4] = {
    {28, 45, 37, 52},
    {31, 62, 48, 55},
    {26, 41, 67, 39}
};

すべての要素を調べる基本形は、次のとおりです。

for (int row = 0; row < 行数; row++) {
    for (int col = 0; col < 列数; col++) {
        if (vibrations[row][col]が条件に合う) {
            /* 必要な処理 */
        }
    }
}

それぞれの制御文には、次の役割があります。

制御文役割
外側のfor文行を順番に進める
内側のfor文現在の行にある列を順番に進める
if文現在の要素が条件に合うか判定する

図1:二重ループとif文による条件処理

この図から分かること

二重ループは、2次元配列の全要素を1つずつ取り出します。取り出した値をif文で調べることで、条件を満たす要素だけを数えたり、別の処理を実行したりできます。

条件に合わない要素があってもループは終了せず、次の要素の確認へ進みます。

条件を満たす要素を数える

条件に合う要素数を求める場合は、カウンタ変数を0で初期化します。

int match_count = 0;

二重ループの中で条件を判定し、条件を満たしたときだけ1増やします。

for (int row = 0; row < ROW_COUNT; row++) {
    for (int col = 0; col < COL_COUNT; col++) {
        if (array[row][col] >= 基準値) {
            match_count++;
        }
    }
}

この形は、次のような処理に利用できます。

  • 50以上の測定値を数える
  • 0の要素を数える
  • 偶数の要素を数える
  • 指定した範囲内の要素を数える

最大値と最小値を探す

2次元配列から最大値と最小値を探す場合は、配列の先頭要素を初期値として使います。

int max_value = array[0][0];
int min_value = array[0][0];

その後、二重ループですべての要素を比較します。

if (array[row][col] > max_value) {
    max_value = array[row][col];
}

if (array[row][col] < min_value) {
    min_value = array[row][col];
}

比較の考え方は次のとおりです。

求める値判定条件成立時の処理
最大値現在の要素 > max_valuemax_valueを更新する
最小値現在の要素 < min_valuemin_valueを更新する

最大値を0、最小値を9999のような固定値で初期化すると、配列の内容によっては正しい結果になりません。配列内に実際に存在するarray[0][0]を使うのが基本です。

設備の振動値から基準超過数と最大値・最小値を求めるプログラム

ファイル名:10_10_1.c

#include <stdio.h>

#define MACHINE_COUNT 3
#define MEASURE_COUNT 4
#define LIMIT_VALUE 50

int main(void)
{
    /* 3台の設備について4回分の振動値を管理する */
    int vibrations[MACHINE_COUNT][MEASURE_COUNT] = {
        {28, 45, 37, 52},
        {31, 62, 48, 55},
        {26, 41, 67, 39}
    };

    int over_limit_count = 0;

    /* 50以上の測定値を数える */
    for (int machine = 0;
         machine < MACHINE_COUNT;
         machine++) {

        for (int measure = 0;
             measure < MEASURE_COUNT;
             measure++) {

            if (vibrations[machine][measure] >= LIMIT_VALUE) {
                over_limit_count++;
            }
        }
    }

    printf("%d以上の測定値は%d個です。\n",
           LIMIT_VALUE, over_limit_count);

    /* 最大値と最小値を先頭要素で初期化する */
    int max_value = vibrations[0][0];
    int min_value = vibrations[0][0];

    /* 配列全体から最大値と最小値を探す */
    for (int machine = 0;
         machine < MACHINE_COUNT;
         machine++) {

        for (int measure = 0;
             measure < MEASURE_COUNT;
             measure++) {

            if (vibrations[machine][measure] > max_value) {
                max_value = vibrations[machine][measure];
            }

            if (vibrations[machine][measure] < min_value) {
                min_value = vibrations[machine][measure];
            }
        }
    }

    printf("最大の振動値は%dです。\n", max_value);
    printf("最小の振動値は%dです。\n", min_value);

    return 0;
}
実行結果の例
50以上の測定値は4個です。
最大の振動値は67です。
最小の振動値は26です。
基準以上の値を数える流れ

LIMIT_VALUEは、基準となる50を表すマクロです。

#define LIMIT_VALUE 50

二重ループで12要素を調べ、50以上のときだけover_limit_countを増やします。

測定値50以上かカウント
28いいえ0
45いいえ0
37いいえ0
52はい1
31いいえ1
62はい2
48いいえ2
55はい3
26いいえ3
41いいえ3
67はい4
39いいえ4

条件を満たす52、62、55、67の4個が数えられます。

最大値と最小値を更新する流れ

最初はvibrations[0][0]の28が、最大値と最小値の両方に入ります。

int max_value = vibrations[0][0];
int min_value = vibrations[0][0];

その後、全要素と比較します。

  • 28より大きい値が見つかるたびにmax_valueを更新する
  • 28より小さい値が見つかるたびにmin_valueを更新する
  • 最後まで調べると、max_valueには67が残る
  • min_valueには26が残る

図2:最大値と最小値の更新

この図から分かること

最大値と最小値は、配列の先頭要素を比較の出発点にします。現在の基準より大きい値や小さい値が見つかるたびに、その値で更新します。

配列の最後まで比較すれば、最大値と最小値がそれぞれの変数に残ります。

2次元配列を処理する順番

一般的な二重ループでは、最初の行を左から右へ調べ、その後に次の行へ進みます。

array[0][0] → array[0][1] → array[0][2] → array[0][3]
array[1][0] → array[1][1] → array[1][2] → array[1][3]
array[2][0] → array[2][1] → array[2][2] → array[2][3]

外側のループが行、内側のループが列を担当するためです。

この順番が分かると、条件に合う要素がいつ処理されるか、最大値や最小値がどのように更新されるかを追いやすくなります。

9行9列の配列を計算で作る

2次元配列は、初期化子で値を入れるだけでなく、二重ループによる計算結果を代入して作ることもできます。

次の例では、1から9までの足し算表を作ります。

1から9までの足し算表を2次元配列に作るプログラム

ファイル名:10_10_2.c

#include <stdio.h>

#define SIZE 9

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

    /* 足し算の結果を配列へ代入する */
    for (int row = 0; row < SIZE; row++) {
        for (int col = 0; col < SIZE; col++) {
            addition_table[row][col] =
                (row + 1) + (col + 1);
        }
    }

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

        printf("\n");
    }

    return 0;
}
実行結果の例
2  3  4  5  6  7  8  9 10
 3  4  5  6  7  8  9 10 11
 4  5  6  7  8  9 10 11 12
 5  6  7  8  9 10 11 12 13
 6  7  8  9 10 11 12 13 14
 7  8  9 10 11 12 13 14 15
 8  9 10 11 12 13 14 15 16
 9 10 11 12 13 14 15 16 17
10 11 12 13 14 15 16 17 18
添字と実際の数の違い

配列の添字は0から8までですが、足し算に使う数は1から9までです。

そのため、計算するときはrowとcolへ1を加えています。

addition_table[row][col] =
    (row + 1) + (col + 1);

対応関係は次のとおりです。

添字足し算で使う数
01
12
23
34
45
56
67
78
89

表示では%2dを使っています。

printf("%2d ", addition_table[row][col]);

%2dは、整数を最低2文字分の幅で表示します。1桁と2桁の数が混ざっても、列をそろえやすくなります。

行ごとの平均から評価を決める

2次元配列の各行について合計と平均を求め、平均値をif~else if~elseで評価できます。

処理の流れは次のとおりです。

  1. 外側のfor文で1人を選ぶ
  2. sumを0で初期化する
  3. 内側のfor文で1人分の値を合計する
  4. 平均を計算する
  5. 平均に応じて評価を決める
  6. 結果を表示する
5人分の処理時間から平均と評価を求めるプログラム

ファイル名:10_10_3.c

#include <stdio.h>

#define OPERATOR_COUNT 5
#define TASK_COUNT 4

int main(void)
{
    int processing_times[OPERATOR_COUNT][TASK_COUNT] = {
        {7, 8, 7, 8},
        {9, 8, 9, 8},
        {6, 7, 6, 7},
        {5, 5, 6, 5},
        {8, 8, 7, 9}
    };

    printf("番号  作業1 作業2 作業3 作業4 平均  評価\n");

    for (int operator = 0;
         operator < OPERATOR_COUNT;
         operator++) {

        int sum = 0;

        for (int task = 0; task < TASK_COUNT; task++) {
            sum += processing_times[operator][task];
        }

        double average = (double)sum / TASK_COUNT;
        char grade;

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

        printf("%d      %d     %d     %d     %d    %.2f   %c\n",
               operator + 1,
               processing_times[operator][0],
               processing_times[operator][1],
               processing_times[operator][2],
               processing_times[operator][3],
               average,
               grade);
    }

    return 0;
}
実行結果の例
番号  作業1 作業2 作業3 作業4 平均  評価
1      7     8     7     8    7.50   B
2      9     8     9     8    8.50   A
3      6     7     6     7    6.50   C
4      5     5     6     5    5.25   D
5      8     8     7     9    8.00   A
sumを外側のループ内で初期化する理由

sumは、1人分の処理を始めるたびに0へ戻す必要があります。

int sum = 0;

この宣言を外側のループより前へ置くと、前の人の合計が次の人へ引き継がれてしまいます。

operator計算する合計
01人目の4項目だけ
12人目の4項目だけ
23人目の4項目だけ
34人目の4項目だけ
45人目の4項目だけ

各行を独立して集計するため、sumは外側のループが1回始まるたびに0で作り直します。

平均をdouble型で計算する理由

整数同士で割り算すると、小数部分が切り捨てられます。

double average = (double)sum / TASK_COUNT;

sumをdouble型へ変換することで、小数を含む平均値を計算できます。

評価条件は大きい基準から並べる

評価は、条件の厳しい順に判定します。

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

if~else if~elseは、最初に真になった処理だけを実行します。そのため、8以上、7以上、6以上というように、大きい基準から並べることが大切です。

図3:行ごとの平均を条件で評価する流れ

この図から分かること

1行分の値を合計して平均を求め、その平均をif~else if~elseで判定します。

合計用のsumは行ごとに0へ戻します。また、評価条件を大きい値から並べることで、平均7.50は正しくBとして判定されます。

合計を比較して順位を求める

順位は、自分より大きい合計値を持つ人が何人いるかを数えることで求められます。

最初に順位を1位と仮定します。

rank[i] = 1;

自分より合計が大きい人を見つけるたびに、順位を1増やします。

if (total[j] > total[i]) {
    rank[i]++;
}
自分より大きい合計の人数順位
0人1位
1人2位
2人3位
3人4位
4人5位
5店舗の発送数を集計して順位を求めるプログラム

ファイル名:10_10_4.c

#include <stdio.h>

#define STORE_COUNT 5
#define WEEK_COUNT 4

int main(void)
{
    int shipments[STORE_COUNT][WEEK_COUNT] = {
        {22, 26, 24, 25},
        {31, 28, 30, 29},
        {18, 20, 19, 21},
        {27, 33, 32, 30},
        {24, 23, 26, 25}
    };

    int totals[STORE_COUNT];
    int ranks[STORE_COUNT];

    /* 店舗ごとの合計発送数を求める */
    for (int store = 0; store < STORE_COUNT; store++) {
        totals[store] = 0;

        for (int week = 0; week < WEEK_COUNT; week++) {
            totals[store] += shipments[store][week];
        }
    }

    /* 合計発送数を比較して順位を求める */
    for (int store = 0; store < STORE_COUNT; store++) {
        ranks[store] = 1;

        for (int other = 0; other < STORE_COUNT; other++) {
            if (totals[other] > totals[store]) {
                ranks[store]++;
            }
        }
    }

    printf("店舗  週1  週2  週3  週4  合計  順位\n");

    for (int store = 0; store < STORE_COUNT; store++) {
        printf("%d     %d   %d   %d   %d   %d    %d\n",
               store + 1,
               shipments[store][0],
               shipments[store][1],
               shipments[store][2],
               shipments[store][3],
               totals[store],
               ranks[store]);
    }

    return 0;
}
実行結果の例
店舗  週1  週2  週3  週4  合計  順位
1     22   26   24   25   97    4
2     31   28   30   29   118   2
3     18   20   19   21   78    5
4     27   33   32   30   122   1
5     24   23   26   25   98    3
処理は合計と順位の2段階

最初の二重ループでは、2次元配列の各行を合計します。

totals[store] += shipments[store][week];

次の二重ループでは、1次元配列totalsの値を互いに比較します。

if (totals[other] > totals[store]) {
    ranks[store]++;
}

店舗1の合計は97です。97より大きい合計は118、122、98の3つなので、順位は1 + 3で4位になります。

この方法では、同じ合計値がある場合、その店舗同士は同じ順位になります。

添字を変換して図形の向きを変える

2次元配列では、値だけでなく保存する位置を変える処理もできます。

変換前の要素をsrc[row][col]、変換後をdstとすると、代表的な位置変換は次のようになります。

変換代入先
右へ90度回転dst[col][SIZE - 1 - row]
左右反転dst[row][SIZE - 1 - col]
上下反転dst[SIZE - 1 - row][col]

左右反転では行を変えず、列の位置だけを反対側へ移します。

dst[row][SIZE - 1 - col] = src[row][col];

たとえばSIZEが6でcolが1なら、変換後の列は次のようになります。

6 - 1 - 1 = 4

元の列1にある要素が、反対側の列4へ移ります。

6行6列の図形を回転・反転するプログラム

ファイル名:10_10_5.c

#include <stdio.h>

#define SIZE 6

void print_grid(const int data[SIZE][SIZE]);

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

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

    printf("変換前の図形\n");
    print_grid(src);

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

    if (mode < 1 || mode > 3) {
        printf("変換モードは1から3で入力してください。\n");
        return 0;
    }

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

    printf("\n変換後の図形\n");
    print_grid(dst);

    return 0;
}

void print_grid(const int data[SIZE][SIZE])
{
    for (int row = 0; row < SIZE; row++) {
        for (int col = 0; col < SIZE; col++) {
            if (data[row][col] == 1) {
                printf("■ ");
            } else {
                printf("□ ");
            }
        }

        printf("\n");
    }
}
実行結果の例

左右反転を選んだ場合の例です。

変換前の図形
□ ■ □ □ □ □
□ ■ □ □ □ □
□ ■ □ □ □ □
□ ■ □ □ □ □
□ ■ ■ ■ ■ □
□ □ □ □ □ □

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

変換後の図形
□ □ □ □ ■ □
□ □ □ □ ■ □
□ □ □ □ ■ □
□ □ □ □ ■ □
□ ■ ■ ■ ■ □
□ □ □ □ □ □
変換モードを先に確認する理由

dstは、二重ループの中で全要素へ値を代入します。ただし、正しいモードが選ばれていなければ、どの代入式も実行されず、dstの要素が不定値のままになる可能性があります。

そのため、二重ループへ入る前にmodeを確認しています。

if (mode < 1 || mode > 3) {
    printf("変換モードは1から3で入力してください。\n");
    return 0;
}

正しいモードだけを受け付けることで、dstのすべての要素へ確実に値を代入できます。

右へ90度回転する場合
dst[col][SIZE - 1 - row] = src[row][col];

元の列が変換後の行になり、元の行は反対側の列へ移ります。

左右反転する場合
dst[row][SIZE - 1 - col] = src[row][col];

行は変えず、列だけを反対側へ移します。

上下反転する場合
dst[SIZE - 1 - row][col] = src[row][col];

列は変えず、行だけを反対側へ移します。

2次元配列の条件処理を読むポイント

確認すること内容
行と列の意味最初の添字と2番目の添字が何を表すか
二重ループの範囲行数と列数に合っているか
if文の条件何を見つけたいのか
更新する変数カウント、最大値、最小値、合計など
初期値カウントは0、最大・最小は先頭要素
行ごとの集計合計を行ごとに0へ戻しているか
平均計算doubleへ型変換しているか
順位計算自分より大きい値を数えているか
位置変換元の行・列が移動先のどこになるか

2次元配列の条件処理は、基本的には全要素を1つずつ取り出し、その場で条件を判定する処理です。

まず外側のループがどの行を選んでいるかを確認し、次に内側のループがどの列を進んでいるかを見ます。そのうえでif文の条件と更新先を確認すると、カウント、最大・最小、評価、順位、位置変換といった処理の目的を読み取りやすくなります。