Обратим генератор на псевдослучайна последователност

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

Нещо като 10-битова резолюция (т.е. положителни цели числа в диапазон от 0 до 1023) е достатъчно и последователност от >100k числа. Това е за просто приложение от тип игра, не ми трябва произволност на силата на криптиране или нещо такова, но искам да се чувства доста произволно. Все пак имам ограничено количество памет, така че не мога просто да генерирам част от произволни данни и да ги прегледам. Трябва да получа числата в "интерактивно време" - лесно мога да прекарам няколко милисекунди в мислене за следващото число, но не е удобно много повече от това. В крайна сметка ще работи на някакъв микроконтролер, вероятно просто Arduino.

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

Но може би ИМА някакъв псевдослучаен генератор, който ви позволява да вървите както напред, така и напред? Трябва да е възможно да се свържат два регистъра за преместване с линейна обратна връзка (LFSR), които да се въртят в различни посоки, нали?

Или може би мога просто да изкривя номера на индекса с помощта на някаква хеш функция? Първо ще опитам това.

Някакви други идеи?


person PapaFreud    schedule 26.05.2010    source източник


Отговори (7)


Зададох много подобен въпрос във форумите на tigsource.

Хеширане

Поне в игрите, хеш функция вероятно може да направи това, което искате. Можеш да го направиш така

class ReversibleRNG {
    int x;
public:
    ReversibleRNG(int seed) : x(seed) {}
    int next(){return yourFavoriteHash(++x);}
    int prev(){return yourFavoriteHash(--x);}
};

Обратим линеен конгруентен генератор (lcg)

Както много хора посочиха, lcg наистина е обратим. В lcg следващото състояние се изчислява по следния начин:

x = (a * prevx + c) mod m

Можем да пренаредим това:

x ≡ a * prevx + c (mod m)
x - c ≡ a * prevx (mod m)

Тъй като a и m са избрани като относително прости в lcg, можем да намерим обратното, като използваме разширения алгоритъм на Евклид.

ainverse = extEuclid(a, m).x;
ainverse * (x - c) ≡ ainverse * a * prevx (mod m)
ainverse * (x - c) ≡ prevx (mod m)

Което означава

prevx = ainverse * (x - c) mod m

Ако изберете внимателно m и a, алгоритъмът може да има период от 2^64

Внедряване

Направих имплементация само за заглавка на този алгоритъм, в случай че някой се интересува.

person bobbaluba    schedule 19.05.2013
comment
благодаря @bobbaluba - намерих тази информация за много полезна. Не съм много добър в математиката, затова се опитах да създам малко манекенско ръководство за обръщане на модулни функции - в случай че някой като мен попадне на това в бъдеще: stackoverflow.com/questions/29569927/ - person Jonathan Basile; 13.04.2015
comment
Имам впечатлението, че хеширането на последователни цели числа ще доведе до подбрандови последователности. Вижте stackoverflow.com/a/41728178/225186 - person alfC; 09.12.2017
comment
Благодаря на @bobbaluba за споделянето на кода. Току-що се опитах да го изпълня и изчисляването на стъпка назад отнема около 1,4 секунди в сравнение с практически моменталната стъпка напред. Това вярно ли е или правя нещо нередно? Предполагам, че това е разширеният алгоритъм на Евклид, който отнема толкова време за обработка, нали? - person Bill; 02.01.2021
comment
@Bill: Може би компилираш с деактивирани оптимизации? ainverse е резултат от функция constexpr, изпълнена с постоянни параметри, повечето компилатори трябва да са достатъчно умни, за да изчислят предварително това, ако оптимизацията е активирана (използвам g++ с -O2 в примерите, което прави мигновено за мен). Във всеки случай, аз прехвърлих изискването в репото по-горе до C++17, което позволява променливи constexpr, които според мен трябва да наложат изчислението на времето за компилиране и в режим на отстраняване на грешки. - person bobbaluba; 03.01.2021

Използването на наистина прост алгоритъм за симетрично криптиране е един от най-лесните начини да направите това. Всяко произволно число се формира чрез просто криптиране на предишното с някакъв фиксиран ключ и за да се върнете назад, просто дешифрирате.

Може да погледнете RC4 - код на http://en.wikipedia.org/wiki/RC4. Можете да използвате много по-малък график на ключовете, за да го накарате да пасне на arduino.

person Cullen Fluffy Jennings    schedule 28.05.2010

Шифровайте последователността 1, 2, 3, ... с произволен шифър и произволен ключ.

AES е наличен в почти всяка скорошна система и е светкавица бързо.

person BlueRaja - Danny Pflughoeft    schedule 28.05.2010

Просто обърнете реда на битовете в нарастваща последователност от цели числа. Например (с 8-битова резолюция):

  • 0 <=> 0
  • 1 <=> 128
  • 2 <=> 64
  • 3 <=> 192
  • 4 <=> 32
  • и т.н

Много е лесно да се движите напред и назад в последователността и е много по-бързо от извикването на криптиращи или хеш функции. Освен това има предимството да генерира най-дългата възможна последователност.

Определено не е криптографски защитен. Ето диаграма на разсейване на генерираните стойности (отново с 8-битова резолюция):

Диаграма на разсейване на произволно генерирани стойности

Можете лесно да видите модели, въпреки че може да е достатъчно „случайно“ за вас.

person Matt Thomas    schedule 18.01.2017
comment
Нямам математическо доказателство, но получената последователност изглежда като подбрандова. en.wikipedia.org/wiki/Low-discrepancy_sequence . Все още може да бъде полезен в много приложения. - person alfC; 08.12.2017
comment
@alfC Моята снимка със сигурност изглежда като набора на Хамърсли и Wolfram казва, че можете да генерирате този набор чрез обръщане на реда на битовете - мисля, че това е доказателството, че сте точно. Благодаря за линка! - person Matt Thomas; 08.12.2017
comment
Точно така, всъщност дори и да беше произволно, би било повече от обратимо/двупосочно, би било произволна последователност с произволен достъп. Винаги съм се чудил дали такова нещо съществува. Това е пропускане на N произволно число (във произволна посока) в O(1). - person alfC; 08.12.2017
comment
@alfC Ако всичко, което искате, е O(1) генериране на i-то число в псевдослучайна последователност и не ви интересува нищо за неговата обратимост, тогава r(i, seed) = hash(i + seed) ще свърши работа; просто изберете любимата си хеш функция - person Matt Thomas; 09.12.2017
comment
Да, но това ще бъде подбрандно, а не произволно. Не поставям под съмнение, че подбрандомът е с произволен достъп (O(1) скокове). Въпросът, който имам, е дали има произволни последователности с произволен достъп. - person alfC; 09.12.2017
comment
@alfC Не разбирам как hash(i + seed) създава последователност с ниско несъответствие (с подходяща хеш функция, напр. SHA256)? - person Matt Thomas; 11.12.2017
comment
Нито пък аз. Това е интуиция, ако произволно число може да бъде генерирано по този начин, то няма да изисква никаква памет и това би било странно. Ако не беше суббранд, щеше да е лош хеш. Моето заключение е, че хешовете произвеждат суббрандомирани последователности, когато се прилагат към програмни последователности. - person alfC; 11.12.2017
comment
@alfC Позволете ми да повторя по-убедително: Криптографски защитен хеш на брояч може също да действа като добър CSPRNG в някои случаи (CSPRNG = криптографски защитен генератор на псевдослучайни числа). Обърнете внимание, че изглежда по-често да се използва функция за криптиране вместо хеш функция, но принципът е същият. - person Matt Thomas; 11.12.2017
comment
@alfC Може би това ще помогне на интуицията: обикновено е лесно да се преработи вътрешното състояние на функция в параметър. С други думи, няма магия, която да идва от функция, която има вътрешно състояние. В случай на използване на хеш/криптираща функция за изпълнение на PRNG, цялото състояние, което е необходимо, е капсулирано в тези два параметъра (i и seed). По подобен начин можете да преработите любимата си PRNG функция, така че всички състояния да влизат като параметри и всички описания на страничните ефекти да се връщат от нея. - person Matt Thomas; 11.12.2017
comment
@alfC Като пример за играчка за моя предишен коментар , помислете за този Линеен конгруентен генератор (PRNG): {int seed = 1337; int rand() { seed = (4321 * seed + 2345) % 53; return seed; }. Тази функция прави точно същото нещо: int rand(int seed) { return (4321 * seed + 2345) % 53; } - person Matt Thomas; 11.12.2017
comment
те ще направят същото, ако семето се съхранява. Което означава, че няма лесни скокове. С други думи, for(int i = 0; i != 1000; ++i) print(rand()); и for(int i = 0; i != 1000; ++i) print(rand(i)); няма да направят едно и също нещо. Бих твърдял, че първото е произволно (т.е. квазислучайно), а второто е подбрандно. Съгласен ли си? - person alfC; 11.12.2017
comment
@alfC Вижте отговора ми в chat.stackoverflow.com/transcript/160976 (имате достъп за редактиране) - person Matt Thomas; 12.12.2017

Ако линеен конгруентен генератор е достатъчно добър, използвайте го. Те са лесно обратими. Въпросът е, че обратният генератор също е LCG. LCG също могат да прескачат във всяка посока (напред и назад) много бързо.

Подробностите могат да бъдат намерени в Изкуството на компютърното програмиране - том 2

По-специално раздел 3.2.1 Уравнения 6-8 на TAOCP и също упражнение 5 дават желаните резултати. В случай, че не можете да решите упражнението, можете лесно да намерите решения за него, напр. тук

person Udo Klein    schedule 02.05.2017

Въпреки че съм съгласен с @BlueRaja, че трябва просто да използвате AES в „режим на брояч“, с произволно или базирано на време начало за вашата последователност, AES може да не е наличен или осъществим във вашата вградена ситуация.

Намерих този интересен документ, който обсъжда как да изграждане на обратим PRNG; има само 10 страници и има много примерни кодове. Дайте го като опитате, ако AES не работи за вас.

person Randolpho    schedule 28.05.2010

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

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

Освен ако играта ви не е само за забавление, без никакви залози от какъвто и да е вид, бих избрал нещо криптографски сигурно, като подхода AES, предложен от други. LCG и други линейни генератори на случайни числа не могат да устоят на интелигентен противник.

person starblue    schedule 29.05.2010