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

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

有些东西似乎是错的,有什么更好的建议,建议吗?

我的代码非常简单:

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

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

但是,我只是觉得在一组20张图像中一次又一次地看到相同图像的概率应该少得多……

输入高度赞赏。

如果你在1000中显示20张图像,那么在下一次迭代中看到20个中任何一个重复的概率约为0.34,所以你不应该对看到图像重复感到惊讶。

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

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

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

因此,看到重复的概率是一减去这个,或大约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); 

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

在PI的前1000个数字中,连续有六个9。 这是否意味着PI的数字不是随机的? 没有。 该模式不会再出现超出您的预期。

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

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

 List 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次调用中看不到相同的图像。

你的直觉对于特定的图像是正确的[你不可能一遍又一遍地看到特定的图像 ],但不适用于一般的图像[你可能会看到一些图像重复]。 这是我们自动直觉错误的概率之一……

这让我想起生日悖论 ,这与直觉相矛盾,并且说 – 对于一群23人来说,他们中2人生日相同的可能性是0.5,远远超过直觉所期望的!

我做了52次洗牌四次不同的时间,并且每次迭代都在完全相同的插槽中重复完全相同的牌时进行标记,这给了我大约中的198张牌,大约93.3%随机。

根据你的问题,我编写了以下程序。 我创建了连续整数列表,并将其洗牌10次,100次,1000次和10000次。 在每个系列的shuffle之后,我检查了数组的第5个位置的元素值,并创建了一个计数器数组:每个数字出现在第5个位置的次数。

这是程序:

 public class MyTest { public static void main(String[] args) { int n = 10; List list = new ArrayList(); for (int i = 0; i < n; i++) { list.add(i); } int[] counters = new int[n]; for(int shuffles : new int[] {10, 100, 1000, 10000}) { Arrays.fill(counters, 0); for (int i = 0; i < shuffles; i++) { Collections.shuffle(list); // check 5-th element int fifth = list.get(5); counters[fifth] = counters[fifth] + 1; } System.out.println(shuffles + ": " + Arrays.toString(counters)); } } } 

以下是结果:

10:[0,1,1,1,2,0,0,3,2,0] 100:[11,9,9,7,10,12,13,13,8,8] 1000:[100 ,101,107,101,95,96,109,83,93,115] 10000:[1015,942,990,1003,1015,1037,977,1060,950,1011]

正如您所看到的,“randomality”取决于shuffle的数量。 如果你将数组洗牌10次,则最小计数器为0,最大值为3.这些值之间的差值为100次shuffles(以每美分计)要小得多。 10000次洗牌的数字几乎相同。

我认为这个测试模拟了你的用例:你在洗牌集合的特定位置显示图像。

请参阅描述shuffle含义的@amitpost。

所以,你的解决方案是将你的arrays洗牌10次。

编辑:@Dave Webb为案件提供了完美的解释。

第二种想法如下:你实际上不需要将1000个元素的列表随机化 ,从中获取20个第一个元素。 它足以取20个随机元素。 您将获得相同的效果,但更有效的解决方案:

 Set show = new HashSet(); Random r = new Random(System.currentTimeMillis()); for (int i = 0; show.size() < 20; i++) { show.add(list.get(r.nextInt())); } 

使用该代码,如果您反复看到相同的图像,则表示列表中存在多次相同的图像。 无论你从哪里获得1000张图片,都有重复的内容。