Sibainu Relax Room

愛犬の柴犬とともに過ごす部屋

C言語でソート 1

あけましておめでとうございます。今年も1年元気で過ごすぞ。ときりっとした顔で言っているようです。

概要

C言語でソートに挑戦することにしました。

成し遂げられるのか分かりませんがとにかくやってみます。

CSVファイルの読み込みを確かめる。

読み込んだデータを配列(構造体)にセットする。

ソートは次回とします。

C言語 マニュアル

私が今回参考にしたC言語マニュアルです。

いずれも非常に分かりやすい文書と例文で書かれています。

ありがとうございました。

https://manpages.ubuntu.com/manpages/kinetic/ja/man3/
https://programming-place.net/ppp/index.html
https://www.ibm.com/docs/ja/i

ファイルを読み込み後、ファイルの作成

とっかかりのコードです。

一文字毎に取り出しファイルに書き込むということをしています。

本来は、1字毎に読み取ってバイト数の判定して処理するのでしょうが、文字の扱いが難しいそうです。

ちょっと大変そうなので別の方法を模索します。

copy

#include <stdio.h>
#include <wchar.h>
int main()
{
  FILE *fin;
  FILE *fout;
  wint_t wc;
  fin = fopen("./input.csv", "r");
  fout = fopen("./output.csv", "w");
  while ((wc = fgetwc(fin)) != WEOF){
    wprintf(L"%ls\n", wc);
    fputwc(wc, fout);
  }
  fclose(fin);
  fclose(fout);
  printf("File has been created...\n");
  return 0;
}

上のコードの実行結果です。

バイト単位で表示しているようです。

次に取り組んだのが、一行毎にバッファに取り込みセパレート文字で分離するという方法です。

試行錯誤して落ち着いたのが次のコードです。

こちらの方が何とか進めれそうなのでこれで行くことにします。

copy

#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <locale.h>

int main()
{
  FILE *fin;
  FILE *fout;
  wchar_t *wc;

  setlocale(LC_CTYPE, "");

  fin = fopen("./input.csv", "r");
  fout = fopen("./output.csv", "w");

  const wchar_t *const separator = L",";

  for (;;)
  {
    wchar_t str[90];
    if (fgetws(str, sizeof(str) / sizeof(str[0]), fin) == NULL)
    {
      if (feof(fin))
      {
        /* ファイルの終わり */
        break;
      }
      else
      {
        fputs("エラーが発生しました。\n", stderr);
        exit(EXIT_FAILURE);
      }
    }

    wchar_t *token = wcstok(str, separator);
    while (token != NULL)
    {
      wprintf(L"%ls\n", token);
      token = wcstok(NULL, separator);
    }

    fwprintf(fout, L"%ls", str);
  }

  fclose(fin);
  fclose(fout);

  printf("\nFile has been created...\n");
  
  return 0;
}

上のコード実行結果です。

セパレータで綺麗に分離されており、文字もきちんと表示しています。

wcstok カンマ区切りで分離

IBM i 資料の wcstok の解説の中にあるコードを手直ししてみました。

参考にしたC言語マニュアルにも wcstok の第三引数 &work があるのですが、私の使っているコンパイラーでは引数が多すぎますとエラーになります。

ですので、第三引数 &work をカットして使ってみましたが、今回の使い方で特に問題はないようです。

copy

#include <stdio.h>
#include <wchar.h>

int main(void)
{
  static wchar_t str1[] = L"?a??b,,,#c";
  static wchar_t str2[] = L"\t \t";
  wchar_t *t;
  /* IBM等 とは違うようです。
  /* wchar_t *ptr1, *ptr2;  */

  /* &work(&ptr1 &ptr2) をカットして順番を入れ替えました。 */
  t = wcstok(str1, L"?");     /* t points to the token L"a"  */
  wprintf(L"t = %ls\n", t);
  t = wcstok(NULL, L",");     /* t points to the token L"?b" */
  wprintf(L"t = %ls\n", t);
  t = wcstok(NULL, L"#,");    /* t points to the token L"c"  */
  wprintf(L"t = %ls\n", t);
  t = wcstok(str2, L" \t,");  /* t is a null pointer         */
  wprintf(L"t = %ls\n", t);
  t = wcstok(NULL, L"?");     /* t is a null pointer         */
  wprintf(L"t = %ls\n", t);
  return 0;
}

上のコードの実行結果です。

想定通りになっています。

2次元配列(構造体)に格納

変更前のトークン部分です。

    wchar_t *token = wcstok(str, separator);
    while (token != NULL)
    {
      wprintf(L"%ls\n", token);
      token = wcstok(NULL, separator);
    }

このようにしたいので、全体的に見直したコードが下のようになりました。

    wchar_t *token = wcstok(str, separator);
    while (token != NULL)
    {
      /* wprintf(L"%ls\n", token); */
      if (f == 0)
      {
        res = wcsncpy(record[count].f1, token, 20);
      }
      else
      {
        res = wcsncpy(record[count].f2, token, 30);
      }
      f += 1;
      token = wcstok(NULL, separator);
    }

copy

#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <locale.h>

int main()
{
  FILE *fin;
  FILE *fout;
  wchar_t *res;
  int f;

  setlocale(LC_CTYPE, "");

  fin = fopen("./input.csv", "r");
  fout = fopen("./output.csv", "w");

  const wchar_t *const separator = L",";

  struct records
  {
    char f1[40];
    char f2[50];
  };

  struct records record[1000];

  int count = 0;
  for (;;)
  {
    wchar_t str[90];
    f = 0;
    if (fgetws(str, sizeof(str) / sizeof(str[0]), fin) == NULL)
    {
      if (feof(fin))
      {
        /* ファイルの終わり */
        break;
      }
      else
      {
        fputs("エラーが発生しました。\n", stderr);
        exit(EXIT_FAILURE);
      }
    }

    wchar_t *token = wcstok(str, separator);
    while (token != NULL)
    {
      /* wprintf(L"%ls\n", token); */
      if (f == 0)
      {
        res = wcsncpy(record[count].f1, token, 20);
      }
      else
      {
        res = wcsncpy(record[count].f2, token, 30);
      }
      f += 1;
      token = wcstok(NULL, separator);
    }
    wprintf(L"%ls\n", record[count].f1);
    wprintf(L"%ls\n", record[count].f2);

    fwprintf(fout, L"%ls", str);
    count += 1;
  }

  fclose(fin);
  fclose(fout);

  printf("\nFile has been created...\n");
  
  return 0;
}

上のコードを実行してみました。

input.csv の文字コードが Shift_JIS ならば文字化けもなくコンソールに表示されます。

しかし、文字コードが UTF-8 ですと、次に示すように文字化けします。

3バイト文字をうまく処理する方法を考えなければなりませんが、今回はShift_JIS のファイル作成を目的としますので、UTF-8からShift_JIS のデコード、逆のエンコードを考えません。

100万件の読み込みができない

不覚でした。関数内で大きな配列を作ることは不適切のようです。

2000件でチェックしていたのですが、大きなデータにしたらメモリ不足で止まりました。

一時しのぎですが、次のように構造体の定義、作成をグローバルにしました。

copy

#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <locale.h>

/* ここから外に出した部分 */
#define N 1200000

struct records
{
  char f1[40];
  char f2[50];
};

struct records record[N];
/* ここまで */

int main()
{
  FILE *fin;
  FILE *fout;
  wchar_t *res;
  int f;

  setlocale(LC_CTYPE, "");

  fin = fopen("./input.csv", "r");
  fout = fopen("./output.csv", "w");

  const wchar_t *const separator = L",";

  int count = 0;
  for (;;)
  {
    wchar_t str[90];
    f = 0;
    if (fgetws(str, sizeof(str) / sizeof(str[0]), fin) == NULL)
    {
      if (feof(fin))
      {
        // ファイルの終わり
        break;
      }
      else
      {
        fputs("エラーが発生しました。\n", stderr);
        exit(EXIT_FAILURE);
      }
    }

    wchar_t *token = wcstok(str, separator);
    while (token != NULL)
    {
      if (f == 0)
      {
        res = wcsncpy(record[count].f1, token, 20);
      }
      else
      {
        res = wcsncpy(record[count].f2, token, 30);
      }
      f += 1;
      token = wcstok(NULL, separator);
    }
    /* wprintf(L"%ls\n", record[count].f1); */
    /* wprintf(L"%ls\n", record[count].f2); */

    fwprintf(fout, L"%ls\n", str);
    count += 1;
  }

  fclose(fin);
  fclose(fout);

  wprintf(L"%ls\n", record[count - 1].f1);
  wprintf(L"%ls\n", record[count - 1].f2);
  printf("\nFile has been created...%d\n", count);

  return 0;
}

上のコードを実行してみました。

時間にして1秒足らずに読み込みました。期待できそうです。

今日の投稿はここまでとします。