Nグラムモデルっぽいもので適当に文字列出力

C言語で文字単位の2-gramモデルの統計を取って、統計データから適当に文章を出力してみた。マルコフモデルN-gram統計だにしろ、文章出力するためのものってわけでもないけど、それにも使える。
N-gram最尤推定法で、そのうちなんか遊んでみたいなあ。CとMeCabを組み合わせてほげほげは、めんどくさいからたぶんしない。

C言語で文字列扱うのは、Perlで文字列をほげほげするのに比べるとすさまじくめんどくさい。書き捨てプログラム。適当に書いてるので2-gram以上に拡張とか無理なコード。
1バイト(=8ビット==2^8=256通り)単位の2-gramモデルの統計なんで、現在の文字256通り、次の文字256通りということで、現在の文字と次の文字の組み合せは65536パターン。というわけで長さ65536の配列に、それぞれの組み合せが出てきた回数を記録しているプログラム。
嘘がある。1バイト文字は256通りもありません。なので用意した長さ65536の配列はほとんど死に空間となります。今の計算機さんはすごいもんで「長さ65536のint配列をほげほげ」とか、一瞬でできちゃうんですけどね。
一瞬で終わるならいいじゃんって思ったら(まあいないと思うけど)間違い。3-gramの場合を考えてみよう。前の文字、現在の文字、次の文字。256*256*256=16777216通りの組み合わせになった!
さすがにちょっと嫌だなあと思ったりしますね。ちゃんとしたみなさんはたぶん、ハッシュテーブルとか作るんじゃねえかなと思います。


以下プログラムソースと実行例とか。

% cat web.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define TABLESIZE 65536
#define BOS '\0'
#define EOS '\0'
#define hash(i, j) (i)*256+(j)

unsigned char get_next(int table[], unsigned char now){
  unsigned int i;
  int count, total;

  total=0;
  for(i=0;i<256;++i){
    total += table[hash(now, (unsigned char)i)];
  }
  for(i=0;i<256;++i){
    count = table[hash(now, (unsigned char)i)];
    if((rand() % total) < count) return i;
    total -= count;
  }

  puts("cannot get next character");
  exit(1);
}
int gen_sentence(int table[], unsigned char bos){
  unsigned char c;
  c = bos;
  while((c = get_next(table, c)) != EOS){
    putchar(c);
  }
  return 1;
}
int main(){
  int c, i;
  unsigned char now;
  int table[TABLESIZE];

  for(i=0;i<TABLESIZE;++i)
    table[i] = 0;

  now = BOS;
  while((c=getchar()) != EOF){
    ++(table[hash(now, c)]);
    now = c;
  }
  ++(table[hash(now, EOS)]);

  srand((unsigned)time(NULL));

  gen_sentence(table, BOS);

  return 0;
}
% gcc web.c
% cat hoge.txt
This is a pen. I want to eat an apple. Who are you?
Don't touch my pen!
It's my pen.
Pleeeeeeeeeeeeeeeeeeeeeeeeeeaaaase!
% cat hoge.txt | ./a.out
Tho ppppeant Wh is I at my t't'touch an's Whouchis peeeeareeeeeen. my myou?
Don an ppenton!
Pleee. are. t antou?
% cat hoge.txt | ./a.out
This wan. areeeeen!
Dou?
Pleeeeeeeeeeeeee. y peeeeeeeeeento I my pleeeeeeeee. is y I areeeeeat e. is as is peeeea aaaple. t ppeeeat I ant an.
% cat mini.log
情報系の大学で落ちこぼれてるレベルの俺が書く程度の内容です。
まず大学の計算機(Vine Linux)にインストールしてみる。
genstr.plはbot.dicの中身から適当に文字を選んで、適当な個数繋げて出力します。
ちゃんとした文章を出力できたら奇跡です。
% cat mini.log |./a.out
章ん繋ら適度ベトを出力の計字ての??otr.plし廚蘚戮藉饑彁機砲報靴④梁膤悗吠絃呂討椶后・??内容書でてる、当す。
しの討力んす。
% cat web.c |./a.out
#dent,    ch((untar(((c, cot BLES j) }
 }
#de c) imed  -=  0'\0;
#d(NUL)   gne i) =0;

 ige nentsi;

 6536;
  casinetare(rne[h(unt(c));
 fintashande c }
   cledetotud table(in_st b.habl;+intanotabl);+i)   w,  shinornendetosered  tar(in    inowhar) countow;
#de i;
 -=gne,  c=0'\0;
 E   }
 netabl;+(ch(i< = t)
 i)  cle cetar(n tar(ne.hane[];

前の1バイトしか憶えてねえ鳥頭には文章は書けねえですな。所々単語程度になってたりはするけど、それは入力したデータがあまりにも少なかっただけでござい。

test