Weighted randomness in Java
2020 Update (interesting how this got 37 upvotes with a glaring bug in the 2011 version below):
- Fix the impossibility to select the last item when
Math.random()
yields a number very close to1.0
, and we are unlucky with floating point precision: random index -1 would be the result, which is obviously wrong. - Some code compaction
- Less variable names used
Item[] items = ...;
// Compute the total weight of all items together.
// This can be skipped of course if sum is already 1.
double totalWeight = 0.0;
for (Item i : items) {
totalWeight += i.getWeight();
}
// Now choose a random item.
int idx = 0;
for (double r = Math.random() * totalWeight; idx < items.length - 1; ++idx) {
r -= items[idx].getWeight();
if (r <= 0.0) break;
}
Item myRandomItem = items[idx];
2011 version (for comparison left in):
Item[] items = ...;
// Compute the total weight of all items together
double totalWeight = 0.0d;
for (Item i : items)
{
totalWeight += i.getWeight();
}
// Now choose a random item
int randomIndex = -1;
double random = Math.random() * totalWeight;
for (int i = 0; i < items.length; ++i)
{
random -= items[i].getWeight();
if (random <= 0.0d)
{
randomIndex = i;
break;
}
}
Item myRandomItem = items[randomIndex];
Simulating weighted random number - Java
A simple approach could be as follows. No need to *2, because probabilities will be the same.
String giantRat []={"Bandage", "Healing Potion", "Minor Healing Potion", "Rat Teeth", "Fur", "Rat Tail", ""};
int[] a = {1,1,1,6,8,3,5};
int sum = 0;
for(int i: a)
sum += i;
Random r = new Random();
int s = r.nextInt(sum); //Get selection position (not array index)
//Find position in the array:
int prev_value = 0;
int current_max_value = 0;
int found_index = -1;
for(int i=0; i< a.length; i++){ //walk through the array
current_max_value = prev_value + a[i];
//is between beginning and end of this array index?
boolean found = (s >= prev_value && s < current_max_value)? true : false;
if( found ){
found_index = i;
break;
}
prev_value = current_max_value;
}
String selection = "unknown";
if( found_index != -1 ){
selection = giantRat[found_index];
}
System.out.println(selection);
Example at http://ideone.com/xyQlvN
Weighted-random probability in Java
If you want to select "one of these Doubles, with an extreme likelihood of it being the first one, then decreasing likelihood for each item, until the final one is close to 0% chance of it being the final item in the list" then it seems like you want an exponential probability function. (p = x2).
However, you will only know whether you have chosen the right function once you have coded a solution and tried it, and if it does not suit your needs then you will need to choose some other probability function, like a sinusoidal (p = sin( x * PI/2 )) or an inverse ratio (p = 1/x).
So, the important thing is to code an algorithm for selecting an item based on a probability function, so that you can then try any probability function you like.
So, here is one way to do it.
Note the following:
I am seeding the random number generator with 10 in order to always produce the same results. Remove the seeding to get different results at each run.
I am using a list of
Integer
for your "percentages" in order to avoid confusion. Feel free to replace with a list ofDouble
once you have understood how things work.I am providing a few sample probability functions. Try them to see what distributions they yield.
Have fun!
import java.util.*;
public final class Scratch3
{
private Scratch3()
{
}
interface ProbabilityFunction<T>
{
double getProbability( double x );
}
private static double exponential2( double x )
{
assert x >= 0.0 && x <= 1.0;
return StrictMath.pow( x, 2 );
}
private static double exponential3( double x )
{
assert x >= 0.0 && x <= 1.0;
return StrictMath.pow( x, 3 );
}
private static double inverse( double x )
{
assert x >= 0.0 && x <= 1.0;
return 1/x;
}
private static double identity( double x )
{
assert x >= 0.0 && x <= 1.0;
return x;
}
@SuppressWarnings( { "UnsecureRandomNumberGeneration", "ConstantNamingConvention" } )
private static final Random randomNumberGenerator = new Random( 10 );
private static <T> T select( List<T> values, ProbabilityFunction<T> probabilityFunction )
{
double x = randomNumberGenerator.nextDouble();
double p = probabilityFunction.getProbability( x );
int i = (int)( p * values.size() );
return values.get( i );
}
public static void main( String[] args )
{
List<Integer> values = Arrays.asList( 10, 11, 12, 13, 14, 15 );
Map<Integer,Integer> counts = new HashMap<>();
for( int i = 0; i < 10000; i++ )
{
int value = select( values, Scratch3::exponential3 );
counts.merge( value, 1, ( a, b ) -> a + b );
}
for( int value : values )
System.out.println( value + ": " + counts.get( value ) );
}
}
Weighted Randomized Ordering
This seems to work fine:
// Can do a weighted sort on weighted items.
public interface Weighted {
int getWeight();
}
/**
* Weighted sort of an array - orders them at random but the weight of each
* item makes it more likely to be earlier.
*
* @param values
*/
public static void weightedSort(Weighted[] values) {
// Build a list containing as many of each item to make up the full weight.
List<Weighted> full = new ArrayList<>();
for (Weighted v : values) {
// Add a v weight times.
for (int i = 0; i < v.getWeight(); i++) {
full.add(v);
}
}
// Shuffle it.
Collections.shuffle(full);
// Roll them out in the order required.
int i = 0;
do {
// Get the first one in the shuffled list.
Weighted next = full.get(0);
// Put it back into the array.
values[i++] = next;
// Remove all occurrences of that one from the list.
full.remove(next);
} while (!full.isEmpty());
}
// A bunch of weighted items.
enum Heavies implements Weighted {
Rare(1),
Few(3),
Common(6);
final int weight;
Heavies(int weight) {
this.weight = weight;
}
@Override
public int getWeight() {
return weight;
}
}
public void test() {
Weighted[] w = Heavies.values();
for (int i = 0; i < 10; i++) {
// Sort it weighted.
weightedSort(w);
// What did we get.
System.out.println(Arrays.toString(w));
}
}
Essentially for each item to be sorted I add it as many times as needed to a new list. I then shuffle the list and pull the top one out and clar all occurrences of it from the remaining.
The last test run produced:
[Rare, Common, Few]
[Common, Rare, Few]
[Few, Common, Rare]
[Common, Few, Rare]
[Common, Rare, Few]
[Few, Rare, Common]
which seems to be about right.
NB - this algorithm will fail under at least the following conditions:
- The original array has the same object in it more than once.
- The weights of the items are insanely huge.
- Zero or negative weights will almost certainly mess with the results.
Added
This implements Rossum's idea - please be sure to give him the credit for the algorithm.
public static void weightedSort2(Weighted[] values) {
// Calculate the total weight.
int total = 0;
for (Weighted v : values) {
total += v.getWeight();
}
// Start with all of them.
List<Weighted> remaining = new ArrayList(Arrays.asList(values));
// Take each at random - weighted by it's weight.
int which = 0;
do {
// Pick a random point.
int random = (int) (Math.random() * total);
// Pick one from the list.
Weighted picked = null;
int pos = 0;
for (Weighted v : remaining) {
// Pick this ne?
if (pos + v.getWeight() > random) {
picked = v;
break;
}
// Move forward by that much.
pos += v.getWeight();
}
// Removed picked from the remaining.
remaining.remove(picked);
// Reduce total.
total -= picked.getWeight();
// Record picked.
values[which++] = picked;
} while (!remaining.isEmpty());
}
JAVA - How do I generate a random number that is weighted towards certain numbers?
public static void main(String[] args){
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(5);list.add(2);// to be continued ...
int randomInt = list.get((int) (Math.random() * list.size()));
System.out.println(randomInt);
}
You'll get your List of values, and then you just need to pick randomly an index and get back the value
Math.random()
will pick a value in [0;1[, if you multiply by the size of a List
of 10 elements, you'll a value in [0;10[ , when you cast as an int
you'll get 10 possibilities of index to look into the List
The ArrayList
(implementation of List
) allows duplicate element so :
if you add 1,2,2,3,4,5,6,7,8,9 (the order does not matter) you'll have 10 elements with each one to be find with 1/10 = 10/100 = 10% of probability (aka chance), but the element 2 which appears twice will have 20% chance to be choosen in the method, more the number appears more it's chance to be choosen increase, with as you said (number of time the number appears)/(total number of element) (this is basic maths)
How to choose a random number in a weighted way
Accumulate the values in list and call the resulting sum S
. Now generate a random number from 0 to S - 1
. Iterate once more over list
and for each element do the following: if the element is greater then S
than choose that element. Otherwise decrease S
by the element's value and continue on to the next element.
Choosing random value from weighted options in java
If you want to keep it simple, you could do the following:
int random = Math.random();
if (random < 0.05) { // 5/100
return "Platinum";
} else if (random < 0.1) { // 10/100
return "Gold";
} else if (random < 0.2) { // 20/100
return "Silver";
} else if (random < 0.3) { // 30/100
return "Bronze";
} else {
return "You didn't get anything.";
}
Related Topics
Java String.Indexof and Empty Strings
Can't Make Jackson and Lombok Work Together
Extracting Pairs of Words Using String.Split()
Java Date Cut Off Time Information
Time Complexity for Java Arraylist
Javafx Entirely Customized Windows
Should Methods in a Java Interface Be Declared with or Without a Public Access Modifier
How to Capture Https with Fiddler, in Java
How to Replace Groups in Java Regex
JSON - Iterate Through JSONarray
How to Get a Index Value from Foreach Loop in Jstl
Cannot Instantiate the Type List<Product>
Getting Enum Associated with Int Value
Compile Time VS Run Time Dependency - Java
How to Ensure in Java That the Current Local Time Is Correct
Why Does Java's Arrays.Sort Method Use Two Different Sorting Algorithms for Different Types