【问题标题】:Random value from enum with probability具有概率的枚举中的随机值
【发布时间】:2011-03-11 05:19:53
【问题描述】:

我有一个枚举,我想从中随机选择一个值,但不是真正随机的。我希望到目前为止,一些值不太可能被选中。这是我到目前为止所拥有的......

private enum Type{
        TYPE_A, TYPE_B, TYPE_C, TYPE_D, TYPE_E;

        private static final List<Type> VALUES =
            Collections.unmodifiableList(Arrays.asList(values()));
          private static final int SIZE = VALUES.size();
          private static final Random RANDOM = new Random();

          public static Type randomType()  {
            return VALUES.get(RANDOM.nextInt(SIZE));
          }
    }

是否有一种有效的方法可以为这些值中的每一个分配概率?

here找到的代码

【问题讨论】:

    标签: java enums


    【解决方案1】:

    有几种方法,其中一种,类似于您的方法

    private enum Type{
        TYPE_A(10 /*10 - weight of this type*/), TYPE_B(1), TYPE_C(5), TYPE_D(20), TYPE_E(7);
    
    private int weight;
    
    private Type(int weight) {
        this.weight = weight;
    }
    
    private int getWeight() {
        return weight;
    }
    
    
        private static final List<Type> VALUES =
            Collections.unmodifiableList(Arrays.asList(values()));
    
        private int summWeigts() {
           int summ = 0;
           foreach(Type value: VALUES) 
              summ += value.getWeight();
           return summ;
        }
        private static final int SIZE = summWeigts();
        private static final Random RANDOM = new Random();
    
        public static Type randomType()  {
            int randomNum = RANDOM.nextInt(SIZE);
            int currentWeightSumm = 0;
            for(Type currentValue: VALUES) {
               if (randomNum > currentWeightSumm && 
                   randomNum <= (currentWeightSumm + currentValue.getWeight()) {
                 break;
               }
               currentWeightSumm += currentValue.getWeight();
            }
    
            return currentValue.get();
        }
    }
    

    【讨论】:

    • 我想你误会了。想法是-您将所有权重相加。现在想象一下最大值 = 计算值的标尺。现在在这条规则上划线,所以首先是(根据我在回答中的例子)0到10,第二个是10到11,第三个是11到16,依此类推。现在将您的手指指向我们标尺的随机位置,然后查看您指向的部分。编辑答案。
    • 这实际上是我最终做的。谢谢!
    • 顺便说一句,如果在构造函数中预先计算类型的边界,您可以提高性能,但在五种类型上它不是实际的。
    • 当只有几个值可供选择时,此算法是标准算法。当许多选项时,您可以使用基于树的选择算法加速算法。为此,请将 [0,1) 区间拆分为若干段(例如 100)。然后,对于每个区间,您确定样本是否恰好有一个可能的结果。如果是这样,请用该值标记树中的那片叶子。对于具有多种可能结果的任何段,您可以构建仅跨越该段的另一棵树。根据您对概率的分辨率限制递归。
    【解决方案2】:

    这是一个通用的approach,用于随机选择enum 值。你可以按照here的建议调整概率。

    【讨论】:

      【解决方案3】:

      假设您有有限数量的值,您可以为每个值设置一个单独的权重数组 (float[] weights;)。这些值将介于 0 和 1 之间。当您选择一个随机值时,还会在两者之间生成另一​​个随机数,并且仅当第二个生成的数字低于该值的权重时才选择该值。

      【讨论】:

        【解决方案4】:

        您可以通过提供自定义构造函数来创建具有关联数据的枚举,并使用构造函数为概率分配权重,然后

        public enum WeightedEnum {
            ONE(1), TWO(2), THREE(3);
            private WeightedEnum(int weight) {
                this.weight = weight;
            }
            public int getWeight() {
                return this.weight;
            }
            private final int weight;
        
            public static WeightedEnum randomType()  {
                // select one based on random value and relative weight
            }
        }
        

        【讨论】:

          【解决方案5】:
          import java.util.*;
          enum R {
              a(.1),b(.2),c(.3),d(.4);
              R(final double p) {
                  this.p=p;
              }
              private static void init() {
                  sums=new double[values().length+1];
                  sums[0]=0;
                  for(int i=0;i<values().length;i++)
                      sums[i+1]=values()[i].p+sums[i];
                  once=true;
              }
              static R random() {
                  if (!once) init();
                  final double x=Math.random();
                  for(int i=0;i<values().length;i++)
                      if (sums[i]<=x&&x<sums[i+1]) return values()[i];
                  throw new RuntimeException("should not happen!");
              }
              static boolean check() {
                  double sum=0;
                  for(R r:R.values())
                      sum+=r.p;
                  return(Math.abs(sum-1)<epsilon);
              }
              final double p;
              static final double epsilon=.000001;
              static double[] sums;
              static boolean once=false;
          }
          public class Main{
              public static void main(String[] args) {
                  if (!R.check()) throw new RuntimeException("values should sum to one!");
                  final Map<R,Integer> bins=new EnumMap<R,Integer>(R.class);
                  for(R r:R.values())
                      bins.put(r,0);
                  final int n=1000000;
                  for(int i=0;i<n;i++) {
                      final R r=R.random();
                      bins.put(r,bins.get(r)+1);
                  }
                  for(R r:R.values())
                      System.out.println(r+" "+r.p+" "+bins.get(r)/(double)n);
              }
          }
          

          【讨论】:

          • 我不明白 main 方法中所做的事情。似乎比@AlexeySviridov 提出的方法要复杂得多,我把它简化了一点。
          • 只是一些代码来执行随机放置并查看分布情况。
          【解决方案6】:

          这是另一个允许在运行时指定分布的替代方法。

          包括 Alexey Sviridov 的建议。当有很多选项时,方法 random() 也可以包含 Ted Dunning 的建议。

               private enum Option {
          
                  OPTION_1, OPTION_2, OPTION_3, OPTION_4;
                  static private final Integer OPTION_COUNT = EnumSet.allOf(Option.class).size();
                  static private final EnumMap<Option, Integer> buckets = new EnumMap<Option, Integer>(Option.class);
                  static private final Random random = new Random();
                  static private Integer total = 0;
          
                  static void setDistribution(Short[] distribution) {
                     if (distribution.length < OPTION_COUNT) {
                        throw new ArrayIndexOutOfBoundsException("distribution too short");
                     }
                     total = 0;
                     Short dist;
                     for (Option option : EnumSet.allOf(Option.class)) {
                        dist = distribution[option.ordinal()];
                        total += (dist < 0) ? 0 : dist;
                        buckets.put(option, total);
                     }
                  }
          
                  static Option random() {
                     Integer rnd = random.nextInt(total);
                     for (Option option : EnumSet.allOf(Option.class)) {
                        if (buckets.get(option) > rnd) {
                           return option;
                        }
                     }
                     throw new IndexOutOfBoundsException();
                  }
               }
          

          【讨论】:

            【解决方案7】:

            您可以使用Apache Commons Math 库中的EnumeratedDistribution

            EnumeratedDistribution<Type> distribution = new EnumeratedDistribution<>(
                    RandomGeneratorFactory.createRandomGenerator(new Random()),
                    List.of(
                            new Pair<>(Type.TYPE_A, 0.2), // get TYPE_A with probability 0.2
                            new Pair<>(Type.TYPE_B, 0.5), // get TYPE_B with probability 0.5
                            new Pair<>(Type.TYPE_C, 0.3)  // get TYPE_C with probability 0.3
                    )
            );
            
            Type mySample = distribution.sample();
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2015-06-08
              • 1970-01-01
              • 2015-05-18
              • 2012-02-11
              • 1970-01-01
              • 2017-05-16
              • 1970-01-01
              相关资源
              最近更新 更多