Бит взломать для генерации всех целых чисел с заданным числом 1s

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

Ответ 1

От Бит Twiddling Hacks

Обновить тестовая программа 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 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 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 года.

Ответ 2

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

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


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

Предположим, что у нас есть шаблон из 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 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 года.

Ответ 3

Чтобы добавить в @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 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 - первая перестановка бит с одним меньшим числом, чем v, за исключением тех случаев, когда v = 000000000, в этом случае w = 01111111, В частности, если вы установите p на 0; цикл в главном будет производить все перестановки с семью 1, а следующая небольшая модификация цикла 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;
}