为什么Math.random忽略了最后一个元素?

时间:2017-07-02 17:46:37

标签: java

我正在创建一个简单的方法来模拟洗牌。我的想法是存储原始牌组的大小,并且只要大小不是负值就重复循环。

在循环中,我从列表中复制一个对象(卡),并将其放在另一个列表中。从原始列表中删除,然后恢复循环。

while(size >= 0){
        int random = (int) ((Math.random() * size) + 0);    
        shuffledDeck.add(this.orderedDeck.get(random));
        orderedDeck.remove(random);
        size--;
    }

这个特殊套牌的大小是

int size = this.getDeckSize()-1; //51 (52 cards, from 0 to 51)

我的问题是,在几次不同的尝试中,洗牌的牌组中的最后一张牌与未洗牌的牌组中的最后一张牌一致。这表明random永远不会等于size-1

我怎样才能让最后一张卡实际上可以洗牌?

(换句话说,为什么random等于size-1?)

3 个答案:

答案 0 :(得分:3)

Math.random()永远不会返回1.0,因为docs会解释:

  

返回带有正号的double值,大于或等于0.0且小于1.0。

即使它确实返回1.0,它也会以极小的概率进行,并且您的代码会向下舍入任何其他值,因此您仍然无法统一获得所需的结果。

正如其他人已经注意到您在代码中有一些进一步的算术错误,但即使纠正它们也不会产生正确分布的随机值。

您不应该使用Math.random()执行此任务 - 而是使用Random.nextInt(n),它旨在正确返回所需范围内的统一值。

正确处理随机数据源非常棘手。根据经验,如果您发现自己在随机值上进行算术运算,那么您可能会做错事(例如生成非均匀结果),您应该寻找一种现有函数来提供您正在寻找的随机性类型相反。

答案 1 :(得分:2)

看起来size已初始化为orderedDeck.size() - 1。 由于(int) (Math.random() * size)会返回[0, size)范围内的值,即size本身从未包含, 最后一个元素永远不会被选中。

您可以使用size + 1作为上限而不是size来修复:

while (size >= 0) {
  int random = (int) (Math.random() * (size + 1));
  shuffledDeck.add(orderedDeck.get(random));
  orderedDeck.remove(random);
  size--;
}

但是,请不要使用此技术来创建混洗列表。

目前的做法非常低效, 因为从列表中间删除元素效率很低。 使用Fisher-Yates shuffle实现高效排序会更好,更容易。 例如:

Random random = new Random();
for (int i = list.size() - 1; i > 0; i--) {
  int j = random.nextInt(i);
  int tmp = list.get(i);
  list.set(i, list.get(j));
  list.set(j, tmp);
}

答案 2 :(得分:0)

int size = this.getDeckSize()-1; //51 (52 cards, from 0 to 51)

问题是你从返回的值中减去1。 getDeckSize()表示你的牌组中有正确数量的元素(52张牌)。你不应该减去1。