作曲・指導・C言語・Linux

金沢音楽制作

金沢音楽制作では、楽曲・楽譜の制作と、作曲や写譜などレッスンを行っています。


173)ケイプロをやってみた

ケイプロ必要/不要論」という記事を以前書いた。しかし、ケイプロをやったこともないのに色々と書いたのはどうかと思ったので実際にやってみた。

結論から述べると、ケイプロは、初心者レベルの問題でもじっくりと取り組むことで、総合的なプログラミング力が鍛えられるものだと感じた。総合的な、とは設計やユーザーインターフェース、また後述するように、公開にあたっての行番号やシンタックスハイライトの作成などである。

今回は、初心者向けの問題である「No.289 数字を全て足そう」を解いてみた。この問題は、1〜10000字のランダムに入力された文字列の中から数字を探し、その数字の総和を求めるものだ。たとえば、「jfi4IE5r_ *9」という文字列ならば、4と5と9を抜き出し、それぞれを足した数の「18」を出力する。

実際に書いたコードを紹介していきたいが、まずは実行結果を示したい。というのも、このプログラムを実行するには、文字列を与える必要があるからだ。本来はケイプロサイトのサーバー側で与えられるものである。今回、二つの手法を導入した。一つは、コマンドライン引数に文字列を入力するもの。もう一つは、自動で1〜10000字のランダムな文字列を入力させるものである。なお、文字列と文字列長の表示はコンパイルオプションとした。

$ ./n289 fjsy849p35ht hg h fha 289  -109+r8432h45
Total: 84
$ ./n289
dmc4rj3254qd8dxqdarjr ...(omitted)... j5pp9l8yl46vhpke2lj7i551mv
Length: 5188
Total : 6339

ではコードを示す。このプログラムは目的が不明で実用性に欠けているので、実用的に改良したものも後述する。

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

// prototypes
static void calculation(int arrayLength, const char *stringArray[]);
static int getTotalNumdigit(const char *p);
static const char *createEvaluationString(void);
static const int getStringLength(const char *p);
static void disposeEvaluationString(const char *bp);

int main(int argc, char **argv)
{
  const char *evalString;

  if (argc == 1) {
    evalString = createEvaluationString();
    calculation(1, &evalString);
    disposeEvaluationString(evalString);
  } else {
    calculation(--argc, (const char**)++argv);
  }

  return 0;
}

// 複数文字列の対応
static void calculation(int arrayLength, const char *stringArray[])
{
  int result = 0;
  int i;

  for (i = 0; i < arrayLength; ++i) {
    result += getTotalNumdigit(stringArray[i]);
  }
  printf("Total: %d\n", result);
}

// 数値totalの計算
static int getTotalNumdigit(const char *p)
{
  int total = 0;

  while (*p != '\0') {
    if (*p >= '0' && *p <= '9') {
      total += (*p - '0');
    }
    ++p;
  }

  return total;
}

// 評価対象の文字列生成
static const char *createEvaluationString(void)
{
  const int MaxEvaluationLength = 10000;
  const char array[] = "0123456789abcdefghijklmnopqrstuvwxyz";
  const int size = getStringLength(array);
  char *target;
  int targetLength;
  int i;

  srand((unsigned)time(NULL));
  targetLength = rand() % MaxEvaluationLength + 1;
  target = malloc(targetLength + 1);  // 領域確保

  for (i = 0; i < targetLength; i++) {  // ランダムにchar設定
    target[i] = array[rand() % size];
  }
  target[i] = '\0';

#ifdef DEBUG
puts(target);
printf("Length: %d\n", targetLength);
#endif

  return target;
}

// 文字列の長さを取得 
static const int getStringLength(const char *p)
{
  int count = 0;

  while (*p++ != '\0') {
    ++count;
  }

  return count;
}

// 評価対象文字列の破棄
static void disposeEvaluationString(const char *bp)
{
  free((char *)bp);
}

さらに発展型として、ファイルを読み込んで、それぞれの総和と合計を出力するようにした(こちらの方が実用的だと思う)。

$ ./n289_kai a.dat b.dat
a.dat: 112
b.dat: 80
Total: 192
#include <stdio.h>

// prototypes
static int getTotalNumdigit(FILE *fp);

int main(int argc, char **argv)
{
  int total = 0;
  int i;

  if (argc == 1) {
    printf("%d\n", getTotalNumdigit(stdin));
  }

  for (i = 1; i < argc; ++i) {
    FILE *fp;
    int tmp;

    if ((fp = fopen(argv[i], "r")) == NULL) {
      fprintf(stderr, "File open error %s\n", argv[i]);
      continue;
    }
    total += tmp = getTotalNumdigit(fp);
    printf("%s: %d\n", argv[i], tmp);
  }

  printf("Total: %d\n", total);

  return 0;
}


static int getTotalNumdigit(FILE *fp)
{
  int subTotal = 0;
  int c;

  while ((c = fgetc(fp)) != EOF) {
    if (c >= '0' && c <= '9') {
      subTotal += c - '0';
    }
  }

  return subTotal;
}

これらのコード作成にかかった正確な時間は覚えていないが、かなりの時間を使った。以前、ケイプロは和声課題と異なるものだ、と述べたが、完璧な答えがなく、また時間をかければそれだけ成果となって返ってくる、という点は類似しているだろう。だが、もし結果さえ出力できればよい、という思想で完成を急いだ実施をするならば、得られるものは少ないのではないか。プログラミング上級者でもない限り、「解けたはい終わり」は避けたほうが賢明だろう。しっかりと吟味して初めて血肉になるものだからだ。

今回、このコードを公開するにあたって、行番号とシンタックスハイライトを付与するスクリプトを書いた。JavaScriptも正規表現もあまり分からないので、かなり強引なものになってしまったがいい経験になった。ケイプロにはこのような副次的な効果も期待できるだろう。

2021-07-03