PRNG用于精确生成n个设置位的数字


12

我目前正在编写一些代码来生成二进制数据。我特别需要生成具有给定数量的设置位的64位数字;更准确地说,该过程应取并返回一个伪随机的64位数字,其中恰好位设置为,其余设置为0。0<n<64n1

我当前的方法涉及以下内容:

  1. 生成一个伪随机数64。k
  2. 计算的位数,并将结果存储在。kb
  3. 如果,输出 ; 否则转到1。b=nk

这行得通,但看起来并不优雅。是否有某种PRNG算法可以比这更优雅地生成带有设置位的数字?n

Answers:


12

您需要的是0到之间的一个随机数。然后的问题是将其变成位模式。(64n)1

这被称为枚举编码,它是最古老的部署压缩算法之一。可能最简单的算法来自Thomas Cover。根据简单的观察,如果您有一个单词,它的长度为位,并且设置的位以最高位顺序为,则该单词在所有具有该属性的单词的词典编排顺序中的位置是:nxkx1

1ik(xii)

因此,例如,对于7位字:

i(0000111)=(23)+(12)+(01)=0
i(0001011)=(33)+(12)+(01)=1
i(0001101)=(33)+(22)+(01)=2

...等等。

要从顺序中获取位模式,只需依次解码每个位。类似于C的语言:

uint64_t decode(uint64_t ones, uint64_t ordinal)
{
    uint64_t bits = 0;
    for (uint64_t bit = 63; ones > 0; --bit)
    {
        uint64_t nCk = choose(bit, ones);
        if (ordinal >= nCk)
        {
            ordinal -= nCk;
            bits |= 1 << bit;
            --ones;
        }
    }
    return bits;
}

请注意,由于只需要不超过64的二项式系数,因此可以对其进行预先计算。


  • 封面,T。,枚举源编码。IEEE Transactions on Information Theory,第IT-19卷,第1号,1973年1月。

美丽优雅!枚举编码看起来非常有用-上面是否有任何良好的资源(最好是教科书形式)?
Koz Ross

实际上这是否会带来更好的性能?(当然,这取决于RNG的速度。)如果不是,则使用更复杂的代码毫无意义。
吉尔斯(Gilles)“所以,别再邪恶了”

1
@Giles我将此解释为计算机科学问题,因为这是cs.se。我之所以只提供源代码,是因为我碰巧在一个RRR数组实现中放了它。(例如,有关其含义的解释,请参见alexbowe.com/rrr。)–
别名

1
@Gilles为了跟进您的问题,我同时实现了我的幼稚方法和Pseudonym在Forth中提供的方法。天真的方法,即使使用非常简单的xorshift PRNG,每个数字也要花费大约20秒的时间,而Pseudonym的方法几乎是瞬时的。我为此使用了预先计算的二项式表。
科兹·罗斯

1
@KozRoss如果生成n位数字,并查找设置了k位的数字,则如果k距离n / 2太远,它们将非常罕见。那会解释它。
gnasher729

3

与通过其他方式获得的Pseudonym的答案非常相似。

可用组合的总数可以通过stars and bars方法达到,因此必须为。您尝试从中采样数字的64位数字总数显然要高得多。c=(64n)

然后您需要的是一个函数,可以将您从一个伪随机数(从到引导到相应的64位组合。k1c

Pascal的三角形可以为您提供帮助,因为每个节点的值都精确地代表了从该节点到三角形根的路径数,并且如果所有的左转都是,则每个路径都可以代表您要查找的字符串之一。标记为,每个右转标记为。10

因此,令为要确定的剩余位数,而为要使用的剩余位数。xy

我们知道,我们可以使用它来正确确定数字的下一位在每一步:(xy)=(x1y)+(x1y1)

whilex>0

ifx>y

ifk>(x1y):ss+"1",kk(x1y),yy1

else:ss+"0"

else:ss+"1",yy1

xx1


2

另一个相当优雅的方法是使用stackoverflow答案中所述的二等分。想法是保留两个单词,一个已知最多设置k个位,另一个已知至少设置k个位,并使用随机性将其中之一移到恰好具有k个位。以下是一些说明它的源代码:

word randomKBits(int k) {
    word min = 0;
    word max = word(~word(0)); // all 1s
    int n = 0;
    while (n != k) {
        word x = randomWord();
        x = min | (x & max);
        n = popcount(x);
        if (n > k)
            max = x;
        else
            min = x;
    }
    return min;
}

对各种方法进行了性能比较,除非已知k非常小,否则这通常是最快的。


0

您可以执行以下操作:

1)产生到之间的随机数。k164

2)将 th设置为。k01

3)重复步骤1和2次n

A[]是全的位数组640

for(i=1 to n)
{
    k=ran(1,65-i) % random number between 1 and 65-i
    for(x=1;x<65;x++)
    {
        if(A[x]==0)k--;
        if(k==0)break;
    }
    A[x]=1;
}

散文似乎与您的代码不匹配?代码从不将1s 分配给数组。同样,当多个ks碰撞时,它似乎并没有产生均匀的分布(甚至没有满足约束的数字)
Bergi 16'Dec 20'16

@Bergi Ya忘记了行...现在添加了它。并且处理k的多次冲突。看到第一个数字在1到64之间选择,第二个在1到“剩余” 63之间选择。因此它在计数时跳过了1 ...请参见线。而且是均匀分布。A[x]=1if(A[x]==0)k;
找不到用户

啊,我明白了。散文算法没有提到跳过。
Bergi

@ArghyaChakraborty您是否在使用基于1的索引?
Koz Ross

@KozRoss开始用,如果发生了什么(当然是将是全零)那么,它会检查,并获得意义给出。因此,在循环外部设置。是的,它是基于1的索引。为了使它0基于所有你需要做的就是改变内到i=1,k=1AA[1]==0truek;k=0A[1]=1for(x=0;x<64;x++)
找不到用户
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.