假设我有一份奖品清单:
PrizeA PrizeB PrizeC
而且,对于他们每个人,我想从我的与会者名单中抽出一名获胜者。
说明我的与会者名单如下:
user1,user2,user3,user4,user5
从该列表中选择用户的无偏见方法是什么?
显然,我将使用加密安全的伪随机数生成器,但如何避免偏向列表前面?我假设我不会使用模数?
修改
所以,这就是我想出的:
class SecureRandom
{
private RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
private ulong NextUlong()
{
byte[] data = new byte[8];
rng.GetBytes(data);
return BitConverter.ToUInt64(data, 0);
}
public int Next()
{
return (int)(NextUlong() % (ulong)int.MaxValue);
}
public int Next(int maxValue)
{
if (maxValue < 0)
{
throw new ArgumentOutOfRangeException("maxValue");
}
if (maxValue == 0)
{
return 0;
}
ulong chop = ulong.MaxValue - (ulong.MaxValue % (ulong)maxValue);
ulong rand;
do
{
rand = NextUlong();
} while (rand >= chop);
return (int)(rand % (ulong)maxValue);
}
}
要注意:
Next()
返回[0,int.MaxValue]范围内的int
Next(int.MaxValue)
返回范围[0,int.MaxValue)
答案 0 :(得分:3)
特殊随机数生成器的伪代码:
rng is random number generator produces uniform integers from [0, max)
compute m = max modulo length of attendee list
do {
draw a random number r from rng
} while(r >= max - m)
return r modulo length of attendee list
这消除了对列表前部的偏差。然后
put the attendees in some data structure indexable by integers
for every prize in the prize list
draw a random number r using above
compute index = r modulo length of attendee list
return the attendee at index
在C#中:
public NextUnbiased(Random rg, int max) {
do {
int r = rg.Next();
} while(r >= Int32.MaxValue - (Int32.MaxValue % max));
return r % max;
}
public Attendee SelectWinner(IList<Attendee> attendees, Random rg) {
int winningAttendeeIndex = NextUnbiased(rg, attendees.Length)
return attendees[winningAttendeeIndex];
}
然后:
// attendees is list of attendees
// rg is Random
foreach(Prize prize in prizes) {
Attendee winner = SelectWinner(attendees, rg);
Console.WriteLine("Prize {0} won by {1}", prize.ToString(), winner.ToString());
}
答案 1 :(得分:1)
假设一个相当分布的随机数生成器...
do {
i = rand();
} while (i >= RAND_MAX / 5 * 5);
i /= 5;
这给出了5个插槽中的每一个
[0 .. RAND_MAX / 5)
[RAND_MAX / 5 .. RAND_MAX / 5 * 2)
[RAND_MAX / 5 * 2 .. RAND_MAX / 5 * 3)
[RAND_MAX / 5 * 3 .. RAND_MAX / 5 * 4)
[RAND_MAX / 5 * 4 .. RAND_MAX / 5 * 5)
并丢弃超出范围的滚动。
答案 2 :(得分:1)
你已经看到了几个非常好的答案,这取决于提前知道列表的长度。
要从列表中公平地选择单个项目,而不需要首先需要知道列表的长度,请执行以下操作:
if (list.empty()) error_out_somehow
r=list.first() // r is a reference or pointer
s=list.first() // so is s
i = 2
while (r.next() is not NULL)
r=r.next()
if (random(i)==0) s=r // random() returns a uniformly
// drawn integer between 0 and i
i++
return s
(如果您将列表存储为链接列表,则非常有用)
要在这种情况下分发奖品,只需在奖品列表中选择每个奖品的随机赢家。 (如果你想防止双赢,那么从参与者名单中删除获胜者。)
为什么会这样?
1/1
1/2
),这意味着第一个项目的概率为1 * (2-1)/2 = 1/2
1/n
的第n个项目,并且每个先前项目的机会减少(n-1)/n
这意味着当你走到尽头时,列表(m
项目)中n
项的可能性是
1/m * m/(m+1) * (m+1)/(m+2) * ... * (n-2)/(n-1) * (n-1)/n = 1/n
并且对于每个项目都是相同的。
如果你正在注意,你会注意到这意味着每次你想从列表中选择一个项目时都要遍历整个列表,所以这对于(比如说)重新排序整个列表并不是最有效的(尽管它确实如此)公平地说。。
答案 3 :(得分:0)
我想一个答案就是为每个项目分配一个随机值,然后根据需要向下钻取最大值或最小值。
我不确定这是否是效率最高的...... ...
答案 4 :(得分:0)
如果您使用的是数字生成器,即使使用模数,您的偏差也会微乎其微。例如,如果您使用的是具有64位熵的随机数生成器和5个用户,那么您对阵列前端的偏差应该是3x10 ^ -19(我的数字可能会关闭,我不会多想想。与后来的用户相比,这是第一个用户获胜的额外三分之三的五分之一。这应该足以在任何人的书中公平。
答案 5 :(得分:0)
您可以从提供商处购买真正的随机位,或使用机械设备。
答案 6 :(得分:0)
Here你会发现Oleg Kiselyov关于纯函数随机改组的讨论。
链接内容的说明(引自该文章开头):
本文将提供两个完美的纯功能程序, 随机均匀地混合一系列任意元素。我们 证明算法是正确的。算法已实现 在Haskell中,可以简单地重写为其他(功能) 语言。我们还讨论了为什么常用的基于排序的shuffle 算法没有完美的改组。
您可以使用它来随机播放列表,然后选择洗牌结果的第一项(或者您可能不希望两个奖品给同一个人 - 然后使用结果的n个初始位置,对于n =奖品数量);或者您可以简化算法以生成第一个项目;或者你可以看看那个网站,因为我可以发誓这里有一篇关于从一个任意树状结构中选择一个随机元素的文章,这个结构具有统一的分布,以纯粹的功能方式,提供正确性证明,但是我的搜索我失败了,似乎无法找到它。
答案 7 :(得分:0)
如果没有真正的随机位,你总会有一些偏见。为客人分配奖品的方式数量远远超过任何普通PRNG的期限,即使是相当少数的客人和奖品也是如此。正如lpthnc所建议的,购买一些真正的随机位,或购买一些随机位生成硬件。
至于算法,只需对随访列表进行随机随机播放即可。要小心,因为天真的改组算法确实有偏见:http://en.wikipedia.org/wiki/Shuffling#Shuffling_algorithms
答案 8 :(得分:0)
您可以100%可靠地从任意列表中随机选择一个随机项目,并且不知道列表中有多少项目。
count = 0.0;
item_selected = none;
foreach item in list
count = count + 1.0;
chance = 1.0 / count;
if ( random( 1.0 ) <= chance ) then item_selected = item;
测试程序比较单个rand()%N与上述迭代的结果:
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
static inline float frand01()
{
return (float)rand() / (float)RAND_MAX;
}
int _tmain(int argc, _TCHAR* argv[])
{
static const int NUM_ITEMS = 50;
int resultRand[NUM_ITEMS];
int resultIterate[NUM_ITEMS];
memset( resultRand, 0, NUM_ITEMS * sizeof(int) );
memset( resultIterate, 0, NUM_ITEMS * sizeof(int) );
for ( int i = 0; i < 100000; i++ )
{
int choiceRand = rand() % NUM_ITEMS;
int choiceIterate = 0;
float count = 0.0;
for ( int item = 0; item < NUM_ITEMS; item++ )
{
count = count + 1.0f;
float chance = 1.0f / count;
if ( frand01() <= chance )
{
choiceIterate = item;
}
}
resultRand[choiceRand]++;
resultIterate[choiceIterate]++;
}
printf("Results:\n");
for ( int i = 0; i < NUM_ITEMS; i++ )
{
printf( "%02d - %5d %5d\n", i, resultRand[i], resultIterate[i] );
}
return 0;
}
输出:
Results:
00 - 2037 2050
01 - 2038 2009
02 - 2094 1986
03 - 2007 1953
04 - 1990 2142
05 - 1867 1962
06 - 1941 1997
07 - 2023 1967
08 - 1998 2070
09 - 1930 1953
10 - 1972 1900
11 - 2013 1985
12 - 1982 2001
13 - 1955 2063
14 - 1952 2022
15 - 1955 1976
16 - 2000 2044
17 - 1976 1997
18 - 2117 1887
19 - 1978 2020
20 - 1886 1934
21 - 1982 2065
22 - 1978 1948
23 - 2039 1894
24 - 1946 2010
25 - 1983 1927
26 - 1965 1927
27 - 2052 1964
28 - 2026 2021
29 - 2090 1993
30 - 2039 2016
31 - 2030 2009
32 - 1970 2094
33 - 2036 2048
34 - 2020 2046
35 - 2010 1998
36 - 2104 2041
37 - 2115 2019
38 - 1959 1986
39 - 1998 2031
40 - 2041 1977
41 - 1937 2060
42 - 1946 2048
43 - 2014 1986
44 - 1979 2072
45 - 2060 2002
46 - 2046 1913
47 - 1995 1970
48 - 1959 2020
49 - 1970 1997