[Back to main] [Printable version] [Search] [Leave a comment] Comments
import java.awt.*;
import java.applet.*;
import java.util.*;

/**
 * An applet to demostrate central limit theorem of probability.
 * It utilizes an expression evaluator class by The-Son LAI.
 * <p>
 * The whole program is distributed under the Modified BSD
 * license with the kind permission from The-Son. See below.
 * <p>
 * Copyright (c) 2001 by Jarno Elonen and The-Son LAI
 * <p>
 * http://iki.fi/elonen/, http://lts.online.fr/java/
 */
public class CentralLimitApplet extends Applet
{
    /*
     * The distribution licence
     * 
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions
     * are met:
     * 
     * Redistributions of source code must retain the above copyright notice,
     * this list of conditions and the following disclaimer. Redistributions in
     * binary form must reproduce the above copyright notice, this list of
     * conditions and the following disclaimer in the documentation and/or other
     * materials provided with the distribution. The name of the author may not
     * be used to endorse or promote products derived from this software without
     * specific prior written permission. 
     *  
     * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     */

    /**
     * Start the applet, create panels
     */
    public void init()
    {
        this.setLayout( null );
        
        // Create the parameter panel
        Panel p = new Panel();
        p.setLayout( new FlowLayout( FlowLayout.LEFT, 0,0 ));
        p.add( new Label( "Examine sum of", Label.LEFT ));
        p.add( sumOfTF );
        p.add( new Label( "occurences of a" ));
        p.add( new Label( "phenomenon" ));
        p.add( new Label( "whose min =" ));
        p.add( minTF );
        p.add( new Label( "," ));
        p.add( new Label( "max =" ));
        p.add( maxTF );
        p.add( new Label( "," ));
        p.add( new Label( "prob. func. P(x)=" ));
        p.add( funcTF );
        p.add( new Label( "and repeat it" ));
        p.add( repeatTF );
        p.add( new Label( "times.            " ));
        p.add( goButt );
        Panel p2 = new Panel( new BorderLayout());
        p2.add( BorderLayout.CENTER, p );
        Panel p3 = new Panel( new BorderLayout());
        p3.add( BorderLayout.CENTER, goButt );
        p2.add( BorderLayout.SOUTH, p3 );
        add( p2 );
        p2.setBounds( 0,0, 100, getSize().height );
        
        myImg = createImage( getSize().width-100, getSize().height );       
        clearCanvas();
        
        repaint();
    }

    /**
     * Redraw the screen
     */
    public void paint( Graphics g )
    {
        g.drawImage( myImg, 100,0, null );
    }

    /**
     * Listen for button strokes
     */
    public boolean action( Event evt, Object what )
    {
        if ( evt.target == goButt )
        {
            simulate();
            repaint();
            return true;
        }
        return false;
    }
    
    /**
     * This can be called from Javascript.
     * Sets the text fields and simulates a round.
     */
    public void simulate( int min, int max, int sum,
                          int repeat, String func )
    {
        minTF.setText( "" + min );
        maxTF.setText( "" + max );
        sumOfTF.setText( "" + sum );
        repeatTF.setText( "" + repeat );
        funcTF.setText( func );
        simulate();
        repaint();
    }
    
    /**
     * Paints the canvas white and draws
     * black borders around it
     */
    private void clearCanvas()
    {       
        int w = myImg.getWidth(null), h = myImg.getHeight(null);
        Graphics g = myImg.getGraphics();
        g.setColor( Color.white );
        g.fillRect( 0,0, 99999, 99999 );
        g.setColor( Color.black );
        g.drawRect( 0,0, w-1,h-1 );     
    }

    /**
     * Runs a simulation with given parameters
     * and draws the result as a histogram to the screen.
     */
    private void simulate()
    {
        try
        {
            int min = getLimited(  minTF, "Min", -9999, 9999 );
            int max = getLimited(  maxTF, "Max", -9999, 9999 );
            int sumOf = getLimited(  sumOfTF, "Sum", 1, 1000 );
            int repeat = getLimited(  repeatTF, "Repeat count", 1, 10000000 );
            
            if ( min >= max )
                throw new Exception( "Min must be < Max." );
            
            int totalMin = (min*sumOf);
            int totalMax = (max*sumOf);     
        
            Graphics g = myImg.getGraphics();
            int w = myImg.getWidth(null), h = myImg.getHeight(null);
        
            clearCanvas();
            repaint();
        
            // Draw the scale
            g.setColor( Color.black );
            g.drawString( "" + totalMin, 2,h-8 );
            String maxStr = "" + totalMax;  
            g.drawString( maxStr, w-g.getFontMetrics().stringWidth( maxStr )-2, h-8 );
            String avgStr = "" + (totalMin+totalMax)/2;
            g.drawString( avgStr, w/2-g.getFontMetrics().stringWidth( avgStr )/2-2, h-8 );

            myEval.setExpression( funcTF.getText());
                    
            int range = max-min+1;
            int totalRange = totalMax-totalMin+1;
        
            // Calculate propabilities from the function
            double fTotal = 0;
            double prob[] = new double[ range ];
            for ( int i=0; i<range; ++i )
            {
                myEval.addVariable( "x", i+min );
                Double dbl = myEval.getValue();
                
                if ( dbl == null )
                    throw new Exception( "Invalid function '" + 
                        funcTF.getText() + "'" );
                
                prob[i] = dbl.doubleValue();
                fTotal += prob[i];
            }
            // Normalize P to 0..1
            for ( int i=0; i<range; ++i )
                prob[i] /= fTotal;
        
            // Simulate the experiment
            double maxFreq = 0;
            double freq[] = new double[ totalRange  ];
            for ( int i=0; i<repeat; ++i )
            {
                int sum = 0;
                for ( int j=0; j<sumOf; ++j )
                {
                    double d = myRnd.nextDouble();
                    int k=0;
                    while ( d>prob[k] && k<=range )
                        d -= prob[k++];
                    sum += k;
                }
                freq[sum]++;
                if ( freq[sum] > maxFreq )
                    maxFreq = freq[sum];
            }
            // Normalize freq to 0...1
            for ( int i=0; i<totalRange; ++i )
                freq[i] /= maxFreq;
        
            // Draw the result histogram
            g.setColor( Color.blue );       
            for ( int x=1; x<w-1; ++x )
            {
                int v = (int)(freq[(int)(totalRange*(double)x/w)] * (h-60));
                g.drawLine( x,h-30, x,h-30-v );
            }
        }
        catch( Exception e )
        {
            clearCanvas();
            myImg.getGraphics().drawString( e.getMessage(), 10,20 );
        }
    }

    /**
     * Gets an integer from given TextField or throw an Exception if
     * it doesn't fall into given limits or is not a number.
     */
    private int getLimited( TextField tf, String name, int min, int max )
        throws Exception
    {
        try
        {
            int val = Integer.parseInt( tf.getText());
            if ( val < min || val > max )
                throw new NumberFormatException();
            return val;
        }
        catch( NumberFormatException nfe )
        {
            String msg = "'" + name + "' must be an integer between [" +
                         min + ", " + max +"]";
            throw new Exception( msg );
        }
    }
    
    private TextField
        sumOfTF = new TextField( "10", 12 ),
        minTF = new TextField(  "1", 8 ),
        maxTF = new TextField(  "6", 8 ),
        funcTF = new TextField( "1/6", 12 ),
        repeatTF = new TextField( "10000", 12 );
    
    private Button goButt = new Button( "Simulate" );   
    private Image myImg;
    
    private MathEvaluator myEval = new MathEvaluator();
    private Random myRnd = new Random();
    
    
    /**
     * <i>Mathematic expression evaluator.</i> Supports the following functions:
     * +, -, *, /, ^, %, cos, sin, tan, acos, asin, atan, sqrt, sqr, log, min, max, ceil, floor, abs, neg, rndr.<br>
     * When the getValue() is called, a Double object is returned. If it returns null, an error occured.<p>
     * <pre>
     * Sample:
     * MathEvaluator m = new MathEvaluator("-5-6/(-2) + sqr(15+x)");
     * m.addVariable("x", 15.1d);
     * System.out.println( m.getValue() );
     * </pre>
     * Refactored slightly for smaller size by Jarno Elonen
     * @version 1.1
     * @author  The-Son LAI, <a href="mailto:Lts@writeme.com">Lts@writeme.com</a>
     * @date     April 2001
     **/
    public static class MathEvaluator
    {
        protected static    Operator[]  operators   = null;
        private             Node        node        = null;
        private             String      expression  = null;
        private             Hashtable   variables   = new Hashtable();

        /***
         * adds a variable and its value in the MathEvaluator
         */
        public void addVariable(String v, double val)
        {
            variables.put(v, new Double(val));
        }

        /***
         * sets the expression
         */
        public void setExpression(String s)
        {
            if ( operators == null )
                initializeOperators();
            
            expression = s;
        }

        /***
         * evaluates and returns the value of the expression
         */
        public Double getValue()
        {
            if (expression == null) return null;

            try
            {
                node = new Node(expression);
                return evaluate(node);
            }
            catch (Exception e)
            {
                e.printStackTrace();
                return null;
            }
        }

        private static Double evaluate(Node n)
        {
            if ( n.hasOperator() && n.hasChild() )
            {
                if ( n.nOperator.getType() == 1 )
                    n.nValue = evaluateExpression( n.nOperator, evaluate( n.nLeft ), null );
                else if ( n.nOperator.getType() == 2 )
                    n.nValue = evaluateExpression( n.nOperator, evaluate( n.nLeft ), evaluate( n.nRight ) );
            }
            return n.nValue;
        }

        private static Double evaluateExpression(Operator o, Double f1, Double f2)
        {
            String op   = o.getOperator();
            Double res  = null;

            if       ( "+".equals(op) )     res = new Double( f1.doubleValue() + f2.doubleValue() );
            else if  ( "-".equals(op) )     res = new Double( f1.doubleValue() - f2.doubleValue() );
            else if  ( "*".equals(op) )     res = new Double( f1.doubleValue() * f2.doubleValue() );
            else if  ( "/".equals(op) )     res = new Double( f1.doubleValue() / f2.doubleValue() );
            else if  ( "^".equals(op) )     res = new Double( Math.pow(f1.doubleValue(), f2.doubleValue()) );
            else if  ( "%".equals(op) )     res = new Double( f1.doubleValue() % f2.doubleValue() );
            else if  ( "&".equals(op) )     res = new Double( f1.doubleValue() + f2.doubleValue() ); // todo
            else if  ( "|".equals(op) )     res = new Double( f1.doubleValue() + f2.doubleValue() ); // todo
            else if  ( "cos".equals(op) )   res = new Double( Math.cos(f1.doubleValue()) );
            else if  ( "sin".equals(op) )   res = new Double( Math.sin(f1.doubleValue()) );
            else if  ( "tan".equals(op) )   res = new Double( Math.tan(f1.doubleValue()) );
            else if  ( "acos".equals(op) )  res = new Double( Math.acos(f1.doubleValue()) );
            else if  ( "asin".equals(op) )  res = new Double( Math.asin(f1.doubleValue()) );
            else if  ( "atan".equals(op) )  res = new Double( Math.atan(f1.doubleValue()) );
            else if  ( "sqr".equals(op) )   res = new Double( f1.doubleValue() * f1.doubleValue() );
            else if  ( "sqrt".equals(op) )  res = new Double( Math.sqrt(f1.doubleValue()) );
            else if  ( "log".equals(op) )   res = new Double( Math.log(f1.doubleValue()) );
            else if  ( "min".equals(op) )   res = new Double( Math.min(f1.doubleValue(), f2.doubleValue()) );
            else if  ( "max".equals(op) )   res = new Double( Math.max(f1.doubleValue(), f2.doubleValue()) );
            else if  ( "exp".equals(op) )   res = new Double( Math.exp(f1.doubleValue()) );
            else if  ( "floor".equals(op) ) res = new Double( Math.floor(f1.doubleValue()) );
            else if  ( "ceil".equals(op) )  res = new Double( Math.ceil(f1.doubleValue()) );
            else if  ( "abs".equals(op) )   res = new Double( Math.abs(f1.doubleValue()) );
            else if  ( "neg".equals(op) )   res = new Double( - f1.doubleValue() );
            else if  ( "rnd".equals(op) )   res = new Double( Math.random() * f1.doubleValue() );

            return res;
        }

        private void initializeOperators()
        {
            operators     = new Operator[25];
            operators[0]  = new Operator("+"    , 2, 0);
            operators[1]  = new Operator("-"    , 2, 0);
            operators[2]  = new Operator("*"    , 2, 10);
            operators[3]  = new Operator("/"    , 2, 10);
            operators[4]  = new Operator("^"    , 2, 10);
            operators[5]  = new Operator("%"    , 2, 10);
            operators[6]  = new Operator("&"    , 2, 0);
            operators[7]  = new Operator("|"    , 2, 0);
            operators[8]  = new Operator("cos"  , 1, 20);
            operators[9]  = new Operator("sin"  , 1, 20);
            operators[10] = new Operator("tan"  , 1, 20);
            operators[11] = new Operator("acos" , 1, 20);
            operators[12] = new Operator("asin" , 1, 20);
            operators[13] = new Operator("atan" , 1, 20);
            operators[14] = new Operator("sqrt" , 1, 20);
            operators[15] = new Operator("sqr"  , 1, 20);
            operators[16] = new Operator("log"  , 1, 20);
            operators[17] = new Operator("min"  , 2, 0);
            operators[18] = new Operator("max"  , 2, 0);
            operators[19] = new Operator("exp"  , 1, 20);
            operators[20] = new Operator("floor", 1, 20);
            operators[21] = new Operator("ceil" , 1, 20);
            operators[22] = new Operator("abs"  , 1, 20);
            operators[23] = new Operator("neg"  , 1, 20);
            operators[24] = new Operator("rnd"  , 1, 20);
        }

        private Double getDouble(String s)
        {
            if ( s == null ) return null;

            Double res = null;
            try {
                res = Double.valueOf( s );
            }
            catch(Exception e) {
                return (Double) variables.get(s);
            }

            return res;
        }

        protected class Operator
        {
            private String op;
            private int type;
            private int priority;

            public Operator(String o, int t, int p)
            {
                op = o;
                type = t;
                priority = p;
            }

            public String getOperator() {
                return op;
            }

            public void setOperator(String o) {
                op = o;
            }

            public int getType() {
                return type;
            }

            public int getPriority() {
                return priority;
            }
        }

        protected class Node
        {
            public String   nString     = null;
            public Operator nOperator   = null;
            public Node     nLeft       = null;
            public Node     nRight      = null;
            public Node     nParent     = null;
            public int      nLevel      = 0;
            public Double   nValue      = null;

            public Node(String s) throws Exception
            {
                init(null, s, 0);
            }

            public Node(Node parent, String s, int level) throws Exception
            {
                init(parent, s, level);
            }

            private void init(Node parent, String s, int level) throws Exception
            {
                s = removeIllegalCharacters(s);
                s = removeBrackets(s);
                s = addZero(s);
                if ( checkBrackets(s) != 0 ) throw new Exception("Wrong number of brackets in [" + s + "]");

                nParent             = parent;
                nString             = s;
                nValue              = getDouble(s);
                nLevel              = level;
                int sLength         = s.length();
                int inBrackets      = 0;
                int startOperator   = 0;

                for (int i=0; i<sLength; i++)
                {
                    if ( s.charAt(i) == '(' )
                        inBrackets++;
                    else if ( s.charAt(i) == ')' )
                        inBrackets--;
                    else
                    {
                        // the expression must be at "root" level
                        if ( inBrackets == 0 )
                        {
                            Operator o = getOperator(nString,i);
                            if ( o != null )
                            {
                                // if first operator or lower priority operator
                                if ( nOperator == null || nOperator.getPriority() >= o.getPriority() )
                                {
                                    nOperator       = o;
                                    startOperator   = i;
                                }
                            }
                        }
                    }
                }

                if ( nOperator != null )
                {
                    // one operand, should always be at the beginning
                    if ( startOperator==0 && nOperator.getType() == 1 )
                    {
                        // the brackets must be ok
                        if ( checkBrackets( s.substring( nOperator.getOperator().length() ) ) == 0 )
                        {
                            nLeft  = new Node( this, s.substring( nOperator.getOperator().length() ) , nLevel + 1);
                            nRight = null;
                            return;
                        }
                        else
                            throw new Exception("Error during parsing... missing brackets in [" + s + "]");
                    }
                    // two operands
                    else if ( startOperator > 0 && nOperator.getType() == 2 )
                    {
                        nOperator = nOperator;
                        nLeft   = new Node( this, s.substring(0,  startOperator), nLevel + 1 );
                        nRight  = new Node( this, s.substring(startOperator + nOperator.getOperator().length()), nLevel + 1);
                    }
                }
            }

            private Operator getOperator(String s, int start)
            {
                String temp = s.substring(start);
                temp = getNextWord(temp);
                for (int i=0; i<operators.length; i++)
                {
                    if ( temp.startsWith(operators[i].getOperator()) )
                        return operators[i];
                }
                return null;
            }

            private String getNextWord(String s)
            {
                int sLength = s.length();
                for (int i=1; i<sLength; i++)
                {
                    char c = s.charAt(i);
                    if ( (c > 'z' || c < 'a') && (c > '9' || c < '0') )
                        return s.substring(0, i);
                }
                return s;
            }

            /***
             * checks if there is any missing brackets
             * @return true if s is valid
             */
            protected int checkBrackets(String s)
            {
                int sLength     = s.length();
                int inBracket   = 0;

                for (int i=0; i<sLength; i++)
                {
                    if      ( s.charAt(i) == '(' && inBracket >= 0 )
                        inBracket++;
                    else if ( s.charAt(i) == ')' )
                        inBracket--;
                }

                return inBracket;
            }

            /***
             * returns a string that doesnt start with a + or a -
             */
            protected String addZero(String s)
            {
                if ( s.startsWith("+") || s.startsWith("-") )
                {
                    int sLength     = s.length();
                    for (int i=0; i<sLength; i++)
                    {
                        if ( getOperator(s, i) != null )
                            return "0" + s;
                    }
                }
                return s;
            }

            protected boolean hasChild() {
                return ( nLeft != null || nRight != null );
            }

            protected boolean hasOperator() {
                return ( nOperator != null );
            }

            protected boolean hasLeft() {
                return ( nLeft != null );
            }
            protected boolean hasRight() {
                return ( nRight != null );
            }

            /***
             * Removes spaces, tabs and brackets at the begining
             */
            public String removeBrackets(String s)
            {
                String res = s;
                if ( s.length() > 2 && res.startsWith("(") && res.endsWith(")") && checkBrackets(s.substring(1,s.length()-1)) == 0 )
                {
                    res = res.substring(1, res.length()-1 );
                }
                if ( res != s )
                    return removeBrackets(res);
                else
                   return res;
            }

            /***
             * Removes illegal characters
             */
            public String removeIllegalCharacters(String s)
            {
                char[] illegalCharacters = { ' ' };
                String res = s;

                for ( int j=0; j<illegalCharacters.length; j++)
                {
                    int i = res.lastIndexOf(illegalCharacters[j], res.length());
                    while ( i != -1 )
                    {
                        String temp = res;
                        res = temp.substring(0,i);
                        res += temp.substring(i + 1);
                        i = res.lastIndexOf(illegalCharacters[j], s.length());
                    }
                }
                return res;
            }
        }
    }   
}

blog comments powered by Disqus