【问题标题】:Randomly sampling unique subsets of an array随机采样数组的唯一子集
【发布时间】:2012-01-19 16:33:49
【问题描述】:

如果我有一个数组:

a = [1,2,3]

如何随机选择数组的子集,使每个子集的元素都是唯一的?也就是说,对于a,可能的子集是:

[]
[1]
[2]
[3]
[1,2]
[2,3]
[1,2,3]

我无法生成所有可能的子集,因为 a 的实际大小非常大,所以有很多很多子集。目前,我正在使用“随机游走”的想法——对于 a 的每个元素,我会“抛硬币”并在硬币正面朝上时将其包括在内——但我不确定这是否真的均匀地采样了空间。它感觉好像偏向中间,但这可能只是我在做模式匹配的想法,因为会有更多中等大小的可能性。

我是否使用了正确的方法,或者我应该如何随机抽样?

(我知道这更像是一个与语言无关的“数学”问题,但我觉得这不是真正的 Mathoverflow 材料——我只需要一个实用的答案。)

【问题讨论】:

  • 我假设a 不会是整数数组?
  • 不,在我的实际示例中它是一个字符串数组。

标签: ruby random sampling


【解决方案1】:

继续您最初的“抛硬币”想法。它均匀地对可能性空间进行采样。

你觉得它偏向“中间”,但那是因为“中间”的可能性最大。想一想:没有元素的可能性只有 1 种,所有元素的可能性只有 1 种。有 1 个元素有 N 种可能性,有 (N-1) 个元素有 N 种可能性。随着所选元素的数量越来越接近 (N/2),可能性的数量增长得非常快。

【讨论】:

  • a.select {|element| rand(2) == 0 }
  • a.select {|_| rand(2).zero? } ;-)
【解决方案2】:

您可以生成随机数,将它们转换为二进制,然后从原始数组中选择位为 1 的元素。这是一个作为 Array 类的猴子补丁的实现:

class Array
  def random_subset(n=1)
    raise ArgumentError, "negative argument" if n < 0
    (1..n).map do
      r = rand(2**self.size)
      self.select.with_index { |el, i| r[i] == 1 }
    end
  end
end

用法:

a.random_subset(3) 
#=> [[3, 6, 9], [4, 5, 7, 8, 10], [1, 2, 3, 4, 6, 9]]

通常这不会表现得那么糟糕,它是 O(n*m),其中 n 是您想要的子集数,m 是数组的长度。

【讨论】:

    【解决方案3】:

    我认为抛硬币很好。

    ar = ('a'..'j').to_a
    p ar.select{ rand(2) == 0 }
    

    一个有 10 个元素的数组有 2**10 种可能的组合(包括 [ ] 和所有 10 个元素),最多不过 10 次(1 或 0)。它确实输出了更多由四个、五个和六个元素组成的数组,因为在 powerset 中有更多的数组。

    【讨论】:

      【解决方案4】:

      从幂集中选择随机元素的方法如下:

      my_array = ('a'..'z').to_a
      power_set_size = 2 ** my_array.length
      random_subset = rand(power_set_size)
      subset = []
      random_subset.to_i(2).chars.each_with_index do |bit, corresponding_element|
        subset << my_array[corresponding_element] if bit == "1"
      end
      

      为了方便起见,这使用了字符串函数,而不是使用真正的“位”和按位运算。您可以通过使用实数将其转换为更快(我猜)的算法。

      它的作用是将array 的幂集编码为02 ** array.length 之间的整数,然后随机选择其中一个整数(实际上是均匀随机)。然后它使用位掩码将整数解码回array 的特定子集(1 = 元素在子集中,0 = 不在子集中)。

      通过这种方式,您可以在阵列的幂集上实现均匀分布。

      【讨论】:

      • 我刚刚注意到 Michael Kohl 发布了一个类似的解决方案,这可能更好。它使用真正的位操作,还让您有机会请求多个子集。
      【解决方案5】:
      a.select {|element| rand(2) == 0 }
      

      对于每个元素,都会掷一枚硬币。如果正面(== 0),则被选中。

      【讨论】:

      • sample(rand * a.size) 产生长度在 0 和 a.size - 1 之间的子集。如果您希望排除空集并包含超集,sample(rand(a.size) + 1)
      • 我使用了rand(a.size + 1),它似乎同时产生了空子集[] 和子集a 本身。所以它可以产生a的所有可能的子集。
      • 请注意 Array#sample 在 Ruby 1.9+ 中可用
      • 这不是a 的幂集上的均匀分布,因为它首先选择一个长度,然后是该长度的样本。在a 的幂集合中,长度2 的集合显然比长度a.length 的集合多得多!
      • @AlbertSantini 我同意你的看法。我改变了答案。
      猜你喜欢
      • 2012-08-09
      • 1970-01-01
      • 1970-01-01
      • 2017-06-01
      • 2012-03-27
      • 2011-03-01
      • 2017-09-03
      • 2019-10-02
      • 1970-01-01
      相关资源
      最近更新 更多