从一组N中随机选择n条记录

时间:2016-01-28 15:40:27

标签: algorithm random-sample correctness

我需要从一组n(其中N)随机选择0 < n < N条记录。

可能的算法是:

  

遍历列表并对每个元素进行选择概率= (number needed) / (number left)

     

因此,如果您有40个项目,那么第一个项目将有5/40被选中。

     

如果是,则下一次有4/39次机会,否则有5/39次机会。等到你结束的时候   你将获得5件物品,而且在此之前你通常会拥有所有这些物品。

假设一个好的伪随机数发生器,这个算法是否正确?

注意

在stackoverflow上有很多这类问题(其中很多被标记为Select N random elements from a List<T> in C#的重复)。

经常提出上述算法(例如Kyle Cronin的回答)和 它一直受到质疑(例如,见 herehereherehere ......)。

我可以对此事做最后的决定吗?

2 个答案:

答案 0 :(得分:8)

算法绝对正确。

这不是突然发明的好海报,它是一种众所周知的技术,称为选择采样 / 算法S (由Fan,Muller和Rezucha发现(1)和由Jones(2)于1962年独立完成,在TAOCP - 第2卷 - 精神数学算法 - 第3.4.2节中有详细描述。

正如Knuth所说:

  

该算法乍一看似乎不可靠,实际上是不正确的。但仔细分析表明它完全值得信赖。

当已经n元素已经N时,算法会从一组大小为t + 1的{​​{1}}元素中选择(n - m) / (N - t)个元素,并且概率为m被选中了。

很容易看出,在选择n项之前,我们永远不会在集合的末尾运行(因为当我们有1个元素可供选择时,概率为k k个元素。)

此外,我们从不选择太多元素(概率0n == m}。

有点难以证明样本是完全无偏的,但它是

  

...尽管我们选择概率为t + 1的{​​{1}}项目,但这是真的。这在已发表的文献中引起了一些混淆

(所以不只是在Stackoverflow上!)

事实上,我们不应混淆条件和无条件概率:

  

例如考虑第二个元素;如果在样本中选择了第一个元素(这种情况以概率n / N发生),则以概率n / N选择第二个元素;如果未选择第一个元素,则以概率(n - 1) / (N - 1)选择第二个元素。

     

选择第二个元素的总体概率为n / (N - 1)

TAOCP - 第2卷 - 第3.4.2节练习3

除了理论上的考虑之外,算法S (以及算法R / reservoir sampling)在许多知名的库中使用(例如SGI's original STL implementationstd::experimental::sample, Python中的random.sample ...)。

当然算法S 并不总是最佳答案

  • 它是(n / N) ((n - 1) / (N - 1)) + (1 - n/N)(n / (N - 1)) = n/N(即使我们通常不必传递所有O(N)条记录:N约为n=2时所考虑的平均记录数;一般情况公式给出 TAOCP - 第2卷 - 第3.4.2节 - 前5/6);
  • 无法使用when the value of N isn't known in advance

无论如何它都有效!

  1. ℃。 T. Fan,M。E. Muller和I. Rezucha,J。Amer。统计。协会。 57(1962),pp 387 - 402
  2. 吨。 G. Jones,CACM 5(1962),第343页
  3. 修改

      

    如何随机选择此项目,概率为7/22

         

    [CUT]

         

    在极少数情况下,您甚至可以在需要5

    时选择4或6个元素

    这来自N3925(为避免公共接口/标签调度而进行的小修改):

    2/3 N

    没有漂浮物。

    • 如果您需要5个元素,则可获得5个元素;
    • 如果template<class PopIter, class SampleIter, class Size, class URNG> SampleIter sample(PopIter first, PopIter last, SampleIter out, Size n, URNG &&g) { using dist_t = uniform_int_distribution<Size>; using param_t = typename dist_t::param_type; dist_t d{}; Size unsampled_sz = distance(first, last); for (n = min(n, unsampled_sz); n != 0; ++first) { param_t const p{0, --unsampled_sz}; if (d(g, p) < n) { *out++ = *first; --n; } } return out; } “有效as advertised”则没有偏见。

答案 1 :(得分:-3)

尽管描述的算法是technically correct,但它依赖于具有返回bool的算法,该bool具有由两个int的比率确定的任意概率。例如,如何以7/22的概率选择此项?就谈话而言,我们称其为bool RandomSelect(int x, int y)方法,或称为RS(x,y)方法,旨在以概率true返回x/y。如果您不太关心准确性,那么经常给出的答案就是使用return Random.NextDouble() < (double)x/(double)y;这是不准确的,因为Random.NextDouble()不精确且不完全一致,而(double)x/(double)y除以也是不精确的。 <<=的选择应该是无关紧要的(但事实并非如此),因为理论上不可能随机选择完全等于指定概率的无限精度随机数。虽然我确定可以创建或找到一个算法,但是为了准确地实现RS(x,y)方法,这将允许您正确地实现所描述的算法,我认为只需回答这个问题为&#34 ;是的,算法是正确的&#34;会误导 - 因为它误导了很多人,使用double来计算和选择元素,而不知道他们引入的偏见。

不要误解我 - 我并不是说每个人都应该避免使用所描述的算法 - 除非你找到更精确的方法来实现RS(x,y)算法,否则我只是说,你的选择会比其他元素更频繁地偏向某些元素。

如果您关心公平性(所有可能结果的概率相等),我认为使用不同的算法会更好,更容易理解,如下所述:

如果你认为随机可用的随机位是随机位,你必须定义一种随机选择技术,在给定二进制随机数据的情况下确保相等的概率。这意味着,如果你想在一个恰好是2的幂的范围内选择一个随机数,你只需选择随机位并返回它们。但是如果你想要一个范围不是2的幂的随机数,你必须得到更多的随机位,并丢弃无法映射到公平结果的结果(扔掉随机数并重试)。我在这里用博客表示和C#示例代码在博客中写道:https://nedharvey.com/blog/?p=284从您的收藏中重复随机选择,直到您拥有n个唯一项目。