简体   繁体   中英

Best c++ way to choose randomly position of set bit in bitset

I have std::bitset<32> word and I want to choose randomly and index (0-31) of some bit which is 1. How can I do that without loops and counters. Is there any std::algorithm suitable for that? If it's easier I can convert the bitset to string or int and make it on the string or int.

Here's a first stab at it:

std::bitset<32> bitset{...};

std::mt19937 prng(std::time(nullptr));
std::uniform_int_distribution<std::size_t> dist{1, bitset.count()};

std::size_t p = 0;
for(std::size_t c = dist(prng); c; ++p)
    c -= bitset[p];

// (p - 1) is now the index of the chosen bit.

It works by counting the set bits, doing the random pick c in that interval, then looking for the c th set bit.

If you have 32-bit (or even 64-bit) bitset, more efficient solution would be to convert to integer and then use bitwise operations on that integer to get random set bit.

Here is how you can convert your bitset to unsigned long:

std::bitset<32> word(0x1028);
unsigned long ulWord = word.to_ulong();    // ulWord == 0x1028

Then you can use “Select the bit position“ function from the Bit Twiddling Hacks page to select random set bit efficiently:

unsigned int bitcnt = word.count();
unsigned int randomSetBitIndex = 63-selectBit(ulWord, random() % bitcnt + 1);
unsigned long randomSetBit = 1 << randomSetBitIndex;

Here is the full code:

// Select random set bit from a bitset

#include <iostream>
#include <bitset>
#include <random>

using namespace std;

unsigned int selectBit(unsigned long long v, unsigned int r) {
    // Source: https://graphics.stanford.edu/~seander/bithacks.html
    // v - Input:  value to find position with rank r.
    // r - Input: bit's desired rank [1-64].
    unsigned int s;      // Output: Resulting position of bit with rank r [1-64]
    uint64_t a, b, c, d; // Intermediate temporaries for bit count.
    unsigned int t;      // Bit count temporary.

    // Do a normal parallel bit count for a 64-bit integer,
    // but store all intermediate steps.
    a =  v - ((v >> 1) & ~0UL/3);
    b = (a & ~0UL/5) + ((a >> 2) & ~0UL/5);
    c = (b + (b >> 4)) & ~0UL/0x11;
    d = (c + (c >> 8)) & ~0UL/0x101;
    t = (d >> 32) + (d >> 48);
    // Now do branchless select!
    s  = 64;
    s -= ((t - r) & 256) >> 3; r -= (t & ((t - r) >> 8));
    t  = (d >> (s - 16)) & 0xff;
    s -= ((t - r) & 256) >> 4; r -= (t & ((t - r) >> 8));
    t  = (c >> (s - 8)) & 0xf;
    s -= ((t - r) & 256) >> 5; r -= (t & ((t - r) >> 8));
    t  = (b >> (s - 4)) & 0x7;
    s -= ((t - r) & 256) >> 6; r -= (t & ((t - r) >> 8));
    t  = (a >> (s - 2)) & 0x3;
    s -= ((t - r) & 256) >> 7; r -= (t & ((t - r) >> 8));
    t  = (v >> (s - 1)) & 0x1;
    s -= ((t - r) & 256) >> 8;
    return 64-s;
}

int main() {
    // Input
    std::bitset<32> word(0x1028);

    // Initialize random number generator
    std::random_device randDevice;
    std::mt19937 random(randDevice());

    // Select random bit
    unsigned long ulWord = word.to_ulong();
    unsigned int bitcnt = word.count();
    unsigned int randomSetBitIndex = 63-selectBit(ulWord, random() % bitcnt + 1);
    unsigned long randomSetBit = 1 << randomSetBitIndex;

    // Output
    cout << "0x" <<  std::hex << randomSetBit << endl;  // either 0x8, 0x20 or 0x1000
    return 0;
}

Run it on Ideone .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM