はじめに
自然言語処理・情報検索界で最も有名なのがZipf's Lawですが,これは,どんなテキストでやっても同様の傾向が見られるはずなので,日本語text8を使ってやってみます。
実装
前処理
Zipfの法則では,別に単語に分割する必要はないのですが,text8は既にスペース区切りになっているので,これをそのまま利用します。プロデルにsplit相当の手順はないので,以前実装したものがありますが,なんとこれがとても遅い。全部で何語あるかというと,
nipo@nipo:~$ wc ja.text8
0 16900732 100000347 ja.text8
nipo@nipo:~$
1690万語です。コンパイルしても非常に遅くなります。原因は恐らく,区切り文字が出現する都度配列に追加しているからです。内部のデータ構造がどうなっているか知りませんが,配列の大きさが変わる場合,全てのデータ+追加したデータをメモリの別の場所に移すはずなので,これを1600万回もやっていたらどんどん遅くなるでしょう。
今回は,別に単語を配列に入れる必要がないので,1語ごとに辞書に出現回数をカウントしていくことにします。よって,次のような,アクセスする度に次の単語を返す手順を持つ種類を書きました。
文字列分割器とは
【分割対象:文字列】は,「」。
【前回位置:整数】は,0。
【今回位置:整数】は,1。
はじめ(対象)の手順
分割対象は,対象。
終わり
自分を【区切り文字:文字列】で分割する手順
繰り返す
もし今回位置が分割対象の文字数より大きいなら
「」を返す
もし終わり
もし今回位置が分割対象の文字数なら
今回位置は,今回位置+1。
分割対象の[前回位置+1]文字目以降を返す。
もし終わり
もし分割対象の[今回位置]文字目が区切り文字なら
【結果】は,分割対象の[前回位置+1]文字目から[今回位置-前回位置-1]文字取り出したもの。
前回位置は,今回位置。
今回位置は,今回位置+1。
結果を返す。
でないなら
今回位置は,今回位置+1。
もし終わり
繰り返し終わり
終わり
終わり
これを使って,良い感じに辞書に頻度を数え上げていきます。
「文字列操作.rdr」を参照する。
「行列.rdr」を参照する。
「グラフ描画.rdr」を参照する。
「色.rdr」を参照する。
「Produire.WinControl.dll」を利用する。
「Produire.PGraphics.dll」を利用する。
コーパスという文字列分割器(「ja.text8」から読み込んだもの)を作る。
【頻度:辞書】。
繰り返す
単語は,コーパスを「 」で分割したもの。
もし単語が「」なら
繰り返しから抜ける
もし終わり
もし頻度が無なら,頻度(「_[単語]」)は,0。
頻度(「_[単語]」)は,頻度(「_[単語]」)+1。
繰り返し終わり
データを並べ替える
サイズを指定しない場合さっきと同じことが起こるので,サイズを指定して配列を作りたいのですが,これには固定長配列という種類を用います。しかし,この固定長配列は並び替え手順が実装されていません(マニュアルには書いてある)。
また,コンパイルすると,配列の要素へのアクセスが非常に難しくなるので,注意します。
【頻度一覧:整数の配列(頻度の個数)】は,固定長配列(頻度の個数)を作ったもの。
カウンタは,1。
限界は,頻度一覧の個数。
頻度の見出しを【キー】へそれぞれ繰り返す
もしカウンタが限界未満なら
頻度一覧(カウンタ)は,頻度(キー)。
もし終わり
カウンタは,カウンタ+1。
繰り返し終わり
さて,これで頻度一覧に頻度の値が全て入ったので,大きい順に並べ替えます。
頻度一覧を大きい順に並び替える
これはエラーです。固定長配列には並び替えの手順が実装されていません。そこで,配列を経由します。
【頻度降順:配列】は,{}。
頻度降順は,頻度一覧のクローン。
頻度降順を大きい順に並び替える。
これで準備ができました。
描画
今回は,20000を超えるデータ数があり,プロットしても何も見えないので,上位600個に限ります。
データという行列(1,600)を作る。
データの中身(1)は,頻度降順の1番目から600個切り出したもの。
グラフというグラフ(600,600)を作る。
グラフにデータで折れ線グラフを描く。
グラフを表示。
待機。
おお~!!Zipfみがありますね!!