/*
 * [RandomWithFloatWeights.java]
 *
 * Summary: random choices with float weights.
 *
 * Copyright: (c) 2014-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 2014-07-30 initial version
 */
package com.mindprod.example;

import org.jetbrains.annotations.NotNull;

import java.util.Arrays;
import java.util.Random;

import static java.lang.System.*;

/**
 * random choices with float weights.
 *
 * @author Roedy Green, Canadian Mind Products
 * @version 1.0 2014-07-28 initial version
 * @see com.mindprod.example.RandomWithIntWeights
 * @since 2014-07-28
 */
public final class RandomWithFloatWeights
    {
    /**
     * true if want extra output
     */
    private static final boolean DEBUGGING = false;

    /**
     * build the translator table for generated weighted random numbers
     *
     * @param weights array of weights
     *
     * @return translator table
     */
    @NotNull
    private static float[] buildTranslator( @NotNull final float[] weights )
        {
        float totalWeights = 0;
        for ( float weight : weights )
            {
            totalWeights += weight;
            }
        final float[] translator = new float[ weights.length ];
        // we normalise the weights to range 0..1
        // then we assign a subrange of each choice, length proportional to weight in 0..1
        float cumulativeWeight = 0;
        for ( int i = 0; i < weights.length; i++ )
            {
            translator[ i ] = cumulativeWeight;  // low bound of the range
            final float normalisedWeight = weights[ i ] / totalWeights;
            cumulativeWeight += normalisedWeight;
            }
        return translator;
        }

    /**
     * Print out 30 ice cream flavours, but weighting toward choclate strawberry and orange.
     *
     * @param args not used
     */
    public static void main( String[] args )
        {
        // we have 7 flavours of ice ceram
        final String[] flavours = { "chocolate", "strawberry", "vanilla", "orange", "lime", "Cherry Garcia", "bubble gum" };
        // we want to randomly pick a different flavour with these weights:
        // 30.2% 40.0% 2% 36% 20.1% 4% 0.5% (Ben & Jerry is very expensive).  This adds up to 134%, but that is ok.
        // Must be 7 weights, one weight for each flavour.
        final float[] weights = { 30.2f, 40.0f, 2.0f, 36.0f, 20.1f, 4.0f, 0.5f };
        final float[] translator = buildTranslator( weights );
        if ( DEBUGGING )
            {
            for ( float lowbound : translator )
                {
                out.println( lowbound );
                }
            }
        final Random wheel = new Random();
        for ( int i = 0; i < 30; i++ )
            {
            final float rawChoice = wheel.nextFloat();
            // will in range 0 <= to < 1
            assert 0 <= rawChoice && rawChoice < 1 : "Random.float is broken";
            // this is considerably slower than the indexing used in RandomWithIntWeights.
            int where = Arrays.binarySearch( translator, rawChoice );
            // if positive, had exact match for the slot.
            // if negative the insertion point is -where - 1, the usual case
            final int weightedChoice;
            if ( where < 0 )
                {
                // no exact match,  insertion point is just above the low bound. So we want insertion point - 1;
                weightedChoice = -where - 2;
                }
            else
                {
                weightedChoice = where;
                }
            if ( DEBUGGING )
                {
                out.println( rawChoice + " " + where + " " + weightedChoice );
                }
            out.println( flavours[ weightedChoice ] );
            // different every time run, but mostly chocolate, orange and stawberry
            }
        } // end main
    }