Java で Zipf 分布な乱数を生成

Zipf 分布な乱数を作るジェネレータを作ってみた。

車輪の再発明なのは重々承知。
たぶんもっとマシなライブラリがどこかにあるだろうけど、頭使わずに使えそうな単機能なのがググってもサクッとでないので作ってみた。

てきとーに作ったので精度は不明。使うときはそこらを十分検討してね。

public class ZipfRandom {
    private double[] cdf = null;
    private Random rnd = null;

    // m 要素数
    // k 偏り具合パラメータ Zipf分布の 1/(n^k) の k
    // rnd 一様乱数生成器を入れる
    public ZipfRandom(int m, double k, Random rnd) {
        if (m <= 0)
            throw new IllegalArgumentException("m にはプラス値を入れれ");
        if (rnd == null)
            throw new IllegalArgumentException("rnd に null はいらんのですよ");

        this.rnd = rnd;
        cdf = new double[m];

        // 正規化用の分母
        double C = 0.0;
        for (int i=0;i<m;i++) {
            C += 1.0/Math.pow(i+1, k);
        }

        // 確率関数の積算を配列に作成
        double sum = 0.0;
        for (int i=0;i<m;i++) {
            sum += 1.0/(Math.pow(i+1, k)*C);
            cdf[i] = sum;
        }
    }

    // [0, m-1] の範囲で zipf 分布に沿った乱数が返る
    public int getNext() {
        double r = rnd.nextDouble();
        for (int i=0; i < cdf.length; i++) {
            if (r < cdf[i])
                return i;
        }
        // ここに来たら駄目だろう。常識的に考えて。
        throw new IllegalStateException();
    }
}

使い方。

public void main(String[] arg) {
    int eles = 10;   // 要素数
    int[] histgram = new int[eles];   // ヒストグラム入れ

    // 要素数とパラメータと乱数生成器を渡して Zipf 生成器を作成
    ZipfRandom rnd = new ZipfRandom(eles, 1.0, new MersenneTwister());

    // 10,000,000 個乱数を取得してヒストグラムを作る
    for (int i = 0; i < 10000000; i++) {
        histgram[rnd.getNext()]++;
    }

    // 結果表示
    for (int e : histgram) {
        System.out.println(e);
    }
}


結果はこんな感じ。

3410155
1707631
1138103
854965
684191
569974
487507
426716
379426
341332

だいたい 1, 1/2, 1/3, 1/4 ... 1/10 になってるから OK っぽい?

ライセンス

コピペするなりなんなりご自由に
バグで誤動作したり実験結果とかが変なことになってても責任持ちません
コメント歓迎いたします

memo

※再投稿しました。