サイズ不定のCSVデータを読み込むCプログラムの例

カンマで区切られた実数値のCSVデータを読み込むC言語プログラムの例。

行数と列数が最初から定数として与えられたプログラム例はよく見かけますが、以下に紹介するプログラム例は、サイズが不定のデータに対応します。

readCSVData関数の概要

指定したcsvファイルを読み込み、各要素をdouble型の配列に格納します。また、もとのCSVデータの要素数、行数(=改行の個数)、列数(=各行に対する要素数の最大値)を取得します。

readCSVData関数の使用例は以下のとおりです:

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

void readCSVData(
  double** pArray,      /* CSVデータ格納用配列 */
  int* pElems,          /* CSVデータの要素数 */
  int* pRows,           /* CSVデータの行数 */
  int* pCols,           /* CSVデータの列数 */
  const char* filename  /* csvファイルの名前 */
);
char* readTextFile(const char* filename);

int main(int argc, char* argv[])
{
  const char *filename = "data.csv";
  double *data = NULL; 
  int elems = 0;  /* 全要素数 */
  int rows = 0;   /* 行の数 */
  int cols = 0;   /* 列の数 */
  int i, j;

  /* 引数からcsvファイルの名前を取得 */
  if (argc >= 2) {
    filename = argv[1];
  }

  /* CSVデータの読込 */
  readCSVData(&data, &elems, &rows, &cols, filename);

  printf("\n");
  printf("filename = %s\n\n", filename);
  /* data[i*cols+j] = i行j列の成分 */
  for (i = 0; i < rows; i++) {
    for (j = 0; j < cols; j++) {
      if (j == cols - 1)
        printf("%.3f\n", data[i*cols+j]);
      else
        printf("%.3f, ", data[i*cols+j]);
    }
  }
  printf("\n");
  printf("要素数 = %d\n", elems);
  printf("行数 = %d\n", rows);
  printf("列数 = %d\n", cols);

  /* メモリを動的に確保するので解放が必要 */
  free(data); data = NULL;

  return 0;
}

void readCSVData(
  double** pArray,      /* CSVデータ格納用配列 */
  int* pElems,          /* CSVデータの要素数 */
  int* pRows,           /* CSVデータの行数 */
  int* pCols,           /* CSVデータの列数 */
  const char* filename  /* csvファイルの名前 */
) {
  /* 後述 */
}

char* readTextFile(const char* filename) 
{
  /* 以前の記事を参照 */
}

上記のプログラムをコンパイルしてできた実行ファイルを実行する時、csvファイルの名前を引数に指定します。引数を省略すると、"data.csv"という名前が指定されます。

readCSVData関数の実装例

void readCSVData(
  double** pArray,      /* CSVデータ格納用配列 */
  int* pElems,          /* CSVデータの要素数 */
  int* pRows,           /* CSVデータの行数 */
  int* pCols,           /* CSVデータの列数 */
  const char* filename  /* csvファイルの名前 */
) {
  char *str = NULL;
  char elem[15];     /* 要素を文字列として格納 */
  char *ep = NULL;
  int countNL = 0;   /* 改行の数をカウント */
  int countSep = 0;  /* カンマの数をカウント */
  int cols = 0;      /* 各行の列数をカウント */
  int i, j, k;
  double val;

  /* テキストファイルの読み込み */
  str = readTextFile(filename);

  /* 要素数, 行数, 列数の取得 */
  for (i = 0; i < strlen(str); i++) {
    switch (str[i]) {
      case ',': countSep++; cols++; break;
      case '\n': 
        countNL++; cols++;
        *pCols = (*pCols > cols) ? *pCols : cols;
        cols = 0;
        break;
    }
  }
  *pRows = countNL;
  *pElems = countSep + countNL; 

  /* CSVデータ格納用配列の生成 */
  *pArray = (double *)calloc(
    (*pRows) * (*pCols) * sizeof(double), sizeof(double));
  if (*pArray == NULL) {
    printf("can't allocate memory. '*pArray' is NULL. \n");
    free(str); str = NULL;
    exit(1);
  }

  /* CSVデータを格納用配列へコピー */
  i = j = 0; 
  k = 0; elem[0] = '0'; elem[1] = '\0';
  while(j < (*pRows) * (*pCols)) {
    if (i < strlen(str)) {
      switch (str[i]) {
        case ',': case '\n':
          val = strtod(elem, &ep);
          if(*ep != '\0') {
            printf("Warning (%d, %d): "
              "Conversion may be incorrect. \n", 
              j/(*pCols), j%(*pCols));
          }
          (*pArray)[j] = val; j++; 
          if (str[i] == '\n') {
            while(j % (*pCols) > 0) {
              (*pArray)[j] = 0.0; j++;
            }
          }
          k = 0; elem[0] = '0'; elem[1] = '\0';
          break;
        default:
          if (k+1 < sizeof(elem)) {
            elem[k] = str[i]; elem[k+1] = '\0'; k++;
          } 
          else if (k+1 == sizeof(elem)) {
            printf("Warning (%d, %d): "
              "Too many digits. \n", 
              j/(*pCols), j%(*pCols));
            k++;
          }
          break;
      }
      i++;
    }
    else {
      (*pArray)[j] = 0.0; j++;
    }
  }

  /* メモリを動的に確保するので解放が必要 */
  free(str); str = NULL;

  return;
}

readTextFile関数でテキストファイルとして読み込み、その内容の文字列を一字ずつ解析していきます。

  1. カンマあるいは改行が現れるまで、文字を次々と配列elemに格納する
  2. カンマあるいは改行が現れたら、elemに格納された文字列を数値に変換して配列*pArrayに格納する

というのが基本的な手順です。

文字列から数値への変換にはstrtod関数を使用しています。

readTextFile関数の実装例については、以下の記事をお読みください:

サイズ不定のテキストファイルを読み込むCプログラムの例

実行結果

例えば、次のような内容のcsvファイル(ファイル名は「data.csv」とする):

-2.978,2.873,2.929,-2.952,3.055,3.256,3.303
3.071,2.951,3.004,3.009,3.151,3.367,3.475
2.863,2.765,2.869,2.872,2.905,3.047,3.214
3.315,3.221,-3.274,3.47,3.596,3.635,3.414
3.436,3.358,3.33,3.562,3.741,3.694,3.56

に対してプログラムを実行すると、以下のような結果が表示されます:

filename = data.csv

-2.978, 2.873, 2.929, -2.952, 3.055, 3.256, 3.303
3.071, 2.951, 3.004, 3.009, 3.151, 3.367, 3.475
2.863, 2.765, 2.869, 2.872, 2.905, 3.047, 3.214
3.315, 3.221, -3.274, 3.470, 3.596, 3.635, 3.414
3.436, 3.358, 3.330, 3.562, 3.741, 3.694, 3.560

要素数 = 35
行数 = 5
列数 = 7

次のような「不完全な」内容のcsvファイル(ファイル名は「data2.csv」とする):

,2.873,2.929,3.055,3.256,3.303
3.071,2.951,3.004,,3.151,-3.367,3.475
2.863,2.765,2.869,,,3.047,3.214
3.315,3.221,3.274,3.47,-3.596,3.635,3.414,2.952
3.436,3.358,3.33,3.562,3.741,3.694,

に対してプログラムを実行すると、次のようになります:

filename = data2.csv

0.000, 2.873, 2.929, 3.055, 3.256, 3.303, 0.000, 0.000
3.071, 2.951, 3.004, 0.000, 3.151, -3.367, 3.475, 0.000
2.863, 2.765, 2.869, 0.000, 0.000, 3.047, 3.214, 0.000
3.315, 3.221, 3.274, 3.470, -3.596, 3.635, 3.414, 2.952
3.436, 3.358, 3.330, 3.562, 3.741, 3.694, 0.000, 0.000

要素数 = 35
行数 = 5
列数 = 8

(要素数)≠(行数)×(列数)なのはバグではありません。要素数は元データのものであり、列数は元データにおける一行あたりの要素数の最大値です。空のセルがある(カンマが連続する)ときや、データの形が長方形でない(各行に対して列数が一定でない)ときには、0.0を適当に補完します。

【theme : プログラミング
【genre : コンピュータ

プロフィール

よしいず

Author:よしいず
MATHEMATICS.PDFというウェブサイトを運営しています。

管理の都合上、トラックバックとコメントはオフにしてあります。ブログ経験者なら分かっていただけると思いますが、スパム(アダルトやその他の宣伝)ばかりなのが現実です。

リンクは自由です。当サイトの記事に対する間違いの指摘・意見・感想などを述べた記事からのリンクは歓迎です。ただし、ブログ記事アップ直後はミスが多く、頻繁に修正します。場合によっては削除する可能性もあります。その際、何も断りもなく修正・削除しますがご了承ください。内容を参考にする場合には投稿後一週間ほど様子を見てからにしてください(笑)。

記事の間違いを指摘するときは、その具体的箇所、理由(仕様に反するなど)・根拠(参考にした文献など)、代替案(同じ結果を得るための正しいやり方)も教えてください。そうしないと、(指摘される側および第三者はその時点では無知の状態なので、)どこが間違いなのか分かりませんし、本当に間違っているのかどうかが判断・検証できません。実際、間違いだと指摘されたことが結局は正しかったというケースもありますので。

このブログのタイトル一覧

リンク
月別アーカイブ
カテゴリ
最新記事
検索フォーム
RSSリンクの表示