C言語基礎|ファイルデータの集計

「ファイルを読むだけで終わらせない。集計して“意味のある結果”にしよう!」

ファイルからデータを読み取れるようになると、次にやりたくなるのが「まとめる」ことです。
たとえば、点数の平均、売上の合計、温度の最大・最小…などなど。読み取ったデータを集計できると、プログラムが一気に“役立つ道具”になります。

ここでは、テキストファイルに並んでいる複数行のデータを1行ずつ読み取り、合計や平均を出す流れをやさしく身につけます。ポイントはシンプルで、

  • fopen でファイルを開く
  • fscanf で1行分の項目を読む(成功した個数で判定)
  • ループしながら合計と件数を増やす
  • 最後に平均などを計算して表示
  • fclose で必ず閉じる

…この形が「ファイル集計の基本フォーム」になります。

今回のサンプルは「商品と価格の平均」を集計する

商品名(文字列)と価格(整数)をファイルから読み取り、一覧表示しつつ、最後に平均価格を出します。

サンプルデータ(ファイル内容)

実行プログラムと同じフォルダに、prices.txt という名前で次を保存してください。

Windows の Visual Studio が実行環境の場合、以下のパスに「prices.txt」を保存します。
C:\Users\<ユーザー名>\source\repos\chap13-4-1\chap13-4-1

prices.txt(例)

Apple 120
Banana 98
Bread 210
Milk 178
Coffee 450
Tea 220
Rice 680
Pasta 260
Cheese 520
Yogurt 150
Eggs 240
Butter 430
Chicken 890
Beef 1280
Fish 760
Tomato 140
Potato 160
Onion 110
Carrot 130
Lettuce 180
Cabbage 200
Orange 130
Grape 360
Strawberry 480
Chocolate 300
Cookie 250
Icecream 420
Juice 190
Water 100
Soda 160
Noodles 280
Soup 230
Cereal 410
Jam 270
Honey 650
Salt 90
Sugar 210
Flour 190
Oil 340
Vinegar 180
Ketchup 260
Mayonnaise 380
Tofu 120
Miso 260
SoySauce 320
Tuna 240
Salmon 980
Shrimp 880
Pizza 900

データのルール(超重要)

  • 1行につき「商品名」と「価格」の2項目
  • 区切りは空白(スペース)
  • 商品名は空白を含まない(Apple みたいに1単語)

図でつかむ:ファイル集計の流れ

図:読み取り→加算→平均

prices.txt
  ↓ fopen
ストリーム fp
  ↓ fscanfで2項目読む(商品名, 価格)
表示しながら
件数 count を +1
合計 sum を +価格
  ↓ EOFなどで読めなくなったら終了
平均 = sum / count を表示
  ↓ fclose

図の説明

  • “読めたら処理、読めなくなったら終了”の形が基本です。
  • EOF(ファイル終端)でも、途中の形式ミスでも「読めた項目数」が変化するので、戻り値チェックが効きます。

サンプルプログラム

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

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

#include <stdio.h>

int main(void)
{
    FILE* fp;

    /* ファイルを読み込みモードで開く */
    fp = fopen("prices.txt", "r");
    if (fp == NULL) {
        printf("Could not open the file. Please check if prices.txt exists.\n");
        return 0;
    }

    int count = 0;     /* 件数 */
    int price;         /* 価格 */
    long sum = 0;      /* 合計(安全のため long 型を使用) */
    char item[100];    /* 商品名 */

    /* 見出しを表示 */
    printf("Item Name     Price\n");
    printf("--------------------\n");

    /* ファイルから1行ずつ読み込む */
    while (fscanf(fp, "%99s%d", item, &price) == 2) {
        printf("%-12s %4d yen\n", item, price);
        count++;
        sum += price;
    }

    printf("--------------------\n");

    /* 平均価格を計算して表示 */
    if (count > 0) {
        double avg = (double)sum / count;
        printf("The average price is %.1f yen.\n", avg);
    }
    else {
        printf("No valid data was found for calculation.\n");
    }

    /* ファイルを閉じる */
    fclose(fp);

    return 0;
}

表で整理:このプログラムの変数は何をしている?

変数役割
fpFILE*ファイルに結び付いたストリームfopen の結果
itemchar配列商品名を受け取るApple
priceint価格を受け取る120
countint読み取った行数(件数)5
sumlong価格の合計1056
avgdouble平均(sum / count)211.2

表の説明

  • count と sum は「集計のための2大変数」です。
  • sum は合計が大きくなり得るので long にしています(安全側)。
  • avg は割り算で小数が出るので double を使います。

fscanf の役割と「戻り値が命」

ファイル集計で最重要なのが、fscanf の戻り値です。戻り値で「ちゃんと読めたか」を判断します。

fscanf の書式

項目内容
ヘッダ#include <stdio.h>
形式int fscanf(FILE *stream, const char *format, ...);
意味指定したストリームから format に従って読み取り、変数に格納する
返り値代入できた項目数(失敗が強いと EOF になることもある)

表の説明

  • scanf が stdin から読むのに対し、fscanf は stream(今回は fp)から読みます。
  • 返り値は「成功した変換の個数」です。

図で理解:while (fscanf(...) == 2) の意味

図:2項目読めたら1行成功

fscanf が返す値
 2 → 商品名と価格の2つが読めた(OK、続ける)
 1 → 片方しか読めない(形式が崩れた、終了)
 0 → 変換できない(終了)
EOF → 入力誤りで何も変換できない / 終端(終了)

図の説明

  • 今回は %s と %d の2項目を読むので、正しく読めたら 2 が返ります。
  • だから while 条件を == 2 にしておくと、「壊れた行」を踏んだ時点で止まってくれます。

なぜ item は %99s なの?

fscanf(fp, "%99s%d", item, &price) の %99s は安全対策です。

表:%s と文字配列の関係

書き方意味目的
%s空白までを文字列として読む商品名など
%99s最大99文字まで読む配列あふれ防止
item[100]100文字分の箱最後に終端文字も必要

表の説明

  • %s は長すぎる単語が来ると配列をはみ出しやすいので、読み取り幅を付けるのが定番です。
  • item[100] に対して %99s にしておくと、終端文字の分が確保できます。

集計の基本パターン(覚えやすい形)

ファイル集計はこの型を覚えると応用が楽です。

図:集計テンプレ

初期化(count=0, sum=0)
   ↓
while (1行読めたら)
    表示
    count++
    sum += 値
   ↓
if (count > 0)
    平均 = sum / count
表示

図の説明

  • count > 0 のチェックは必須級です。データが0件のときに割り算すると困ります。
  • このテンプレを「最大値」「最小値」「条件付き件数」などに広げていけます。

演習問題

演習13-3:読み込んだデータを価格順にソートして表示

prices.txt から読み込んだデータを、価格の安い順にソートして表示したあと、平均価格も表示するプログラムを作成せよ。

解答例(シンプルなバブルソート版)

読み込む件数は最大100件とします。

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

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

実行プログラムと同じフォルダに、prices.txt という名前で次を保存してください。

Windows の Visual Studio が実行環境の場合、以下のパスに「prices.txt」を保存します。
C:\Users\<ユーザー名>\source\repos\chap13-4-2\chap13-4-2

#include <stdio.h>
#include <string.h>

/* 商品名と価格をまとめる構造体 */
typedef struct {
    char item[100];  /* 商品名 */
    int  price;      /* 価格 */
} Record;

int main(void)
{
    /* ファイルを読み込みモードで開く */
    FILE *fp = fopen("prices.txt", "r");
    if (fp == NULL) {
        printf("Could not open the file. Please check prices.txt.\n");
        return 0;
    }

    Record data[100];  /* 最大100件のデータを格納 */
    int n = 0;         /* 読み込んだ件数 */
    long sum = 0;      /* 価格の合計 */

    /* ファイルからデータを読み込む */
    while (n < 100 && fscanf(fp, "%99s%d", data[n].item, &data[n].price) == 2) {
        sum += data[n].price;
        n++;
    }

    /* ファイルを閉じる */
    fclose(fp);

    /* 価格の安い順にソート(バブルソート) */
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (data[j].price > data[j + 1].price) {
                Record tmp = data[j];
                data[j] = data[j + 1];
                data[j + 1] = tmp;
            }
        }
    }

    /* 結果を表示 */
    printf("Sorted by Price (Lowest to Highest)\n");
    printf("Item Name     Price\n");
    printf("--------------------\n");

    for (int i = 0; i < n; i++) {
        printf("%-12s %4d yen\n", data[i].item, data[i].price);
    }

    printf("--------------------\n");

    /* 平均価格を計算して表示 */
    if (n > 0) {
        double avg = (double)sum / n;
        printf("The average price is %.1f yen.\n", avg);
    } else {
        printf("No valid data was found for calculation.\n");
    }

    return 0;
}

解説

  • まず fscanf で配列 data に読み込みます(n が件数)。
  • ソートは「価格」を比較して入れ替えるだけです。
  • 先に sum を作っておくと、ソート後でも平均がすぐ出せます。
  • 100件制限を入れているので、読みすぎ事故も防げます。