lucasparmentier388
lucasparmentier388

Reputation: 27

Randomly getting value with percentage of chance in Java

I would like to be able to return (and in our case, for the example, display) an item of an enumeration, according to a certain predefined percentage of chance for each item. An illustration in code of this enumeration just below :

package me.lucas.test;

public enum Item {

    ITEM_1("Item 1", 30),
    ITEM_2("Item 2", 30),
    ITEM_3("Item 3", 10),
    ITEM_4("Item 4", 65);

    private final String name;
    private final int percentage;

    Item(String name, int percentage) {
        this.name = name;
        this.percentage = percentage;
    }

    public String getName() {
        return name;
    }

    public int getPercentage() {
        return percentage;
    }

}

Also, these items will be stored in a List.

package me.lucas.test;

import java.util.ArrayList;
import java.util.List;

public class Program {

    private final List<Item> items = new ArrayList<>();

    /**
     * Displays an item among those in the list, according to their percentage chance of appearing.
     * @param items The items
     */
    public void displayItem(List<Item> items) {
        Item item;

        item = ?

        System.out.println("Selected item : " + item.getName() + " with percentage : " + item.getPercentage());
    }

}

Namely that, as in this example, two items can have the same percentage of chance to appear.

I also looked at the following topic (Java Random Percentage Chance) but in my case, the percentages are not predefined in the expressions directly but in the enumeration.

So I would appreciate your help. Thank you in advance for taking the time to help me.

Upvotes: 0

Views: 1673

Answers (2)

Oleg Cherednik
Oleg Cherednik

Reputation: 18245

BE SIMPLE!!!

  1. Create an array.
  2. Put into this array all items, percentage count each.
  3. Shuffle the array.
  4. Retrieve random element form this array.

public class Foo {

    public static void main(String... args) {
        Item[] items = generateItemArray();
        Random random = new Random();

        while (true) {
            System.out.println(items[random.nextInt(items.length)]);
        }
    }

    private static Item[] generateItemArray() {
        List<Item> items = new ArrayList<>();

        for (Item item : Item.values())
            for (int i = 0; i < item.percentage; i++)
                items.add(item);

        Collections.shuffle(items);
        return items.toArray(Item[]::new);
    }

    public enum Item {
        ITEM_1("Item 1", 30),
        ITEM_2("Item 2", 30),
        ITEM_3("Item 3", 10),
        ITEM_4("Item 4", 65);

        private final String name;
        private final int percentage;

        Item(String name, int percentage) {
            this.name = name;
            this.percentage = percentage;
        }

    }
}

Upvotes: 1

ToxicWaste
ToxicWaste

Reputation: 135

Visually speaking you implement a Wheel of Chance

The wheel will land on any position 1 to 100. Now you just need to read out, which possibilities are at this position. If the sum is < 100 there may be nothing in this position. If the sum is > 100 there may be more than one item in this position.

Whheel of Chacne

All Percentages are wrapping from 1 to 100 - achieved by modulo. The random Number you generate is between 1 and 100, or in this case between 0 and 99 (see Javadoc of Random).

In Code this could look as follows:

public class RandomExample<T extends Object> {
    List<Item> items = new LinkedList<>();
    Random rand = new Random();
    
    public void addItem(int percentage, T object) {
        items.add(new Item(percentage, object));
    }
    
    public List<Item> getRandom() {
        List<Item> result = new ArrayList<>();
        int hit = rand.nextInt(100);
        items.forEach(i -> {
            if(i.isHit(hit)) {
                result.add(i);
            }
        });
        return result;
    }
    
    public class Item {
        private int percentage;
        private T object;
        private int hitLow;
        private int hitHigh;
        
        public Item(int percentage, T object) {
            this.percentage = percentage;
            this.object = object;
            int sum = 0;
            for(Item i : items) {
                sum+=i.getPercentage();
            }
            hitLow = sum % 100;
            hitHigh = (sum+percentage) % 100;
        }
        
        public int getPercentage() {
            return percentage;
        }
        
        public T getObject() {
            return object;
        }
        
        public boolean isHit(int hitVal) {
            boolean isHit = hitLow == hitHigh; //exactly 100%
            isHit |= hitLow <= hitVal && hitHigh >= hitVal;
            return isHit;
        }
    }

}

The method RandomExample.Item.isHit(int) still needs an extra condition to handle chances correctly, which wrap around 100 (eg. hitLow = 90 and hitHigh = 10). But you get the concept and should be able to adapt it to your needs.

Another question you need to ask yourself is: what happens if somebody passes a percentage greater than 100. My intuition says you should throw an exception. But that is for you to decide and implement.

Upvotes: 1

Related Questions