【问题标题】:Random element from array list with different chances具有不同机会的数组列表中的随机元素
【发布时间】:2016-08-06 10:37:31
【问题描述】:

正如标题中所说,我想使用不同的“随机因子”从列表中获取一个随机元素。上下文如下:

  • 我有一个班级列表,但我不知道班级的数量。
  • 此列表中的所有类都扩展了一个公共超类,其中一个方法返回了它们在列表中被选中的机会百分比。

我可能有一个想法,例如将所有类别出现的机会百分比相加,如果超过 100 %,则将每个百分比除以使相对机会仍然相同,但是 总百分比不超过 100 %。可能不是很清楚,如果不是我再解释一下。

【问题讨论】:

    标签: java


    【解决方案1】:

    只需对数字求和,在[0, 1) 中创建一个随机值,乘以总和并遍历列表,从结果中减去数字,直到得到一个数字

    List<MyClass> elements = ...
    double sum = elements.stream().mapToDouble(MyClass::getChance).sum();
    double rand = Math.random() * sum;
    MyClass choice = null;
    for (MyClass e : elements) {
        choice = e;
        rand -= e.getChance();
        if (rand < 0) {
            break;
        }
    }
    

    【讨论】:

      【解决方案2】:

      假设您在列表中有 3 个对象,这些对象的“百分比”(您应该真正称之为“重量”,因为它不是百分比)分别为 4、7 和 9。

      所有权重之和:20。

      因此,第一个元素平均应从 20 次中选出 4 次,第二个元素应从 20 次中选出 7 次,以此类推。

      因此,生成一个介于 0 和 20 之间的整数。如果结果介于 0 和 4 之间,则选择第一个元素。如果结果在 4 到 11 之间,则选择第二个,如果结果在 11 到 20 之间,则选择最后一个。

      剩下的只是实现细节。

      【讨论】:

      • 就是这样!我不知道我怎么忘记了这样的事情,谢谢。
      【解决方案3】:

      如果您只想进行一次(或几次)查找,那么answer by @fabian 很好。

      但是,如果您要经常这样做,那么该解决方案执行的顺序搜索效率不高。

      要获得更有效的解决方案,您需要更有针对性的查找,因此您需要按累积机会来组织数据。这可以使用二进制搜索作为数组来完成,也可以通过累积机会作为NavigableMap 来完成。

      使用NavigableMap,例如TreeMap,然后您可以使用higherEntry(K key) 查找所选对象:

      返回与严格大于给定键的最小键关联的键值映射,如果没有这样的键,则返回null

      所以,这里是示例代码:

      public class MyObj {
          private final String name;
          private final int weight;
          public MyObj(String name, int weight) {
              this.name = name;
              this.weight = weight;
          }
          public String getName() {
              return this.name;
          }
          public int getWeight() {
              return this.weight;
          }
          @Override
          public String toString() {
              return this.name;
          }
      
          public static void main(String[] args) {
              // Build list of objects
              List<MyObj> list = Arrays.asList(
                      new MyObj("A", 2),
                      new MyObj("B", 6),
                      new MyObj("C", 12)
              );
      
              // Build map keyed by cumulative weight
              NavigableMap<Integer, MyObj> weighedMap = new TreeMap<>();
              int totalWeight = 0;
              for (MyObj obj : list) {
                  totalWeight += obj.getWeight();
                  weighedMap.put(totalWeight, obj);
              }
              System.out.println(weighedMap);
      
              // Pick 20 objects randomly according to weight
              Random rnd = new Random();
              for (int i = 0; i < 20; i++) {
                  int pick = rnd.nextInt(totalWeight);
                  MyObj obj = weighedMap.higherEntry(pick).getValue();
                  System.out.printf("%2d: %s%n", pick, obj);
              }
          }
      }
      

      样本输出

      {2=A, 8=B, 20=C}
      14: C
      10: C
       9: C
       5: B
      11: C
       3: B
       1: A
       0: A
       1: A
       7: B
       4: B
      11: C
      17: C
      15: C
       4: B
      16: C
       9: C
      17: C
      19: C
       2: B
      

      【讨论】:

      • 解释得很好。然而@JB Nizet 的答案更适合我的问题,因为我只需要用总重量初始化我的班级,为每个班级定义一个重量的“阈值”,然后随机化。
      猜你喜欢
      • 2023-03-29
      • 1970-01-01
      • 2017-11-10
      • 1970-01-01
      • 1970-01-01
      • 2018-09-10
      • 2011-01-09
      • 2013-11-14
      • 2010-10-23
      相关资源
      最近更新 更多