package bjc.utils.gen; import java.util.Random; import bjc.utils.data.IHolder; import bjc.utils.data.IPair; import bjc.utils.data.Identity; import bjc.utils.funcdata.FunctionalList; import bjc.utils.funcdata.IList; /** * Represents a random number generator where certain results are weighted more * heavily than others. * * @author ben * * @param * The type of values that are randomly selected. */ public class WeightedRandom { /* The list of probabilities for each result */ private final IList probabilities; /* The list of possible results to pick from */ private final IList results; /* The source for any needed random numbers */ private Random source; /* The total chance for all values. */ private int totalChance; private final static Random BASE = new Random(); /** * Create a new weighted random generator with the specified source of * randomness. * * @param src * The source of randomness to use. */ public WeightedRandom(Random src) { probabilities = new FunctionalList<>(); results = new FunctionalList<>(); if(src == null) throw new NullPointerException("Source of randomness must not be null"); source = src; } /** * Create a new weighted random generator. */ public WeightedRandom() { this(BASE); } /** * Add a probability for a specific result to be given. * * @param chance * The chance to get this result. * * @param result * The result to get when the chance comes up. */ public void addProbability(final int chance, final E result) { probabilities.add(chance); results.add(result); totalChance += chance; } /** * Generate a weighted random value. * * @return A random value selected in a weighted fashion. */ public E generateValue() { return generateValue(source); } public E generateValue(Random rn) { int target = rn.nextInt(totalChance); int i = 0; for(int prob : probabilities) { if(target < prob) return results.getByIndex(i); target -= prob; i += 1; } throw new NullPointerException("Fell off the end of the results list"); } /** * Return a list of values that can be generated by this generator * * @return A list of all the values that can be generated */ public IList getResults() { return results; } /** * Return a list containing values that can be generated paired with the * probability of those values being generated * * @return A list of pairs of values and value probabilities */ public IList> getValues() { return probabilities.pairWith(results); } public E getDescent(int factor) { return getDescent(factor, source); } public E getDescent(int factor, Random rn) { for(E res : results) { if(rn.nextInt(factor) == 0) continue; return res; } return results.getByIndex(results.getSize() - 1); } public E getBinomial(int target, int bound, int trials) { return getBinomial(target, bound, trials, source); } public E getBinomial(int target, int bound, int trials, Random rn) { int numSuc = 0; for(int i = 0; i < trials; i++) { /* * Adjust for zero, because it's easy to think of this * as rolling a bound-sided dice and marking a success * for every roll less than or equal to target. */ int num = rn.nextInt(bound) + 1; if(num <= target) { //System.err.printf("\t\tTRACE: mark binomial success (%d <= 1d%d, %d)\n", target, bound, num); numSuc += 1; } } //System.err.printf("\tTRACE: got %d success for binomial trials (%d <= 1d%d, %d times)\n", numSuc, target, bound, trials); return results.getByIndex(Math.min(numSuc, results.getSize() - 1)); } }