【问题标题】:Most efficient method of generating a random number with a fixed number of bits set生成具有固定位数集的随机数的最有效方法
【发布时间】:2012-11-29 04:04:31
【问题描述】:

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

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

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

请注意,这是一组简化的数字。更多地考虑“选择一个恰好设置了 40 位的随机 64 位数字”。集合中的每个数字出现的可能性必须相同。

【问题讨论】:

  • 为设置位选择N随机位置。

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


【解决方案1】:

从所有位位置的集合中随机选择,然后设置这些位。

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

【讨论】:

  • 只要确保你没有两次选择同一个。
  • 集合将是 nCr 大小的。 C(64,40) = 64! / ( 40! (64 - 40)! ) = 250649105469666120 个条目。太大而无法放入内存,可能需要进行某种压缩。
  • 您需要考虑到您可能会选择两次相同的位置
  • @Uday,我说的是“位位置”——其中只有 64 个。我希望我添加的代码示例能让它更清晰。
  • @frankc,如果我不清楚,请原谅我。 “随机选择”是指从一个集合中的成员已经是唯一的集合中进行选择。
【解决方案2】:

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

平均而言:

  • 用一个随机数除以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,width-1,width) 也能正常工作。

结果示例:

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 有趣。我想知道编写一个“按位洗牌”来洗牌实际位是否合理。
    • 应该是可以的。众所周知的最佳洗牌称为fisher-yates。它只是涉及巧妙地交换位置,所以我不明白为什么它不能用按位运算来完成
    【解决方案4】:

    这是另一个在实践中非常简单且速度相当快的选项。

    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... 掷硬币获得“正面”的预期次数是两次,这里的每次迭代都有超过 1/2 的成功机会。

    【讨论】:

      【解决方案5】:

      如果您没有 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或者26,所以上面乘积的最大值肯定小于224。事实上,如果您使用 64 位 prng,您可以从中提取多达 10 个随机数。但是,除非您知道您使用的 prng 会产生独立的随机位,否则不要这样做。

      【讨论】:

      • 关于将长随机数分割成更小的范围的提示值得记住。
      【解决方案6】:

      我还有一个基于枚举的建议:选择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

      【讨论】:

        【解决方案7】:

        不完全是算法建议,只是在 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;
            }
        };
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-09-25
          • 2014-02-24
          • 2015-12-19
          • 1970-01-01
          • 2016-05-06
          • 1970-01-01
          相关资源
          最近更新 更多