/* * [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 }