Java中的随机加权选择

Java中的随机加权选择,java,random,double,Java,Random,Double,我想从一组中随机选择一个项目,但选择任何项目的机会应与相关的权重成比例 输入示例: item weight ---- ------ sword of misery 10 shield of happy 5 potion of dying 6 triple-edged sword 1 因此,如果我有4个可能的项目,得到任何一个项目没有重量的机会将是1/4 在这种情况下,用户

我想从一组中随机选择一个项目,但选择任何项目的机会应与相关的权重成比例

输入示例:

item                weight
----                ------
sword of misery         10
shield of happy          5
potion of dying          6
triple-edged sword       1
因此,如果我有4个可能的项目,得到任何一个项目没有重量的机会将是1/4

在这种情况下,用户获得痛苦之剑的可能性应该是三刃剑的10倍


如何在Java中进行加权随机选择?

您将找不到解决此类问题的框架,因为请求的功能只不过是一个简单的函数。这样做:

interface Item {
    double getWeight();
}

class RandomItemChooser {
    public Item chooseOnWeight(List<Item> items) {
        double completeWeight = 0.0;
        for (Item item : items)
            completeWeight += item.getWeight();
        double r = Math.random() * completeWeight;
        double countWeight = 0.0;
        for (Item item : items) {
            countWeight += item.getWeight();
            if (countWeight >= r)
                return item;
        }
        throw new RuntimeException("Should never be shown.");
    }
}
接口项{
双getWeight();
}
类随机项选择器{
公用项目选择按重量(列出项目){
双全重=0.0;
用于(项目:项目)
completeWeight+=item.getWeight();
double r=Math.random()*完全权重;
双计数权重=0.0;
用于(项目:项目){
countWeight+=item.getWeight();
if(countWeight>=r)
退货项目;
}
抛出新的RuntimeException(“不应显示”);
}
}

我会使用导航地图

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();
    }
}
公共类随机集合{
private final NavigableMap map=新树映射();
私有最终随机;
私人双总=0;
公共图书馆馆藏(){
这个(新的Random());
}
公共随机收集(随机){
这个。随机=随机;
}
公共随机集合添加(双倍权重,E结果){

如果(weightApache Commons中现在有一个用于此的类:

或者在Java 8中:

itemSet.stream().map(i -> new Pair(i, i.getWeight())).collect(toList());
注意:
Pair
这里需要是
org.apache.commons.math3.util.Pair
,而不是
org.apache.commons.lang3.tuple.Pair

使用别名方法 如果你要掷很多次(比如在游戏中),你应该使用别名方法

下面的代码确实是这种别名方法的相当长的实现。但这是因为初始化部分。元素的检索非常快(请参阅
next
applyAsInt
方法,它们不会循环)

用法
公共类随机集合{
private final NavigableMap map=新树映射();
私人双总=0;
公共无效添加(双重权重,E结果){

如果(权重如果选择后需要删除元素,则可以使用其他解决方案。将所有元素添加到“LinkedList”中,每个元素的添加次数必须与其权重相同,然后使用
集合。shuffle()
根据

使用默认的随机性源随机排列指定的列表。所有排列的可能性大致相等

最后,使用
pop()
removeFirst()

Map Map=newhashmap(){{
放(五,五);;
付诸表决(“四”,4);
付诸表决(“三”,3);
放(二),二;;
付诸表决(“一”,1);
}};
LinkedList=新建LinkedList();
对于(Map.Entry:Map.entrySet()){
for(int i=0;i
139

有一个简单的算法用于随机挑选项目,其中项目具有单独的权重:

  • 计算所有权重之和

  • 选择一个大于等于0且小于权重之和的随机数

  • 一次检查一个项目,从随机数中减去它们的重量,直到得到随机数小于该项目重量的项目


  • 项目列表应该使用哪个顺序?从高到小?谢谢。您不需要对列表进行排序。顺序无关紧要。列表中项目的顺序无关紧要,因为
    r
    的值是均匀分布的随机数,这意味着
    r
    成为某个值的概率等于所有其他值er值
    r
    可能是。因此列表中的项目不“受欢迎”它们在列表中的位置并不重要。如果您使用
    countWeight>=r
    ,则可以选择权重为零的项,如果它恰好是第一个项,并且r=0,则可能发生。问题是关于加权随机。这并不能解释这一点。@GuillaumePerrot实际上,此代码是加权选择的。我现在如何得到它b但是如果你的权重值很大,那么这是非常低效的,比如说100000的权重,你需要向列表中添加100000个元素。@GuillaumePerrot是的,你是对的。尽管如此,这种算法在某些情况下可以使用。在处理这个问题的项目中,请检查基准:@cpu\u meldown The cost of log(n);)我假设这是因为higherEntry?的实现,肯定是使用了您的代码并对其进行了调整。再次感谢。感谢您的回答Peter!它工作得很好。如果有人像我一样想知道
    如果(weight@PeterLawrey谢谢,如果我们想支持随机删除和随机选择,您将如何更新此内容?这在答案列表中应该更高…为什么要重新发明轮子?此外,
    EnumeratedDistribution
    允许一次选择多个样本,这非常简洁。最佳答案是常识数学3现在不受支持。
    EnumeratedDistribution
    的功能已在库中移动到。
    Item selectedItem = new EnumeratedDistribution<>(itemWeights).sample();
    
    final List<Pair<Item, Double>> itemWeights = Collections.newArrayList();
    for (Item i: itemSet) {
        itemWeights.add(new Pair(i, i.getWeight()));
    }
    
    itemSet.stream().map(i -> new Pair(i, i.getWeight())).collect(toList());
    
    Set<Item> items = ... ;
    ToDoubleFunction<Item> weighter = ... ;
    
    Random random = new Random();
    
    RandomSelector<T> selector = RandomSelector.weighted(items, weighter);
    Item drop = selector.next(random);
    
    import static java.util.Objects.requireNonNull;
    
    import java.util.*;
    import java.util.function.*;
    
    public final class RandomSelector<T> {
    
      public static <T> RandomSelector<T> weighted(Set<T> elements, ToDoubleFunction<? super T> weighter)
          throws IllegalArgumentException {
        requireNonNull(elements, "elements must not be null");
        requireNonNull(weighter, "weighter must not be null");
        if (elements.isEmpty()) { throw new IllegalArgumentException("elements must not be empty"); }
    
        // Array is faster than anything. Use that.
        int size = elements.size();
        T[] elementArray = elements.toArray((T[]) new Object[size]);
    
        double totalWeight = 0d;
        double[] discreteProbabilities = new double[size];
    
        // Retrieve the probabilities
        for (int i = 0; i < size; i++) {
          double weight = weighter.applyAsDouble(elementArray[i]);
          if (weight < 0.0d) { throw new IllegalArgumentException("weighter may not return a negative number"); }
          discreteProbabilities[i] = weight;
          totalWeight += weight;
        }
        if (totalWeight == 0.0d) { throw new IllegalArgumentException("the total weight of elements must be greater than 0"); }
    
        // Normalize the probabilities
        for (int i = 0; i < size; i++) {
          discreteProbabilities[i] /= totalWeight;
        }
        return new RandomSelector<>(elementArray, new RandomWeightedSelection(discreteProbabilities));
      }
    
      private final T[] elements;
      private final ToIntFunction<Random> selection;
    
      private RandomSelector(T[] elements, ToIntFunction<Random> selection) {
        this.elements = elements;
        this.selection = selection;
      }
    
      public T next(Random random) {
        return elements[selection.applyAsInt(random)];
      }
    
      private static class RandomWeightedSelection implements ToIntFunction<Random> {
        // Alias method implementation O(1)
        // using Vose's algorithm to initialize O(n)
    
        private final double[] probabilities;
        private final int[] alias;
    
        RandomWeightedSelection(double[] probabilities) {
          int size = probabilities.length;
    
          double average = 1.0d / size;
          int[] small = new int[size];
          int smallSize = 0;
          int[] large = new int[size];
          int largeSize = 0;
    
          // Describe a column as either small (below average) or large (above average).
          for (int i = 0; i < size; i++) {
            if (probabilities[i] < average) {
              small[smallSize++] = i;
            } else {
              large[largeSize++] = i;
            }
          }
    
          // For each column, saturate a small probability to average with a large probability.
          while (largeSize != 0 && smallSize != 0) {
            int less = small[--smallSize];
            int more = large[--largeSize];
            probabilities[less] = probabilities[less] * size;
            alias[less] = more;
            probabilities[more] += probabilities[less] - average;
            if (probabilities[more] < average) {
              small[smallSize++] = more;
            } else {
              large[largeSize++] = more;
            }
          }
    
          // Flush unused columns.
          while (smallSize != 0) {
            probabilities[small[--smallSize]] = 1.0d;
          }
          while (largeSize != 0) {
            probabilities[large[--largeSize]] = 1.0d;
          }
        }
    
        @Override public int applyAsInt(Random random) {
          // Call random once to decide which column will be used.
          int column = random.nextInt(probabilities.length);
    
          // Call random a second time to decide which will be used: the column or the alias.
          if (random.nextDouble() < probabilities[column]) {
            return column;
          } else {
            return alias[column];
          }
        }
      }
    }
    
    public class RandomCollection<E> {
      private final NavigableMap<Double, E> map = new TreeMap<Double, E>();
      private double total = 0;
    
      public void add(double weight, E result) {
        if (weight <= 0 || map.containsValue(result))
          return;
        total += weight;
        map.put(total, result);
      }
    
      public E next() {
        double value = ThreadLocalRandom.current().nextDouble() * total;
        return map.ceilingEntry(value).getValue();
      }
    }
    
    Map<String, Integer> map = new HashMap<String, Integer>() {{
        put("Five", 5);
        put("Four", 4);
        put("Three", 3);
        put("Two", 2);
        put("One", 1);
    }};
    
    LinkedList<String> list = new LinkedList<>();
    
    for (Map.Entry<String, Integer> entry : map.entrySet()) {
        for (int i = 0; i < entry.getValue(); i++) {
            list.add(entry.getKey());
        }
    }
    
    Collections.shuffle(list);
    
    int size = list.size();
    for (int i = 0; i < size; i++) {
        System.out.println(list.pop());
    }