Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Random weighted selection in Java

I want to choose a random item from a set, but the chance of choosing any item should be proportional to the associated weight

Example inputs:

item                weight ----                ------ sword of misery         10 shield of happy          5 potion of dying          6 triple-edged sword       1 

So, if I have 4 possible items, the chance of getting any one item without weights would be 1 in 4.

In this case, a user should be 10 times more likely to get the sword of misery than the triple-edged sword.

How do I make a weighted random selection in Java?

like image 518
yosi Avatar asked Jun 20 '11 10:06

yosi


People also ask

What is weighted random sampling?

In weighted random sampling (WRS) the items are weighted and the probability of each item to be selected is determined by its relative weight.

How do you create a weighted random number?

To generated a random number, weighted with a given probability, you can use a helper table together with a formula based on the RAND and MATCH functions. Notice, we are intentionally shifting the cumulative probability down one row, so that the value in D5 is zero.

How does Python generate weighted random numbers?

Use the random. choices() Function to Generate Weighted Random Choices. Here, the random module of Python is used to make random numbers. In the choices() function, weighted random choices are made with a replacement.


2 Answers

I would use a NavigableMap

public class RandomCollection<E> {     private final NavigableMap<Double, E> map = new TreeMap<Double, E>();     private final Random random;     private double total = 0;      public RandomCollection() {         this(new Random());     }      public RandomCollection(Random random) {         this.random = random;     }      public RandomCollection<E> add(double weight, E result) {         if (weight <= 0) return this;         total += weight;         map.put(total, result);         return this;     }      public E next() {         double value = random.nextDouble() * total;         return map.higherEntry(value).getValue();     } } 

Say I have a list of animals dog, cat, horse with probabilities as 40%, 35%, 25% respectively

RandomCollection<String> rc = new RandomCollection<>()                               .add(40, "dog").add(35, "cat").add(25, "horse");  for (int i = 0; i < 10; i++) {     System.out.println(rc.next()); }  
like image 124
Peter Lawrey Avatar answered Sep 22 '22 23:09

Peter Lawrey


There is now a class for this in Apache Commons: EnumeratedDistribution

Item selectedItem = new EnumeratedDistribution<>(itemWeights).sample(); 

where itemWeights is a List<Pair<Item, Double>>, like (assuming Item interface in Arne's answer):

final List<Pair<Item, Double>> itemWeights = Collections.newArrayList(); for (Item i: itemSet) {     itemWeights.add(new Pair(i, i.getWeight())); } 

or in Java 8:

itemSet.stream().map(i -> new Pair(i, i.getWeight())).collect(toList()); 

Note: Pair here needs to be org.apache.commons.math3.util.Pair, not org.apache.commons.lang3.tuple.Pair.

like image 20
kdkeck Avatar answered Sep 20 '22 23:09

kdkeck