Створити (повністю детермінований) псевдослучайний потік бітів


11

Натхненний Random з зв’язаними руками :


Мета

Мета цього виклику - написати програму, яка генерує псевдослучайний потік бітів, який є рядком 1s і 0s, який видається чисто випадковим, але насправді генерується детермінованим способом. Ваша програма повинна виводити рядок у розмірі 1 та 0 (з необов’язковим пробілом) і повинна відповідати наступним вимогам:

  1. Враховуючи необмежений час і пам'ять, ваша програма повинна продовжувати виводити рядок 1s і 0s назавжди
  2. Ваша програма повинна виводити більше 1000 випадкових бітів за одну хвилину на розумній машині. Якщо ця вимога неможлива, я її зменшу.
  3. Рядок бітів може повторюватися, але довжина ділянки, що повторюється, повинна бути більше 1000 біт.
  4. Рядок бітів повинен пройти якомога більше тестів на випадковість (описаних нижче).
  5. Програма не повинна приймати жодних вхідних даних із будь-яких зовнішніх джерел або використовувати будь-яку вбудовану функцію rand ().
  6. Через вищезазначену вимогу програма повинна виводити однаковий точний рядок бітів щоразу, коли вона виконується.

Тест на випадковість №1

Рядок псевдовипадкових бітів не повинен містити очевидних зразків при візуальному огляді.

Тест на випадковість №2 (може змінюватися на основі коментарів)

Рядок бітів повинен містити рівний розподіл 1s і 0s. Щоб перевірити це (та й інші речі), потік бітів розбивається на сегменти довжиною 3 біти, наприклад 101|111|001.

З усіх цих сегментів, 1/8 з них повинні мати три 1s, а не 0s, 3/8 з них повинні мати два 1s і один 0, 3/8 з них повинні мати один 1 і два 0s, і 1/8 з них не повинно бути ні 1, а три 0.

Тест на випадковість №3

"Виконання" визначається як послідовний ряд бітів, які мають однакове значення. Рядок 1001001110має три прогони розміром 1 ( 1..1.....0), два прогони розміром 2 ( .00.00....) та один запуск розміром 3 ( ......111.). Зауважте, що прогони не перетинаються.

Із рядка з 1000 випадкових бітів повинно бути близько 250 прогонів розміром 1, 125 прогонів розміром 2, 62 прогони розміром 3 і т. Д. Загалом, для розміру R має бути приблизно 1000/(2**(R+1))прогонів такого розміру.

Тест на випадковість №4

Перші 840 біт розділені на дві половини по 420 біт кожна. Кожен біт на першому таймі порівнюється з відповідним бітом на другому таймі. Два біта повинні відповідати приблизно п’ятдесяти відсотків часу.


Ось вихідний код програми Perl, яка виконує тести з 2 по 4. На даний момент потрібно, щоб рядок бітів не містила пробілів.


Об'єктивний критерій виграшу!

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


№2 і №3 насправді не дуже хороші критерії випадковості. Особливо для №2 випадкова вибірка, ймовірно, не проявляє цієї характеристики. Можливо, ви можете зробити більший розмір вибірки? Я б запропонував щось середнє між 100 і 300.
Джоел Корнетт

Кращим методом вимірювання буде ковзаюча середня величина, оскільки середнє значення за великим вікном бітового потоку не сильно зміниться (і повинно бути близько 0,5)
Джоел Корнетт

@JoelCornett Дякую за пораду. Я мало знаю про тести на випадковість. Я перейду №2 на щось інше, і читаю про ковзаючі середні.
PhiNotPi

1
Нема проблем. Випадкові послідовності, як правило, стискаються і не розподіляються рівномірно. Це факт, який іноді використовується в обліку для виявлення шахрайства. (Шахрайські номери часто надто рівномірно розподіляються, тому що люди, які вигадують їх, помиляються з однаковістю для випадковості)
Джоел Корнетт

Чи можу я використовувати вбудовані функції криптовалюти (наприклад, AES або SHA-2)?
CodesInChaos

Відповіді:


8

C, 61

main(s,n){for(n=1u<<31;putchar((s%=n)/(n/2)&1|48);s*=65539);}

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

з | головка -c840
$ ./a.out | головка -c840 | perl tester.pl
Тест 2: 1 (1) 2.93333333333333 (3) 3.1 (3) 0.966666666666667 (1)
Тест 3: 214 99 71 24 7 5 1 1 2 2
Тест 4: 0.495238095238095

Довжина періоду - 2 ².


6
Це свідчить про те, як важко сказати випадковість із того, що широко відомо, що є одним з найгірших генераторів випадкових чисел. +1.
PhiNotPi

8

Математика 78 53 символів

Цифри двійкового зображення Pi схоже поводяться так, ніби вони хаотично отримані, хоча це недоведено.

Наступний простий порядок детерміновано повертає у вигляді рядка двійкові цифри pi, відповідні dдесятковим цифрам:

f[d_]:=ToString@FromDigits@RealDigits[N[Pi,d],2][[1]]

Використання

Якщо ми запитаємо аналог 301 десяткових цифр Pi, ми отримуємо 1000 двійкових цифр.

f[301]
StringLength[%]

(* out *)
1100100100001111110110101010001000100001011010001100001000110100110001001100011001100010100010111000000011011100000111001101000100101001000000100100111000001000100010100110011111001100011101000000001000001011101111101010011000111011000100111001101100100010010100010100101000001000011110011000111000110100000001001101110111101111100101010001100110110011110011010011101001000011000110110011000000101011000010100110110111110010010111110001010000110111010011111110000100110101011011010110110101010001110000100100010111100100100001011011010101110110011000100101111001111110110001101111010001001100010000101110100110100110001101111110110101101011000010111111111101011100101101101111010000000110101101111110110111101110001110000110101111111011010110101000100110011111101001011010111010011111001001000001000101111100010010110001111111100110010010010010100001100110010100011110110011100100010110110011110111000010000000000111110010111000101000010110001110111111000001011001100011011010010010000011011000011100011

1000 (* characters *)

Оскільки Пі - ірраціональне число, періоду немає. Однак існують практичні обмеження через апаратне забезпечення, яке працює.

Тест 1 Мені добре виглядає.

Тест 2

d=301;
Partition[RealDigits[N[Pi,d],2][[1]],{3}];
Tally[%]
(* out *)
{{{1,1,0},35},{{0,1,0},45},{{0,0,0},41},{{1,1,1},40},
{{0,1,1},50},{{1,0,1},32},{{1,0,0},43},{{0,0,1},47}}

Більш ретельна перевірка:

d=10^6;
Partition[RealDigits[N[Pi,d],2][[1]],{3}];
Tally[%]

{{{1,1,0},138565},{{0,1,0},138146},{{0,0,0},138260},{{1,1,1},138427},
{{0,1,1},139119}, {{1,0,1},138404},{{1,0,0},137926},{{0,0,1},138462}}

Тест 3: Виконання

d=10^6;
res3=SortBy[Tally@Split@RealDigits[N[Pi,d],2][[1]],Last]/.{a_,b_}:> {Length[a],b}
ListPlot[res3 ,AxesLabel-> {"Run Length","Runs"},AxesOrigin->{0,0}]

Я провів велику кількість справ, щоб систематично перевірити розподіл пробігів. Приблизно в 3 мільйони двійкових цифр було 830k прогонів 1, 416k пробігу 2, 208k пробігу 3, 104k пробігу 4 і т.д.

працює 2 Тест 4: Узгодження першої та другої половини даних

Матчі - це 212 випадків 0 і 2; невідповідністю є 208 випадків, коли сума відповідних цифр дорівнює 1.

d=301;
Tally[Plus@@Partition[Take[RealDigits[N[Pi,d],2][[1]],840],420]]

(* out *)
{{1,208},{0,108},{2,104}}

Хронометраж

Для обчислення 3321928 двійкових цифр (що відповідає 10 ^ 6 десятковим цифрам) потрібно менше двох секунд.

(r=f[10^6]);//AbsoluteTiming
StringLength[r]

(*out*)
{1.785928,Null}    
3321928


1
Низько висячі фрукти, правда?
DavidC

Не могли б ви використати eзамість того, piщоб зберегти один байт?
pppery

Є чи eрозподілені хаотично?
DavidC

3

Пітона, 90

g=[19]
print(''.join("01"[(g.append((11*g[-1]+13)%1024)or g[-1])>512]for i in range(1000)))

g- значення насіння. Випадкова вибірка демонструє надзвичайно нормальне розподіл, повторне випадкове відбір вибіркових засобів дало середнє значення 0.506та стандартне відхилення .0473(розмір вибірки 1000). На жаль, випадковість дуже чутлива до початкового насіння. Насіння в наведеному вище коді дало мені найкращу випадковість: p

ОНОВЛЕННЯ

Давайте подивимося, як цей код відповідає тестам ОП:

Тест №1

Це трохи суб’єктивно ... але мені це виглядає досить нерегулярно.

Тест №2

Три 1-х: 0,141
Два 1-х: 0,371
Один 1: 0,353
Нуль 1-х: 0,135

Тест №3

Виконує за розміром:

8: 11
7: 3
6: 7
5: 13
4: 32
3: 67
2: 119
1: 216

Тест №4

Коефіцієнт рівностей: 0,94 Це друкарська помилка. Незабаром оновиться правильним номером.


1
Ви можете видалити пробіл перед "за".
daniero

2

Haskell 74 58

main=print$iterate(read.take 9.show.(^3))7>>=show.(`mod`2)

Завдяки siona за спрощення. Результати:

/ псевдовипадкові | головка -c 1000

./псевдослучайні | головка -c 1000 | perl test.pl

Тест 2: 0.966666666666667 (1) 2.4 (3) 3.3 (3) 1.33333333333333 (1)

Тест 3: 260 108 66 33 15 11 5 2

Тест 4: 0,495238095238095

Це також жахливий псевдовипадковий генератор (подібний до того, який використовує фон-Нейман). Для тих, хто не знав concatMap == (=<<) == flip . (>>=)(для списків)


Ви можете замінити \x->if odd x then"1"else"0"на show.(`mod`2).
shiona

1

Питання по суті еквівалентно "впровадженню шифру потоку". Тому я реалізую RC4, оскільки це порівняно просто.

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

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

var s=Enumerable.Range(0,256).ToArray();
byte i=0,j=0;
for(int k=0;;k++)
{
    i++;
    j+=(byte)s[i];
    var t=s[i];s[i]=s[j];s[j]=t;
    if(k>99999)
        Console.Write(s[i]+s[j]&1);
}

Або без пробілів:

var s=Enumerable.Range(0,256).ToArray();byte i=0,j=0;for(int k=0;;k++){i++;j+=(byte)s[i];var t=s[i];s[i]=s[j];s[j]=t;if(k>99999)Console.Write(s[i]+s[j]&1);}

C #, 156 знаків, працює в режимі оператора LinqPad. Для повної програми C # додайте звичайну котлову панель.


Ми також могли б використовувати вбудовані в крипто-примітиви (рішення Cheater):

var h=SHA256.Create();for(BigInteger i=0;;i++){Console.Write(h.ComputeHash(i.ToByteArray())[0]%2);}

(C #, 99 символів, працює в режимі операторів LinqPad. Для звичайного компілятора C # вам потрібно буде додати трохи котла)

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


1

C, 52 символи

main(a){for(a=1;putchar(48+a%2);a=a/2^-(a%2)&576);}

Це 10-бітний LFSR, результати тесту:

$ ./a.out |head -c 1000 | perl randtest.pl
Test 2: 1.13333333333333 (1) 2.86666666666667 (3) 3.16666666666667 (3) 0.833333333333333 (1)
Test 3:  251 122 64 32 16 8 4 2  1
Test 4: 0.466666666666667

aмає починатися як 1, (якщо припустити, що це викликано без аргументів). Також ви можете дотримуватися a=середини, щось на кшталт a=a/2^-!putchar(49-a%2)%576(брати деякі свободи за алгоритмом)
пройти

@walpen: Моя початкова реалізація не встановлена a, я змінив її через " The program must not take any input from any external sources"
Hasturkun

1

Мудрець / Пітон

Ця програма друкує найправіші бінарні цифри, які є загальними для кожної досить високої вежі експоненціації форми 3 3 3 3 . . . З усього, що могло б коли-небудь було здійснено, це найправіші двійкові цифри числа Грема . Послідовність цифр нескінченна і не є періодичною.

m = 1; x = 3; last = 0
while True:
    m *= 2; x = pow(3,x,m); l = len(bin(x))
    print '1' if l > last else '0',
    last = l

На 1000 цифр це зайняло менше 2 секунд; однак час збільшиться набагато швидше, ніж лінійно кількість цифр.

Результати випробувань за допомогою програми ОП є

Test 2: 1.26666666666667 (1) 3.16666666666667 (3) 2.8 (3) 0.766666666666667 (1)
Test 3:  268 126 61 30 20 7 2  1 1
Test 4: 0.466666666666667

(Див. Чи є крайні праві цифри G випадковими? Більше 32000 цифр та додаткові статистичні тести.)


1

Ява, 371 317

На основі 128-бітового LFSR (бітові крани - із примітки 52 xilinx )

EDIT: Я не був задоволений використанням BigInteger, тому ця версія не відповідає. Збережено деякі символи. Вихід може бути трохи менш випадковим, тому що я не міг придумати хороший метод «висіву».

Новий код: Аргументи: BITS_TO_PRINT

class R{public static void main(String[]a){int L=65536;int[]v={0,128,126,101,99};int[]b=new int[L];for(int x=0;x<L;x++)b[x]=(x*x)&1;for(int i=0;i<Integer.parseInt(a[0])+L;i++){if(1!=(b[v[1]]^b[v[2]]^b[v[3]]^b[v[4]]))b[v[0]]=1;else b[v[0]]=0;if(i>L)System.out.print(b[v[0]]);for(int j=0;j<5;j++)v[j]=(v[j]-1)&(L-1);}}}

Стара версія: Аргументи: SEED, BITS_TO_PRINT

import java.math.BigInteger;class R{public static void main(String[]a){BigInteger v=new BigInteger(a[0]);BigInteger m=new BigInteger("ffffffffffffffffffffffffffffffff",16);for(int i=Integer.parseInt(a[1]);i>0;i--){v=v.shiftLeft(1);if(!(v.testBit(128)^v.testBit(126)^v.testBit(101)^v.testBit(99))){v=v.setBit(0);}v=v.and(m);java.lang.System.out.print(v.testBit(0)?1:0);}}}

Нова версія: Приклад виведення, біт = 100:

011001100111000110010100100111011100100111000111001111110110001001100000100111111010111001100100011

1
До речі, я припускаю, що обидва рахунки Ноя з цієї посади - одна і та ж людина. Якщо це так, ви можете попросити модератора об’єднати їх у meta.codegolf.stackexchange.com
Пітер Тейлор

0

JavaScript - від 1 мс до 2 мс для 1000 псевдовипадкових бітів (139 мс до 153 мс для 100000 біт)

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

var getDeterministicPseudoRandString = function(length){
    var randString = '';

    var i = 2;
    var prevRand = '';

    outerLoop:
    while(randString.length < length){
        var nextRand, nextFullRand = Math.sqrt(i++).toString(2).substring(1).replace('.', '');
        nextRand = nextFullRand;
        for(var j = prevRand.length; j > 0; j--){
            var replaceString = prevRand.substring(0, j);

            nextRand = nextFullRand;

            if(nextFullRand.indexOf(replaceString) == 0){
                if(j == prevRand.length && j > 30){
                    //start i over at 2
                    console.log('max i reached: ' + i);

                    i = 2;
                    continue outerLoop;
                } else {
                    nextRand = nextFullRand.replace(replaceString, '');
                }

                break;
            }
        }
        prevRand = nextFullRand;

        randString += nextRand;
    }

    return randString.substring(0, length);//Return the substring with the appropriate length
};

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


0

Пітон

import hashlib
x=''
while 1:
    h=hashlib.sha512()
    h.update(x)
    x=h.digest()
    print ord(x[0])%2

Повинен мати період близько 2 ^ 512.


0

perl, 44 байти

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

$x=1/7;print substr($x*=4-4*$x,9,1)%2while 1

Період довший 3 мільярди, але мені не вистачало місця на диску, щоб обчислити більше.


1
Ви можете зберегти 3 $x=1/7;print substr($x*=4-4*$x,9,1)%2while 1
знаки, розмістивши
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.