Как да генерирате непредубедено произволно число в произволен диапазон, като използвате най-малко битове

Поради принципа на гълъбовата дупка, не можете просто да използвате

output = min + (rand() % (int)(max - min + 1))

за генериране на безпристрастен, еднакъв резултат. Този отговор на подобен въпрос предоставя едно решение, но е много разточително по отношение на консумираните произволни битове.

Например, ако произволният диапазон на източника е нисък, тогава шансовете да се наложи да генерирате втора стойност от източника могат да бъдат доста високи. Алтернативно, използването на по-голям обхват на източника също е разточително.

Въпреки че съм сигурен, че може да се извлече оптимален размер на диапазона на източника, това не отговаря на въпроса, че може да има по-добър алгоритъм, вместо да оптимизира този.

[РЕДАКТИРАНЕ] Моята идея е показана в отговорите, за да доведе до пристрастни резултати.

Един подход, който ми хрумва, е

  1. Консумирайте минималния брой битове, необходими за покриване на желания диапазон.
  2. Ако тази стойност е извън желания диапазон, изхвърлете само един бит и консумирайте още един.
  3. Повторете при необходимост.

person Eoin    schedule 27.12.2013    source източник
comment
Страницата с ръководство за rand() предлага нещо по следния начин: min + (int) (((double) rand() / (RAND_MAX + 1.0)) * (max - min + 1)). Идеята е да мащабирате изхода от rand() в дроб в диапазона [0, 1), след това да умножите по вашия диапазон и да добавите вашия минимум, за да получите число в диапазона [min, max). Вероятно има начини да го направите, без да прибягвате до по-бавни операции с плаваща запетая, но дали искате да ги използвате ще зависи от вашите очаквания за производителност и колко сложни искате да получите...   -  person twalberg    schedule 27.12.2013
comment
Вашият подход би довел до неравномерно разпределение на резултатите.   -  person Henry    schedule 27.12.2013
comment
rand() връща фиксиран брой произволни битове, предлагате ли да използвате само някои от тях и да запазите останалите за по-късно? Това изглежда много по-тромаво от необходимото, тъй като битовете са безплатни. Ако се притеснявате от изтощаването на битовия поток (което наистина означава, че сте започнали да повтаряте произволната последователност), тогава трябва да помислите за по-добър (истински?) произволен източник.   -  person Dwayne Towell    schedule 27.12.2013
comment
@twalberg, ако беше прочел въпроса и връзките в него, щеше да разбереш недостатъка в този подход. Работи, когато е приемливо малко отклонение във вашите произволни числа, което обхваща много практически приложения. Не е универсален.   -  person Mark Ransom    schedule 27.12.2013
comment
@DwayneTowell За моя случай на употреба произволните битове са сравнително скъпи за производство. Мислете в съответствие с източник като random.org.   -  person Eoin    schedule 28.12.2013
comment
възможен дубликат на Как ефективно конвертирате няколко байта в цяло число между диапазон?   -  person Lior Kogan    schedule 28.12.2013
comment
@LiorKogan, не е дубликат. Вижте моя отговор за това как този въпрос се различава от другите.   -  person Mark Ransom    schedule 29.12.2013
comment
stackoverflow.com/a/17749722/2417578 съдържа примерен алгоритъм за пестене на битове и връзка към документ, анализиращ различни алгоритми за спестяване срещу различни разходи за източник на RNG. Трябва да има всичко необходимо.   -  person sh1    schedule 09.08.2019


Отговори (2)


Общият подход за елиминиране на отклонението е да се изхвърлят числа, които са извън желания диапазон. Както беше отбелязано, това е разточително. Възможно е да се минимизира загубата, като се започне с по-голям брой битове и се генерират множество случайни числа едновременно; можете да постигнете по-добро съответствие между диапазона от входове и изходи.

Например вземете хвърляне на зар. Изходът има 6 възможности. Наивният подход би отнел 3 произволни бита за всяко произведено произволно число. Първият пример демонстрира проблема с гълъбите.

def pigeon_die(total_bit_count):
    for i in xrange(total_bit_count // 3):
        bits = random.getrandbits(3)
        yield 1 + bits * 6 // 8

1 : 832855
2 : 417835
3 : 416012
4 : 833888
5 : 416189
6 : 416554
total 3333333
max/min 2.00448063998

Вторият пример е често използваният разточителен подход. Можете да видите, че той генерира по-малко произволни числа от същия брой произволни битове, но отклонението е елиминирано.

def wasteful_die(total_bit_count):
    for i in xrange(total_bit_count // 3):
        bits = random.getrandbits(3)
        if bits < 6:
            yield 1 + bits

1 : 417043
2 : 415812
3 : 417835
4 : 416012
5 : 416645
6 : 417243
total 2500590
max/min 1.00486517946

Последният пример отнема 13 бита наведнъж и генерира 5 произволни числа от него. Това генерира дори повече числа от наивния подход!

def optimized_die(total_bit_count):
    for i in xrange(total_bit_count // 13):
        bits = random.getrandbits(13)
        if bits < 6**5:
            for j in range(5):
                yield 1 + bits % 6
                bits //= 6

1 : 608776
2 : 608849
3 : 608387
4 : 608119
5 : 607855
6 : 608559
total 3650545
max/min 1.00163525841

Изборът от 13 бита беше направен чрез вземане на логаритъм с основа 6 на степени на 2 и избор на този, който е най-близо до цяло число.

def waste_list(n):
    for bit in range(1, 31):
        potential = math.log(2**bit, n)
        count = int(potential)
        if count > 0:
            waste = potential - count
            yield waste, bit, count

for waste, bit, count in sorted(waste_list(6)):
    print bit, count, waste
    if bit == 3:
        break

13 5 0.029086494049
26 10 0.0581729880981
8 3 0.0948224578763
21 8 0.123908951925
3 1 0.160558421704

Както можете да видите, има 4 избора, по-добри от простите 3 бита.

person Mark Ransom    schedule 28.12.2013
comment
Съжалявам за късния отговор, но това изглежда като отличен подход, тъй като трябва да генерирам множество стойности. - person Eoin; 16.01.2014

Опасявам се, че предложеният от вас подход е пристрастен.

Да предположим, че генераторът на произволни числа е направил числа от 0 до 255, но вие искате произволно число в диапазона от 0 до 254.

Ако генераторът на случайни числа произведе 255 (1111_1111 в двоична система), вашият подход ще изхвърли един бит и ще добави още един, докато в крайна сметка ще завършите с числото 254 (1111_1110 в двоична система). (Ако съм разбрал правилно вашия подход.)

Следователно вашите генерирани числа ще имат вероятност от 1/256 за всяко число, с изключение на 254, което ще има вероятност от 1/128.

person Peter de Rivaz    schedule 27.12.2013
comment
Изглежда, че сте напълно прави. Все още се чудя дали съществува алтернативен алгоритъм. - person Eoin; 28.12.2013
comment
@Eoin, вярвам, че златният стандарт е методът на д-р Жак. Прекарах известно време в разтягане на моята теория на числата (която е лоша), за да се опитам да я оптимизирам допълнително и в крайна сметка се свързах с автора, за да обсъдим откритията си, но заедно не можахме да докажем някакви осезаеми печалби, дължащи се на това, което измислих . Просто увеличете максимално N, за да постигнете максимална ефективност. - person sh1; 09.08.2019