Collections.shuffle() 真的足够随机吗?实际例子似乎否认了这一说法

2022-09-04 23:57:41

我在一个中有1000个唯一对象,每个对象都引用一个图像,1000列表中的每个图像都是唯一的,现在我想洗牌它们,这样我就可以使用前20个对象并将它们呈现给网站用户。然后,用户可以单击一个显示“Shuffle”的按钮,然后我再次从头开始检索1000张图像并再次调用 。但是,似乎在1000个图像对象中,我经常在20个图像选择之间一次又一次地看到相同的图像。java.util.Listshuffle()

似乎有些地方不对劲,有什么更好的建议,建议吗?

我的代码非常简单:

List<String> imagePaths = get1000Images();
Collections.shuffle(imagePaths);

int i = 0;
for (String path: imagePaths) {
  ... do something with the path ...
  i++;
  if (i >= 20) break;
}

我知道这是很好的分布:例如,请参阅 http://blog.ryanrampersad.com/2012/03/03/more-on-shuffling-an-array-correctly/Collections.shuffle()

但是,我只是觉得在1000张图像中的20张图像中一遍又一遍地看到相同图像的可能性应该小得多......

非常感谢您的意见。


答案 1

看到不存在的模式是人性。许多人认为行星和恒星的模式是指导他们生活的。

在 PI 的前 1000 位数字中,有一行六个 9。这是否意味着PI的数字不是随机的?不。该模式不会再次发生,超出您的预期。

话虽如此,Random不是完全随机的,它会在2 ^ 48次调用后重复。(它使用 48 位种子)这意味着不可能生产所有可能或使用它。如果你想要更多的随机性,你可以使用SecureRandom和随机播放。longdouble

听起来你想要的是这样的东西

List<String> imagePaths = new ArrayList<>();

// called repeatedly
if (imagePaths.size() <= 500) {
    imagePaths = get1000Images();
    Collections.shuffle(imagePaths);
}

for (String path: imagePaths.subList(0, 20)) {
  ... do something with the path ...
}

imagePaths = imagePaths.subList(20, imagePaths.size());

这将确保您在最近的 500 次呼叫中看不到相同的图像。


答案 2

如果您显示 1000 张图像中的 20 张,则在下一次迭代中看到 20 张重复图像中的任何一张的概率约为 0.34,因此看到图像重复时,您不会感到惊讶。

看到特定图像的机会仍然是千分之一,但如果你正在寻找二十张图像,那么机会要高得多。

我们可以计算出前20张图像中没有一张重复的概率为:

 980   979         961
———— × ——— × ... × ——— ≈ 0.66
1000   999         981

因此,看到重复的概率是1减去这个,或大约0.34。

在接下来的两次迭代中,看到图像重复的概率是:

1 - (0.66 × 0.66) ≈ 0.56

换句话说,您更有可能在随后的两个周期中看到重复的图像。(这不包括从第三个周期的第二个周期重复的图像,这只会使它更有可能。

对于它的价值,这里有一些Java代码来执行上述计算:

float result = 1.0f;
int totalImages = 1000;
int displayedImages = 20;

for (int i = 0; i < displayedImages; i++) {
  result = result * (totalImages - displayedImages - i) / (totalImages - i);
}

System.out.println(result);