C言語関係掲示板

過去ログ

No.120.ファイル内の値の平均を求める


No.700

ファイル内の値の平均を求める
投稿者---Tomo(2001/12/16 01:30:25)


 いろいろ参考書やページを見ても分からなかったので、分かる方がございましたらお教え下さい。

ファイル内に

A00001 23
A00002 45
A00003 89
A00004 76
ー中略ー
A09999 67

 という番号と値があって、値の平均を求めるということなんですが、

構造体を用いて 番号(A〜)、人数(9999人)、値
の構造を作ってから全員の値を合計して人数で割ればいいと思うのですが、

A00001 23
の23という値を取り出す方法が分かりません。
fgetc で一文字ずつ読みこんでスペースのあとの数字を読むように?
するのでしょうか。

 私はC初心者で、自分で調べて分からなかったのでよろしくお願いします。




No.701

Re:ファイル内の値の平均を求める
投稿者---kikk(2001/12/16 02:57:11)


ども。


>A00001 23
>の23という値を取り出す方法が分かりません。
>fgetc で一文字ずつ読みこんでスペースのあとの数字を読むように?
>するのでしょうか。

そのようにしてもいいですが、きっとめんどうです。

このような処理をする場合、fgets()で1行読んで、sscanf()で値を取り込む
というのが常套手段でしょうか。

#defile BUFFER_SIZE 1024 /* これだけあれば1行分として十分でしょう(たぶん) */


char buf[BUFFER_SIZE];
int value,sum=0;

/* データが9999個で固定ならforでもいいかも */
while(fgets(buf,BUFFER_SIZE,fp)!=NULL) {
 if(sscanf(buf,"%*s%d",&value)<1) {
  /* フォーマットがおかしい */
  break; /* おかしいのでとりあえずbreak */
 }
 sum+=value;
}


入力ファイルのフォーマットのエラーチェックをまったくしなくていいの
なら、以下のような方法でもよいかと。

while (fscanf(fp,"%*s%d",&value)==1) {
 sum+=value;
}

こういう問題は、ファイル操作になれないうちは、とりあえずscanf()で
実装して、あとからfscanf()とかfgets()&sscanf()に書き換えるといいかも
しれません。それと、エラーチェックする必要があれば、fscanf()や
sscanf()の返り値が役に立ちます(ので、できるだけチェックしましょう)。


では。

No.704

Re:ファイル内の値の平均を求める
投稿者---B.Smith(2001/12/16 15:36:53)


解決方法は、kikkさんの解説された通り、fgetsで読み込み、scanf系関数で編集するのが良いと思います。
ここでは、今後の参考になるよう、ほんのちょっと踏み込んだお話しをしたいと思います。

● データが固定長である場合、構造体を直に使用することができます。
typedef struct  {
    char    Number[6];  /* 番号 */
    char    Space;
    char    Numeric[2]; /* 値 */
} RECORD;

この場合、ストリーム入出力であるならば関数freadを使用して1レコード分(1データ分)まとめて読み込むことができます。

実践的には、構造体のメンバNumericの後ろにヌルを入れる領域を用意しておきます。通常、ヌル文字が入っていない文字列を数値に変換する場合、別バッファにコピーして…という手段をとったりしますが、構造体内にあらかじめヌル値を設定する領域を取っておけば、数値に変換する時の手順を簡略化することができます(データ構造の拡充)。
ただし、この方法が有効なのは、今回のように、基本的なデータ構造は崩さず、かつ実データとは何の関係もない領域にヌル文字を設定できる場合だけです。
例1
typedef struct  {
    char    Number[6];  /* 番号 */
    char    Space;
    char    Numeric[2]; /* 値 */
    char    Terminate;  /* ヌル値…実データとは関係がありません */
} RECORD;
            ・
            ・
    RECORD      Rec;
    FILE        *fp;
    int         Summary;

    Summary = 0;

    /* NumericとTerminateはメモリ上連続なので、Numericを */
    /* 末尾ヌル付きの文字列として扱うことができる        */
    Rec.Terminate = 0;

    /* 実際のデータ内にはヌル分のデータは含まれないため、*/
    /* "-sizeof(char )"は重要です                        */
    while(fread(&Rec,sizeof(Rec ) - sizeof(char ),1,fp))
        Summary += atoi(Rec.Numeric);

プログラムの可読性、という見地からすれば、データの構造に手を加えることは、あまり良くないことなのですが、バッファ操作が減る分、速度効率が向上します。

● ファイル内のデータが区切り文字、例えば改行文字等で区切られている場合、fgets等で1レコード分読み込みます。読み込んだデータを構造体に当てはめても良いのですが、ここでは違った方法で処理してみます。

今回は、数値の前に空白文字が付いています。空白から後ろが数値と定められている場合、この決め事が、このデータにおける「文法」と見ることができます。「1レコードが改行文字で区切られている」「各データが空白文字で区切られている」という決め事があれば、各データ長は自由になります(可変長のデータ)。テキストファイルの構文解析等でよくあるパターンです。

1レコード分のデータを文字列バッファStrに読み込んであるとして、以下のような方法で各データ項目を切り出すことができます。
例2
    char            *tk;
    int             Summary;
    static char     *Separator = " \r\n";
              ・
              ・
    /* 最初のトークン・番号 */
    tk = strtok(Str,Separator);
    if (!tk){
        /* 文法エラー */
    }

    /*** 番号データの処理 ***/

    /* 2番目のトークン・数値 */
    tk = strtok(NULL,Separator);
    if (!tk){
        /* 文法エラー */
    }

    /*** 数値の加算 ***/
    Summary += atoi(tk);


ちょっと分かりにくいかもしれませんが、Separatorで指定されている文字は空白文字・改行文字です。
関数strtokは、区切り文字をヌル文字に置き換えますので、与えられたポインタを、何の加工もせずに使用することができます。




No.709

Re:ファイル内の値の平均を求める
投稿者---kikk(2001/12/17 03:38:16)


ども。


>
例1
typedef struct  {
    char    Number[6];  /* 番号 */
    char    Space;
    char    Numeric[2]; /* 値 */
    char    Terminate;  /* ヌル値…実データとは関係がありません */
} RECORD;
            ・
            ・
    RECORD      Rec;
    FILE        *fp;
    int         Summary;

    Summary = 0;

    /* NumericとTerminateはメモリ上連続なので、Numericを */
    /* 末尾ヌル付きの文字列として扱うことができる        */
    Rec.Terminate = 0;

    /* 実際のデータ内にはヌル分のデータは含まれないため、*/
    /* "-sizeof(char )"は重要です                        */
    while(fread(&Rec,sizeof(Rec ) - sizeof(char ),1,fp))
        Summary += atoi(Rec.Numeric);

たぶんケアレスミスだと思いますが。。

scanf()系のコードが書いてあったのでつられてしまったのかもしれません
が、改行文字のことが考慮されていないようです。whileのなかで改行文字分
だけポインタを進めなければならないと思います。


>プログラムの可読性、という見地からすれば、データの構造に手を加えることは、あまり良くないことなのですが、バッファ操作が減る分、速度効率が向上します。

上記のコードの限りでは、Recの使い方はbuf[]と同じようなものにとどまって
いるため、速度向上があるとすれば、scanf()系より高速な動作が期待できる
atoi()を対象のフィールドに直接適用しているというのが、より正確な理由
となると思います。

なお、(B.Smithさんはわかっていると思いますが)buf[]を利用した場合でも、
scanf()系(というかsscanf())を使わずに、atoi()で処理することも可能で、
atoi(buf+6+1)みたいにすればOKです。この場合、番号が6桁じゃなかったり
スペースが1個じゃなかった場合は破綻するのでご注意あれ。
# 破綻したかどうか検出できないのがatoi()の欠点ですね。。


結局、エラーチェックをどこまで考えるかというのがポイントになるのでしょう。
きっと。


ちなみに。問題をみたときに、瞬間的に
struct { char Number[6+1]; int Numeric; }
のような構造体の配列を考えたのですが、よく読んでみると、文章の限り
ではデータを格納しなくても解決するようだったので(平均を求める以外
のことをしたいとは書いてないので)、構造体は使いませんでした。


あと、細かいことですが、
・変数名 Summary(概略) -> Sum(合計)
(sumにsummaryの意味はあるけど、逆はない。手元の3つの辞書ともそうでした)
・「構文解析」->「字句解析」
のような気が。。まちがってたらすみません。。。


では。

No.710

Re:ファイル内の値の平均を求める
投稿者---B.Smith(2001/12/17 15:20:30)


こんにちは。

>scanf()系のコードが書いてあったのでつられてしまったのかもしれません
>が、改行文字のことが考慮されていないようです。whileのなかで改行文字分
>だけポインタを進めなければならないと思います。

申し訳ないです。説明に少し幅を持たせたかったため盛り込みませんでした。テキストファイルでも、固定長のデータだと改行文字を含まない場合がありますしね。
でも、聞く人が勘違いするような説明になってはいけませんね。これから気をつけます。

>上記のコードの限りでは、Recの使い方はbuf[]と同じようなものにとどまって
>いるため、速度向上があるとすれば、scanf()系より高速な動作が期待できる
>atoi()を対象のフィールドに直接適用しているというのが、より正確な理由
>となると思います。

ヌルを含まない文字列に文字列操作を適用する場合、連続した領域内にヌルを含むように加工しなければなりません。(前回の構造体を使用した場合)正当な方法では、以下のような操作になると思います。この手順が一切不要になる、ということでの説明でした。
    char    Num[6+1];

    Num[6] = 0;
    memcpy(Num,Rec.Number,6);
    Sum += atoi(Num);

scanf系の関数内でも、同じように文字列操作を行っていますので、「関数atoiを使用することによって速度が向上する」というのは、まったくその通りだと思います。

scanf系の関数は大変便利ではあるのですが、製品に組み込まれるPGは(特に制御系では)scanf系、printf系のような汎用性の高い関数の使用が禁じられる場合があります。
Tomoさんが学業として学ばれているのであれば、それほど気にする必要はありませんが、将来C言語でご飯を食べていくつもりであれば、scanf系の関数を(できればそれ以外の関数も)使用しなくでもこれらの操作ができるように、ご自分でいろいろ試されておくと良いと思います。また、この時得られた解決方法は後で必ず役に立ちますので、大切に保存しておくことをお勧めします。

>・変数名 Summary(概略) -> Sum(合計)
>(sumにsummaryの意味はあるけど、逆はない。手元の3つの辞書ともそうでした)
>・「構文解析」->「字句解析」
>のような気が。。

そうでしたね。Summaryは間違いです。
「構文解析」については、「字句」だと限定し過ぎのような気がしたので、(言葉として)「構文」としています。
あの方法は、かなり広い範囲で応用が利きますしね。



戻る


「初心者のためのポイント学習C言語」 Last modified:2002.01.11
Copyright(c) 2000-2002 TOMOJI All Rights Reserved