【问题标题】:Does Array#sample guarantee random order?Array#sample 是否保证随机顺序?
【发布时间】:2017-12-01 20:21:23
【问题描述】:

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

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

documentation 说:

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

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

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

  • 返回[1, 2][1, 3][2, 1][2, 3][3, 1][3, 2],每个概率为1/6。当您选择一个随机元素作为第一个输出元素,然后选择另一个随机元素(从剩余的元素中)作为第二个输出元素时,这就是您得到的结果。
  • 返回[1, 2][1, 3][2, 3],每个概率为1/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

【问题讨论】:

  • @engineersmnky Ruby 使用 Mersenne Twister,而不是 C 的 rand

标签: ruby random


【解决方案1】:

我希望方法的名称来源于统计中的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] sorted),但是 Ruby 必须做一些工作才能做出这样的决定。如果元素不可比较,我想 Ruby 可以尝试对样本进行排序并挽救异常,但这似乎有点牵强。

【讨论】:

  • 你误会了。我不是在问结果是否被订购。我在问它是否被随机订购。它很可能没有。元素保持它们的顺序。输出是输入的子序列。您在谈论另一种“订单”。数组有序的。 所有数组。根据定义。看到第一件事,甚至 Ruby 自己的 Array documentation 说:“数组是有序的,任何对象的整数索引集合”。或者 Stack Overflow 的 array 标签文本:“数组是有序的数据结构”.
  • 我想我现在明白你在问什么了。实际上,您的第二种解释等同于从数组中随机选择元素,无需替换,将这些元素及其索引添加到数组 (arr &lt;&lt; [e,idx]),按索引对数组进行排序,然后将数组的元素映射到值(arr.map(&amp;:first))。正确的? (续)..
  • 是的,这是一种方法。但我不认为它“限制了有用性”。如果它像那样稳定并且您想要随机顺序,您可以简单地附加.shuffle。完毕。另一方面,如果它是随机顺序并且您想要稳定的,那并不是那么微不足道。因此,如果它是稳定的,它将满足想要稳定的人和想要随机顺序的人。因此,我会说使其稳定会增加有用性。
  • 必须写 arr.shuffle.sample(3) 会让任何熟悉统计抽样的人感到困惑。
  • 不,非常不同。这怎么会让人困惑?它清楚地表明您不仅想要一个随机子集,而且还想要它以随机顺序排列。你认为返回数组的sample 方法可以被信任以产生随机顺序吗? 在您自己链接到的页面上的五种算法中有三或四种不会产生随机顺序,但稳定或部分稳定的。跨度>
【解决方案2】:

当文档说“......确保一个元素不会重复自身,除非数组已经包含重复元素”时,这听起来像是一种复杂的说法,即“无需替换的样本”。如果您从大小为 n 的总体中抽样 k 个项目而不进行替换,则第一个元素可以是具有相同概率的 n 个项目中的任何一个,第二个可以是任何 n - 1 个剩余项目,第三个可以是任何 n - 2 个剩余项目,依此类推,直到 kth 项。这意味着您可以获得 n!/(n-k)!可能的结果,每个结果都具有相同的可能性,这会产生您的第一个解释。

我同意文档可以更清晰,但在您提出之前不会怀疑它是在进行抽样而不更换。

【讨论】:

  • 是的,这就是我将如何实现它以及参考实现如何实现它(如果我正确阅读源代码的话)。但也许这只是因为这是“显而易见”的方式,而不是因为这是预期的结果。顺便说一句,我认为您对 n 选择 k 有误。这是我的第二解释,不是吗?
  • @StefanPochmann 感谢您使用 choose 发现错误,当我在现实世界中试图赶上最后期限时,我应该知道最好不要盲目行事。跨度>
  • @StefanPochmann 我认为这是预期的结果,因为文档描述的是没有替换的采样,即使它不是最清晰的措辞。如果您同意这就是文档所说的,那就是答案。如果你不这样做,我们就会进入谁的意见是正确的领域。
  • 也许“选择”是你的潜意识告诉你它应该是什么:-)。 WikipediaMathWorld 也只是说“子集”。我对你引用的部分有不同的解释。我认为它没有描述过程,而只是保证了结果。除非输入已经包含重复项,否则输出不包含重复项。无论如何......我找到了一些东西(见我的答案),但也许我应该寻找比 Stack Overflow 更适合这个问题的地方......
【解决方案3】:

好的,我现在从参考实现中获得了随机顺序的弱证据。

如果所需的样本量 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 的“较低”或“较小”,而g 表示“较大”)。

对于从 4 到 10 的 nit still does the same,根据需要增加后面的索引。但这里它使用一个名为sorted 的额外数组来按排序顺序跟踪适应的索引(即扮演lg 的更一般的角色)。 现在这是我的论点:它不仅将调整后的索引存储在sorted 中,还将它们写回idx 数组,并使用idx 最终收集值。如果不打算随机顺序,那么它就不必这样做。它可以简单地使用sorted 索引。许多特殊情况向我表明,代码应该被优化。那么为什么不使用sorted的优化而不回写idx呢?我怀疑这是因为随机顺序。

【讨论】:

    猜你喜欢
    • 2016-04-16
    • 1970-01-01
    • 2021-10-04
    • 2014-07-22
    • 1970-01-01
    • 1970-01-01
    • 2022-08-02
    • 1970-01-01
    • 2011-02-08
    相关资源
    最近更新 更多