I’m trying to figure out the best way to code the random occurance of something based on percent without hard coding if-else for each percentage since it will be dynamic.
Ex:
Item 1 drops 20% of the time
Item 2 drops 60% of the time
Item 3 drops 20% of the time
I’m storing the itemID and percentage as HashMaps
// itemID, percentage
HashMap<Integer, Integer>
But is there a better way to store these pairs? I could create a new class with only 2 variables called Pair. Would a Pair class be more efficient than a HashMap for storing multiple itemID and percentage?
I somehow have to go through each item in the HashMap and get the percentages, then add all them up (20 + 60 + 20) to get 100.
I then have to do some random number generation to get a number then check which item will drop. To do this I may have to order check from lowest percentage to highest, but if 2 percentages are the same (20 + 20), then I’m not sure how to pick which one should be dropped.
You would use a java.util.Random object. Define one somewhere, and use it like this (assuming your HashMap is of item IDs to percentages, if not you might need to change it a bit):
boolean dropped = false;
int highest = -1;
int highAmt = -1;
// Lets loop through all the items, because you said you didn't want it hardcoded
for (Entry<Integer, Integer> entry : drops.entrySet()) { // Where drops is the HashMap you spoke of. HashMaps are extremely fast, don't worry about that
if (entry.getValue() > highAmt) {
highest = entry.getKey();
}
// Note <, not <=, because nextInt(100) will generate a number from 0-99, not 1-100.
if (random.nextInt(100) < entry.getValue()) { // Will happen every (entry.getValue() / 100) times
world.dropItemById(x, y, z, entry.getKey()); // Or however you do this
dropped = true; // Set this to true for usage a bit later
break; // Remove this if you want the chance of multiple drops
}
}
if (!dropped) {
world.dropItemById(x, y, z, highest); // Slightly crude way of ensuring something is dropped. If we don't drop something, drop whatever has the highest percentage. If you want the possibility of no drop remove this
}
I assume you’re implementing some sort of loot table. I’ve previously used something along the lines of the following:
public class LootTable {
private final Map<Integer, Object> table = new LinkedHashMap<Integer, Object>();
public void add( int range, Object item ) {
table.put( range, item );
}
public Object get( int value ) {
int range = 0;
for( Entry<Integer, String> entry : table.entrySet() ) {
range += entry.getKey();
if( value < range ) return entry.getValue();
}
return null;
}
}
The LinkedHashMap orders the entries by insertion order. The code is pretty crude (just boshed it out from memory), you’d probably want to add some checks to ensure that the percentiles total 100.
You mention efficiency - did you mean speed or storage space or both? Personally I wouldn’t worry in either case, I assume this would not be a performance-critical part of your code (correct me if I make the wrong assumption) so just design whatever is the easiest to maintain.
Also is there any reason why you were using item IDs instead of the items themselves (I used Object in the above)?
The insertion order technique means you don’t need to worry about ranges of the same value.
An alternative is to just use a simple Map and insert multiple entries to emulate percentile ranges.
e.g. in your example you would add items 1 and 3 once, but item 2 three times, and just used a random value in the range 0 to size-of-table (5) to select one out.
Think weights (not percentages) and cumulative. Builder sorts, sums the weights, divides each by the sum, then each item is the total of this value. One float and binary search.