PRNG для генерування чисел з п ять заданих бітів


12

В даний час я пишу код для створення двійкових даних. Мені спеціально потрібно генерувати 64-бітні числа з заданою кількістю встановлених бітів; точніше, процедура повинна приймати деяку і повертати псевдовипадкове 64-бітове число з точно яти бітами, встановленими на , а решта встановити на 0.n 10<n<64n1

Мій сучасний підхід передбачає щось подібне:

  1. Створити псевдовипадкове 64-розрядне число .k
  2. Підраховуйте біти в , зберігаючи результат у .bkb
  3. Якщо , вихід ; інакше перейдіть до 1.kb=nk

Це працює, але це здається неелегантним. Чи існує якийсь алгоритм PRNG, який може генерувати числа з заданих бітів більш елегантно, ніж цей?n

Відповіді:


12

Вам потрібно випадкове число між 0 і . Проблема тоді полягає в тому, щоб перетворити це на бітовий зразок.(64n)1

Це відомо як кодувальне кодування, і це один із найдавніших розгорнутих алгоритмів стиснення. Напевно, найпростіший алгоритм - від Thomas Cover. Він заснований на простому спостереженні, що якщо у вас є слово, яке має біт, де задані біти є у найбільш значущому порядку розрядів, то положення цього слова в лексикографічному упорядкуванні всіх слів із цією властивістю є: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, ви можете попередньо обчислити їх.


  • Обкладинка, Т., Переліку кодувань джерел . Угоди IEEE з інформаційної теорії, т. ІТ-19, № 1, січ 1973.

Красиво і елегантно! Чисельне кодування виглядає як щось дуже корисне - чи є на ньому хороші ресурси (бажано у формі підручника)?
Коз Росс

Це насправді дає кращі результати на практиці? (Звичайно, це залежить від швидкості RNG.) Якщо ні, тоді немає сенсу використовувати складніший код.
Жил "ТАК - перестань бути злим"

1
@Giles Я трактував це як питання інформатики, оскільки це cs.se. Я дав вихідний код лише тому, що у мене трапилося, що він лежить навколо реалізації масиву RRR. (Див., Наприклад, alexbowe.com/rrr для пояснення того, що це означає.)
Псевдонім

1
@Gilles Щоб продовжити ваше запитання, я реалізував як свій наївний метод, так і той, який надав псевдонім у Forth. Наївний метод, навіть при використанні дуже простого PRSG xorshift, займав щось у порядку 20 секунд на число , тоді як метод псевдоніму був майже миттєвим. Для цього я використав таблиці попередньо обчислених двочленів.
Коз Росс

1
@KozRoss Якщо ви генеруєте n бітових чисел і шукаєте числа з набором k біт, вони будуть досить рідкісними, якщо k далеко від n / 2; це пояснило б це.
gnasher729

3

Дуже схожа на відповідь псевдоніма, отриману іншими способами.

Загальна кількість доступних комбінацій доступна методом зірок і брусків , тому вона повинна бути . Загальна кількість 64-розрядних чисел, з яких ви намагаєтеся відібрати свій номер, було б, очевидно, набагато вищим за це.c=(64n)

Тоді вам потрібна функція, яка може привести вас від псевдовипадкового числа , починаючи від до , до відповідної 64-бітної комбінації.1 ck1c

Трикутник Паскаля може допомогти вам у цьому, тому що значення кожного вузла відображає саме кількість шляхів від цього вузла до кореня трикутника, і кожен шлях може бути зроблений таким чином, щоб він відображав один з рядків, який ви шукаєте, якщо всі ліві повороти є позначено позначкою , а кожен правий поворот .010

Тож нехай - кількість бітів, що залишилися для визначення, а - кількість оставшихся для використання.у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) Утворіть випадкове число, між і .k164

2) Встановіть th до .k01

3) Повторіть кроки 1 і 2 разиn

A[] - це бітний масив з усіма s640

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 масиву s. Також, схоже, не утворюється рівномірний розподіл (і навіть не числа, що задовольняють обмеженням) при 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.