【问题标题】:Choose random array element satisfying certain property选择满足特定属性的随机数组元素
【发布时间】:2009-06-08 17:57:53
【问题描述】:

假设我有一个名为elements 的列表,每个列表都满足或不满足某个布尔属性p。我想通过均匀分布随机选择满足p 的元素之一。我提前不知道有多少项目满足这个属性p

下面的代码会这样做吗?:

pickRandElement(elements, p)
     randElement = null
     count = 0
     foreach element in elements
          if (p(element))
               count = count + 1
               if (randInt(count) == 0)
                    randElement = element

     return randElement

randInt(n) 返回一个随机整数 k0 <= k < n。)

【问题讨论】:

  • 我会认为“随机”和“平均分配”是相互排斥的,我错过了什么?
  • @Binary:他只是意味着它必须是一个公平的随机数。所有满足 p 的元素每次都必须有相同的机会被随机选择。如果这是真的,那么只要有足够的时间,它们就会被平均分配。
  • 随机分布可以有各种形状,这些形状可能会偏向一组元素或另一组元素。在这里,保罗问的是一个均匀(或均匀)分布,其中每个元素都有相同的被选中概率。
  • @Nate,目前尚不清楚每个元素具有相等的概率。我的阅读是 P 组和 Not-P 组具有相等的概率。如果每个元素都是一致的,那么我的答案就离得很远了.... ;-)
  • 没有必要使用浮点数。就个人而言,我只是选择一个介于 0 和 count - 1 之间的随机整数,然后选择结果为 0 的元素。这样更有效,并且可以保护您免受舍入错误。

标签: algorithm arrays statistics probability


【解决方案1】:

它在数学上起作用。可以通过归纳证明。

显然适用于 n = 1 个满足 p 的元素。

对于n+1个元素,我们会选择概率为1/(n+1)的元素n+1,所以它的概率是可以的。但这对选择前 n 个元素之一的最终概率有何影响?

在我们找到元素 n+1 之前,每个先前的 n 都有机会以 1/n 的概率被选中。现在,在找到 n+1 之后,有 1/(n+1) 的机会选择元素 n+1,因此有 n/(n+1) 的机会选择之前选择的元素。这意味着它在 n+1 找到后被选中的最终概率是 1/n * (n/n+1) = 1/n+1 - 这是我们希望所有 n+1 个元素均匀分布的概率。

如果它适用于 n = 1,并且它适用于给定 n 的 n+1,那么它适用于所有 n。

【讨论】:

  • 归纳在计算证明中救了我太多次了!
  • 实际上有一种更简单的方法可以证明这一点。对于 n 个元素,我们将以 1/n 的概率选择元素 n。但是前面的 n-1 个元素呢?好吧,根据归纳,我们知道所有这些 n-1 个元素具有相同的概率。所以每个必须的概率是 1/n,因为 1/n 是唯一与 n 相乘时为 1 的数字。 qed :)
【解决方案2】:

是的,我相信是的。

当你第一次遇到匹配的元素时,你肯定会选择它。下一次,您以 1/2 的概率选择新值,因此这两个元素中的每一个都有相等的机会。接下来,您以 1/3 的概率选择新值,而其他每个元素的概率也为 1/2 * 2/3 = 1/3。

我正在尝试查找有关此策略的 Wikipedia 文章,但目前失败...

请注意,更一般地说,您只是从长度未知的序列中随机抽取一个样本。您的序列恰好是通过获取初始序列并对其进行过滤来生成的,但该算法根本不需要该部分。

我以为我在 MoreLINQ 中有一个 LINQ 运算符来执行此操作,但我在存储库中找不到它...编辑:幸运的是,它仍然存在于 this answer

public static T RandomElement<T>(this IEnumerable<T> source,
                                 Random rng)
{
    T current = default(T);
    int count = 0;
    foreach (T element in source)
    {
        count++;
        if (rng.Next(count) == 0)
        {
            current = element;
        }            
    }
    if (count == 0)
    {
        throw new InvalidOperationException("Sequence was empty");
    }
    return current;
}

【讨论】:

  • 乔恩,据我所知,这个算法总是会选择第一个满足 p 的元素,我错过了什么?
  • @tekBlues:它在选择了第一个之后继续运行。
  • 如果随机生成器正常工作,我很确定这个算法有效。
  • aakashm,他说他只想选择一个元素
  • 而不是“挑选”,将算法视为“持有”元素。它保存第一个,然后可能会放下它来保存第二个,依此类推。最后,您只剩下一些元素,这是您返回的元素。
【解决方案3】:

编程实践中,pg。 70、(马尔可夫链算法)有一个类似的算法:

[...]
  nmatch = 0;
  for ( /* iterate list */ )
    if (rand() % ++nmatch == 0) /* prob = 1/nmatch */
      w = suf->word;
[...]

"注意选择一个的算法 当我们不知道如何时随机项目 有很多项目。变量 nmatch 将匹配数计算为 列表被扫描。表达式

rand() % ++nmatch == 0

增加 nmatch 然后为真 概率为 1/nmatch。”

【讨论】:

    【解决方案4】:

    decowboy 有一个很好的证据证明这适用于 TopCoder

    【讨论】:

      【解决方案5】:

      为了清楚起见,我会尝试:

      pickRandElement(elements, p)
           OrderedCollection coll = new OrderedCollection
           foreach element in elements
                if (p(element))
                     coll.add(element)
           if (coll.size == 0) return null
           else return coll.get(randInt(coll.size))
      

      对我来说,这让你更清楚你想要做什么并且是自我记录。最重要的是,它更简单、更优雅,而且现在很明显,每一个都会以均匀分布的方式被挑选出来。

      【讨论】:

      • 这就是我们现在拥有的代码(我承认它更清晰)。我希望有更有效的东西。我猜,如果提议的替代方案可行,那么创建一个列表并向其中添加元素会有些低效和浪费。
      • 一旦你看到提议的算法在做什么,它就是非常优雅的 IMO。
      • 是的,如果效率是重中之重的话,你是对的。我会说我提供的更明显的解决方案更具可读性。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多