前言

适用场景:一个箱子中有3个元素A,B,C,抽到A的概率为50%,B概率为20%,C概率为30%。我们可以给A,B,C各附加一个权重值,如50,20,30。

实现原理

以上面的A,B,C为例,A的权重区间为[0,50),B的区间为[50,70), C区间为[70,100),生成一个100之内的随机值,看落在哪个区间内,就是那个元素。

代码实现

import java.util.SortedMap;
import java.util.TreeMap;


public class WeightRandom<T> {

  private final TreeMap<Integer, T> weightMap;

  /**
   * 创建权重随机获取器
   *
   * @param <T> 权重随机获取的对象类型
   * @return {@link WeightRandom}
   */
  public static <T> WeightRandom<T> create() {
    return new WeightRandom<>();
  }

  /**
   * 构造
   */
  private WeightRandom() {
    weightMap = new TreeMap<>();
  }

  /**
   * 增加对象
   *
   * @param obj 对象
   * @param weight 权重
   */
  public void add(T obj, int weight) {
    if (weight > 0) {
      int lastWeight = (this.weightMap.size() == 0) ? 0 : this.weightMap.lastKey();
      this.weightMap.put(weight + lastWeight, obj);// 权重累加
    }
  }

  /**
   * 清空权重表
   */
  public void clear() {
    this.weightMap.clear();
  }

  /**
   * 下一个随机对象
   *
   * @return 随机对象
   */
  public T next() {
    int randomWeight = (int) (this.weightMap.lastKey() * Math.random());
    SortedMap<Integer, T> tailMap = this.weightMap.tailMap(randomWeight, false);
    return this.weightMap.get(tailMap.firstKey());
  }

}

使用

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

public class Client {

  public static void main(String[] args) {
    WeightRandom<String> weightRandom = WeightRandom.create();
    weightRandom.add("A", 50);
    weightRandom.add("B", 20);
    weightRandom.add("C", 30);
    testRate(weightRandom);
  }

  private static void testRate(WeightRandom<String> weightRandom) {
    Map<String, Integer> countMap = new HashMap<>();
    int total = 100_000_000;
    for (int i = 0; i < total; i++) {
      String randomKey = weightRandom.next();
      countMap.put(randomKey, countMap.getOrDefault(randomKey, 0) + 1);
    }
    for (Entry<String, Integer> entry : countMap.entrySet()) {
      System.out.println(String.format("%s count: %s,freq: %s", entry.getKey(), entry.getValue(),
          1.0 * entry.getValue() / total));
    }
  }
}

输出结果为

A count: 49994992,freq: 0.49994992
B count: 20005474,freq: 0.20005474
C count: 29999534,freq: 0.29999534

频率和概率基本相同。