生成具有固定位数的随机数的最有效方法

时间:2012-12-11 15:27:58

标签: algorithm optimization language-agnostic random bit-manipulation

我需要生成一个随机数,但需要从具有相同设置位数的二进制数集中选择。例如。选择一个正好设置2位的随机字节值...

00000000 - no
00000001 - no
00000010 - no
00000011 - YES
00000100 - no
00000101 - YES
00000110 - YES
...

=> Set of possible numbers 3, 5, 6...

请注意,这是一组简化的数字。更多地考虑“选择一个正确的40位设置的随机64位数字”。该组中的每个数字必须同样可能出现。

7 个答案:

答案 0 :(得分:6)

从所有位位置进行随机选择,然后设置这些位。

Python中的示例:

def random_bits(word_size, bit_count):
    number = 0
    for bit in random.sample(range(word_size), bit_count):
        number |= 1 << bit
    return number

运行上述10次的结果:

0xb1f69da5cb867efbL
0xfceff3c3e16ea92dL
0xecaea89655befe77L
0xbf7d57a9b62f338bL
0x8cd1fee76f2c69f7L
0x8563bfc6d9df32dfL
0xdf0cdaebf0177e5fL
0xf7ab75fe3e2d11c7L
0x97f9f1cbb1f9e2f8L
0x7f7f075de5b73362L

答案 1 :(得分:5)

我找到了一个优雅的解决方案:随机二分法。

理念是平均的:

    带有随机数的
  • 除以设定位数<2,
  • 正在添加50%的设置位。

使用gcc编译的C代码(具有__builtin_popcountll):

#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
/// Return a random number, with nb_bits bits set out of the width LSB
uint64_t random_bits(uint8_t width, uint8_t nb_bits)
{
    assert(nb_bits <= width);
    assert(width <= 64);
    uint64_t x_min = 0;
    uint64_t x_max = width == 64 ? (uint64_t)-1 : (1UL<<width)-1;
    int n = 0;

    while (n != nb_bits)
    {
        // generate a random value of at least width bits
        uint64_t x = random();
        if (width > 31)
            x ^= random() << 31;
        if (width > 62)
            x ^= random() << 33;

        x = x_min | (x & x_max); // x_min is a subset of x, which is a subset of x_max
        n = __builtin_popcountll(x);
        printf("x_min = 0x%016lX, %d bits\n", x_min, __builtin_popcountll(x_min));
        printf("x_max = 0x%016lX, %d bits\n", x_max, __builtin_popcountll(x_max));
        printf("x     = 0x%016lX, %d bits\n\n", x, n);
        if (n > nb_bits)
            x_max = x;
        else
            x_min = x;
    }

    return x_min;
}

通常需要少于10个循环来达到所请求的位数(幸运的是,它可能需要2或3个循环)。拐角情况(nb_bits = 0,1,宽度-1,宽度)即使特殊情况更快也能正常工作。

结果示例:

x_min = 0x0000000000000000, 0 bits
x_max = 0x1FFFFFFFFFFFFFFF, 61 bits
x     = 0x1492717D79B2F570, 33 bits

x_min = 0x0000000000000000, 0 bits
x_max = 0x1492717D79B2F570, 33 bits
x     = 0x1000202C70305120, 14 bits

x_min = 0x0000000000000000, 0 bits
x_max = 0x1000202C70305120, 14 bits
x     = 0x0000200C10200120, 7 bits

x_min = 0x0000200C10200120, 7 bits
x_max = 0x1000202C70305120, 14 bits
x     = 0x1000200C70200120, 10 bits

x_min = 0x1000200C70200120, 10 bits
x_max = 0x1000202C70305120, 14 bits
x     = 0x1000200C70201120, 11 bits

x_min = 0x1000200C70201120, 11 bits
x_max = 0x1000202C70305120, 14 bits
x     = 0x1000200C70301120, 12 bits

width = 61, nb_bits = 12, x = 0x1000200C70301120

当然,你需要一个好的技巧。否则你可能面临无限循环。

答案 2 :(得分:4)

假设要设置的位数是b,字大小是w。我将创建一个长度为w的向量v,其中第一个b值设置为1,其余值设置为0.然后只是随机播放v。

答案 3 :(得分:2)

这是另一种选择,它在实践中非常简单且相当快。

choose a bit at random
if it is already set
    do nothing
else
    set it
    increment count
end if

重复直到count等于你想要设置的位数。

当你想要设置的位数(称为k)超过字长的一半(称之为N)时,这只会很慢。在这种情况下,请使用算法来设置N - k位,然后翻转结果中的所有位。

我打赌这里的预期运行时间非常好,虽然我现在太懒惰/愚蠢无法计算它。但是我可以将它限制为小于2 * k ...预计获得“头部”的硬币翻转次数为2次,并且每次迭代都有超过1/2次成功的机会。 / p>

答案 4 :(得分:1)

如果您没有Python random.sample的便利,可以使用经典的顺序采样算法在C中执行此操作:

unsigned long k_bit_helper(int n, int k, unsigned long bit, unsigned long accum) {
  if !(n && k)
    return accum;
  if (k > rand() % n)
    return k_bit_helper(n - 1, k - 1, bit + bit, accum + bit);
  else
    return k_bit_helper(n - 1, k, bit + bit, accum);
}

unsigned long random_k_bits(int k) {
  return k_bit_helper(64, k, 1, 0);
}

上述成本将由生成随机数的成本决定(在其他解决方案中也是如此)。如果通过批处理有一个好的prng,你可以稍微优化一下:例如,因为你知道随机数将在稳定下降的范围内,你可以获得nn-3的随机数通过获取0..(n * (n - 1) * (n - 2) * (n - 3))范围内的随机数,然后提取单个随机数:

r = randint(0, n * (n - 1) * (n - 2) * (n - 3) - 1);
rn  = r % n; r /= n
rn1 = r % (n - 1); r /= (n - 1);
rn2 = r % (n - 2); r /= (n - 2);
rn3 = r % (n - 3); r /= (n - 3);

n的最大值大概为64或2 6 ,因此上述产品的最大值肯定小于2 24 。实际上,如果你使用64位prng,你可以从中提取多达10个随机数。但是,除非您知道使用的prng产生独立的随机位,否则不要这样做。

答案 5 :(得分:1)

我有另一个基于枚举的建议:选择1和n之间的随机数i选择k,并生成第i个组合。例如,对于n = 6,k = 3,20种组合是:

000111
001011
010011
100011
001101
010101
100101
011001
101001
110001
001110
010110
100110
011010
101010
110010
011100
101100
110100
111000

假设我们随机选择组合编号7.我们首先检查它在最后一个位置是否有1:它有,因为前10个(5个选择2个)组合有。然后我们递归检查剩余的位置。这是一些C ++代码:

word ithCombination(int n, int k, word i) {
    // i is zero-based
    word x = 0;
    word b = 1;
    while (k) {
        word c = binCoeff[n - 1][k - 1];
        if (i < c) {
            x |= b;
            --k;
        } else {
            i -= c;
        }
        --n;
        b <<= 1;
    }
    return x;
}
word randomKBits(int k) {
    word i = randomRange(0, binCoeff[BITS_PER_WORD][k] - 1);
    return ithCombination(BITS_PER_WORD, k, i);
}

为了快速,我们在binCoeff中使用预先计算的二项式系数。函数randomRange返回两个边界之间的随机整数(包含)。

我做了一些时间安排(source)。使用C ++ 11默认随机数生成器,大部分时间用于生成随机数。然后这个解决方案是最快的,因为它使用可能的绝对最小随机位数。如果我使用快速随机数发生器,那么mic006的解决方案是最快的。如果已知k非常小,则最好随机设置位,直到设置k为止。

答案 6 :(得分:0)

不完全是算法建议,只是在 JavaScript 中找到了一个非常巧妙的解决方案,可以使用 ArrayBuffer 直接从 Math.random 输出位中获取随机位。

//Swap var out with const and let for maximum performance! I like to use var because of prototyping ease
var randomBitList = function(n){
    var floats = Math.ceil(n/64)+1;
    var buff = new ArrayBuffer(floats*8);
    var floatView = new Float64Array(buff);
    var int8View = new Uint8Array(buff);
    var intView = new Int32Array(buff);
    for(var i = 0; i < (floats-1)*2; i++){
        floatView[floats-1] = Math.random();
        int8View[(floats-1)*8] = int8View[(floats-1)*8+4];
        intView[i] = intView[(floats-1)*2];
    }
    this.get = function(idx){
        var i = idx>>5;//divide by 32
        var j = idx%32;
        return (intView[i]>>j)&1;
        //return Math.random()>0.5?0:1;
    };
    this.getBitList = function(){
        var arr = [];
        for(var idx = 0; idx < n; idx++){
            var i = idx>>5;//divide by 32
            var j = idx%32;
            arr[idx] = (intView[i]>>j)&1;
        }
        return arr;
    }
};