Розширити випадковий діапазон від 1–5 до 1–7


692

З огляду на функцію, яка виробляє випадкове ціле число в діапазоні від 1 до 5, запишіть функцію, яка виробляє випадкове ціле число в діапазоні від 1 до 7.

  1. Що таке просте рішення?
  2. Що є ефективним рішенням для зменшення використання пам'яті або роботи на повільному процесорі?

Це виявилося несподівано цікавою проблемою, я все ще думаю, як 1) зробити це у встановлений час і 2) не зіпсувати рівномірний розподіл (якщо був)
eugensk

У нас була аналогічна проблема, коли вибирали одного гравця з 5 із кістки. Ми кидали кістки по черзі, обирається той, хто отримує максимальний бал. Рівномірність була досягнута, але не постійність часу :)
eugensk

Мені буде присвоєно відповідь, якщо я опублікував відповідь, в якій сказано, що проблема не передбачає, що ви повинні використовувати задану функцію і просто написати ту, яка повертається 1-7 випадковим чином?
Доктор Блакитний

Про що 7 * rand5() / 5?
kiwixz

@kiwixz, це призведе до "від 1 до 7", але ви не отримаєте 3 або 6: {1: 19,96, 2: 20,02, 4: 20,01, 5: 19,99, 7: 20,02} грубі відсотки тестувати вручну. 7 * .2, 7 * .4, 7 * .6, 7 * .8, 7 * 1.
пітонхоррі

Відповіді:


572

Це еквівалентно рішенню Адама Розенфілда, але може бути дещо зрозумілішим для деяких читачів. Він передбачає, що rand5 () - це функція, яка повертає статистично випадкове ціле число в діапазоні від 1 до 5 включно.

int rand7()
{
    int vals[5][5] = {
        { 1, 2, 3, 4, 5 },
        { 6, 7, 1, 2, 3 },
        { 4, 5, 6, 7, 1 },
        { 2, 3, 4, 5, 6 },
        { 7, 0, 0, 0, 0 }
    };

    int result = 0;
    while (result == 0)
    {
        int i = rand5();
        int j = rand5();
        result = vals[i-1][j-1];
    }
    return result;
}

Як це працює? Подумайте про це так: уявіть собі, як роздрукувати цей двовимірний масив на папері, прив’язавши його до дошки з дартсом і випадково кидаючи на неї дротики. Якщо ви потрапили на ненульове значення, це статистично випадкове значення між 1 і 7, оскільки для вибору є рівне число ненульових значень. Якщо ви потрапили в нуль, просто продовжуйте кидати дротик, поки не потрапите на нуль. Ось що робить цей код: індекси i та j випадковим чином вибирають місце на дошці дартса, і якщо ми не отримаємо хорошого результату, ми продовжуємо кидати дартс.

Як сказав Адам, у гіршому випадку це може тривати вічно, але статистично найгірший випадок ніколи не буває. :)


5
Я зрозумів логіку цього рішення, але не можу зрозуміти, що це призводить до рівномірної ймовірності? Хтось може пояснити математику?
користувач1071840

6
@ user1071840 - якщо rand5рівномірна, кожна комірка в valsсітці має однакову ймовірність вибору. Сітка містить рівно три копії кожного цілого числа в інтервалі [1, 7] плюс чотири нулі. Таким чином, "сировинний" потік результатів має тенденцію до рівномірної суміші [1, 7] значень, плюс деякі нулі, які трапляються в tad частіше, ніж будь-яке окреме дозволене значення. Але це не має значення, оскільки нулі знімаються, залишаючи лише рівну суміш [1, 7] значень.
Даніель Ервікер

3
Швидкий шлях до вирішення проблеми з цим: якщо ви телефонуєте лише rand5 () лише один раз, то у вас є лише 5 можливих результатів. Очевидно, немає способу перетворити це на понад 5 можливих результатів, не додаючи більше випадковості.
Даніель Ервікер

1
Більш довга версія: rand5 () може мати лише значення (1, 2, 3, 4, 5). Тому rand5 () * 5 може мати лише значення (5, 10, 15, 20, 25), що не те саме, що повний діапазон (1 ... 25). Якби це було, віднімання 4 зробить це (-3 ... 21), але в цьому випадку це стає (1, 6, 11, 16, 21), тож кінцеві точки є правильними, але є чотири великі дірки: ( 2..5), (7..10), (12..15), (17..21). Нарешті ви робите мод 7 і додаєте 1, даючи (2, 7, 5, 3, 1). Тож ні 4, ні 6 ніколи не трапляються. Але (див. Вище ярлик) ми знали, що в результаті може бути лише 5 чисел у результуючому діапазоні, тому повинно бути два прогалини.
Даніель Ервікер

1
Ах, тому що у нас є лише rand5 (), а не rand2 () :-)
gzak

353

Немає (точно правильного) рішення, яке працюватиме за постійну кількість часу, оскільки 1/7 - це нескінченний десятковий знак у базі 5. Одним простим рішенням буде використання вибірки відхилення, наприклад:


int i;
do
{
  i = 5 * (rand5() - 1) + rand5();  // i is now uniformly random between 1 and 25
} while(i > 21);
// i is now uniformly random between 1 and 21
return i % 7 + 1;  // result is now uniformly random between 1 and 7

Це очікуване час виконання 25/21 = 1,19 ітерацій циклу, але існує нескінченно мало мала ймовірність циклу назавжди.


7
-1 не потрібен, якщо> 21 перевернуто до> 26 б / с, не має значення, куди я знижую нижню карту,
BCS

26
Займіться поясненням, чому це правильно: Скажіть, що я хочу написати програму, яка виводить потік рівномірних випадкових чисел від 1 до 25; для цього я просто поверну 5 * (rand5 () - 1) + rand5 (), як у коді у відповіді. Тепер, якщо я хочу створити потік рівномірних випадкових чисел між 1 і 21, якщо я просто використовую перший потік, але відфільтрую його, щоб числа в [22, 25] були відхилені, я можу створити і цей потік. Далі, якщо я беру цей потік і фільтрую його так, щоб для кожного елемента x виводився x% 7 + 1, у мене є потік рівномірних випадкових чисел від 1 до 7! Досить просто, чи не так? : D
Паггас

6
І ви маєте рацію, що це зводиться до того, чи хочете ви ідеального розподілу з необмеженим найгіршим випадком виконання, або недосконалим розподілом із обмеженим часом виконання. Це наслідок того, що всі потужності 5 не діляться на 7, або рівнозначно, якщо у вас 5 ^ n однаково ймовірно послідовності довжини n, немає способу присвоїти кожній послідовності число від 1 до 7 таким чином, щоб кожен з 1..7 однаково ймовірно.
Адам Розенфілд

5
@Jules Olléon: Припустимо, існувало рішення, яке працює постійно, який гарантовано здійснював не більше ніж Nдзвінки rand5()в гіршому випадку. Потім, існує 5 ^ N можливих результатів послідовності викликів rand5, кожен з яких має вихід 1-7. Отже, якщо скласти всі можливі послідовності викликів, вихід яких становить kдля кожного 1≤k≤7, то ймовірність того, що вихід k- m / 5 ^ N, де m - кількість таких послідовностей. Так, m / 5 ^ N = 1/7, але можливих цілих розв’язків (N, m) цього ==> протиріччя немає.
Адам Розенфілд

4
@paxdiablo: Ви неправі. Шанс справжнього RNG генерувати нескінченну послідовність 5 становить рівно 0, використовуючи подібні міркування того, що гортання монети нескінченно багато разів гарантується, щоб не створити нескінченну кількість послідовних голів . Це також означає, що ймовірність того, що цей код зациклюється назавжди, рівно дорівнює 0 (хоча є й позитивний шанс, що він зациклюється на будь-яку довільну кількість ітерацій).
BlueRaja - Danny Pflughoeft

153

Я хотів би додати ще одну відповідь, окрім своєї першої відповіді . Ця відповідь намагається мінімізувати кількість дзвінків на rand5()один дзвінок rand7(), щоб максимально використовувати випадковість. Тобто, якщо ви розглядаєте випадковість як дорогоцінний ресурс, ми хочемо використовувати якомога більше його, не відкидаючи жодних випадкових біт. Ця відповідь також має деякі подібності з логікою, представленою в відповіді Івана .

Ентропія випадкової величини є добре визначеною величиною. Для випадкової змінної, яка приймає N станів з однаковими ймовірностями (рівномірний розподіл), ентропія являє собою log 2 N. Таким чином, rand5()має приблизно 2.32193 біт ентропії та rand7()має приблизно 2.80735 біт ентропії. Якщо ми сподіваємося максимально використати випадковість, нам потрібно використовувати всі 2.32193 біти ентропії від кожного виклику до rand5()та застосувати їх для отримання 2.80735 біт ентропії, необхідних для кожного дзвінка rand7(). Принциповим обмеженням є те, що ми можемо робити не краще, ніж log (7) / log (5) = 1.20906 викликівrand5() один виклик rand7().

Бічні примітки: усі логарифми у цій відповіді будуть базою 2, якщо не вказано інше. rand5()буде прийнято повертати числа в діапазоні [0, 4], і rand7()буде прийнято повертати числа в діапазоні [0, 6]. Регулювання діапазонів у [1, 5] та [1, 7] відповідно тривіально.

То як нам це зробити? Ми генеруємо нескінченно точне випадкове дійсне число між 0 і 1 (зробимо вигляд, що ми могли насправді обчислити і зберегти таке нескінченно точне число - це ми виправимо пізніше). Ми можемо генерувати таке число, генеруючи його цифри в базі 5: ми вибираємо випадкове число 0. a1 a2 a3 ..., де кожна цифра а iвибирається викликом до rand5(). Наприклад, якщо наш RNG обрав a i= 1 для всіх i, то ігноруючи той факт, що це не дуже випадково, це відповідало б дійсному номеру 1/5 + 1/5 2 + 1/5 3 + ... = 1/4 (сума геометричного ряду).

Гаразд, тому ми вибрали випадкове дійсне число між 0 і 1. Зараз я стверджую, що таке випадкове число розподілено рівномірно. Інтуїтивно це зрозуміти легко, оскільки кожна цифра була підібрана рівномірно, а число нескінченно точне. Однак формальне підтвердження цього дещо більше, оскільки зараз ми маємо справу з безперервним розподілом замість дискретного розподілу, тому нам потрібно довести, що ймовірність того, що наше число лежить в інтервалі [a , b], дорівнює довжині цей інтервал,b - a . Доведення залишається як вправа для читача =).

Тепер, коли у нас є випадкове дійсне число, вибране рівномірно з діапазону [0, 1], нам потрібно перетворити його в ряд рівномірно випадкових чисел у діапазоні [0, 6], щоб генерувати вихід rand7() . Як ми це робимо? Просто зворотний результат того, що ми тільки що зробили - ми перетворюємо його в нескінченно точний десятковий знак у базі 7, і тоді кожна 7-цифрова база буде відповідати одному виходуrand7() .

Беручи приклад з попереднього, якщо наш rand5() виробляють нескінченний потік 1, то наше випадкове дійсне число буде 1/4. Перетворивши 1/4 в базу 7, ми отримаємо нескінченний десятковий 0,15151515 ..., тому ми отримаємо як вихід 1, 5, 1, 5, 1, 5 і т.д.

Гаразд, у нас є основна ідея, але у нас залишилися дві проблеми: ми насправді не можемо обчислити або зберегти нескінченно точне дійсне число, так як ми маємо справу з лише кінцевою його частиною? По-друге, як насправді перетворити його на базу 7?

Один із способів перетворення числа між 0 і 1 в базу 7 полягає в наступному:

  1. Помножте на 7
  2. Невід’ємною частиною результату є наступна основна 7 цифра
  3. Відніміть цілісну частину, залишивши лише дробову частину
  4. Перехід до кроку 1

Для вирішення проблеми нескінченної точності ми обчислюємо частковий результат, а також зберігаємо верхню межу того, яким може бути результат. Тобто, припустимо, ми зателефонували rand5()двічі, і він повернувся 1 обидва рази. Поки що ми створили 0,11 (основа 5). Незалежно від решти нескінченних серій викликів, які ми rand5()виробляємо, випадкове дійсне число, яке ми генеруємо, ніколи не буде більшим за 0,12: завжди вірно, що 0,11 ≤ 0,11xyz ... <0,12.

Отже, відслідковуючи поточне число дотепер і максимальне значення, яке воно може колись прийняти, ми перетворюємо обидва числа в базові 7. Якщо вони погоджуються на перші kцифри, то ми можемо сміливо виводити наступні kцифри - незалежно від того, що нескінченний потік базових 5 цифр є, вони ніколи не впливатимуть на наступні kцифри представлення бази 7!

І ось алгоритм - щоб генерувати наступний висновок rand7(), ми генеруємо лише стільки цифр, rand5()скільки нам потрібно для того, щоб ми з певністю знали значення наступної цифри при перетворенні випадкового реального числа в базу 7. Ось реалізація Python із тестовим джгутом:

import random

rand5_calls = 0
def rand5():
    global rand5_calls
    rand5_calls += 1
    return random.randint(0, 4)

def rand7_gen():
    state = 0
    pow5 = 1
    pow7 = 7
    while True:
        if state / pow5 == (state + pow7) / pow5:
            result = state / pow5
            state = (state - result * pow5) * 7
            pow7 *= 7
            yield result
        else:
            state = 5 * state + pow7 * rand5()
            pow5 *= 5

if __name__ == '__main__':
    r7 = rand7_gen()
    N = 10000
    x = list(next(r7) for i in range(N))
    distr = [x.count(i) for i in range(7)]
    expmean = N / 7.0
    expstddev = math.sqrt(N * (1.0/7.0) * (6.0/7.0))

    print '%d TRIALS' % N
    print 'Expected mean: %.1f' % expmean
    print 'Expected standard deviation: %.1f' % expstddev
    print
    print 'DISTRIBUTION:'
    for i in range(7):
        print '%d: %d   (%+.3f stddevs)' % (i, distr[i], (distr[i] - expmean) / expstddev)
    print
    print 'Calls to rand5: %d (average of %f per call to rand7)' % (rand5_calls, float(rand5_calls) / N)

Зверніть увагу, що rand7_gen()повертає генератор, оскільки він має внутрішній стан, що включає перетворення числа в базу 7. Тестовий джгут викликає next(r7)10000 разів для отримання 10000 випадкових чисел, а потім вимірює їх розподіл. Використовується лише ціла математика, тому результати точно правильні.

Також зауважте, що цифри тут стають дуже великими, дуже швидкими. Повноваження 5 і 7 швидко ростуть. Отже, продуктивність почне помітно погіршуватися після генерації безлічі випадкових чисел через арифметику біньюма. Але пам’ятайте тут, моя мета полягала в тому, щоб максимально використовувати випадкові біти, а не максимізувати продуктивність (хоча це вторинна мета).

За один проміжок цього я здійснив 12091 виклик rand5()для 10000 дзвінків rand7(), досягнувши мінімуму дзвінків журналу (7) / журналу (5) в середньому до 4 значущих цифр, і отриманий результат був рівномірним.

Щоб перенести цей код на мову, яка не має довільно великих цілих чисел, вам доведеться обмежувати значення pow5та pow7максимальне значення вашого рідного інтегрального типу - якщо вони занадто великі, то скиньте все і почніть спочатку. Це збільшить середню кількість дзвінків rand5()на виклик rand7()дуже незначно, але, сподіваємось, він не повинен надто збільшуватися навіть для 32- або 64-бітних цілих чисел.


7
+1 для дійсно цікавої відповіді. Чи можливо, замість скидання на певне значення, просто змістити біти, які були використані, і перемістити інші біти вгору, і в основному лише зберегти біти, які будуть використані? Або я щось пропускаю?
Кріс Лутц

1
Я не впевнений на 100%, але я вважаю, що якби ви це зробили, ви б так сильно перекосили розподіл (хоча я сумніваюся, що таке перекос було б вимірним без трильйонів випробувань).
Адам Розенфілд

FTW! Я намагався зробити бінтуми меншими, але цього неможливо зробити, тому що жодна потужність 5 не має спільних факторів із потужністю 7! Також добре використовувати ключове слово. Дуже добре зроблено.
Eyal

2
Дуже хороший! Чи можемо ми зберегти додаткову ентропію без зростаючого стану? Хитрість полягає в тому, щоб помітити, що і верхня, і нижня межі завжди є раціональними числами. Ми можемо їх додавати, віднімати і множувати, не втрачаючи точності. Якщо ми все це робимо в базі-35, ми майже там. Залишок (множення на сім і збереження дробової частини) залишається як вправа.
Ян

@adam Ви повинні посилатися на "обмеження значень pow5 та pow7 до максимального значення вашого рідного інтегрального типу". Я вважаю, що це призведе до перекосу розподілу, принаймні, якщо це буде зроблено наївно.
каталізатор

36

(Я вкрав відповідь Адама Розенфельда і змусив його працювати приблизно на 7% швидше.)

Припустимо, що rand5 () повертає один із {0,1,2,3,4} з рівним розподілом, а мета - поверненням {0,1,2,3,4,5,6} з рівним розподілом.

int rand7() {
  i = 5 * rand5() + rand5();
  max = 25;
  //i is uniform among {0 ... max-1}
  while(i < max%7) {
    //i is uniform among {0 ... (max%7 - 1)}
    i *= 5;
    i += rand5(); //i is uniform {0 ... (((max%7)*5) - 1)}
    max %= 7;
    max *= 5; //once again, i is uniform among {0 ... max-1}
  }
  return(i%7);
}

Ми відслідковуємо найбільше значення, яке цикл може внести у змінну max. Якщо поки що результат відхилення від максимального% 7 до max-1, результат буде рівномірно розподілений у цьому діапазоні. Якщо ні, ми використовуємо залишок, який є випадковим значенням між 0 і макс.% 7-1, та інший виклик rand (), щоб створити нове число та новий макс. Тоді ми починаємо знову.

Редагувати: очікуйте, що кількість разів викликати rand5 () буде x у цьому рівнянні:

x =  2     * 21/25
   + 3     *  4/25 * 14/20
   + 4     *  4/25 *  6/20 * 28/30
   + 5     *  4/25 *  6/20 *  2/30 * 7/10
   + 6     *  4/25 *  6/20 *  2/30 * 3/10 * 14/15
   + (6+x) *  4/25 *  6/20 *  2/30 * 3/10 *  1/15
x = about 2.21 calls to rand5()

2
Результати каталогізовані в 1 000 000 спроб: 1 = 47216; 2 = 127444; 3 = 141407; 4 = 221453; 5 = 127479; 6 = 167536; 7 = 167465. Як бачите, розподілу бракує щодо шансів отримати 1.
Роберт К

2
@ The Wicked Flea: Я думаю, ти помилився. Ви впевнені, що вхідний rand5 (), який ви використовували для свого тесту, давав 0-4 замість 1-5, як зазначено в цьому рішенні?
Адам Розенфілд

5
додавання рівномірно розподілених чисел не призводить до отримання рівномірно розподіленого числа. Насправді вам потрібно лише підбити 6 таких рівномірно розподілених змінних, щоб отримати розумне наближення до нормального розподілу.
Мітч Пшеничний

2
@MitchWheat - Додавання двох рівномірно розподілених цілих чисел насправді призводить до рівномірного розподілу випадкових цілих чисел, за умови, що кожну можливу суму можна генерувати точно одним способом. Так трапляється в виразі 5 * rand5() + rand5().
Тед Хопп

28

Алгоритм:

7 може бути представлений у послідовності з 3 біт

Використовуйте rand (5), щоб випадковим чином заповнити кожен біт 0 або 1.
Наприклад: виклик rand (5) та

якщо результат 1 або 2, заповніть біт 0,
якщо результат 4 або 5, залийте біт 1,
якщо результат 3, ігноруйте і повторіть це (відмова)

Таким чином ми можемо заповнити 3 біти випадковим чином 0/1 і таким чином отримати число від 1-7.

EDIT: Це здається найпростішою та найефективнішою відповіддю, ось ось який код:

public static int random_7() {
    int returnValue = 0;
    while (returnValue == 0) {
        for (int i = 1; i <= 3; i++) {
            returnValue = (returnValue << 1) + random_5_output_2();
        }
    }
    return returnValue;
}

private static int random_5_output_2() {
    while (true) {
        int flip = random_5();

        if (flip < 3) {
            return 0;
        }
        else if (flip > 3) {
            return 1;
        }
    }
}

1
Завжди слабкий спектр проблеми зупинки, оскільки поганий генератор випадкових чисел може просто створити багато трійки в якийсь момент.
Алекс Норт-Кіс

"якщо результат 1 або 2, заповніть біт 0, якщо результат 4 або 5, залийте біт 1". Яка логіка, за якою 1,2,4,5 було прийнято, а 3 було відхилено? Чи можете ви пояснити це?
gkns

@gkns Логіки немає, ви можете мати середнє заповнення 0 і 2, а 0 і 3 середнє значення заповнення з 1. Важливим є те, що кожен варіант має 50% шансів виникнення, таким чином гарантуючи, що випадковість вашої функції щонайменше так само випадково, як оригінальна функція rand (5). Це відмінне рішення!
Пн Бейджі

Це не є простим, ані ефективним. Кількість звернень до random_5 за random_7 в кращому випадку 3 зазвичай більше. Інші рішення на цій сторінці ближчі до фактично найкращого, який становить приблизно 2,2.
Eyal

1
Не маю на увазі, я пропустив частину "while returnValue == 0"
NicholasFolk

19
int randbit( void )
{
    while( 1 )
    {
        int r = rand5();
        if( r <= 4 ) return(r & 1);
    }
}

int randint( int nbits )
{
    int result = 0;
    while( nbits-- )
    {
        result = (result<<1) | randbit();
    }
    return( result );
}

int rand7( void )
{
    while( 1 )
    {
        int r = randint( 3 ) + 1;
        if( r <= 7 ) return( r );
    }
}

2
Правильне рішення: в середньому 30/7 = 4,29 дзвінків до rand5 () за виклик до rand7 ().
Адам Розенфілд

17
rand7() = (rand5()+rand5()+rand5()+rand5()+rand5()+rand5()+rand5())%7+1

Правка: Це не зовсім працює. Він вимикається приблизно на 2 частини в 1000 (припускаючи ідеальний rand5). Відра отримують:

value   Count  Error%
1       11158  -0.0035
2       11144  -0.0214
3       11144  -0.0214
4       11158  -0.0035
5       11172  +0.0144
6       11177  +0.0208
7       11172  +0.0144

Перейшовши на суму

n   Error%
10  +/- 1e-3,
12  +/- 1e-4,
14  +/- 1e-5,
16  +/- 1e-6,
...
28  +/- 3e-11

схоже, набирає порядку на кожен 2 додані

BTW: наведена вище таблиця помилок не була створена за допомогою вибірки, а за допомогою наступного відношення:

p[x,n]це кількість способів, за якими output=xможна nтелефонувати rand5.

  p[1,1] ... p[5,1] = 1
  p[6,1] ... p[7,1] = 0

  p[1,n] = p[7,n-1] + p[6,n-1] + p[5,n-1] + p[4,n-1] + p[3,n-1]
  p[2,n] = p[1,n-1] + p[7,n-1] + p[6,n-1] + p[5,n-1] + p[4,n-1]
  p[3,n] = p[2,n-1] + p[1,n-1] + p[7,n-1] + p[6,n-1] + p[5,n-1]
  p[4,n] = p[3,n-1] + p[2,n-1] + p[1,n-1] + p[7,n-1] + p[6,n-1]
  p[5,n] = p[4,n-1] + p[3,n-1] + p[2,n-1] + p[1,n-1] + p[7,n-1]
  p[6,n] = p[5,n-1] + p[4,n-1] + p[3,n-1] + p[2,n-1] + p[1,n-1]
  p[7,n] = p[6,n-1] + p[5,n-1] + p[4,n-1] + p[3,n-1] + p[2,n-1]

8
Це не рівномірний розподіл. Це дуже близько до рівномірного, але не ідеально рівномірного.
Адам Розенфілд

Ах! Кубики та 7-х. Якщо ви збираєтесь сказати, що я помиляюся, не слід залишати доказ читачем.
BCS

45
Доказ того, що це не рівномірно, є простим: існує 5 ^ 7 можливих способів випадковості, і оскільки 5 ^ 7 не є кратним 7, неможливо, що всі 7 сум є однаково вірогідними. (В основному, він зводиться до 7, відносно простим до 5, або рівно 1/7, не є кінцевим десятком у базі 5.) Насправді це навіть не "найбільш рівномірний" можливий за цим обмеженням: прямі обчислення показують, що 5 ^ 7 = 78125 сум, кількість разів, коли ви отримуєте значення від 1 до 7, це {1: 11145, 2: 11120, 3: 11120, 4: 11145, 5: 11190, 6: 11215, 7: 11190}.
ShreevatsaR

@ShreevatsaR Так що, якби замість того, щоб взяти суму rand5 () сім разів, ми зробили це 5 * 7 приймає - чи не буде це працювати? 35 ^ 7% 7 = 35 ^ 5% 7 = 0.
кба

4
@ KristianAntonsen: Скільки разів ви робите rand5 (), ви не отримаєте рівномірного розподілу. Якщо ви зробите це N разів, можливі 5 ^ N можливих виходів, що не ділиться на 7. (Якщо ви це зробите 35 разів, то це 5 ^ 35, а не 35 ^ 7). уніфікуйте більшу кількість викликів, які ви використовуєте (і це може бути будь-яка кількість, не обов'язково ділити на 7), але IMHO замість того, щоб використовувати дуже велику кількість дзвінків для рандингу (), ви також можете використовувати ймовірнісні алгоритм у верхній відповіді, який дає точний рівномірний розподіл і очікувана кількість дзвінків на rand () невелика.
ShreevatsaR

15
int ans = 0;
while (ans == 0) 
{
     for (int i=0; i<3; i++) 
     {
          while ((r = rand5()) == 3){};
          ans += (r < 3) >> i
     }
}

2
Правильне рішення: в середньому 30/7 = 4,29 дзвінків до rand5 () за виклик до rand7 ().
Адам Розенфілд

3
Для роботи алгоритму потрібно залишити ліву зміну :ans += (r < 3) << i
bootsfie

13

Далі виробляється рівномірний розподіл на {1, 2, 3, 4, 5, 6, 7} з використанням генератора випадкових чисел, що виробляє рівномірний розподіл на {1, 2, 3, 4, 5}. Код безладний, але логіка зрозуміла.

public static int random_7(Random rg) {
    int returnValue = 0;
    while (returnValue == 0) {
        for (int i = 1; i <= 3; i++) {
            returnValue = (returnValue << 1) + SimulateFairCoin(rg);
        }
    }
    return returnValue;
}

private static int SimulateFairCoin(Random rg) {
    while (true) {
        int flipOne = random_5_mod_2(rg);
        int flipTwo = random_5_mod_2(rg);

        if (flipOne == 0 && flipTwo == 1) {
            return 0;
        }
        else if (flipOne == 1 && flipTwo == 0) {
            return 1;
        }
    }
}

private static int random_5_mod_2(Random rg) {
    return random_5(rg) % 2;
}

private static int random_5(Random rg) {
    return rg.Next(5) + 1;
}    

2
Правильне рішення (яке ставить вас попереду кривої), хоча і не дуже ефективне. Це складає в середньому 25/6 = 4,17 дзвінків на random_5_mod_2 за справедливий фліп монети, загальне середнє значення 100/7 = 14,3 виклику до random_5 () за виклик до random_7 ().
Адам Розенфілд

Перевага цього рішення перед іншими полягає в тому, що його можна легко розширити, щоб отримати будь-який інший рівномірно розподілений діапазон. Просто випадковим чином виберіть кожен з бітів, повторно прокатуючи недійсні значення (наприклад, значення 0 у нашому поточному рішенні, яке дає 8 чисел).
DenTheMan

1
можливі нескінченні петлі тощо
robermorales

1
@robermorales: Надзвичайно малоймовірно.
Jason

13
int rand7() {
    int value = rand5()
              + rand5() * 2
              + rand5() * 3
              + rand5() * 4
              + rand5() * 5
              + rand5() * 6;
    return value%7;
}

На відміну від обраного рішення, алгоритм буде працювати в постійному часі. Однак він робить ще 2 виклики на rand5, ніж середній час роботи вибраного рішення.

Зауважте, що цей генератор не є ідеальним (число 0 має 0,0064% більше шансів, ніж будь-яке інше число), але для більшості практичних цілей гарантія постійного часу, ймовірно, переважає цю неточність.

Пояснення

Це рішення випливає з того, що число 15 624 ділиться на 7, і, якщо ми можемо випадково та рівномірно генерувати числа від 0 до 15 624, а потім взяти mod 7, ми можемо отримати майже рівномірний генератор rand7. Числа від 0 до 15 624 можна рівномірно генерувати прокручуванням rand5 6 разів та використовуючи їх для формування цифр базового числа 5 таким чином:

rand5 * 5^5 + rand5 * 5^4 + rand5 * 5^3 + rand5 * 5^2 + rand5 * 5 + rand5

Однак властивості моди 7 дозволяють нам трохи спростити рівняння:

5^5 = 3 mod 7
5^4 = 2 mod 7
5^3 = 6 mod 7
5^2 = 4 mod 7
5^1 = 5 mod 7

Тому

rand5 * 5^5 + rand5 * 5^4 + rand5 * 5^3 + rand5 * 5^2 + rand5 * 5 + rand5

стає

rand5 * 3 + rand5 * 2 + rand5 * 6 + rand5 * 4 + rand5 * 5 + rand5

Теорія

Число 15 624 було обрано не випадковим чином, але його можна виявити, використовуючи маленьку теорему Ферма, яка стверджує, що якщо p - просте число, то

a^(p-1) = 1 mod p

Отже, це дає нам,

(5^6)-1 = 0 mod 7

(5 ^ 6) -1 дорівнює

4 * 5^5 + 4 * 5^4 + 4 * 5^3 + 4 * 5^2 + 4 * 5 + 4

Це число в базовій формі 5, і тому ми можемо бачити, що цей метод може бути використаний для переходу від будь-якого генератора випадкових чисел до будь-якого іншого генератора випадкових чисел. Хоча при використанні експонента p-1 завжди вводиться невеликий ухил до 0.

Щоб узагальнити цей підхід і бути більш точним, ми можемо мати таку функцію:

def getRandomconverted(frm, to):
    s = 0
    for i in range(to):
        s += getRandomUniform(frm)*frm**i
    mx = 0
    for i in range(to):
        mx = (to-1)*frm**i 
    mx = int(mx/to)*to # maximum value till which we can take mod
    if s < mx:
        return s%to
    else:
        return getRandomconverted(frm, to)

1
Цей генератор точний, але не ідеально рівномірний. Для цього слід врахувати той факт, що рівномірний генератор у [0,15624] має 15625 можливих результатів, що не ділиться на 7. Це вводить зміщення числа 0 (яке має шанс 2233/15625, а інші просто 2232/15625). Зрештою, використовуючи маленьку теорему Ферма на перший погляд може здатися правильною, вона говорить, що (5 ^ 6)% 7 = 1, а не (5 ^ 6)% 7 = 0. Останнє, очевидно, неможливо для будь-якого показника, оскільки 5 і 7 - це прості числа. Я думаю, що це все ще прийнятне рішення, і я відредагував вашу публікацію, щоб це відобразити.
авіатор

12

Чи дозволені проблеми домашнього завдання тут?

Ця функція виконує неочищену математику "5 базових", щоб генерувати число між 0 і 6.

function rnd7() {
    do {
        r1 = rnd5() - 1;
        do {
            r2=rnd5() - 1;
        } while (r2 > 1);
        result = r2 * 5 + r1;
    } while (result > 6);
    return result + 1;
}

3
Правильне рішення (яке ставить вас попереду кривої), хоча і не дуже ефективне. Це робить у середньому 5 дзвінків до rnd5 () за кожен виклик до rnd7 ().
Адам Розенфілд

потрібно ще кілька пояснень pls
Barry

1
@Barry - По-перше, ви не можете просто додати два випадкових числа разом, ви не отримаєте лінійного рішення (розгляньте пару кісток). Тепер розглянемо "Основа 5": 00, 01, 02, 03, 04, 10, 11. Це 0-6 в базі 5. Отже, нам просто потрібно сформувати 2 цифри базового числа 5 і додати їх, поки ми отримати той, який знаходиться в межах діапазону. Ось що робить r2 * 5 + r1. Цикл r2> 1 є, тому що ми ніколи не хотіли б високої цифри> 1.
Буде Хартюн

Це рішення не генерує рівномірного розподілу. Числа 1 і 7 можна генерувати лише одним способом, але 2 - 6 можна генерувати двома способами: з r1, рівним числу мінус 1, і r2, рівним 0, або з r1, рівним числу мінус 2, і r2 дорівнює 1. Таким чином, від 2 до 6 повертається в середньому вдвічі частіше 1 або 7.
Тед Хопп

12

Якщо ми розглянемо додаткове обмеження спроби дати найбільш ефективну відповідь, тобто таку, яка задала вхідний потік, Iз рівномірно розподілених цілих чисел довжиною mвід 1-5 виводить потік O, рівномірно розподілених цілих чисел від 1-7 найдовшої відносної довжини до m, скажімо L(m).

Найпростіший спосіб проаналізувати це - трактувати потоки I та O5-арійні та 7-арі числа відповідно. Це досягається ідеєю основної відповіді взяти потік a1, a2, a3,... -> a1+5*a2+5^2*a3+..і аналогічно для потоку O.

Тоді, якщо ми візьмемо ділянку вхідного потоку довжиною, m choose n s.t. 5^m-7^n=cде c>0і як можна менше. Тоді є рівномірна карта від вхідного потоку довжиною m до цілих чисел від 1до 5^mта ще одна рівномірна карта від цілих чисел від 1 до 7^nвихідного потоку довжини n, де нам, можливо, доведеться втратити кілька випадків із вхідного потоку, коли відображене ціле число перевищує 7^n.

Таким чином, це дає значення, L(m)навколо m (log5/log7)якого приблизно .82m.

Труднощі наведеного вище аналізу є рівняння , 5^m-7^n=cяке так легко вирішити точно і випадок , коли однорідне значення від 1до 5^mперевищує 7^nі ми втрачаємо ефективність.

Питання полягає в тому, наскільки близьким до найкращого можливого значення m (log5 / log7) можна досягти. Наприклад, коли це число наближається до цілого числа, чи можемо ми знайти спосіб досягти цього точного інтегрального числа вихідних значень?

Якщо 5^m-7^n=cтоді з вхідного потоку ми ефективно генеруємо рівномірне випадкове число від 0до (5^m)-1та не використовуємо значення, що перевищує значення 7^n. Однак ці значення можна врятувати і використовувати знову. Вони ефективно генерують єдину послідовність чисел від 1 до 5^m-7^n. Тоді ми можемо спробувати використовувати їх і перетворити їх у 7-річні числа, щоб ми могли створити більше вихідних значень.

Якщо допустимо T7(X)середню довжину вихідної послідовності random(1-7)цілих чисел, отриману з рівномірного введення розміру X, і припускаючи це 5^m=7^n0+7^n1+7^n2+...+7^nr+s, s<7.

Тоді T7(5^m)=n0x7^n0/5^m + ((5^m-7^n0)/5^m) T7(5^m-7^n0)оскільки у нас немає довжини без послідовності з ймовірністю 7 ^ n0 / 5 ^ m з залишком довжини 5^m-7^n0з ймовірністю (5^m-7^n0)/5^m).

Якщо ми просто продовжуємо замінювати, ми отримуємо:

T7(5^m) = n0x7^n0/5^m + n1x7^n1/5^m + ... + nrx7^nr/5^m  = (n0x7^n0 + n1x7^n1 + ... + nrx7^nr)/5^m

Звідси

L(m)=T7(5^m)=(n0x7^n0 + n1x7^n1 + ... + nrx7^nr)/(7^n0+7^n1+7^n2+...+7^nr+s)

Ще один спосіб цього:

If 5^m has 7-ary representation `a0+a1*7 + a2*7^2 + a3*7^3+...+ar*7^r
Then L(m) = (a1*7 + 2a2*7^2 + 3a3*7^3+...+rar*7^r)/(a0+a1*7 + a2*7^2 + a3*7^3+...+ar*7^r)

Найкращий можливий випадок - мій оригінал вище, де 5^m=7^n+s, де s<7.

Тоді T7(5^m) = nx(7^n)/(7^n+s) = n+o(1) = m (Log5/Log7)+o(1)як і раніше.

Найгірший випадок, коли ми можемо знайти лише k і st 5 ^ m = kx7 + s.

Then T7(5^m) = 1x(k.7)/(k.7+s) = 1+o(1)

Інші випадки десь між ними. Було б цікаво побачити, наскільки добре ми можемо зробити для дуже великих m, тобто наскільки добре ми можемо отримати термін помилки:

T7(5^m) = m (Log5/Log7)+e(m)

e(m) = o(1)Загалом це здається неможливим, але, сподіваємось, ми зможемо довести e(m)=o(m).

Потім вся річ спирається на розподіл 7-річних цифр 5^mдля різних значень m.

Я впевнений, що існує багато теорії, яка висвітлює це, я можу поглянути і доповісти в якийсь момент.


+2 (якщо я міг) - це була єдина добра відповідь (на відміну від просто адекватної). Ви отримали другу найкращу відповідь, яка вміститься в 32 бітні цілі числа.
Рекс Керр

10

Ось робоча реалізація відповіді Адама на Python .

import random

def rand5():
    return random.randint(1, 5)

def rand7():
    while True:
        r = 5 * (rand5() - 1) + rand5()
        #r is now uniformly random between 1 and 25
        if (r <= 21):
            break
    #result is now uniformly random between 1 and 7
    return r % 7 + 1

Мені подобається кидати алгоритми, на які я дивлюся в Python, щоб я міг пограти з ними, подумав, що опублікую його тут, сподіваючись, що комусь це корисно, а не тому, що спіткати потрібно багато часу.


Ні, це досить відрізняється від моєї відповіді. Ви лупцюєте 21 раз і відкидаєте результати перших 20 ітерацій. Ви також використовуєте вхідні дані rand4 () та rand5 (), що цілком очевидно порушує правила використання лише rand5 (). Нарешті, ви виробляєте нерівномірний розподіл.
Адам Розенфілд

Вибач за те. Я дуже втомився, коли переглянув це питання, набрид, що повністю неправильно прочитав ваш алгоритм. Я насправді кинув його в Python, тому що я не міг зрозуміти, чому ти лупаєшся 21 раз. Має набагато більше сенсу зараз. Я зробив річ random.randint (1, 4) як скорочення, але, мабуть, ви правильні, це суперечить духу питання. Я виправив код.
Джеймс Макмахон

@robermorales - Як пояснив Адам Розенфельд у своїй відповіді , кожне рішення, яке дає справжнє рівномірне розподіл на [1, 7], буде включати певний цикл прийняття-відхилення, який потенційно є нескінченним. (Однак, якщо rand5()це гідний PRNG, то цикл не буде нескінченним, оскільки в кінцевому підсумку 5*(rand5() - 1) + rand5()обов'язково буде <= 21.)
Тед

10

Чому б не зробити це просто?

int random7() {
  return random5() + (random5() % 3);
}

Шанси отримати 1 і 7 у цьому рішенні нижчі через модуль, однак, якщо ви просто хочете швидкого та читаного рішення, це шлях.


13
Це не дає рівномірного розподілу. Це дає число 0-6 з ймовірністю 2/25, 4/25, 5/25, 5/25, 5/25, 3/25, 1/25, як це можна перевірити, підрахувавши всі 25 можливих результатів.
Адам Розенфілд

8

Якщо припустити, що rand (n) тут означає "випадкове ціле число в рівномірному розподілі від 0 до n-1 ", ось зразок коду з використанням рандінта Python, який має такий ефект. Він використовує лише randint (5) та константи для отримання ефекту randint (7) . Насправді трохи нерозумно

from random import randint
sum = 7
while sum >= 7:
    first = randint(0,5)   
    toadd = 9999
    while toadd>1:
        toadd = randint(0,5)
    if toadd:
        sum = first+5
    else:
        sum = first

assert 7>sum>=0 
print sum

1
@robermorales Тому що Python не має do ... while. Це могло бути 1337, або 12345, або будь-яке число> 1.
tckmn

8

Припущення за правильною відповіддю Адама Розенфілда:

  • x = 5 ^ n (у його випадку: n = 2)
  • маніпулювати n rand5-дзвінками, щоб отримати число y в межах [1, x]
  • z = ((int) (x / 7)) * 7
  • якщо y> z, спробуйте ще раз. інше повернути y% 7 + 1

Коли n дорівнює 2, у вас є 4 можливості викидання: y = {22, 23, 24, 25}. Якщо ви використовуєте n дорівнює 6, у вас є лише 1 викид: y = {15625}.

5 ^ 6 = 15625
7 * 2232 = 15624

Ви дзвоните rand5 більше разів. Однак у вас є набагато менший шанс отримати значення викидання (або нескінченну петлю). Якщо є спосіб отримати неможливе значення викидання для y, я його ще не знайшов.


1
Немає жодного випадку без значень викиду - якби не було викиду, 5 ^ n та 7 ^ m мали б спільний фактор. Але вони (повноваження) праймес, тому вони цього не роблять.
Рекс Керр

8

Ось моя відповідь:

static struct rand_buffer {
  unsigned v, count;
} buf2, buf3;

void push (struct rand_buffer *buf, unsigned n, unsigned v)
{
  buf->v = buf->v * n + v;
  ++buf->count;
}

#define PUSH(n, v)  push (&buf##n, n, v)

int rand16 (void)
{
  int v = buf2.v & 0xf;
  buf2.v >>= 4;
  buf2.count -= 4;
  return v;
}

int rand9 (void)
{
  int v = buf3.v % 9;
  buf3.v /= 9;
  buf3.count -= 2;
  return v;
}

int rand7 (void)
{
  if (buf3.count >= 2) {
    int v = rand9 ();

    if (v < 7)
      return v % 7 + 1;

    PUSH (2, v - 7);
  }

  for (;;) {
    if (buf2.count >= 4) {
      int v = rand16 ();

      if (v < 14) {
        PUSH (2, v / 7);
        return v % 7 + 1;
      }

      PUSH (2, v - 14);
    }

    // Get a number between 0 & 25
    int v = 5 * (rand5 () - 1) + rand5 () - 1;

    if (v < 21) {
      PUSH (3, v / 7);
      return v % 7 + 1;
    }

    v -= 21;
    PUSH (2, v & 1);
    PUSH (2, v >> 1);
  }
}

Це трохи складніше, ніж інші, але я вважаю, що це мінімізує виклики до rand5. Як і в інших рішеннях, є невелика ймовірність, що це може тривалий час циклічно.


Це призводить до того, що дистрибуція не сильно відрізняється від інших рішень, але має додатковий недолік, коли вона є непотрібною. Він також страждає від явно неправильної недетермінованої циклу - назавжди можливості, якщо числа справді випадкові. Я все ще думаю, що ті, які виробляють дещо менш рівномірний розподіл (хоча все ще набагато більш ніж адекватний), але гарантують детерміновану поведінку, краще.
paxdiablo

@Pax: Будь ласка, просвіти мене, як це створює нерівномірний розподіл. Мій аналіз коду, а також моє власне тестування показують, що це дає рівномірний розподіл. Як ми вже обговорювали раніше, неможливо одночасно здійснити ідеально рівномірний розподіл і мати гарантовану постійну тривалість часу верхньої межі часу роботи.
Адам Розенфілд


6

Поки на вибір не залишилося семи можливостей, намалюйте ще одне випадкове число, яке помножить число можливостей на п'ять. У Perl:

$num = 0;
$possibilities = 1;

sub rand7
{
  while( $possibilities < 7 )
  {
    $num = $num * 5 + int(rand(5));
    $possibilities *= 5;
  }
  my $result = $num % 7;
  $num = int( $num / 7 );
  $possibilities /= 7;
  return $result;
}

ваш розподіл не є рівномірним, принаймні під час першого дзвінка. Дійсно, $possibilitiesзавжди потрібно виростати до 25, щоб вийти з циклу і повернутися. Отже, ваш перший результат [0-124] % 7, який розподілений не рівномірно, оскільки 125 % 7 != 0(це фактично 6).
bernard paulus

6

Мені не подобаються діапазони починаючи з 1, тому я почну з 0 :-)

unsigned rand5()
{
    return rand() % 5;
}

unsigned rand7()
{
    int r;

    do
    {
        r =         rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
        r = r * 5 + rand5();
    } while (r > 15623);

    return r / 2232;
}

Це переможець. Це дає всі 7 результатів з однаковою ймовірністю. from collections import defaultdict def r7(n): if not n: yield [] else: for i in range(1, 6): for j in r7(n-1): yield [i] + j def test_r7(): d = defaultdict(int) for x in r7(6): s = (((((((((x[5] * 5) + x[4]) * 5) + x[3]) * 5) + x[2]) * 5) + x[1]) * 5) + x[0] if s <= 15623: d[s % 7] += 1 print d
hughdbrown

5

Там ви їдете, рівномірний розподіл і нульові дзвінки rand5.

def rand7:
    seed += 1
    if seed >= 7:
        seed = 0
    yield seed

Потрібно заздалегідь встановити насіння.


5

Я знаю, що на нього відповіли, але це здається, що це нормально, але я не можу сказати, чи має він упередженість. Моє "тестування" припускає, що принаймні розумне.

Можливо, Адам Розенфілд був би добрим, щоб прокоментувати?

Моя (наївна?) Ідея така:

Накопичуйте rand5, поки не буде достатньо випадкових біт, щоб зробити rand7. Це займає максимум 2 rand5. Для отримання числа rand7 я використовую накопичене значення mod 7.

Щоб уникнути переповнення акумулятора, а оскільки акумулятор моди 7, я беру мод 7 акумулятора:

(5a + rand5) % 7 = (k*7 + (5a%7) + rand5) % 7 = ( (5a%7) + rand5) % 7

Функція rand7 () наступна:

(Я дозволяю діапазону rand5 бути 0-4, а rand7 - так само 0-6.)

int rand7(){
  static int    a=0;
  static int    e=0;
  int       r;
  a = a * 5 + rand5();
  e = e + 5;        // added 5/7ths of a rand7 number
  if ( e<7 ){
    a = a * 5 + rand5();
    e = e + 5;  // another 5/7ths
  }
  r = a % 7;
  e = e - 7;        // removed a rand7 number
  a = a % 7;
  return r;
}

Редагувати: додано результати для 100 мільйонів випробувань.

'Реальні' функції rand mod 5 або 7

rand5: avg = 1.999802 0: 20003944 1: 19999889 2: 20003690 3: 19996938 4: 19995539 rand7: avg = 3.000111 0: 14282851 1: 14282879 2: 14284554 3: 14288546 4: 14292388 5: 14288736 6: 14280046

Мій rand7

Середнє виглядає нормально, і кількість розподілів також виглядає нормально.

randt: avg = 3.000080 0: 14288793 1: 14280135 2: 14287848 3: 14285277 4: 14286341 5: 14278663 6: 14292943


Вам, мабуть, слід поглянути на послідовну кореляцію. Я думаю, якщо ви візьмете послідовні пари (кожне "випадкове" число в парі з попередником), то ви можете виявити дивовижні речі. Ви не пояснили, ЧОМУ слід у будь-якому разі зберігати рівномірний розподіл. Робоча програма зазвичай повинна починатися з пояснення, чому вона працює.
Ян

Чи застосовуватиметься послідовна кореляція для багатьох цих рішень?
philcolbourn

Чи застосовуватиметься послідовна кореляція для багатьох цих рішень? Минув час, коли я спробував це, і я подумав, що це пояснив. Дивлячись на це зараз, схоже, я накопичую випадкові біти в пулі від rand5, гарантуючи, що було накопичено достатньо, перш ніж вивести достатньо, щоб зробити номер rand7, і гарантую, що я не переповнюю свій акумулятор.
philcolbourn

4

Наведені вище елегантні алгоритми, але ось один із способів наблизитись до цього, хоча це може бути круговим. Я припускаю значення, породжені від 0.

R2 = генератор випадкових чисел, що дає значення менше 2 (простір вибірки = {0, 1})
R8 = генератор випадкових чисел, що дає значення менше 8 (простір вибірки = {0, 1, 2, 3, 4, 5, 6, 7 })

Для того, щоб генерувати R8 з R2, ви будете запускати R2 тричі і використовувати комбінований результат усіх 3 прогонів як двійкове число з 3 цифрами. Ось діапазон значень, коли R2 проводиться тричі:

0 0 0 -> 0
.
.
1 1 1 -> 7

Тепер для генерації R7 з R8 ми просто запускаємо R7 знову, якщо він повертає 7:

int R7() {
  do {
    x = R8();
  } while (x > 6)
  return x;
}

Рішення навколо кола - генерувати R2 з R5 (так само, як ми генерували R7 з R8), потім R8 з R2, а потім R7 з R8.


як і ряд інших, цей підхід може зайняти довільно довгий час за R7-дзвінок, оскільки ви можете отримати довгу струну сімки від R8.
Алекс Норт-Кіс

4

Ось рішення, яке повністю вписується в цілі числа і знаходиться в межах приблизно 4% від оптимальних (тобто використовує 1,26 випадкових чисел у {0..4} для кожного з {0..6}). Код в Scala, але математика повинна бути чітко зрозумілою будь-якою мовою: ви скористаєтеся тим, що 7 ^ 9 + 7 ^ 8 дуже близький до 5 ^ 11. Таким чином, ви вибираєте 11-значне число в базі 5, а потім інтерпретуєте його як 9-значний номер у базі 7, якщо він знаходиться в діапазоні (даючи 9 базових 7 чисел), або як 8-значний номер, якщо він перевищує 9-значний номер тощо. .:

abstract class RNG {
  def apply(): Int
}

class Random5 extends RNG {
  val rng = new scala.util.Random
  var count = 0
  def apply() = { count += 1 ; rng.nextInt(5) }
}

class FiveSevener(five: RNG) {
  val sevens = new Array[Int](9)
  var nsevens = 0
  val to9 = 40353607;
  val to8 = 5764801;
  val to7 = 823543;
  def loadSevens(value: Int, count: Int) {
    nsevens = 0;
    var remaining = value;
    while (nsevens < count) {
      sevens(nsevens) = remaining % 7
      remaining /= 7
      nsevens += 1
    }
  }
  def loadSevens {
    var fivepow11 = 0;
    var i=0
    while (i<11) { i+=1 ; fivepow11 = five() + fivepow11*5 }
    if (fivepow11 < to9) { loadSevens(fivepow11 , 9) ; return }
    fivepow11 -= to9
    if (fivepow11 < to8) { loadSevens(fivepow11 , 8) ; return }
    fivepow11 -= to8
    if (fivepow11 < 3*to7) loadSevens(fivepow11 % to7 , 7)
    else loadSevens
  }
  def apply() = {
    if (nsevens==0) loadSevens
    nsevens -= 1
    sevens(nsevens)
  }
}

Якщо вставити тест в інтерпретатор (фактично REPL), ви отримаєте:

scala> val five = new Random5
five: Random5 = Random5@e9c592

scala> val seven = new FiveSevener(five)
seven: FiveSevener = FiveSevener@143c423

scala> val counts = new Array[Int](7)
counts: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0)

scala> var i=0 ; while (i < 100000000) { counts( seven() ) += 1 ; i += 1 }
i: Int = 100000000

scala> counts
res0: Array[Int] = Array(14280662, 14293012, 14281286, 14284836, 14287188,
14289332, 14283684)

scala> five.count
res1: Int = 125902876

Розподіл є приємним і рівним (у межах приблизно 10 к 1/7 від 10 ^ 8 у кожному відро, як і очікувалося від приблизно-гаусського розподілу).


3

За допомогою загальної сукупності , ви можете обидва

  • підтримувати рівний розподіл; і
  • не доведеться жертвувати жодним елементом у випадковій послідовності.

Обидві ці проблеми є проблемою із спрощеним rand(5)+rand(5)...типом рішення. У наступному коді Python показано, як його реалізувати (більшість із них доводить розподіл).

import random
x = []
for i in range (0,7):
    x.append (0)
t = 0
tt = 0
for i in range (0,700000):
    ########################################
    #####            qq.py             #####
    r = int (random.random () * 5)
    t = (t + r) % 7
    ########################################
    #####       qq_notsogood.py        #####
    #r = 20
    #while r > 6:
        #r =     int (random.random () * 5)
        #r = r + int (random.random () * 5)
    #t = r
    ########################################
    x[t] = x[t] + 1
    tt = tt + 1
high = x[0]
low = x[0]
for i in range (0,7):
    print "%d: %7d %.5f" % (i, x[i], 100.0 * x[i] / tt)
    if x[i] < low:
        low = x[i]
    if x[i] > high:
        high = x[i]
diff = high - low
print "Variation = %d (%.5f%%)" % (diff, 100.0 * diff / tt)

І цей результат показує результати:

pax$ python qq.py
0:   99908 14.27257
1:  100029 14.28986
2:  100327 14.33243
3:  100395 14.34214
4:   99104 14.15771
5:   99829 14.26129
6:  100408 14.34400
Variation = 1304 (0.18629%)

pax$ python qq.py
0:   99547 14.22100
1:  100229 14.31843
2:  100078 14.29686
3:   99451 14.20729
4:  100284 14.32629
5:  100038 14.29114
6:  100373 14.33900
Variation = 922 (0.13171%)

pax$ python qq.py
0:  100481 14.35443
1:   99188 14.16971
2:  100284 14.32629
3:  100222 14.31743
4:   99960 14.28000
5:   99426 14.20371
6:  100439 14.34843
Variation = 1293 (0.18471%)

Спрощений rand(5)+rand(5), ігнорування тих випадків, коли цей показник повертає більше 6, має типову відхилення в 18%, в 100 разів більше, ніж метод, показаний вище:

pax$ python qq_notsogood.py
0:   31756 4.53657
1:   63304 9.04343
2:   95507 13.64386
3:  127825 18.26071
4:  158851 22.69300
5:  127567 18.22386
6:   95190 13.59857
Variation = 127095 (18.15643%)

pax$ python qq_notsogood.py
0:   31792 4.54171
1:   63637 9.09100
2:   95641 13.66300
3:  127627 18.23243
4:  158751 22.67871
5:  126782 18.11171
6:   95770 13.68143
Variation = 126959 (18.13700%)

pax$ python qq_notsogood.py
0:   31955 4.56500
1:   63485 9.06929
2:   94849 13.54986
3:  127737 18.24814
4:  159687 22.81243
5:  127391 18.19871
6:   94896 13.55657
Variation = 127732 (18.24743%)

І за порадою Nixuz я очистив сценарій, щоб ви могли просто витягнути та використати rand7...речі:

import random

# rand5() returns 0 through 4 inclusive.

def rand5():
    return int (random.random () * 5)

# rand7() generator returns 0 through 6 inclusive (using rand5()).

def rand7():
    rand7ret = 0
    while True:
        rand7ret = (rand7ret + rand5()) % 7
        yield rand7ret

# Number of test runs.

count = 700000

# Work out distribution.

distrib = [0,0,0,0,0,0,0]
rgen =rand7()
for i in range (0,count):
    r = rgen.next()
    distrib[r] = distrib[r] + 1

# Print distributions and calculate variation.

high = distrib[0]
low = distrib[0]
for i in range (0,7):
    print "%d: %7d %.5f" % (i, distrib[i], 100.0 * distrib[i] / count)
    if distrib[i] < low:
        low = distrib[i]
    if distrib[i] > high:
        high = distrib[i]
diff = high - low
print "Variation = %d (%.5f%%)" % (diff, 100.0 * diff / count)

2
Помилка, дозвольте перефразувати це. З огляду на те, що певний х був створений у певний момент послідовності, для наступного числа у послідовності може бути створено лише 5 із 7 чисел. Справжній RNG мав би, щоб усі зразки не були незалежними один від одного, але в цьому випадку їх явно немає.
Адам Розенфілд

3
Це правда, що в початковому запитанні не вказано, чи створюють функції вводу та виводу незалежні та однаково розподілені зразки (iid), але я думаю, що обґрунтовано сподіваємось, що якщо вхідний rand5 () є iid, то вихідний rand7 () також повинен бути ід. Якщо ви не вважаєте, що це розумно, розважайтеся, використовуючи неідентифікований RNG.
Адам Розенфілд

1
Отже, яке слово від математиків в університеті?
Адам Розенфілд

1
Це рішення явно порушено. Очевидно, що вам потрібно дзвонити rand5 (в середньому) не один раз за виклик rand7, і це рішення не робить. Тому результати не можуть бути випадковими за будь-якого розумного визначення випадкового.
Кріс Сутер

1
@Pax При кожній ітерації вашої функції він може повернути лише одне з п’яти різних значень (хоча і в діапазоні 0-6). Найперша ітерація може повернути лише число в діапазоні 0-4. Отже, повинно бути зрозуміло, що, хоча ваша функція може мати рівномірний розподіл, вибірки не є незалежними, тобто вони співвідносяться, що не є чимось, що потрібно в генераторі випадкових чисел.
Кріс Сутер

3

Ця відповідь є скоріше експериментом із отримання максимально можливої ​​ентропії за допомогою функції Rand5. Отже, t дещо незрозуміло і майже напевно набагато повільніше, ніж інші реалізації.

Припускаючи рівномірний розподіл від 0-4 і результуючий рівномірний розподіл від 0-6:

public class SevenFromFive
{
  public SevenFromFive()
  {
    // this outputs a uniform ditribution but for some reason including it 
    // screws up the output distribution
    // open question Why?
    this.fifth = new ProbabilityCondensor(5, b => {});
    this.eigth = new ProbabilityCondensor(8, AddEntropy);
  } 

  private static Random r = new Random();
  private static uint Rand5()
  {
    return (uint)r.Next(0,5);
  }

  private class ProbabilityCondensor
  {
    private readonly int samples;
    private int counter;
    private int store;
    private readonly Action<bool> output;

    public ProbabilityCondensor(int chanceOfTrueReciprocal,
      Action<bool> output)
    {
      this.output = output;
      this.samples = chanceOfTrueReciprocal - 1;  
    }

    public void Add(bool bit)
    {
      this.counter++;
      if (bit)
        this.store++;   
      if (counter == samples)
      {
        bool? e;
        if (store == 0)
          e = false;
        else if (store == 1)
          e = true;
        else
          e = null;// discard for now       
        counter = 0;
        store = 0;
        if (e.HasValue)
          output(e.Value);
      }
    }
  }

  ulong buffer = 0;
  const ulong Mask = 7UL;
  int bitsAvail = 0;
  private readonly ProbabilityCondensor fifth;
  private readonly ProbabilityCondensor eigth;

  private void AddEntropy(bool bit)
  {
    buffer <<= 1;
    if (bit)
      buffer |= 1;      
    bitsAvail++;
  }

  private void AddTwoBitsEntropy(uint u)
  {
    buffer <<= 2;
    buffer |= (u & 3UL);    
    bitsAvail += 2;
  }

  public uint Rand7()
  {
    uint selection;   
    do
    {
      while (bitsAvail < 3)
      {
        var x = Rand5();
        if (x < 4)
        {
          // put the two low order bits straight in
          AddTwoBitsEntropy(x);
          fifth.Add(false);
        }
        else
        { 
          fifth.Add(true);
        }
      }
      // read 3 bits
      selection = (uint)((buffer & Mask));
      bitsAvail -= 3;     
      buffer >>= 3;
      if (selection == 7)
        eigth.Add(true);
      else
        eigth.Add(false);
    }
    while (selection == 7);   
    return selection;
  }
}

Кількість бітів, доданих до буфера за виклик до Rand5, наразі становить 4/5 * 2, так що 1.6. Якщо включено значення ймовірності 1/5, що збільшується на 0,05, тому 1,65, але дивіться коментар у коді, де мені довелося це відключити.

Біти, спожиті при виклику до Rand7 = 3 + 1/8 * (3 + 1/8 * (3 + 1/8 * (...
Це 3 + 3/8 + 3/64 + 3/512 ... так приблизно 3,42

Витягуючи інформацію із сімки, я повертаю 1/8 * 1/7 біт за виклик, тобто приблизно 0,018

Це дає чисте споживання 3,4 біта на виклик, тобто коефіцієнт становить 2,125 викликів до Rand5 за кожен Rand7. Оптимум повинен бути 2,1.

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


Ваше рішення видається правильним, окрім деяких простих помилок: "if (count> 1)" має бути "if (count <= 1)", а "i ++", що виникає незабаром після цього, повинен знаходитися всередині фігурних дужок, що передують йому. Я не впевнений, правильно чи ні BitsSet (), але це дещо не має значення.
Адам Розенфілд

Однак у цілому вашу функцію дуже важко зрозуміти. Це робить дещо кращим використання ентропії, ніж це було б інакше, ціною більшого ускладнення. Також немає причин спочатку заповнювати буфер 35 випадковими бітами під час першого виклику, коли 3 буде достатньо.
Адам Розенфілд

Я виправив <= дякую, хоч i ++ дійсно має бути там. Це має статися на нулі та 1 випадку (додавання 1 або нуля відповідно до буфера). Це абсолютно не те, що я б запропонував використовувати, це жахливо складно. Мене просто цікавило, як я наблизився до теоретичних меж ентропії, притаманних проблемі ... Дякую за відгуки. За іронією долі заповнення буфера під час першого дзвінка полягало у спрощенні написання :)
ShuggyCoUk

Я переробив це, щоб було легше зрозуміти (ціною швидкості), але також зробив це правильним. Це ще не оптимально, чомусь 1/5 біт викликає проблеми, хоча вони є рівномірними.
ShuggyCoUk

3

в php

function rand1to7() {
    do {
        $output_value = 0;
        for ($i = 0; $i < 28; $i++) {
            $output_value += rand1to5();
        }
    while ($output_value != 140);
    $output_value -= 12;
    return floor($output_value / 16);
}

циклів для отримання випадкового числа між 16 і 127, ділиться на шістнадцять, щоб створити поплавок між 1 і 7.9375, потім обводиться вниз, щоб отримати int між 1 і 7. Якщо я не помиляюся, є шанс отримати 16/112 будь-який із 7 результатів.


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

3
extern int r5();

int r7() {
    return ((r5() & 0x01) << 2 ) | ((r5() & 0x01) << 1 ) | (r5() & 0x01);
}

проблема: це повертається нерівномірно в діапазоні 0-7, а не 0-6. У самому справі, ви можете мати 7 = 111bзp(7) = 8 / 125
Бернарда Паулюс

3

Я думаю, що у мене є чотири відповіді, два дають точні рішення, як рішення @Adam Rosenfield, але без нескінченної проблеми циклу, а інші два з майже ідеальним рішенням, але швидше реалізуються, ніж перший.

Найкраще точне рішення вимагає 7 дзвінків rand5, але давайте продовжуємо, щоб зрозуміти.

Спосіб 1 - Точний

Сила відповіді Адама полягає в тому, що він дає ідеальний рівномірний розподіл, і існує дуже висока ймовірність (21/25), що будуть потрібні лише два дзвінки на rand5 (). Однак найгірший випадок - нескінченна петля.

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

Ось реалізація R:

rand5 <- function() sample(1:5,1)

rand7 <- function()  (sum(sapply(0:6, function(i) i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6)) %% 7) + 1

Для людей, які не знайомі з R, ось спрощена версія:

rand7 = function(){
  r = 0 
  for(i in 0:6){
    r = r + i + rand5() + rand5()*2 + rand5()*3 + rand5()*4 + rand5()*5 + rand5()*6
  }
  return r %% 7 + 1
}

Поширення rand5буде збережено. Якщо ми робимо математику, кожна з 7 ітерацій циклу має 5 ^ 6 можливих комбінацій, таким чином, загальна кількість можливих комбінацій є (7 * 5^6) %% 7 = 0. Таким чином, ми можемо розділити випадкові числа, згенеровані на рівні групи 7. Дивіться метод другий для більшого обговорення цього питання.

Ось всі можливі комбінації:

table(apply(expand.grid(c(outer(1:5,0:6,"+")),(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)

    1     2     3     4     5     6     7 
15625 15625 15625 15625 15625 15625 15625 

Я думаю, що прямо вперед показати, що метод Адама буде працювати набагато швидше. Ймовірність наявності rand5у рішенні Адама 42 або більше дзвінків дуже мала ( (4/25)^21 ~ 10^(-17)).

Спосіб 2 - Неточний

Тепер другий метод, майже однорідний, але вимагає 6 дзвінків rand5:

rand7 <- function() (sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1

Ось спрощена версія:

rand7 = function(){
  r = 0 
  for(i in 1:6){
    r = r + i*rand5()
  }
  return r %% 7 + 1
}

Це по суті одна ітерація методу 1. Якщо ми генеруємо всі можливі комбінації, тут підраховується підрахунок:

table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6),1,sum) %% 7 + 1)

   1    2    3    4    5    6    7 
2233 2232 2232 2232 2232 2232 2232

Одне число з’явиться ще раз у 5^6 = 15625випробуваннях.

Тепер у способі 1, додавши 1 до 6, переносимо число 2233 до кожної послідовної точки. Таким чином, загальна кількість комбінацій збігатиметься. Це працює тому, що 5 ^ 6 %% 7 = 1, а потім робимо 7 відповідних варіацій, тому (7 * 5 ^ 6 %% 7 = 0).

Спосіб 3 - Точний

Якщо аргументи методів 1 і 2 зрозумілі, слід метод 3 і вимагає лише 7 викликів до rand5. На даний момент я відчуваю, що це мінімальна кількість дзвінків, необхідних для точного рішення.

Ось реалізація R:

rand5 <- function() sample(1:5,1)

rand7 <- function()  (sum(sapply(1:7, function(i) i * rand5())) %% 7) + 1

Для людей, які не знайомі з R, ось спрощена версія:

rand7 = function(){
  r = 0 
  for(i in 1:7){
    r = r + i * rand5()
  }
  return r %% 7 + 1
}

Поширення rand5буде збережено. Якщо ми робимо математику, кожна з 7 ітерацій циклу має 5 можливих результатів, тому загальна кількість можливих комбінацій є (7 * 5) %% 7 = 0. Таким чином, ми можемо розділити випадкові числа, згенеровані на рівні групи 7. Дивіться метод один і два для більшого обговорення цього питання.

Ось всі можливі комбінації:

table(apply(expand.grid(0:6,(1:5)),1,sum) %% 7 + 1)

1 2 3 4 5 6 7  
5 5 5 5 5 5 5 

Я думаю, що прямо вперед показати, що метод Адама все одно буде працювати швидше. Ймовірність наявності rand5у рішенні Адама 7 або більше дзвінків все ще невелика ( (4/25)^3 ~ 0.004).

Метод 4 - Неточний

Це незначна варіація другого способу. Він майже рівномірний, але вимагає 7 дзвінків rand5, тобто один із додаткових методів 2:

rand7 <- function() (rand5() + sum(sapply(1:6,function(i) i*rand5())) %% 7) + 1

Ось спрощена версія:

rand7 = function(){
  r = 0 
  for(i in 1:6){
    r = r + i*rand5()
  }
  return (r+rand5()) %% 7 + 1
}

Якщо ми генеруємо всі можливі комбінації, ось такі підрахунки:

table(apply(expand.grid(1:5,(1:5)*2,(1:5)*3,(1:5)*4,(1:5)*5,(1:5)*6,1:5),1,sum) %% 7 + 1)

    1     2     3     4     5     6     7 
11160 11161 11161 11161 11161 11161 11160

Два числа з’являться один раз менше у 5^7 = 78125випробуваннях. Для більшості цілей я можу з цим жити.


1
Я не знайомий з R, але якщо я не розумію, як вони працюють, тоді метод 1 не є точним. Він має (5 ^ 6) ^ 7 = 5 ^ 42 можливих результатів, а не (5 ^ 6) * 7; 5 ^ 42 не ділиться на 7. Так само метод 3 не є точним. Він має 5 ^ 7 можливих результатів, а не 5 * 7. (Остання ітерація циклу в методі 3 з i=7також не має ефекту, оскільки додавання 7*rand5()до rне змінює значення rмоди 7.)
Адам Розенфілд,

2

Вам потрібна функція rand1_7 () , я написав rand1_5 (), щоб ви могли її протестувати та побудувати.

import numpy
def rand1_5():
    return numpy.random.randint(5)+1

def rand1_7():
    q = 0
    for i in xrange(7):  q+= rand1_5()
    return q%7 + 1
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.