选择一个随机加权元素,带样本,无替换

时间:2016-09-19 20:03:01

标签: java random probability

给出一个表示战利品表中奖励的结构,其中a是奖励类型,2是整数加权,这意味着a被拉出的可能性是d的两倍。

Map{
  "a" -> 2
  "b" -> 2
  "c" -> 2
  "d" -> 1
  "e" -> 1
  "f" -> 1
}

如何生成用于显示目的的样本+获胜者?

我当前的(伪)代码:

list out;
foreach(entry:map){
  for(entry.value){
    out.add(a)
  }
}

然后创建一个样本进行显示。

Collections.shuffle(out);
List display = out.stream()
  .distinct()
  .limit(8)
  .collect(Collectors.toList());

有了这段代码,我是否可以信任。如果我选​​择胜利者,我可以信任。但不能歪曲赔率

winner = display.get(0);

我意识到添加最后一个元素可能会导致结果偏差,因为在发生不同的调用之后,它会使更多选择一个权重更低的数字。

但是选择流的第一个元素应该值得信任吗?因为它是在之前选择的.distinct有它的状态诱导效果吗?

3 个答案:

答案 0 :(得分:1)

查看Stochastic universal samplingFitness proportionate selection。根据权重取一个样本的简单方法可以通过将每个元素表示为与其权重成比例的长度来解释。 E.g:

Map{
  "a" -> 2 // weight 2
  "b" -> 2
  "c" -> 2
  "d" -> 1
  "e" -> 1
  "f" -> 1
}
=>
Map{
  "a" -> (0,2) // weight 2 -- is now length of the interval
  "b" -> (2,4) // ...
  "c" -> (4,6)
  "d" -> (6,7)
  "e" -> (7,8)
  "f" -> (8,9)
}

然后你从0到9选择随机数9*Math.random()(作为指向范围的指针)并检查它属于哪个区间 - 这是你输入权重的随机样本w.r.t。重复,直到获得所需数量的样本(如果愿意,忽略重复样本)......

当然这是一个惯用的解释,在实际代码中你只保留上限,因为较低的只是前一个元素的上半部分。然后你将选择第一个具有随机指针上方边界的元素。

更新:从数学的角度来看,重复元素的原始方法是可以的(双重权重的拾取概率是双倍),但是当权重很高时,这将是一个问题:Map{"a"->1000 "b"->100000}。它也不能很好地处理实值权重。

答案 1 :(得分:1)

我喜欢马丁的答案,但我也会根据他提出的表现问题发布我自己作为警告/替代方案。使用Map可以实现与他自己非常相似的实现(我将使用HashMap,因为它是我的最爱)。

private final AtomicLong idxCounter = new AtomicLong(0);
private final Map<Long, Item> dropTable = new HashMap<>();
public void addDrop(Item item, long relativeFrequency) {
    while (relativeFrequency-- > 0) {
        Long nextIdx = idxCounter.getAndIncrement();
        dropTable.put(nextIdx, item);
    }
}

private static final Random rng = new Random(System.currentTimeMillis());
public Item getRandomDrop() {
    Long size = idxCounter.get();
    // randomValue will be something in the interval [0, size), which 
    // should cover the whole dropTable.
    // See http://stackoverflow.com/questions/2546078 for a fair
    // implementation of nextLong.
    Long randomValue = nextLong(rng, size); 
    return dropTable.get(randomValue); 
}

通过HashMap中的键获取值非常快。您可以通过指定dropTable初始容量和加载因子(请参阅javadoc for HashMap)进一步优化它,但这取决于您自己的判断。

只要没有别的东西可以用dropTable玩弄它,它也是线程安全的!

答案 2 :(得分:0)

您的数据结构实现似乎有点奇怪。我会做这样的事情:

Map{
  0 -> "a"
  2 -> "b"
  4 -> "c"
  5 -> "d"
  6 -> "e"
  7 -> "f"
}

然后,为了使事情更快(或允许一个非常大的战利品表),我有一个像int maxValue = 7的值。现在,为了从表中获取战利品,我可以在lootDrop0(包括)之间调用随机整数maxValue。然后我可以遍历我的表,找到小于或等于lootdrop的最大值。如果你需要将地图保持为string to integer映射,并控制整数映射,那么这样做也是相当简单的。

如果您不想走那么远,您可以在解决方案中获得0到8之间的随机整数,这仍然有效。

你有没有理由坚持这种表述?