36
If you want runtime selection efficiency then taking a little more time on the setup would probably be best. Here is one possible solution. It has more code but guarantees log(n) selection.
WeightedItemSelector Implements selection of a random object from a collection of weighted objects. Selection is guaranteed to run in log(n) time.
public class WeightedItemSelector<T> {
private final Random rnd = new Random();
private final TreeMap<Object, Range<T>> ranges = new TreeMap<Object, Range<T>>();
private int rangeSize; // Lowest integer higher than the top of the highest range.
public WeightedItemSelector(List<WeightedItem<T>> weightedItems) {
int bottom = 0; // Increments by size of non zero range added as ranges grows.
for(WeightedItem<T> wi : weightedItems) {
int weight = wi.getWeight();
if(weight > 0) {
int top = bottom + weight - 1;
Range<T> r = new Range<T>(bottom, top, wi);
if(ranges.containsKey(r)) {
Range<T> other = ranges.get(r);
throw new IllegalArgumentException(String.format("Range %s conflicts with range %s", r, other));
}
ranges.put(r, r);
bottom = top + 1;
}
}
rangeSize = bottom;
}
public WeightedItem<T> select() {
Integer key = rnd.nextInt(rangeSize);
Range<T> r = ranges.get(key);
if(r == null)
return null;
return r.weightedItem;
}
}
Range Implements range selection to leverage TreeMap selection.
class Range<T> implements Comparable<Object>{
final int bottom;
final int top;
final WeightedItem<T> weightedItem;
public Range(int bottom, int top, WeightedItem<T> wi) {
this.bottom = bottom;