/*
 * [TestSummary.java]
 *
 * Summary: Demonstrate various ways of summarising/averaging a list of doubles with a single number.
 *
 * Copyright: (c) 2009-2017 Roedy Green, Canadian Mind Products, http://mindprod.com
 *
 * Licence: This software may be copied and used freely for any purpose but military.
 *          http://mindprod.com/contact/nonmil.html
 *
 * Requires: JDK 1.8+
 *
 * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/
 *
 * Version History:
 *  1.0 2009-09-02
 */
package com.mindprod.example;

import static java.lang.System.*;

/**
 * Demonstrate various ways of summarising/averaging a list of doubles with a single number.
 *
 * @author Roedy Green, Canadian Mind Products
 * @version 1.0 2009-09-02
 * @since 2009-09-02
 */
public final class TestSummary
    {
    /**
     * test numbers to average
     */
    private static final double[] TEST_DATA = { 223.0, 223.8, 221.8, 227.3, 223.6, 224.4, 222.6, 227.9, 227.2 };

    /**
     * test weights, need not be normalised. Must be one for each element in TEST_DATA.
     */
    private static final double[] TEST_WEIGHTS = { .10, .5, .20, .5, .5, .5, .5, .3, .4 };

    /**
     * Compute ordinary arithmetic average
     *
     * @param numbers array of doubles
     *
     * @return the ordinary arithmetic average or NaN for an empty array.
     * @throws NullPointerException if numbers is null
     */
    @SuppressWarnings( { "WeakerAccess" } )
    public static double arithmeticMean( double... numbers )
        {
        if ( numbers.length == 0 )
            {
            return Double.NaN;
            }
        double sum = 0;
        for ( double number : numbers )
            {
            sum += number;
            }
        return sum / numbers.length;
        }

    /**
     * Compute geometric mean, nth root of the product of all the numbers.
     *
     * @param numbers array of doubles
     *
     * @return the geometric mean or NaN for an empty array.
     * @throws NullPointerException if numbers is null
     * @see <a href="http://en.wikipedia.org/wiki/Geometric_mean">Wikipedia on geometric mean</a>
     */
    @SuppressWarnings( { "WeakerAccess" } )
    public static double geometricMean( double... numbers )
        {
        if ( numbers.length == 0 )
            {
            return Double.NaN;
            }
        double sum = 0;
        for ( double number : numbers )
            {
            // Math.log is base e, natural log, ln
            sum += Math.log( number );
            }
        return Math.exp( sum / numbers.length ); // note divide BEFORE taking exp
        }

    /**
     * Compute harmonic mean, the reciprocal of the arithmetic mean of the reciprocals.
     *
     * @param numbers array of doubles
     *
     * @return the harmonic mean or NaN for an empty array.
     * @throws NullPointerException if numbers is null
     * @see <a href="http://en.wikipedia.org/wiki/Harmonic_mean">Wikipedia on harmonic mean</a>
     */
    @SuppressWarnings( { "WeakerAccess" } )
    public static double harmonicMean( double... numbers )
        {
        if ( numbers.length == 0 )
            {
            return Double.NaN;
            }
        double sum = 0;
        for ( double number : numbers )
            {
            // Math.log is base e, natural log, ln
            sum += 1 / number;
            }
        return 1 / ( sum / numbers.length ); // note divide BEFORE taking reciprocal
        }

    /**
     * main program to test/demonstrate average/summarising methods.
     *
     * @param args command line not used.
     */
    public static void main( String[] args )
        {
        out.println( minimum( TEST_DATA ) );        // 221.8
        out.println( powerMean( -50, TEST_DATA ) ); // 224.1488630356682
        out.println( harmonicMean( TEST_DATA ) );   // 224.60200930223507
        out.println( powerMean( -1, TEST_DATA ) );  // 224.60200930223507 (same as harmonicMean)
        out.println( powerMean( -.5, TEST_DATA ) ); // 224.60705292339645
        out.println( geometricMean( TEST_DATA ) );  // 224.6121030029759
        out.println( powerMean( .5, TEST_DATA ) );  // 224.61715946234176
        out.println( powerMean( .9, TEST_DATA ) );  // 224.6212091700313
        out.println( arithmeticMean( TEST_DATA ) );  // 224.62222222222223
        out.println( powerMean( 1, TEST_DATA ) );   // 224.62222222222223 (same as arithmeticMean)
        out.println( rmsMean( TEST_DATA ) );        // 224.6323663232883
        out.println( powerMean( 2, TEST_DATA ) );   // 224.6323663232883  (same as rmsMean)
        out.println( weightedMean( TEST_DATA, TEST_WEIGHTS ) );  // 224.78857142857146
        out.println( powerMean( 50, TEST_DATA ) );  // 225.1341964090114
        out.println( maximum( TEST_DATA ) );        // 227.9
        out.println( arithmeticMean( Double.MAX_VALUE, Double.MAX_VALUE / 2 ) ); // Infinity (sum calculation overflows)
        }

    /**
     * Find the biggest number
     *
     * @param numbers array of doubles
     *
     * @return the maximum value or NaN for an empty array.
     * @throws NullPointerException if numbers is null
     */
    @SuppressWarnings( { "WeakerAccess" } )
    public static double maximum( double... numbers )
        {
        if ( numbers.length == 0 )
            {
            return Double.NaN;
            }
        double max = numbers[ 0 ];
        for ( double number : numbers )
            {
            if ( number > max )
                {
                max = number;
                }
            }
        return max;
        }

    /**
     * Find the smallest number
     *
     * @param numbers array of doubles
     *
     * @return the minimum value or NaN for an empty array.
     * @throws NullPointerException if numbers is null
     */
    @SuppressWarnings( { "WeakerAccess" } )
    public static double minimum( double... numbers )
        {
        if ( numbers.length == 0 )
            {
            return Double.NaN;
            }
        double min = numbers[ 0 ];
        for ( int i = 1; i < numbers.length; i++ )
            {
            if ( numbers[ i ] < min )
                {
                min = numbers[ i ];
                }
            }
        return min;
        }

    /**
     * Compute power average, a generalisation of rms average. Aka H&ouml;lder or generalised mean.
     *
     * @param power,  need not be integral. Must not be 0.  power=2 is same as rms average.
     * @param numbers array of doubles
     *
     * @return the generalised power mean or NaN for an empty array.
     * @throws NullPointerException if numbers is null
     * @see <a href="http://en.wikipedia.org/wiki/Generalized_mean>Wikipedia on generalised mean</a>
     */
    @SuppressWarnings( { "WeakerAccess" } )
    public static double powerMean( double power, double... numbers )
        {
        if ( numbers.length == 0 )
            {
            return Double.NaN;
            }
        double sum = 0;
        for ( double number : numbers )
            {
            sum += Math.pow( number, power );
            }
        return Math.pow( sum / numbers.length, 1 / power ); // note divide BEFORE taking root
        }

    /**
     * Compute rms, (root mean square) average, aka quadratic average
     *
     * @param numbers array of doubles
     *
     * @return the rms average or NaN for an empty array.
     * @throws NullPointerException if numbers is null
     * @see <a href="http://en.wikipedia.org/wiki/Root_mean_square">Wikipedia on rms mean</a>
     */
    @SuppressWarnings( { "WeakerAccess" } )
    public static double rmsMean( double... numbers )
        {
        if ( numbers.length == 0 )
            {
            return Double.NaN;
            }
        double sum = 0;
        for ( double number : numbers )
            {
            sum += number * number;
            }
        return Math.sqrt( sum / numbers.length ); // note divide BEFORE taking sqrt
        }

    /**
     * Compute ordinary arithmetic average, with weights on each value.
     * If all weights are equal e.g. 1, this is the same as an arithmetic mean.
     *
     * @param numbers array of doubles
     * @param weights array of weights.  Need non be normalised (i.e. add up to 1 or 100).
     *
     * @return the weighted arithmetic average or NaN for an empty array.
     * @throws NullPointerException if numbers or weights is null
     */
    @SuppressWarnings( { "WeakerAccess" } )
    public static double weightedMean( double[] numbers, double[] weights )
        {
        assert numbers.length == weights.length : "Must have one weight per number";
        if ( numbers.length == 0 )
            {
            return Double.NaN;
            }
        double sum = 0;
        double weightSum = 0;
        for ( int i = 0; i < numbers.length; i++ )
            {
            sum += numbers[ i ] * weights[ i ];
            weightSum += weights[ i ];
            }
        return sum / weightSum; // We do not also divide by numbers.length
        }
    }