teratailの質問に回答しようとしたものがブログ記事並みに長大になったのでQiitaのブログ記事にしましたズコー
質問
teratail - C言語においてファイルから一列ずつ文字列を読み取り、特定の箇所を数値化する方法
回答
printfデバッグの手法を学んでください。あと、C言語の標準関数の使い方をすぐ探す方法についてもですね。これからそれを実演します。
まず、この文字列2014/1/1,0:00,128
について、128
を数字で取り出す方法ですね。
まず一番最初に思いつくのはscanf関数。scanf関数はユーザーの入力を受け付けます。そのバリエーションとしてfscanfとsscanfがあるのをご存知でしょうか。sscanfは文字列を入力とします。sscanfで強引に文字列をリード(パース、解釈)してみます。
# include <stdio.h>
int main() {
int a, b, c, d, e, f, g;
sscanf("2014/1/1,0:00,128",
"%d/%d/%d,%d,%d:%d,%d",
&a, &b, &c, &d, &e, &f, &g);
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
printf("d = %d\n", d);
printf("e = %d\n", e);
printf("f = %d\n", f);
printf("g = %d\n", g);
return 0;
}
実行結果
a = 2014
b = 1
c = 1
d = 0
e = 21911
f = -1907481120
g = 32767
最初の4つはうまくいっているようですが、後ろの方はうまく数字が拾えていないようです。どうしてでしょうか。scanfのマニュアルページをながめてもヒントになりそうなものはありませんでした。
/
か,
か:
が悪さをしているのでしょうか。3つのバージョンを試してみます。ちなみにscanf(scanf系の関数)の返り値も重要らしいのでこれもprintfしておきます。
# include <stdio.h>
int main() {
int a, b, c, d, e, f, g;
int retval;
retval = sscanf("2014/1/1/0/00/128",
"%d/%d/%d/%d/%d/%d/%d",
&a, &b, &c, &d, &e, &f, &g);
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
printf("d = %d\n", d);
printf("e = %d\n", e);
printf("f = %d\n", f);
printf("g = %d\n", g);
printf("retval = %d\n", retval);
return 0;
}
実行結果
a = 2014
b = 1
c = 1
d = 0
e = 0
f = 128
g = -2079744000
retval = 6
# include <stdio.h>
int main() {
int a, b, c, d, e, f, g;
int retval;
retval = sscanf("2014,1,1,0,00,128",
"%d,%d,%d,%d,%d,%d,%d",
&a, &b, &c, &d, &e, &f, &g);
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
printf("d = %d\n", d);
printf("e = %d\n", e);
printf("f = %d\n", f);
printf("g = %d\n", g);
printf("retval = %d\n", retval);
return 0;
}
実行結果
a = 2014
b = 1
c = 1
d = 0
e = 0
f = 128
g = 934701248
retval = 6
# include <stdio.h>
int main() {
int a, b, c, d, e, f, g;
int retval;
retval = sscanf("2014:1:1:0:00:128",
"%d:%d:%d:%d:%d:%d:%d",
&a, &b, &c, &d, &e, &f, &g);
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
printf("d = %d\n", d);
printf("e = %d\n", e);
printf("f = %d\n", f);
printf("g = %d\n", g);
printf("retval = %d\n", retval);
return 0;
}
実行結果
a = 2014
b = 1
c = 1
d = 0
e = 0
f = 128
g = -1325543248
retval = 6
おっと?ちょっとわざとくさくもありますが7個数字を拾うつもりが6個数字を拾っていたようですね。さっきのプログラムにそれを反映させます。
よく見てみると対象の文字列に合わせて書いてたつもりのフォーマットも微妙に間違ってたようなので直します。
# include <stdio.h>
int main() {
int a, b, c, d, e, f;
int retval;
retval = sscanf("2014/1/1,0:00,128",
"%d/%d/%d,%d:%d,%d",
&a, &b, &c, &d, &e, &f);
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
printf("d = %d\n", d);
printf("e = %d\n", e);
printf("f = %d\n", f);
printf("retval = %d\n", retval);
return 0;
}
実行結果
a = 2014
b = 1
c = 1
d = 0
e = 0
f = 128
retval = 6
変数fに目的の数字が拾えてるようですね。これをCSVファイルに応用できないでしょうか?例えばscanfをしまくる方法です。それでもいいのですが、一行拾ってsscanfに流していったほうが安全な気がします。Cで一行読み込む方法を検索するために「C言語 一行読み込む」で検索します。すると質問者さんが使っていたfgets関数がそれに該当することに気づきます。質問者さんが書いていたソースコードをコピペしてそれらしいコードにしてみましょう。
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# define MAX 100
int main(void)
{
char s[MAX];
int a, b, c, d, e, f, retval;
FILE *fp=fopen("year-2014.csv","r");
while(fgets(s,MAX, fp)!=NULL){
retval = sscanf(s, "%d/%d/%d,%d:%d,%d", &a, &b, &c, &d, &e, &f);
if (retval == 6) {
printf("f = %d, f+3 = %d\n", f, f + 3);
} else {
printf("この行は正しく読めませんでした。\n");
}
}
fclose(fp);
return 0;
}
実行結果
f = 128, f+3 = 131
f = 131, f+3 = 134
f = 255, f+3 = 258
f = 245, f+3 = 248
f = 239, f+3 = 242
f = 239, f+3 = 242
f = 247, f+3 = 250
f = 260, f+3 = 263
f = 271, f+3 = 274
この行は正しく読めませんでした。
このプログラムは概ね正しく動いているようです。
研究課題
さて、先ほどのプログラムでは一行だけ解釈できない行がありました。私がCSVの最後で改行したためです。そこで問題です。CSVの空白行を無視するためにはどうすればいいでしょうか?
[ヒント] 改行しただけならその行の長さは0になるはずです。文字列の長さを取得するCの標準関数はなんですか?