package math.statistics;

import java.math.BigInteger;

import math.number.BigRational;
import stdlib.StdRandom;
import awt.Plot;


/*************************************************************************
 *  Compilation:  javac Bernoulli.java
 *  Execution:    java Bernoulli N T
 *  Dependencies: StdDraw.java
 *  
 *  Each experiment consists of flipping N fair coins T times.
 *  Plots a histogram of the number of times i of the N coins are heads.
 *
 *  % java Bernoulli 32 1000
 *
 *  % java Bernoulli 64 1000
 *
 *  % java Bernoulli 128 1000
 *
 *************************************************************************/



public class Bernoulli { 

    // number of heads when flipping N biased-p coins
    public static int binomial(int N, double p) {
        int heads = 0;
        for (int i = 0; i < N; i++) {
            if (StdRandom.bernoulli(p)) {
                heads++;
            }
        }
        return heads;
    } 

    // number of heads when flipping N fair coins
    // or call binomial(N, 0.5)
    public static int binomial(int N) {
        int heads = 0;
        for (int i = 0; i < N; i++) {
            if (StdRandom.bernoulli(0.5)) {
                heads++;
            }
        }
        return heads;
    } 



    public static void main(String[] args) { 
    	args = new String[]{"32", "1000"};
        int N = Integer.parseInt(args[0]);   // number of coins to flip per trial
        int T = Integer.parseInt(args[1]);   // number of trials

    	Plot plot = new Plot();
    	plot.setYscale(0, 0.2);

        // flip N fair coins, T times
        int[] freq = new int[N+1];
        for (int t = 0; t < T; t++) {
            freq[binomial(N)]++;
        }

        // plot normalized values
        double[] normalized = new double[N+1];
        for (int i = 0; i <= N; i++) {
            normalized[i] = (double) freq[i] / T;
        }
        plot.bars(normalized);

        // plot Gaussian approximation
        double mean = N / 2.0;
        double stddev = Math.sqrt(N) / 2.0;
        double[] phi  = new double[N+1];
        for (int i = 0; i <= N; i++) {
            phi[i] = Gaussian.phi(i, mean, stddev);
        }
        plot.lines(phi);
    } 
    
    
    /**
     * Print out the first N Bernoulli numbers.
     * 
     * % java Bernoulli 20
     * 1
     * -1/2
     * 1/6
     * 0
     * -1/30
     * 0
     * 1/42
     * 0
     * -1/30
     * 0
     * 5/66
     * 0
     * -691/2730
     * 0
     * 7/6
     * 0
     * -3617/510
     * 0
     * 43867/798
     * 0
     */
    public static void main() {
        int N = 20;

        // precompute binomial coefficients
        BigInteger[][] binomial = new BigInteger[N+1][N+1];
        for (int n = 1; n <= N; n++) binomial[0][n] = BigInteger.ZERO;
        for (int n = 0; n <= N; n++) binomial[n][0] = BigInteger.ONE;

        // bottom-up dynamic programming
        for (int n = 1; n <= N; n++)
            for (int k = 1; k <= N; k++)
                binomial[n][k] = binomial[n-1][k-1].add(binomial[n-1][k]);


        // now compute Bernoulli numbers
        BigRational[] bernoulli = new BigRational[N+1];
        bernoulli[0] = new BigRational(1, 1);
        bernoulli[1] = new BigRational(-1, 2);
        for (int k = 2; k < N; k++) {
            bernoulli[k] = new BigRational(0, 1);
            for (int i = 0; i < k; i++) {
                BigRational coef = new BigRational(binomial[k + 1][k + 1 - i], BigInteger.ONE);
                bernoulli[k] = bernoulli[k].minus(coef.times(bernoulli[i]));
            }
            bernoulli[k] = bernoulli[k].divides(new BigRational(k+1, 1));
        }

        // print out the first N Bernoulli numbers
        for (int i = 0; i < N; i++)
            System.out.println(bernoulli[i]);

    }
}