ぼくのかんがえた csvread(内容は double に限る)

Visual C++ 2008 Express Edition で確認した.

main() の data に CSV ファイルの内容が入る.後に示すコードでは,各列の平均値を画面表示している.行列数はそれぞれ決め打ち.コマンドライン引数で指定できるように拡張したらいいんじゃないの.

変数説明

変数名 説明
DATA_COLS 列数.表の横方向
DATA_ROWS 行数.表の縦方向
LINE_BUFFER_SIZE 読み込む CSV ファイルの,1行のバイト数.fgets() で使う
SEP_CAHR 区切り文字.タブなら '\t' となる.strchr() の仕様に合わせ,int にキャストした
argv[1] 読み込むファイル.( argc == 1 ) が偽の時は default_filename を読む

メモ

  • atof() に与えるポインタを変えていけばいいじゃないと考えた
    • SEP_CHAR を探し,その位置に '\0' と上書きするコードを書いたら,別のところに書き込まれる事態となった
      • atof() は '\0' まで判定をするわけじゃなかった.「数字でない文字まで」読み込むわけで,'\0' を書き込む必要が無かった
      • それならば fgets() 後の '\n' 削除も要らないなあ.なんとなく残している
  • 2次元配列の動的確保方法が分からなかった.「2次元配列 malloc」を検索して解決した
  • 読み込みファイルが想定外のフォーマットだと困る
    • ダブルクオートがあると困る
    • 行列の過不足は困る
      • realloc() すれば良いんだろうけれど,毎行やるのはなんか嫌だなあ
  • ファイル読み込み失敗したとき free( data ) すべきかも.やべえ
  • fclose( fp ) が抜けている.読み込み時は別にいいってどっかで見たんだけど,その理由は何だったっけなあ

コード

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <error.h>
#include <malloc.h>

const int DATA_COLS = 3;
const int DATA_ROWS = 52040;
const int LINE_BUFFER_SIZE = 128;
const int SEP_CHAR = (int)',';

void csvread(const char *filename, double **data, const int COLS, const int ROWS, const int SEP_CHAR)
{
	FILE *fp;
	errno_t err = fopen_s( &fp, filename, "r");
	if ( err == EINVAL )
	{
		puts("file open error");
		exit(0);
	}

	char line_buffer[LINE_BUFFER_SIZE];
	for(int row = 0;
		( (int)fgets( line_buffer, LINE_BUFFER_SIZE - 1, fp ) != EOF )
		&& row < ROWS;
		row++)
	{
		data[row] = (double *)malloc( sizeof( double ) * COLS );

		if ( strchr( line_buffer, '\n') != NULL )
		{
			line_buffer[strlen(line_buffer)-1] = '\0';
		}

		char *sep_char_pointer;
		char *data_pointer = line_buffer;
		for ( int col = 0; col < COLS; col++ )
		{
			data[row][col] = atof( data_pointer );
			sep_char_pointer = strchr( data_pointer, SEP_CHAR);
			data_pointer = sep_char_pointer + 1;
		}
	}
}

void csvfree(double **data, const int ROWS)
{
	for ( int row = 0; row < ROWS; row++ )
	{
		free( data[row] );
	}
}

int main(int argc, char **argv)
{
	char *default_filename = "c:\\you.csv";
	char *filename;
	if ( argc > 1 )
	{
		filename = argv[1];
	}else{
		filename = default_filename;
	}

	double **data;
	data = (double **)malloc( sizeof( double *) * DATA_ROWS );
	csvread(filename, data, DATA_COLS, DATA_ROWS, SEP_CHAR);

	double average[] = {0.0, 0.0, 0.0};
	for ( int row = 0; row < DATA_ROWS; row++)
	{
		for ( int col = 0; col < DATA_COLS; col++ )
		{
			average[col] += data[row][col];
		}
	}
	for ( int col = 0; col < DATA_COLS; col++ )
	{
		printf("%.10lf\n", average[col] / (double)DATA_ROWS );
	}
	
	csvfree(data, DATA_ROWS);
	free( data );

	return 0;
}