Array#sample是否保证随机顺序?

时间:2017-12-01 20:21:23

标签: ruby random

它只保证随机子集,还是随机顺序?

用例正在使用('a'..'z').to_a.sample(8).join生成一个秘密字符串。我想知道我是否可以相信所有26⋅25⋅24⋅23⋅22⋅21⋅20⋅19可能的结果同样可能。

documentation说:

  

从数组中选择[...] n个随机元素。

     

通过在数组中使用随机和唯一索引来选择元素,以确保元素不会重复,除非数组已包含重复元素。

我看到三种可能的解释。例如[1, 2, 3].sample(2)

  • 以1/6的概率返回[1, 2][1, 3][2, 1][2, 3][3, 1][3, 2]。当你选择一个随机元素作为第一个输出元素,然后选择另一个随机元素(来自其余元素)作为第二个输出元素时,这就是你得到的。
  • 以1/3的概率返回[1, 2][1, 3][2, 3]。这是通过选择索引的子集然后遍历数组获得的结果,如果它们的索引是所选择的,则收集元素。
  • 这两者之间有些奇怪。例如,返回[1, 2][1, 3],每个的概率为1/3,或[2, 3][3, 2],每个概率为1/6。

我测试了它,第一个解释是发生了什么。在查看源代码时,我也得到了这样的印象,即它的功能。但我担心我会忽略某些东西,或者说这只是当前实现的一个副作用,并且它可能会在未来发生变化(或者在某些Ruby实现中已经有所不同)。我可以想象第二种解释/实施是有益的,要么是因为一个想要这样的“稳定”采样,要么是出于效率原因。

我的第一个解释是它应该做的吗?我可以依靠结果,不仅是一个随机子集,而且还有一个随机顺序?文档是否应该更清楚?

这是我的测试代码,包含统计信息,以防您想自己尝试:

array = (1..3).to_a
n = 2

count = Hash.new(0)
(10**6).times do
  count[array.sample(n)] += 1
end

puts "#{count.size} different samples occurred."
puts "Smallest was #{count.keys.min}, largest was #{count.keys.max}."
puts "Frequencies ranged from #{count.values.min} to #{count.values.max}."

输出例如:

6 different samples occurred.
Smallest was [1, 2], largest was [3, 2].
Frequencies ranged from 165698 to 167234.

编辑:我创建了一个Ruby issue

3 个答案:

答案 0 :(得分:3)

我希望方法的名称来自统计中samples are drawn without replacement的方式。在这种情况下,被采样的人口的元素不是必需的,如果它们被排序,则与采样的方式无关。

在没有替换的情况下解释采样的通常方法是从容器中随机抽取一定数量的球,在抽出下一个球之前将每个球放在一边。球可以通过颜色或数字或以其他方式识别,但采样结果不反映任何排序概念。

请记住,方法sample是在类Array上定义的,但数组的元素不一定是有序的。例如,

[1, "1", :one, 1..2, { a: 1 }].sample(2) # => [{:a=>1}, :one]

显然,无法订购此样本的元素。

可以想象sample可以构造成如果它们具有可比性,它会对样本的元素进行排序,但我不能想到另一种行为方式的Ruby方法。而且,实施这将是有问题的。很容易确定[1,2,3,4]的元素是可比较的(使用Integer#<=>),但它并不总是那么简单。例如,假设数组是

[1, 2.5, 3/2r, BigDecimal.new("2.1")]

这些元素实际上是可比较的([1, (3/2), 0.21e1, 2.5]已排序),但Ruby必须做一些工作才能做出决定。我想Ruby可以尝试对样本进行排序,如果元素不具有可比性,则可以解除异常,但这似乎相当长。

答案 1 :(得分:1)

当文档说“......确保元素不会重复,除非数组已包含重复元素”,这听起来像是一种复杂的说法“无需替换的样本”。如果您在没有替换大小 n 的情况下对 k 项进行采样,则第一个元素可以是具有相同概率的任何 n 项,第二个可以是任何 n - 1个剩余项目,第三个可以是任何 n - 2剩余等等,直到 k < / em> th 项目。这意味着您可以获得 n !/( n-k )!可能的结果,每个都有相同的可能性,这将产生你的第一个解释。

我同意文件可以更清楚,但在你提出之前,不会想到它会在没有替换的情况下进行抽样。

答案 2 :(得分:0)

好的,我现在有一些证据表明随机顺序来自参考实现。

如果所需的样本量n很小(最多10个),则first creates random indexes的范围缩小。例如,对于n = 2和大小为10的数组,它会创建一个从0到9的随机索引,然后从0到8创建另一个索引。然后它increments the second index if it's larger than or equal to the first。这模拟了第一个索引处的拾取和删除值,然后将第二个索引处的值选入剩余的九个值。

对于n = 3它也是如此,possibly incrementing the second index and possibly incrementing the third index once or twice(我认为l表示ij的“较低”或“较低”,{ {1}}表示“更大”)。

对于g从4到10 it still does the same,根据需要经常递增后面的索引。但是在这里它使用一个名为n的额外数组来按照排序顺序跟踪适应的索引(即,扮演sortedl的更一般角色。 现在这里是我的论点:它不只是将修改后的索引存储在g中,而且还将它们写回sorted数组,并最终使用idx收集价值观。如果没有意图随机顺序,则不必这样做。它可以简单地使用idx索引。许多特殊情况告诉我,代码应该被优化。那么为什么不使用sorted的优化而不回写sorted呢?我怀疑这是因为随意订单。