PRNG для генерации чисел с n установленными битами точно
12
В настоящее время я пишу код для генерации двоичных данных. Мне конкретно нужно генерировать 64-битные числа с заданным количеством установленных битов; Точнее, процедура должна занять около 0<n<64 и вернуть псевдослучайное 64-битное число с точно n битами, установленными в 1 , а остальные - в 0.
Мой текущий подход включает в себя что-то вроде этого:
Генерация псевдослучайного 64-битного числа k .
Посчитайте биты в k , сохранив результат в b .
Если b=n , выведите k ; в противном случае перейдите к 1.
Это работает, но, кажется, не элегантно. Существует ли какой-нибудь алгоритм PRNG, который может генерировать числа с n установленными битами более элегантно, чем этот?
Вам нужно случайное число от 0 до . Проблема тогда состоит в том, чтобы превратить это в битовую комбинацию.(64n)−1
Это известно как перечислительное кодирование, и это один из самых старых развернутых алгоритмов сжатия. Вероятно, самый простой алгоритм от Томаса Ковер. Это основано на простом наблюдении, что если у вас есть слово длиной битов, где заданные биты в порядке разрядов, то позиция этого слова в лексикографическом порядке всех слов с этим свойством является:x k … x 1nxk…x1
∑1≤i≤k(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-подобном языке:
Красиво и элегантно! Перечислительное кодирование выглядит как нечто очень полезное - есть ли на нем хорошие ресурсы (желательно в виде учебника)?
Коз Росс
Это на самом деле дает лучшую производительность на практике? (Конечно, это зависит от скорости ГСЧ.) Если нет, то нет смысла использовать более сложный код.
Жиль "ТАК - перестань быть злым"
1
@ Джайлз Я интерпретировал это как вопрос информатики, так как это cs.se. Я дал исходный код только потому, что случайно оказался в реализации массива RRR. (См., Например, alexbowe.com/rrr для объяснения того, что это значит.)
Псевдоним
1
@Gilles Чтобы ответить на ваш вопрос, я применил как мой наивный метод, так и метод, предоставленный псевдонимом в Forth. Наивный метод, даже при использовании очень простого PRNG ксоршифта, занимал что-то порядка 20 секунд на число , тогда как метод псевдонима был почти мгновенным. Для этого я использовал таблицы предварительно вычисленных биномов.
Коз Росс
1
@KozRoss Если вы генерируете n битовых чисел и ищете числа с установленными k битами, они будут довольно редкими, если k далеко от n / 2; это объяснило бы это.
gnasher729
3
Очень похоже на ответ псевдонима, полученный другими способами.
Еще один довольно элегантный метод - использовать разделение пополам, как описано в этом ответе 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;
}
Проза не соответствует вашему коду? Код никогда не присваивает 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++)
Очень похоже на ответ псевдонима, полученный другими способами.
источник
Еще один довольно элегантный метод - использовать разделение пополам, как описано в этом ответе stackoverflow . Идея состоит в том, чтобы сохранить два слова, одно из которых имеет не более k установленных битов, а другое - как минимум k установленных битов, и использовать случайность, чтобы переместить одно из них к точно k битам. Вот некоторый исходный код, чтобы проиллюстрировать это:
Я провел сравнение производительности различных методов, и этот метод, как правило, самый быстрый, если известно, что k очень мало.
источник
Вы можете сделать следующее:
1) Генерация случайного числа, между и .1 64k 1 64
2) Установите th на .0 1k 0 1
3) Повторите шаги 1 и 2 разn
64 0A[] - это битный массив со всеми с64 0
источник
1
s массиву. Кроме того, кажется, что он не генерирует равномерное распределение (и даже числа, которые удовлетворяют ограничениям), когдаk
сталкиваются несколько s