【问题标题】:Python: How to obtain a random subsetPython:如何获得随机子集
【发布时间】:2019-07-12 11:56:19
【问题描述】:

如何在 python 中获得一组s 的随机子集?我试过做

from random import sample, randint

def random_subset(s):
    length = randint(0, len(s))
    return set(sample(s, length))

但我现在意识到这显然不起作用,因为 len(s) 的分布,其中 s 是一个随机子集,从 0n 的分布不均匀。

我确信我可以计算该分布并使用带有概率的 numpy 样本或类似的东西,但我希望最好使用纯 python。

【问题讨论】:

    标签: python python-3.x set subset


    【解决方案1】:

    我刚刚意识到我可以简单地检查s 中的每个元素并独立决定是否保留它。像这样的

    from random import randint
    
    def random_subset(s):
        out = set()
        for el in s:                                                                                                                    
            # random coin flip
            if randint(0, 1) == 0:
                out.add(el)
        return out
    

    这有正确的分布。

    【讨论】:

      【解决方案2】:

      您获得的子集在很大程度上取决于您指定的包含或排除元素的标准。如果你有一个函数criterion,它接受一个元素并返回一个布尔值来表示包含在子集中,那么实际的创建过程就变得简单了

      from random import randrange
      
      def random_subset(s, criterion=lambda x: randrange(2)):
          return set(filter(criterion, s))
      

      filter 创建一个惰性生成器,因此返回子集是存储选择的唯一位置。默认标准非常简单,分布均匀。 randrangerandint 类似,只是它在右边界是独占的。至少从 Python 3.2+ 开始,无论范围大小如何,这两个函数都会产生相当一致的结果。

      您可以使用random 进一步细化标准:

      from random import random
      
      criterion = lambda x: random() < 0.5
      

      应用这样的阈值可能看起来有点矫枉过正,但它可以让您调整分布。您可以使用一个函数为您喜欢的任何阈值生成标准:

      def make_criterion(threshold=0.5):
          return lambda x: random() < threshold
      

      您可以使用它来获得更小的子集:

      random_subset(s, make_criterion(0.1))
      

      事实上,您可以根据需要使标准复杂化。以下示例是对字符串集进行操作的人为的可调用类。如果已经添加了一个匹配第一个字符的字符串,它会自动拒绝当前元素。如果已经看到第二个字母,则将包含的概率设置为 0.25。否则,它会抛硬币:

      class WeirdCriterion:
      
          def __init__(self):
              self.first = set()
              self.second = set()
      
          def __call__(self, x):
              n = len(x)
              if n > 0:
                  if x[0] in self.first:
                      return False
                  self.first.add(x[0])
                  if n > 1:
                      if x[1] in self.second:
                          return not randrange(4)
                      self.second.add(x[1])
              return randrange(2)
      

      这个例子在实践中不是很好,因为集合是无序的,并且可以在同一脚本的不同运行之间给出不同的迭代顺序。然而,它显示的是一种创建随机标准的方法,但会根据子集中已经存在的元素进行调整。

      避免使用 Numpy

      现在我对您的初衷有了更好的理解,您可以利用 Python 3 具有无限长度整数以及 choices 接受长度参数来获得正确长度的事实。不过我不推荐这种方法:

      from random import choices, sample
      from math import factorial
      
      def random_subset(s):
          n = len(s)
          nf = factorial(n)
          # yes, there are better ways of doing this, even in pure python
          weights = [nf / (factorial(k) * factorial(n - k)) for k in range(n + 1)]
          length = choices(range(n + 1), weights, k=1)[0]
          return sample(s, length)
      

      计算二项式系数的更好解决方案可能是:

      def pascal(n):
          result = [1] * (n + 1)
          if n < 2:
              return result
          for i in range(2, n + 1):
              for j in range(i - 1, 0, -1):
                  result[j] += result[j - 1]
          return result
      

      【讨论】:

      • 为什么 randrange 比 randint 更干净?我很确定 randint 调用了 randrange。
      • @Enrico。它更干净,因为它涉及更少的函数调用。界面是一个偏好问题。如果你愿意,我会修改那一点。
      • 啊,我想这是少了一个电话。我只是好奇你说的清洁剂是什么意思。感谢您的答复。您的回答是对我的回答的概括。我正在寻找一个随机子集,它在集合 S 的所有可能子集上均匀采样。
      • 我更新了一个示例,表明您可以按照您最初的预期方式进行操作。
      猜你喜欢
      • 2022-08-19
      • 2018-12-22
      • 2023-01-19
      • 1970-01-01
      • 2011-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-18
      相关资源
      最近更新 更多