如何从列表中公平选择项目?

时间:2009-12-17 03:15:59

标签: algorithm language-agnostic random

假设我有一份奖品清单:

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)

中的int

9 个答案:

答案 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. 的第一项开始
  3. 在下一个传递中,您选择第二个项目的一半时间(1/2),这意味着第一个项目的概率为1 * (2-1)/2 = 1/2
  4. 在进一步迭代时,您选择概率为1/n的第n个项目,并且每个先前项目的机会减少(n-1)/n
  5. 因子

    这意味着当你走到尽头时,列表(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%可靠地从任意列表中随机选择一个随机项目,并且不知道列表中有多少项目。

Psuedo代码:

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
相关问题