生成(完全确定的)伪随机比特流


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个1而没有0,其中3/8应该具有2个1和一个0,其中3/8应该具有1个和2个0,以及1/8他们应该没有1和三个0。

随机测试#3

“游程”定义为所有具有相同值的连续比特系列。该字符串1001001110具有三个大小为1(1..1.....0)的行,两个大小为2(.00.00....)的行和一个大小为3(......111.)的行。请注意,运行不会重叠。

在1000个随机位的字符串中,应该有大约250个大小为1的游程,大小为2的125个游程,大小为3的62个游程,等等。通常,对于游程大小R,应该有大约1000/(2**(R+1))该大小的游程。

随机测试#4

前840位分为两半,每个420位。将前半部分的每个位与后半部分的相应位进行比较。这两位应大约占百分之五十的时间。


是执行测试2到4的Perl程序的源代码。到目前为止,它要求位字符串中不能包含任何空格。


客观的获胜标准时间!

获胜者是通过了所有6项要求和所有随机性测试的程序,其程度与随机性没有区别。如果有多个程序可以完成此任务,则花费时间最多的重复程序将获胜。如果有多个程序可以完成此任务,那么我可能不得不寻找更多的随机性测试来充当决胜局。


#2和#3并不是随机性的好标准。特别是对于#2,随机样本可能不会显示此特性。也许您可以做更大的样本量?我建议100和300之间的东西
乔尔·科尼特

更好的测量方法是移动平均值,因为在比特流的大窗口上的平均值不会有太大变化(应该在0.5左右)
Joel Cornett 2012年

@JoelCornett感谢您的建议。我对随机性测试了解不多。我将#2更改为其他内容,并且正在阅读有关移动均线的信息。
PhiNotPi 2012年

1
没问题。随机序列趋向于聚集而不是均匀分布,这是有时在会计中用于检测欺诈的事实。(欺诈性数字通常分布得太均匀,因为发明它们的人们将均匀性误认为是随机性)
Joel Cornett

我可以使用内置的加密功能(例如AES或SHA-2)吗?
CodesInChaos

Answers:


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 2012年

8

Mathematica 78 53个字符

尽管未经证实,但Pi的二进制表示形式的数字似乎表现得好像是混沌生成的。

以下简单例程确定性地返回pi的二进制数字(对应于d十进制数字)作为字符串:

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

用法

如果我们要求Pi的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}]

我运行了很多案例来系统地检查运行的分布。在大约300万个二进制数字中,有830k个运行1,1,416k个运行2,208k个运行3,104k个运行4,等等。

运行2 测试4:数据的前半部分和后半部分的匹配

匹配为0和2的212个案例;不匹配是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个十进制数字)需要不到2秒的时间。

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

(*out*)
{1.785928,Null}    
3321928

1
我知道有人会这么做...
停止了逆时针的作用

1
低挂的水果,对不对?
DavidC 2012年

您不能使用e而不是pi保存一个字节吗?
pppery 2015年

e杂乱分布?
DavidC

3

Python,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
您可以在“ for”之前删除空格。
daniero 2012年

2

哈斯克尔74 58

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

感谢shiona的简化。结果:

/伪随机| 头-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

这也是一个可怕的伪随机生成器(类似于von-Neuman使用的生成器)。对于那些不知道的人concatMap == (=<<) == flip . (>>=)(对于列表)


您可以替换\x->if odd x then"1"else"0"show.(`mod`2)
shiona 2012年

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#程序,请添加通常的样板。


我们还可以使用内置的加密原语(作弊器解决方案):

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

@walpen:我的初始实现未设置a,因为“ The program must not take any input from any external sources”而更改了它
Hasturkun 2012年

1

鼠尾草/ 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秒;但是,时间的增加远快于数字位数的线性增加。

使用OP程序测试结果

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

(有关32000多个数字和其他统计检验,请参阅G的最右边数字是随机的吗?


1

Java中,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上
Taylor

0

JavaScript-1000伪随机比特为1毫秒至2毫秒(100000比特为139毫秒至153毫秒)

该解决方案利用了平方根不合理的事实,因此几乎是随机的。基本上,它以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

周期超过30亿,但是我已经用光磁盘空间来计算更多内容。


1
您可以通过并置数字常量和关键字并分配4来节省3个字符:$x=1/7;print substr($x*=4-4*$x,9,1)%2while 1
ardnew 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.