素ハフマン圧縮プログラム。

なんか、ハフマン符号化が少しわかった気がするぞ。あと二分木とか。プログラムを書いてようやく。実践を伴わない知識っていうのは宙に浮いてるようで、なんか居心地が悪い。実際に使ってみると、なんだか錨で固定したような気分になれるなあ。二分木に関しては「たぶんこんな感じのデータ構造だったっけ?」と適当に使ったから、スゲー間抜けなことしてるかも。というかそんなこと言い出したら全部そうか。

まず元データを読み込み、バイトごとの出現回数を数える。この出現回数表を書庫の先頭に記述。その回数からハフマン木を作成。ハフマン木により、1バイト->ビット列の変換表を作成。元データを頭から読み込み、先の変換表により書庫に追記。
というプログラムを作成。
以下ソース。

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

#define HCODEMAX 256

typedef struct{
  unsigned char c;
  int count;
} item;
struct node{
  item data;
  struct node *lbranch, *rbranch;
};

struct node** isort_count(struct node *table[], int length){
  int i, j;
  struct node *tmp;
  for(i=1;i<length;++i){
    tmp = table[i];
    for(j=i;j>0 && table[j-1]->data.count>tmp->data.count;--j)
      table[j] = table[j-1];
    table[j] = tmp;
  }
  return table;
}
struct node* mknode(struct node *lbranch, struct node *rbranch){
  struct node *n;
  n = (struct node *)malloc(sizeof(struct node));
  n->data.count = 0;
  n->lbranch = lbranch;
  n->rbranch = rbranch;
  return n;
}
struct node* mktree(struct node *table[], int length){
  struct node *root, *n;

  if(length == 1) return table[0];
  n = mknode(table[0], table[1]);
  n->data.count = table[0]->data.count + table[1]->data.count;
  table[1] = n;
  isort_count(&table[1], length-1);
  root = mktree(&table[1], length-1);

  return root;
}
int print_table(FILE *fp, struct node *table[]){
  int i;
  for(i=0;i<256;++i){
    if(table[i]->data.count > 0){
      fwrite(&(table[i]->data.c), 1, 1, fp);
      fwrite(&(table[i]->data.count), 4, 1, fp);
    }
  }
}
int mkhtable(char *htable[], struct node *root, char *precode){
  char lcode[HCODEMAX], rcode[HCODEMAX];
  strcpy(lcode, precode);
  strcpy(rcode, precode);

  if(root->lbranch != NULL)
    mkhtable(htable, root->lbranch, strcat(lcode, "0"));
  if(root->rbranch != NULL)
    mkhtable(htable, root->rbranch, strcat(rcode, "1"));
  if(root->lbranch == NULL && root->rbranch == NULL){
    htable[root->data.c] = (char *)malloc(sizeof(char)*HCODEMAX);
    strcpy(htable[root->data.c], precode);
  }

  return 1;
}
int putbits(char *bitstream, FILE *fp){
  static int length = 0;
  static unsigned char bits = 0;
  if(bitstream != NULL){
    while(*bitstream != '\0'){
      bits = (bits << 1) | (*bitstream - '0');
      ++length;
      if(length == 8){
        fwrite(&bits, 1, 1, fp);
        length = 0;
        bits = 0;
      }
      ++bitstream;
    }
  } else {
    if(length != 0){
      bits = bits << 8 - length;
      fwrite(&bits, 1, 1, fp);
      fwrite(&length, 1, 1, fp);
    }
  }
  return 1;
}
int write_archive(FILE *arch, FILE *plain, char *htable[]){
  int c;
  while((c = fgetc(plain)) != EOF) putbits(htable[c], arch);
  putbits(NULL, arch);
  return 1;
}
int compress(char *plainname){
  char archname[256];
  int i;
  FILE *plain, *arch;
  struct node *table[256], *root;
  char *htable[256];

  strcpy(archname, plainname);
  strcat(archname, ".huf");

  if((plain = fopen(plainname, "r")) == NULL){
    perror("cannot open plain");
    return -1;
  }
  if((arch = fopen(archname, "w")) == NULL){
    perror("cannot open arch");
    return -1;
  }

  for(i=0;i<256;++i){
    table[i] = mknode(NULL, NULL);
    table[i]->data.count = 0;
    table[i]->data.c = i;
  }
  while((i=fgetc(plain)) != EOF) ++table[i]->data.count;

  print_table(arch, table);

  isort_count(table, 256);
  for(i=0;table[i]->data.count == 0;++i);
  root = mktree(&table[i], 256-i);

  for(i=0;i<256;++i) htable[i] = NULL;
  mkhtable(htable, root, "");

  fseek(plain, 0, SEEK_SET);

  i = 0;
  fwrite(&i, 2, 1, arch);
  write_archive(arch, plain, htable);

  fclose(plain);
  fclose(arch);
  return 1;
}
int main(int argc, char *argv[]){
  if(argc>1)
    if(!compress(argv[argc-1]))
      puts("cannot compress");
  return 0;
}

test