PRNG для генерации чисел с n установленными битами точно

12

В настоящее время я пишу код для генерации двоичных данных. Мне конкретно нужно генерировать 64-битные числа с заданным количеством установленных битов; Точнее, процедура должна занять около 0<n<64 и вернуть псевдослучайное 64-битное число с точно n битами, установленными в 1 , а остальные - в 0.

Мой текущий подход включает в себя что-то вроде этого:

  1. Генерация псевдослучайного 64-битного числа k .
  2. Посчитайте биты в k , сохранив результат в b .
  3. Если b=n , выведите k ; в противном случае перейдите к 1.

Это работает, но, кажется, не элегантно. Существует ли какой-нибудь алгоритм PRNG, который может генерировать числа с n установленными битами более элегантно, чем этот?

Коз Росс
источник

Ответы:

12

Вам нужно случайное число от 0 до . Проблема тогда состоит в том, чтобы превратить это в битовую комбинацию.(64n)1

Это известно как перечислительное кодирование, и это один из самых старых развернутых алгоритмов сжатия. Вероятно, самый простой алгоритм от Томаса Ковер. Это основано на простом наблюдении, что если у вас есть слово длиной битов, где заданные биты в порядке разрядов, то позиция этого слова в лексикографическом порядке всех слов с этим свойством является:x kx 1nxkx1

1ik(xii)

Так, например, для 7-битного слова:

i(0001011)= ( 3

i(0000111)=(23)+(12)+(01)=0
i(0001101)= ( 3
i(0001011)=(33)+(12)+(01)=1
i(0001101)=(33)+(22)+(01)=2

...и так далее.

Чтобы получить битовый шаблон из порядкового номера, вы просто декодируете каждый бит по очереди. Примерно так, на C-подобном языке:

uint64_t decode(uint64_t ones, uint64_t ordinal)
{
    uint64_t bits = 0;
    for (uint64_t bit = 63; ones > 0; --bit)
    {
        uint64_t nCk = choose(bit, ones);
        if (ordinal >= nCk)
        {
            ordinal -= nCk;
            bits |= 1 << bit;
            --ones;
        }
    }
    return bits;
}

Обратите внимание, что поскольку вам нужны только биномиальные коэффициенты до 64, вы можете их предварительно вычислить.


  • Cover, T., Enumerative Source Encoding . IEEE Труды по теории информации, том IT-19, № 1, январь 1973 г.
Псевдоним
источник
Красиво и элегантно! Перечислительное кодирование выглядит как нечто очень полезное - есть ли на нем хорошие ресурсы (желательно в виде учебника)?
Коз Росс
Это на самом деле дает лучшую производительность на практике? (Конечно, это зависит от скорости ГСЧ.) Если нет, то нет смысла использовать более сложный код.
Жиль "ТАК - перестань быть злым"
1
@ Джайлз Я интерпретировал это как вопрос информатики, так как это cs.se. Я дал исходный код только потому, что случайно оказался в реализации массива RRR. (См., Например, alexbowe.com/rrr для объяснения того, что это значит.)
Псевдоним
1
@Gilles Чтобы ответить на ваш вопрос, я применил как мой наивный метод, так и метод, предоставленный псевдонимом в Forth. Наивный метод, даже при использовании очень простого PRNG ксоршифта, занимал что-то порядка 20 секунд на число , тогда как метод псевдонима был почти мгновенным. Для этого я использовал таблицы предварительно вычисленных биномов.
Коз Росс
1
@KozRoss Если вы генерируете n битовых чисел и ищете числа с установленными k битами, они будут довольно редкими, если k далеко от n / 2; это объяснило бы это.
gnasher729
3

Очень похоже на ответ псевдонима, полученный другими способами.

c=(64n)

k1c

10

xy

(xy)=(x1y)+(x1y1)

whilex>0

ifx>y

ifk>(x1y):ss+"1",kk(x1y),yy1

else:ss+"0"

else:ss+"1",yy1

xx1

Андре Соуза Лемос
источник
2

Еще один довольно элегантный метод - использовать разделение пополам, как описано в этом ответе stackoverflow . Идея состоит в том, чтобы сохранить два слова, одно из которых имеет не более k установленных битов, а другое - как минимум k установленных битов, и использовать случайность, чтобы переместить одно из них к точно k битам. Вот некоторый исходный код, чтобы проиллюстрировать это:

word randomKBits(int k) {
    word min = 0;
    word max = word(~word(0)); // all 1s
    int n = 0;
    while (n != k) {
        word x = randomWord();
        x = min | (x & max);
        n = popcount(x);
        if (n > k)
            max = x;
        else
            min = x;
    }
    return min;
}

Я провел сравнение производительности различных методов, и этот метод, как правило, самый быстрый, если известно, что k очень мало.

Фальк Хюффнер
источник
0

Вы можете сделать следующее:

1) Генерация случайного числа, между и .1 64k164

2) Установите th на .0 1k01

3) Повторите шаги 1 и 2 разn

64 0A[] - это битный массив со всеми с640

for(i=1 to n)
{
    k=ran(1,65-i) % random number between 1 and 65-i
    for(x=1;x<65;x++)
    {
        if(A[x]==0)k--;
        if(k==0)break;
    }
    A[x]=1;
}
Пользователь не найден
источник
Проза не соответствует вашему коду? Код никогда не присваивает 1s массиву. Кроме того, кажется, что он не генерирует равномерное распределение (и даже числа, которые удовлетворяют ограничениям), когда kсталкиваются несколько s
Берги
@Bergi Я забыл строку ... добавил ее сейчас. И многократное столкновение k обрабатывается. Смотрите, первое число выбирается в диапазоне от 1 до 64, второе - от 1 до «оставшихся» 63. Таким образом, оно пропускает 1 при подсчете ... см.линия. И это равномерное распределение. A[x]=1if(A[x]==0)k;
Пользователь не найден
Ах, теперь я вижу. Алгоритм прозы не упомянул пропуск.
Берги
@ArghyaChakraborty Используете ли вы там индексирование на основе 1?
Коз Росс
@KozRoss Начните с того, что произойдет, если (конечно, будет все нули) Итак, он проверит и получит значениечто дает . Итак, устанавливает вне цикла. Так что да, это индексация на основе 1. Чтобы сделать его на основе 0, все, что вам нужно сделать, это изменить внутреннее значение наA A [ 1 ] = = 0 t r u e k - - ; k = 0 A [ 1 ] = 1 f o r ( x = 0 ; x < 64 ; x + + )i=1,k=1AA[1]==0truek;k=0A[1]=1for(x=0;x<64;x++)
пользователь не найден