並べ換え関数

配列に格納されたデータ

ある試験を受けた生徒の結果を処理する場合を考えよう。このような場合、 データを格納するには構造体を使って行くと良い。数学、英語、国語 の3科目を行なうのであれば、以下のような構造体定義にすると良いだろう。

struct student {
  char name[30];
  int  mathpt;
  int  engpt;
  int  jpnpt;
};

あるテストの全員の得点を記録したデータファイルが 以下のようになっているものとする。

score.txt

山田太郎	50	70	20
公益太郎	90	80	70
飯森花子	91	79	72
鶴岡一人	60	60	40
酒田三吉	52	70	80
三川一二三	12	34	99

これを、構造体変数に読み込んで行くプログラムは以下のようになる。 起動時にデータファイルをコマンドライン引数として与えられるように なっている。

readscore.c

#include <stdio.h>
#define NMAX	100

struct student {
  char name[30];
  int  mathpt;
  int  engpt;
  int  jpnpt;
};

int main(int argc, char *argv[])
{
  FILE *stream;
  char buf[500];
  struct student pt[NMAX+1];
  int i, n;
  void dispall(struct student[]);
  
  if (NULL == argv[1]) {
    stream = stdin;
  } else {
    stream = fopen(argv[1], "r");
    if (NULL == stream) {
      fprintf(stderr, "Cannot open data file [%s]\n", argv[1]);
      exit(1);
    }
  }
  i = 0;
  while (NULL != fgets(buf, sizeof buf, stream)) {
    if (i >= NMAX) {
      fprintf(stderr, "Reached to maximum number(%d)\n", NMAX);
      break;
    }
    n = sscanf(buf, "%29s %d %d %d",
               pt[i].name, &pt[i].mathpt, &pt[i].engpt, &pt[i].jpnpt);
    if (n == 4) {
      i++;
    }
  }
  if (stdin != stream)
    fclose(stream);
  pt[i].name[0] = 0;            /* 終端の印 */

  dispall(pt);
}

void dispall(struct student pt[])
{
  int i, sum;
  for (i=0; pt[i].name[0] != 0; i++) {
    sum = pt[i].mathpt+pt[i].engpt+pt[i].jpnpt;
    printf("%2d: %-20s, %d, %d, %d\n",
           i+1, pt[i].name, pt[i].mathpt, pt[i].engpt, pt[i].jpnpt);
  }
}

このプログラムでは、各生徒の氏名、数学得点、英語得点、国語得点 を含む構造体がいくつも集まった配列 pt[] にデータを格納している。 この配列に格納されているのは

pt[0]「山田太郎」のデータ
pt[1]「公益太郎」のデータ
pt[2]「飯森花子」のデータ
pt[3]「鶴岡一人」のデータ
pt[4]「酒田三吉」のデータ
pt[5]「三川一二三」のデータ

であり、これはデータの読み込み順になっている。これを並べ換えよう。

qsort()関数

qsort() 関数を使うと任意の型の配列に格納された データを昇順(小さい順)、または降順(大きい順)に並べ換えることができる。

qsort() 関数は、並べ換えたい配列を用意した後、 以下の書式で利用する。

#include <stdlib.h>
qsort(配列の先頭アドレス, 配列の要素数, 配列1要素のバイトサイズ,
      大小比較のための関数ポインタ);

大小比較のための関数 は、各要素へのポインタを引数として 受け取り、それぞれの大小比較の結果をintで返すように作る必要がある。 2つの引数を比べて、最初のものが大きかったら「正の値」、 等しかったらゼロ、最初のものが小さかったら「負の値」を返すようにする。 もしintどうしの比較なら

int hikaku(const void *x, const void *y)
{
  int *a = (int*)x;
  int *b = (int*)y;
  return (*a - *b);
}

とする。比較関数は必ず2つの引数を受け取り、どちらも const void * 型で宣言するように書かねばならない。 const void * 型とは「どんな型の値が渡されるか分からない」 ときに宣言する型のことで、そのままでは実際の値を取り出せない。

  int *a = (int*)x;
  int *b = (int*)y;

の2行で、「xをint型のポインタだとみなしてaに 代入」し、「yをint型のポインタだとみなしてbに 代入」している。こうすることで *a, *b が比べるべき2つの intの値を表すようになる。

もっとも簡単な例として、int型配列に格納されたデータを 昇順に並べ換えるプログラムを見てみよう。

sort-int.c

#include <stdio.h>
#include <stdlib.h>             /* qsort()のために必要 */

int compare_int(const void *x, const void *y)
     /* 必ず2引数を const void* でもらう */
{
  int *a = (int*)x;             /* intへのポインタに変換 */
  int *b = (int*)y;
  return (*a - *b);
}

int main()
{
  int data[] = {10, 20, 3, 48, 7, 2};
  int n = sizeof data / sizeof (int);
  int i;
  qsort(data, n, sizeof (int), compare_int);
  for (i=0; i<n; i++) {
    printf("%d\n", data[i]);
  }
}

構造体配列の並べ換え

構造体の配列も、int型配列の並べ換えと同様の手順で 行なえば良い。変わるのは比較関数を作る部分だけである。 struct student の配列を並べ換える場合、比較関数は 以下のようにすれば良い。

int comp_point(const void *x, const void *y)
{
  struct student *a = (struct student *)x;
  struct student *b = (struct student *)y;
  /* a と b は構造体へのポインタ
   したがって、メンバへのアクセスは ピリオドでなく -> を使う */
  int sum_a, sum_b;
  sum_a = a->mathpt + a->engpt + a->jpnpt;
  sum_b = b->mathpt + b->engpt + b->jpnpt;
  /* 引算の順番に注意 */
  return sum_b - sum_a;
}

以上をまとめると、読み込んだ試験結果ファイルをもとに、 合計点の大きい順に並べ換えるプログラムは以下のようになる。

sort-struc.c

#include <stdio.h>
#include <stdlib.h>
#define NMAX	100

struct student {
  char name[30];
  int  mathpt;
  int  engpt;
  int  jpnpt;
};

int main(int argc, char *argv[])
{
  FILE *stream;
  char buf[500];
  struct student pt[NMAX+1];
  int i, n;
  /* プロトタイプ宣言 */
  void dispall(struct student[]);
  int comp_point(const void*, const void*);
  
  if (NULL == argv[1]) {
    stream = stdin;
  } else {
    stream = fopen(argv[1], "r");
    if (NULL == stream) {
      fprintf(stderr, "Cannot open data file [%s]\n", argv[1]);
      exit(1);
    }
  }
  i = 0;
  while (NULL != fgets(buf, sizeof buf, stream)) {
    if (i >= NMAX) {
      fprintf(stderr, "Reached to maximum number(%d)\n", NMAX);
      break;
    }
    n = sscanf(buf, "%29s %d %d %d",
               pt[i].name, &pt[i].mathpt, &pt[i].engpt, &pt[i].jpnpt);
    if (n == 4) {
      i++;
    }
  }
  if (stdin != stream)
    fclose(stream);
  pt[i].name[0] = 0;            /* 終端の印 */

  qsort(pt, i, sizeof pt[0], comp_point);
  dispall(pt);
}

void dispall(struct student pt[])
{
  int i, sum;
  for (i=0; pt[i].name[0] != 0; i++) {
    sum = pt[i].mathpt+pt[i].engpt+pt[i].jpnpt;
    printf("%2d: %-20s, %d, %d, %d | %3d\n",
           i+1, pt[i].name, pt[i].mathpt, pt[i].engpt, pt[i].jpnpt, sum);
  }
}

/* 比較関数 */
int comp_point(const void *x, const void *y)
{
  struct student *a = (struct student *)x;
  struct student *b = (struct student *)y;
  int sum_a, sum_b;
  sum_a = a->mathpt + a->engpt + a->jpnpt;
  sum_b = b->mathpt + b->engpt + b->jpnpt;
  return sum_b - sum_a;
}

目次