Бит хак для генерации всех целых чисел с заданным числом 1 с - PullRequest
11 голосов
/ 27 ноября 2011

Я забыл немного взломать, чтобы генерировать все целые числа с заданным числом 1 с.Кто-нибудь помнит это (и, вероятно, может объяснить это)?

Ответы [ 3 ]

18 голосов
/ 27 ноября 2011

От Битовые хаки

Обновление Тестовая программа Live On Coliru

#include <utility>
#include <iostream>
#include <bitset>

using I = uint8_t;

auto dump(I v) { return std::bitset<sizeof(I) * __CHAR_BIT__>(v); }

I bit_twiddle_permute(I v) {
    I t = v | (v - 1); // t gets v's least significant 0 bits set to 1
    // Next set to 1 the most significant bit to change, 
    // set to 0 the least significant ones, and add the necessary 1 bits.
    I w = (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(v) + 1));  
    return w;
}

int main() {
    I p = 0b001001;
    std::cout << dump(p) << "\n";
    for (I n = bit_twiddle_permute(p); n>p; p = n, n = bit_twiddle_permute(p)) {
        std::cout << dump(n) << "\n";
    }
}

печать

00001001
00001010
00001100
00010001
00010010
00010100
00011000
00100001
00100010
00100100
00101000
00110000
01000001
01000010
01000100
01001000
01010000
01100000
10000001
10000010
10000100
10001000
10010000
10100000
11000000

Вычислить лексикографически следующую битовую перестановку

Предположим, у нас есть набор из N битов, установленный в 1 в целом числе, и мы хотим следующую перестановку из N 1 битов в лексикографическом смысле. Например, если N равно 3, а битовая комбинация - 00010011, следующие комбинации будут 00010101, 00010110, 00011001,00011010, 00011100, 00100011 и так далее. Ниже приведен быстрый способ вычисления следующей перестановки.

unsigned int v; // current permutation of bits 
unsigned int w; // next permutation of bits

unsigned int t = v | (v - 1); // t gets v's least significant 0 bits set to 1
// Next set to 1 the most significant bit to change, 
// set to 0 the least significant ones, and add the necessary 1 bits.
w = (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(v) + 1));  

Встроенный компилятор __builtin_ctz(v) GNU C для процессоров x86 возвращает число конечных нулей. Если вы используете компиляторы Microsoft для x86, присуще значение _BitScanForward. Они оба испускают инструкцию bsf, но эквиваленты могут быть доступны для других архитектур. Если нет, то рассмотрите возможность использования одного из методов подсчета последовательных нулевых битов, упомянутых ранее.

Вот еще одна версия, которая обычно медленнее из-за оператора деления, но она не требует подсчета завершающих нулей.

unsigned int t = (v | (v - 1)) + 1;  
w = t | ((((t & -t) / (v & -v)) >> 1) - 1);  

Спасибо Дарио Снейдерманису из Аргентины, который предоставил это 28 ноября 2009 года.

11 голосов
/ 27 ноября 2011

Для битовых хаков я хотел бы сослаться на эту страницу: Битовые хаки Twiddling .

Что касается вашего конкретного вопроса, прочитайте часть, озаглавленную Вычислите лексикографически следующую битовую перестановку .


Вычислить лексикографически следующую битовую перестановку

Предположим, у нас есть набор из N битов, равный 1 в целом числе, и мы хотим следующую перестановкуN 1 бит в лексикографическом смысле.Например, если N равно 3, а битовая комбинация - 00010011, следующие комбинации будут 00010101, 00010110, 00011001,00011010, 00011100, 00100011 и так далее.Ниже приведен быстрый способ вычисления следующей перестановки.

unsigned int v; // current permutation of bits 
unsigned int w; // next permutation of bits

unsigned int t = v | (v - 1); // t gets v's least significant 0 bits set to 1
// Next set to 1 the most significant bit to change, 
// set to 0 the least significant ones, and add the necessary 1 bits.
w = (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(v) + 1));  

Встроенный компилятор GNU C __builtin_ctz (v) для процессоров x86 возвращает число конечных нулей.Если вы используете компиляторы Microsoft для x86, присуще _BitScanForward.Они оба испускают инструкцию bsf, но эквиваленты могут быть доступны для других архитектур.Если нет, то рассмотрите возможность использования одного из методов подсчета последовательных нулевых битов, упомянутых ранее.Вот еще одна версия, которая, как правило, работает медленнее из-за оператора деления, но не требует подсчета завершающих нулей.

unsigned int t = (v | (v - 1)) + 1;  
w = t | ((((t & -t) / (v & -v)) >> 1) - 1);  

Спасибо Дарио Снейдерманису из Аргентины, предоставившему это 28 ноября 2009 года.

2 голосов
/ 11 октября 2017

Добавить на ответ @ sehe, включенный ниже (первоначально от Дарио Снейдерманиса, также на http://graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation.)

#include <utility>
#include <iostream>
#include <bitset>

using I = uint8_t;

auto dump(I v) { return std::bitset<sizeof(I) * __CHAR_BIT__>(v); }

I bit_twiddle_permute(I v) {
    I t = v | (v - 1); // t gets v's least significant 0 bits set to 1
    // Next set to 1 the most significant bit to change, 
    // set to 0 the least significant ones, and add the necessary 1 bits.
    I w = (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(v) + 1));  
    return w;
}

int main() {
    I p = 0b001001;
    std::cout << dump(p) << "\n";
    for (I n = bit_twiddle_permute(p); n>p; p = n, n = bit_twiddle_permute(p)) 
{
        std::cout << dump(n) << "\n";
    }
}

Существуют пограничные проблемы с bit_twiddle_permute (I v). Всякий раз, когда v - последняя перестановка, t - все 1 (например, 2 ^ 8 - 1), (~t & -~t) = 0, а w - первая перестановка битов с одним меньшим 1 с, чем v, за исключением случаев, когда v = 000000000, в этом случае w = 01111111 , В частности, если вы установите p в 0; цикл в main произведет все перестановки с семью единицами, а следующая небольшая модификация цикла for будет циклически перебирать все перестановки с 0, 7, 6, ..., 1 установленным битом -

for (I n = bit_twiddle_permute(p); n>p; n = bit_twiddle_permute(n)) 

Если это намерение, возможно, оно заслуживает комментария. Если нет, то это тривиально исправить, например,

if (t == (I)(-1)) { return v >> __builtin_ctz(v); }

Итак, с дополнительным небольшим упрощением

I bit_twiddle_permute2(I v) {
    I t = (v | (v - 1)) + 1;
    if (t == 0) { return v >> __builtin_ctz(v); }
    I w = t | ((~t & v) >> (__builtin_ctz(v) + 1));
    return w;
}

int main() {
    I p = 0b1;
    cout <<  dump(p) << "\n";
    for (I n = bit_twiddle_permute2(p); n>p; n = bit_twiddle_permute2(n)) {
        cout << dump(n) << "\n";
    }
}

Следующей адаптации идеи Дарио Снейдерманиса может быть немного легче следовать

I bit_twiddle_permute3(I v) {
    int n = __builtin_ctz(v);
    I s = v >> n;  
    I t = s + 1;  
    I w = (t << n) | ((~t & s) >> 1);
    return w;
}

или с аналогичным решением проблемы, о которой я говорил в начале этого поста

I bit_twiddle_permute3(I v) {
    int n = __builtin_ctz(v);
    I s = v >> n;  
    I t = s + 1;  
    if (v == 0 || t << n == 0) { return s; }
    I w = (t << n) | ((~t & s) >> 1);
    return w;
}
...