【问题标题】:Why is Math.random ignoring the last element? [duplicate]为什么 Math.random 忽略最后一个元素? [复制]
【发布时间】:2017-07-02 17:46:36
【问题描述】:

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

在循环过程中,我从列表中复制了一个对象(卡片),并将其放在另一个列表中。从原始列表中删除,并继续循环。

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?)

【问题讨论】:

  • 你说你有52张卡片,然后继续说大小是51。大小应该是卡片的数量,即52。(Math.random()永远不会返回1.0,如果那是什么你担心)
  • 你考虑过new Random().nextInt(size)吗?
  • 请编辑您的代码以显示minimal reproducible example
  • int random = (int) ((Math.random() * size) + 0); 是干什么用的?为什么要加 0?

标签: java


【解决方案1】:

Math.random() 永远不会返回 1.0,正如 docs 解释的那样:

返回一个带正号的双精度值,大于或等于 0.0 且小于 1.0。

即使它确实返回了1.0,它也会以极小的概率这样做,并且您的代码会将任何其他值向下舍入,因此您仍然不会一致地得到您想要的结果。

正如其他人指出的那样,您的代码中还有一些算术错误,但即使纠正它们也不会产生正确分布的随机值。

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

正确处理随机数据源很棘手。根据经验,如果您发现自己对随机值进行算术运算,则很可能您做错了什么(例如生成不均匀的结果),您应该寻找提供您正在寻找的随机类型的现有函数代替。

【讨论】:

  • 从 RNG 返回 1.0 不是问题,因为这会给出一个太大的索引。
  • 明确提到将double 转换为int 向下舍入也可能对这个答案有用。如果将 size 增加 1,Math.random 应该可以正常工作,但 nextInt 确实似乎更合适且不易出错。
  • @Code-Apprentice 即使纠正算术错误Math.random() 也不是从离散范围生成随机值的稳健方法。结果仍然是不均匀的,使用Random类的正确方法是避免这些问题的方法。
  • @dimo414 我想了解更多。我决定发布一个新问题,而不是继续在 cmets 中。有时间请看一下:stackoverflow.com/questions/44874801/…
【解决方案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);
}

【讨论】:

    【解决方案3】:
    int size = this.getDeckSize()-1; //51 (52 cards, from 0 to 51)
    

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

    【讨论】:

    • 它必须是size = 51,因为如果我有size=52,它会提示Math.random() 能够返回52(我已经对此进行了测试)。这会导致java.lang.IndexOutOfBoundsException:,因为那里的列表只有 52 个元素,并且不可能指向元素 52(因为列表从元素 0 开始)
    • @Oak 请编辑问题以显示给出 IndexOutOfBoundsException 的代码。
    • @Oak "那里的列表只有 52 个元素,并且指向元素 52 是不可能的(因为列表从元素 0 开始)" 请注意,0 到 51 之间的索引提供 52 种可能的选择。因此,size 应该是 52。除非你错误地命名了你的变量,它应该是 maxIndex 而不是 size
    猜你喜欢
    • 2017-09-25
    • 2016-06-12
    • 1970-01-01
    • 2018-11-17
    • 2019-08-03
    • 1970-01-01
    • 1970-01-01
    • 2010-12-25
    • 1970-01-01
    相关资源
    最近更新 更多