almost 4 years ago

『確率的言語モデル』を読んでいたらジップの法則というものを見つけたので実際に自分で試してみました

ジップの法則というのは 出現頻度がk番目に大きい要素が全体に占める割合が1/kに比例するという経験則 で言語で言えば 単語の使用頻度がk番目に大きい要素が全体に占める割合が1/kに比例する となり要するに 単語の出現順位と回数は反比例する というふうになります。

これを見てみたくてProject Gutenbergからクリスマス・キャロルを題材に単語の出現頻度を数えてみました。
使ったプログラムはこちら。

import Data.List
import Data.Char
import System.IO

toPure :: String -> String
toPure xs
  | head xs == '\"' = toPure (tail xs)
  | head xs == '\'' = toPure (tail xs)
  | head xs == '-'  = toPure (tail xs)
  | last xs == '\"' = toPure (init xs)
  | last xs == ','  = toPure (init xs)
  | last xs == '\'' = toPure (init xs)
  | last xs == '!'  = toPure (init xs)
  | last xs == '.'  = toPure (init xs)
  | last xs == ':'  = toPure (init xs)
  | last xs == ';'  = toPure (init xs)
  | last xs == '?'  = toPure (init xs)
  | otherwise        = xs

wordCount :: [String] -> [(Int, String)]
wordCount xs = reverse $ sort $ map (\ys -> (length ys, head ys)) $ group $ sort $ map ((map toLower).toPure) xs

main = do
  handle <- openFile "A_Christmas_Carol.txt" ReadMode
  contents <- hGetContents handle
  let singlewords = words contents
  print $ map fst $ wordCount singlewords
  hClose handle

重要なのは wordCount でこれは単語のリストから(どの単語が, 何回登場した)というタプルのリストを頻度の降順にソートして返してくれます。
toPure の関数は最初はなかったのですが結果を見てみると one! とか \"This のように前後に記号が入ってしまって別の単語とカウントされているものがあったので取り除くためにはさみました。
結果の使用頻度上位10位を見てみると

  1. the, 1568
  2. and, 1050
  3. a, 699
  4. of, 660
  5. to, 658
  6. in, 518
  7. it, 513
  8. he, 485
  9. was, 427
  10. his, 420

という感じになっていて(andめっちゃおおいやんディケンズ)
肝心の単語の出現順位と回数のグラフはこんな感じ。


縦軸が出現回数で横軸が順位です。
両対数グラフなので傾きが負の直線になってるってことはちゃんと反比例してるってこと!(指数まではちゃんと求めてないけど)
グラフを見てると真ん中の方はほぼほぼ直線になってます。ジップの法則すごい!

とまぁこんな風に盛り上がりたくてやってみた結果なのですが途中のHaskellが意外に面白かった。
単語の出現頻度をプログラムで数えようと思うと普通はハッシュを使って前から順番に文字通り数えていくと思うのですが Haskell だとこういった原始的な操作は書きにくいのでどうするかというと 単語の配列をソートしてグループして終わり!
あぁ、なるほどね。言われないと気づかないわ
ちなみにこのプログラム、実行にかかった時間は僕のMBPで 0.161s
速い!!スクリプト言語なんか目じゃない!!(Python 君のことを言ってるんだよ)
こんな感じで最近Haskellを勉強しています。

← 素数定理を検証してみた #haskell k-means method を実装してみた →
 
comments powered by Disqus