C言語基礎|配列を使った分布処理

配列で値を読み込めるようになって、最大・最小も取れるようになったら、次はもう一段レベルアップです。
それが 分布処理。つまり「どの範囲にデータが多い?」を集計して、見える化するやつです。

たとえば…

  • テスト点の 0~9 点が何人、10~19 点が何人…
  • 気温の 0~4℃ が何日、5~9℃ が何日…
  • 年齢の 20 代が何人、30 代が何人…

こういう “集計用の配列(カウンタ配列)” を作ると、配列の面白さが一気に広がります。

まず全体像:2種類の配列が登場する

分布処理では、たいてい次の2つを同時に使います。

配列役割
入力データの配列元データを保存するdata[i] に入力値
分布(カウント)の配列範囲ごとの件数を数えるhist[k] に件数

図でイメージするとこんな感じです。

「入力」と「集計」を分けると、コードも考え方もスッキリします。

配列の要素数を多めに確保して、一部だけ使う(num方式)

現場でもよくやる方法です。

  • 配列は最大サイズ MAX で確保しておく
  • 実際に使うのは先頭 num 個だけ
変数/マクロ意味
MAX配列に入れられる最大件数(上限)
num実際に入力した件数(実行中に決まる)

この方式のメリットは、「人数が変わっても配列のサイズ自体はいじらなくていい」こと。
入力制限(1~MAX)を入れておくと、配列範囲外アクセスも防げます。

サンプルプログラム

1日の気温(0~40℃) を複数日分入力して、5℃刻みの分布 を表示するプログラムを例に解説をします。

  • 入力件数:1~MAX_DAYS
  • 気温:0~40
  • 分布:0~4, 5~9, …, 35~39, 40(最後だけ単独)

プロジェクト名:chap5-7-1 ソースファイル名:chap5-7-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

// 気温を読み込んで分布(ヒストグラム)を表示
#include <stdio.h>

#define MAX_DAYS 60      // 入力日数の上限
#define MAX_TEMP 40      // 気温の最大値(0~40)
#define STEP 5           // 階級幅(5℃刻み)
#define BINS (MAX_TEMP / STEP + 1)  // 0..40 を 5刻み → 9個(最後に40用)

int main(void)
{
    int days;                 // 実際に入力する日数
    int temp[MAX_DAYS];       // 気温データ
    int hist[BINS] = {0};     // 分布(件数)

    printf("入力する日数を入れてください:");
    do {
        scanf("%d", &days);
        if (days < 1 || days > MAX_DAYS)
            printf("1~%dの範囲で入力してください:", MAX_DAYS);
    } while (days < 1 || days > MAX_DAYS);

    printf("%d日分の気温(0~40)を入力してください。\n", days);
    for (int i = 0; i < days; i++) {
        printf("%d日目:", i + 1);
        do {
            scanf("%d", &temp[i]);
            if (temp[i] < 0 || temp[i] > MAX_TEMP)
                printf("0~%dで入力してください:", MAX_TEMP);
        } while (temp[i] < 0 || temp[i] > MAX_TEMP);

        hist[temp[i] / STEP]++;   // 分布カウント
    }

    puts("\n--- 気温の分布 ---");
    printf("%d:", MAX_TEMP);
    for (int j = 0; j < hist[BINS - 1]; j++)
        putchar('*');
    putchar('\n');

    for (int k = BINS - 2; k >= 0; k--) {
        printf("%2d~%2d:", k * STEP, k * STEP + (STEP - 1));
        for (int j = 0; j < hist[k]; j++)
            putchar('*');
        putchar('\n');
    }

    return 0;
}

分布カウントの肝:hist[temp[i] / STEP]++

ここがこの節のいちばん気持ちいいポイントです。
整数 / 整数 の割り算は小数部が切り捨てられるので、範囲分けにピッタリ。

例(STEP が 5 のとき)

temp[i]temp[i] / 5増える要素どの範囲?
00hist[0]0~4
40hist[0]0~4
51hist[1]5~9
91hist[1]5~9
102hist[2]10~14
397hist[7]35~39
408hist[8]40(最後)

つまり「入力値を割るだけで、どの箱に入るかが決まる」わけです。

表示部分の考え方:分布配列を星で描く

分布配列 hist の中身は「件数」です。
件数回だけ * を出せば、横向き棒グラフになります。

やっていることコードの形
hist[k] 回だけ繰り返すfor (j = 0; j < hist[k]; j++)
1文字出力putchar('*');

この節で使う入力チェック(do-while)の意味

入力は「範囲外が来たら聞き直す」のが実用的です。
そこで do-while が活躍します。

do-while の書式

do {
    繰り返す処理
} while (条件);

何をする命令?
必ず1回は処理を実行し、そのあと条件が真なら繰り返します。
「入力→チェック→ダメなら再入力」という流れにピッタリです。

登場する命令・演算子の書式と役割まとめ

名前書式何をする?
#define 指令#define 名前 値翻訳時に定数へ置換し、上限や刻み幅を一元管理する。
for 文for (初期化; 条件; 更新) { }配列を走査したり、星を出したりする繰り返し。
do-while 文do { } while (条件);入力値が正しいまで再入力させる。
if 文if (条件) 文;範囲外のときメッセージを出す。
scanfscanf(書式, 格納先アドレス);キーボード入力を配列要素へ格納
putsputs(文字列);1行表示(改行付き)
putcharputchar(文字);1文字表示(グラフ用の*など)
/(整数除算)a / b小数部切り捨て、階級(bin)計算に使う。
++hist[k]++件数を1増やす。

分布処理の流れ

(1) days を入力(1~MAX)
        |
        v
(2) temp[i] を入力(0~MAX_TEMP)
        |
        v
(3) bin = temp[i] / STEP を計算
        |
        v
(4) hist[bin] を 1 増やす
        |
        v
(5) hist を使って棒グラフ表示

この流れを覚えると、題材が点数でも気温でも年齢でも、同じパターンで作れます。

演習問題

演習 5-6

配列に格納するデータ数と要素の値を読み込み、全要素をコンマとスペース区切りで { と } で囲んで表示せよ。
配列の要素数の上限はオブジェクト形式マクロで定義すること。

解答例

プロジェクト名:chap5-7-2 ソースファイル名:chap5-7-2.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

#define MAX 100

int main(void)
{
    int n, a[MAX];

    printf("データ数:");
    do {
        scanf("%d", &n);
        if (n < 1 || n > MAX)
            printf("1~%dで入力してください:", MAX);
    } while (n < 1 || n > MAX);

    for (int i = 0; i < n; i++) {
        printf("%d番:", i + 1);
        scanf("%d", &a[i]);
    }

    putchar('{');
    for (int i = 0; i < n; i++) {
        if (i > 0) printf(", ");
        printf("%d", a[i]);
    }
    puts("}");

    return 0;
}

演習5-7

分布グラフの表示を逆順(低い範囲から高い範囲)に表示するプログラムを作成せよ。

解答例(表示部分だけ)

puts("\n--- Distribution (low to high) ---");
for (int k = 0; k < BINS - 1; k++) {
    printf("%2d~%2d:", k * STEP, k * STEP + (STEP - 1));
    for (int j = 0; j < hist[k]; j++)
        putchar('*');
    putchar('\n');
}
printf("%d:", MAX_TEMP);
for (int j = 0; j < hist[BINS - 1]; j++)
    putchar('*');
putchar('\n');

演習5-8

分布グラフを縦方向に表示するプログラムを作成せよ。

解答例(考え方+簡易コード)
まず最大件数(棒の高さ)を求めて、その高さから下へ描きます。

int maxc = hist[0];
for (int k = 1; k < BINS; k++)
    if (hist[k] > maxc) maxc = hist[k];

for (int row = maxc; row >= 1; row--) {
    for (int k = 0; k < BINS; k++) {
        if (hist[k] >= row) printf("* ");
        else                printf("  ");
    }
    putchar('\n');
}

for (int k = 0; k < BINS; k++) printf("--");
putchar('\n');

for (int k = 0; k < BINS - 1; k++) printf("%d ", k * STEP);
printf("%d\n", MAX_TEMP);