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

11

Вдохновленный Рэндом со связанными руками :


Цель

Цель этой задачи - написать программу, которая генерирует псевдослучайный поток битов, который представляет собой строку из 1 и 0, которая выглядит чисто случайной, но фактически генерируется детерминированным способом. Ваша программа должна вывести строку из 1 и 0 (с необязательным пробелом) и должна соответствовать следующим требованиям:

  1. Учитывая неограниченное время и память, ваша программа должна продолжать выводить строки 1 и 0 навсегда
  2. Ваша программа должна вывести более 1000 случайных битов за одну минуту на приемлемой машине. Если это требование невозможно, я уменьшу его.
  3. Строка битов может повторяться, но длина повторяющейся секции должна быть больше 1000 бит.
  4. Строка битов должна пройти как можно больше тестов случайности (описанных ниже).
  5. Программа не должна принимать какие-либо входные данные от каких-либо внешних источников или использовать какую-либо встроенную функцию rand ().
  6. Из-за вышеуказанного требования программа должна выводить одну и ту же строку битов при каждом запуске.

Тест случайности № 1

Строка псевдослучайных битов не должна содержать какой-либо очевидной картины при визуальном осмотре.

Тест на случайность # 2 (возможны изменения в зависимости от комментариев)

Строка битов должна содержать равное распределение 1 и 0. Чтобы проверить это (и другие вещи), поток битов разбивается на сегменты длиной 3 бита, например 101|111|001.

Из всех этих сегментов 1/8 из них должны иметь три единицы и не иметь нулей, 3/8 из них должны иметь две единицы и одну цифру 0, 3/8 из них должны иметь одну 1 и две цифры 0 и 1/8 из них не должно быть 1 и три 0.

Тест случайности № 3

«Выполнение» определяется как последовательный ряд битов, которые имеют одинаковое значение. Строка 1001001110имеет три цикла размера 1 ( 1..1.....0), два цикла размера 2 ( .00.00....) и один цикл размера 3 ( ......111.). Обратите внимание, что пробеги не перекрываются.

Из строки из 1000 случайных битов должно быть около 250 прогонов с размером 1, 125 прогонов с размером 2, 62 прогона с размером 3 и т. Д. В целом, для 1000/(2**(R+1))прогонов размера R должно быть около прогонов такого размера.

Тест случайности № 4

Первые 840 бит разделены на две половины по 420 бит каждая. Каждый бит в первой половине сравнивается с соответствующим битом во второй половине. Два бита должны совпадать примерно в пятидесяти процентах случаев.


Вот исходный код программы на Perl, которая выполняет тесты со 2 по 4. На данный момент требуется, чтобы строка битов не содержала пробелов.


Объективный критерий времени победы!

Победителем становится программа, которая проходит все 6 требований и все тесты на случайность до такой степени, что она неотличима от случайности. Если несколько программ выполнят это, то выиграет та, которая повторяется дольше всех. Если несколько программ выполнят это, то мне, возможно, придется найти больше тестов случайности, чтобы действовать в качестве тай-брейков.

PhiNotPi
источник
№ 2 и № 3 на самом деле не очень хорошие критерии случайности. Особенно для № 2 случайная выборка, вероятно, не будет демонстрировать эту характеристику. Может быть, вы можете сделать больший размер выборки? Я бы предложил что-то между 100 и 300.
Джоэл Корнетт
Лучшим методом измерения была бы скользящая средняя, ​​так как среднее по большому окну в битовом потоке не сильно изменится (и должно быть около 0,5)
Джоэл Корнетт
@JoelCornett Спасибо за совет. Я не знаю много о тестах на случайность. Я изменю # 2 на что-то другое, и я читаю о скользящих средних.
PhiNotPi
1
Нет проблем. Случайные последовательности имеют тенденцию к слипанию и неравномерному распределению, это факт, который иногда используется в учете для выявления мошенничества. (Мошеннические числа часто будут распределяться слишком равномерно, потому что люди, которые их изобретают, ошибочно принимают единообразие за случайность)
Джоэл Корнетт
Могу ли я использовать встроенные функции шифрования (такие как AES или SHA-2)?
CodesInChaos

Ответы:

8

С, 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

Mathematica 78 53 символов

Цифры двоичного представления числа Пи, кажется, ведут себя так, как будто они хаотически получены, хотя это не доказано.

Следующая простая процедура детерминистически возвращает в виде строки двоичные цифры числа pi, соответствующие dдесятичным цифрам:

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

использование

Если мы запрашиваем аналог 301 десятичного знака числа Пи, мы получаем 1000 двоичных знаков.

f[301]
StringLength[%]

(* out *)
1100100100001111110110101010001000100001011010001100001000110100110001001100011001100010100010111000000011011100000111001101000100101001000000100100111000001000100010100110011111001100011101000000001000001011101111101010011000111011000100111001101100100010010100010100101000001000011110011000111000110100000001001101110111101111100101010001100110110011110011010011101001000011000110110011000000101011000010100110110111110010010111110001010000110111010011111110000100110101011011010110110101010001110000100100010111100100100001011011010101110110011000100101111001111110110001101111010001001100010000101110100110100110001101111110110101101011000010111111111101011100101101101111010000000110101101111110110111101110001110000110101111111011010110101000100110011111101001011010111010011111001001000001000101111100010010110001111111100110010010010010100001100110010100011110110011100100010110110011110111000010000000000111110010111000101000010110001110111111000001011001100011011010010010000011011000011100011

1000 (* characters *)

Поскольку Pi - иррациональное число, периода нет. Тем не менее, будут практические ограничения из-за аппаратного обеспечения.

Тест 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 миллионах двоичных разрядов было 830 тыс. Циклов из 1, 416 тыс. Циклов из 2, 208 тыс. Циклов из 3, 104 тыс. Циклов из 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
DavidC
источник
1
Я знал, что кто-то сделает это ...
перестал поворачиваться против часовой стрелки
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

ОБНОВИТЬ

Давайте посмотрим, как этот код выдерживает тесты OP:

Тест № 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)

Спасибо Шиону за упрощение. Результаты:

/ псевдослучайный | голова -c 1000

./pseudorandom | голова -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 . (>>=)(для списков)

walpen
источник
Вы можете заменить \x->if odd x then"1"else"0"на show.(`mod`2).
Шион
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 # вам нужно добавить немного шаблона)

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

CodesInChaos
источник
1

С, 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
Hasturkun
источник
aдолжен начинаться с 1 (при условии, что он вызывается без аргументов). Кроме того, вы можете вставить a=посередине, что-то вроде a=a/2^-!putchar(49-a%2)%576(взять некоторые свободы с алгоритмом)
Walpen
@walpen: Моя первоначальная реализация не установлена a, я изменил ее из-за " The program must not take any input from any external sources"
Hasturkun
1

Sage / Python

Эта программа печатает самые правые двоичные цифры, которые являются общими для каждой достаточно высокой башни возведения в степень 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 (биты взяты из заметки приложения xilinx 52 )

РЕДАКТИРОВАТЬ: я не был удовлетворен использованием 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
};

Я еще не проходил тесты, но думаю, что это хорошо с ними справится. Вот скрипка, чтобы вы могли увидеть это в действии. Для моего времени я просто запускал программу несколько раз и принимал самые быстрые и самые медленные значения в качестве диапазонов.

Briguy37
источник
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 миллиардов, но мне не хватило места на диске, чтобы рассчитать больше.

skibrianski
источник
1
Вы можете сохранить 3 символа, сопоставляя числовые константы и ключевые слова, а также распределяя эти 4:$x=1/7;print substr($x*=4-4*$x,9,1)%2while 1
ardnew