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++)
пользователь не найден
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.