每日随机高尔夫#4:贝特朗悖论


19

关于系列

首先,您可以像对待其他任何代码高尔夫挑战赛一样对待它,并回答它而不必担心系列赛。但是,在所有挑战中都有排行榜。您可以在第一篇文章中找到排行榜以及有关该系列的更多信息。

尽管我在本系列中有很多想法,但未来的挑战还没有定下来。如果您有任何建议,请在相关的沙箱帖子上让我知道。

第4洞:贝特朗悖论

贝特朗悖论是一个有趣的问题,它显示了在一个圆圈采摘随机和弦可以产生和弦,它们的中点和它们的长度的不同分布如何不同的方法。

在这一挑战中,您应该使用“右”方法生成单位圆的随机和弦,即产生在分布和平移下不变的和弦分布的方法。在链接的Wikipedia文章中,“方法2”就是这样的方法。

确切的规则如下:

  • 您应该使用一个正整数N,该整数指定应返回多少个和弦。输出应该是一个N和弦列表,每个和弦都指定为单位圆上的两个点,以其弧度的极角表示。
  • 您的代码应该能够为两个角度中的每个角度至少返回2 20个不同的值。如果可用的RNG范围较小,则必须首先在内置的RNG之上构建具有足够大范围的RNG,或者必须实现自己合适的RNG此页面可能对此有所帮助。
  • 和弦的分布必须与链接的Wikipedia文章中“方法2”产生的和弦没有区别。如果您采用其他算法来选择和弦,请提供正确性证明。无论您选择实施哪种算法,从理论上讲,它都必须能够在单位圆中生成任何有效的和弦(不包括基础PRNG或有限精度数据类型的限制)。
  • 您的实现应使用并返回浮点数(至少32位宽)定点数(至少24位宽),并且所有算术运算都应在16 ulp以内准确。

您可以编写完整的程序或函数,并通过STDIN(或最接近的替代方案),命令行自变量或函数自变量接受输入,并通过STDOUT(或最接近的替代方案),函数返回值或函数(out)参数产生输出。

输出可以采用任何方便的列表或字符串格式,只要可以清楚地区分各个数字并且它们的总数始终为偶数即可。

这是代码高尔夫球,因此最短的提交(以字节为单位)获胜。当然,每位用户最短的提交时间也将进入该系列的整体排行榜。

可视化

您可以使用以下代码段渲染生成的线并检查其分布。只需将成对的角度列表粘贴到文本区域即可。该代码段应该能够处理几乎所有列表格式,只要这些数字是简单的十进制数字(无科学计数法)即可。我建议您至少使用1000行来了解分布情况。我还提供了以下文章中介绍的不同方法的一些示例数据。

用方法1生成的示例数据

方法2生成的示例数据

用方法3生成的示例数据

排行榜

该系列的第一篇文章将产生一个排行榜。

为确保您的答案显示出来,请使用以下Markdown模板以标题开头每个答案:

# Language Name, N bytes

N您提交的文件大小在哪里。如果您提高了分数,则可以通过打败旧分数保持标题。例如:

# Ruby, <s>104</s> <s>101</s> 96 bytes

(目前未显示该语言,但是该代码段确实需要并对其进行解析,并且将来我可能会添加一个语言排行榜。)

Answers:


12

IA-32机器码,54个字节

代码的十六进制转储:

68 00 00 00 4f 0f c7 f0 50 db 04 24 58 d8 34 24
f7 d9 78 f1 d9 c0 dc c8 d9 e8 de e1 d9 fa d9 c9
d9 f3 dc c0 d9 eb de ca d8 c1 dd 1a dd 5a 08 83
c2 10 e2 d1 58 c3

它使用了维基百科描述的(稍作修改)算法。用伪代码:

x = rand_uniform(-1, 1)
y = rand_uniform(-1, 1)
output2 = pi * y
output1 = output2 + 2 * acos(x)

我使用该范围-1...1是因为在该范围内易于生成随机数:该rdrand指令会在-2^31和之间生成一个整数2^31-1,该整数可以很容易地除以2 ^ 31。

我应该使用0...1其他随机数(x)的范围,该范围被输入acos; 但是,负数部分与正数部分是对称的-负数产生的和弦的跨度大于pi弧度,但是出于说明贝特朗悖论的目的,这并不重要。

由于80386(或x87)指令集没有专用acos指令,因此我只能使用以下atan指令来表示计算:

acos(x) = atan(sqrt(1-x^2)/x)

这是生成上面机器代码的源代码:

__declspec(naked) void __fastcall doit1(int n, std::pair<double, double>* output)
{
    _asm {
        push 0x4f000000;        // [esp] = float representation of 2^32

    myloop:
        rdrand eax;             // generate a random number, -2^31...2^31-1
        push eax;               // convert it
        fild dword ptr [esp];   // to floating-point
        pop eax;                // restore esp
        fdiv dword ptr [esp];   // convert to range -1...1
        neg ecx;
        js myloop;              // do the above 2 times

        // FPU stack contents:  // x           | y
        fld st(0);              // x           | x   | y
        fmul st(0), st;         // x^2         | x   | y
        fld1;                   // 1           | x^2 | x | y
        fsubrp st(1), st;       // 1-x^2       | x   | y
        fsqrt;                  // sqrt(1-x^2) | x   | y
        fxch;                   // x           | sqrt(1-x^2) | y
        fpatan;                 // acos(x)     | y
        fadd st, st(0);         // 2*acos(x)   | y
        fldpi;                  // pi          | 2*acos(x) | y
        fmulp st(2), st;        // 2*acos(x)   | pi*y
        fadd st, st(1);         // output1     | output2
        fstp qword ptr [edx];   // store the numbers
        fstp qword ptr [edx + 8];

        add edx, 16;            // advance the output pointer
        loop myloop;            // loop

        pop eax;                // restore stack pointer
        ret;                    // return
    }
}

为了生成两个随机数,该代码使用了嵌套循环。为了组织循环,代码利用了ecx寄存器(外循环计数器)为正的优势。它会暂时取反ecx,然后再次执行以恢复原始值。jsecx为负数时,指令将重复循环(仅发生一次)。


我喜欢使用IA32汇编的答案!只是说:这并不是严格的386机器代码,因为80386显然没有rdrand指令,也没有必要使用FP协处理器
user5572685

是的,IA32是一个更好的名称。不够具体,但可能超过80386正确的
anatolyg

7

Pyth,25 23 22字节

rcrmn的C ++ 11答案的端口。这是我对Pyth的首次使用,而且我玩得很开心!

VQJ,*y.n0O0.tOZ4,sJ-FJ

23字节版本:

VQJ*y.n0O0K.tOZ4+JK-JKd

通过更改程序以使用folds + sums和将J设置为元组并删除K来削减字节。

原版的:

VQJ**2.n0O0K.tO0 4+JK-JKd

由于@orlp,减少了2个字节。

说明:

VQ                         loop as many times as the input number
  J,                       set J to the following tuple expression
    *y.n0O0                2 * .n0 (pi) * O0 (a random number between 0 and 1)
            .tOZ4          .tOZ 4 (acos of OZ (a random number))
                 ,sJ-FJ    print the sum of J and folding J using subtraction in parenthesis, separated by a comma, followed by another newline

1
Pyth提示:*2_与相同y_。变量Z最初为0,因此您可以.tO0 4通过写入来删除空格.tOZ4
orlp 2015年

1
我正在计算25个字节...
Maltysen

另外,您可以使用,+JK-JK
Maltysen

@Maltysen都固定。谢谢!
kirbyfan64sos 2015年

@orlp我一点都不知道,y也忘记了Z。固定; 谢谢!
kirbyfan64sos 2015年

6

朱莉娅,48个字节

n->(x=2π*rand(n);y=acos(rand(n));hcat(x+y,x-y))

像到目前为止的大多数答案一样,它使用方法2的算法。它创建一个lambda函数,该函数接受一个整数输入并返回一个nx 2数组。要给它起个名字,例如f=n->...

取消+说明:

function f(n::Int64)
    # The rand() function returns uniform random numbers using
    # the Mersenne-Twister algorithm

    # Get n random chord angles
    x = 2π*rand(n)

    # Get n random rotations
    y = acos(rand(n))

    # Bind into a 2D array
    hcat(x+y, x-y)
end

我非常喜欢可视化的外观,因此我将其中之一。这是的结果f(1000)

圈


5

Pyth,22个字节

C ++答案的端口。我还有另一个23字节的解决方案(现在是22!),但是它几乎是@ kirbyfan64sos的pyth答案的副本,带有优化,所以我不得不在框外稍微思考一下,并创造性地(ab)使用fold运算符。

m,-Fdsdm,y*.nZOZ.tOZ4Q

请注意,由于引入之后fold运算符中的错误,目前无法正常使用reduce2。我要提出要求。

m             Map    
 ,            Tuple of
  -Fd         Fold subtraction on input
  sd          Fold addition on input
 m      Q     Map over range input
  ,           Tuple           
   y          Double
    *         Product
     .nZ      Pi
     OZ       [0, 1) RNG
  .t  4       Acos
    OZ        [0, 1) RNG

作为参考,这是我的另一种解决方案,其工作方式相同: VQKy*.nZOZJ.tOZ4,+KJ-KJ


您拼错了我的用户名... :(
kirbyfan64sos

@ kirbyfan64sos derp。抱歉;)
Maltysen,2015年

4

IDL,65个字节

显然,这是与@rcrmn相同的算法,即使在阅读他们的答案之前我是独立派生它的。

read,n
print,[2,2]#randomu(x,n)*!pi+[-1,1]#acos(randomu(x,n))
end

IDL的randomu函数使用Mersenne Twister,其周期为2 19937 -1。

编辑:我通过上面的可视化器运行了1000个和弦,这是结果的屏幕截图:

IDL伯特兰


4

C ++ 11,214个字节

#include<random>
#include<iostream>
#include<cmath>
int main(){using namespace std;int n;cin>>n;random_device r;uniform_real_distribution<> d;for(;n;--n){float x=2*M_PI*d(r),y=acos(d(r));cout<<x+y<<' '<<x-y<<';';}}

因此,这是Wikipedia页面上正确算法的直接实现。这里打高尔夫球的主要问题是随机生成器类具有如此之长的名称。但是,与优兰特相反,它至少适当地均匀。

说明:

#include<random>
#include<iostream>
#include<cmath>
int main()
{
    using namespace std;
    int n;
    cin>>n; // Input number
    random_device r; // Get a random number generator
    uniform_real_distribution<> d;   // Get a uniform distribution of 
                                     // floats between 0 and 1
    for(;n;--n)
    {
        float x = 2*M_PI*d(r),       // x: Chosen radius angle
              y = acos(d(r));        // y: Take the distance from the center and 
                                     // apply it an inverse cosine, to get the rotation

        cout<<x+y<<' '<<x-y<<';';    // Print the two numbers: they are the rotation
                                     // of the radius +/- the rotation extracted from
                                     // the distance to the center
    }
}

1
这个因素M_PI_2看起来很可疑。我认为应该改为1。
anatolyg 2015年

是的,完全正确,现在要修复它!非常感谢!
rorlork 2015年

4

APL,46个字节

f←{U←⍵ 2⍴∊{(○2×?0)(¯1○?0)}¨⍳⍵⋄⍉2⍵⍴∊(+/U)(-/U)}

我的第一个APL程序!当然可以大大改善它(因为我缺乏对APL的总体了解),因此任何建议都是很棒的。这创建了一个功能f,以整数作为输入,使用方法2计算成对的和弦点,并打印用换行符分隔的每对和弦。

您可以 在线尝试

说明:

f←{ ⍝ Create the function f which takes an argument ⍵

    ⍝ Define U to be an ⍵ x 2 array of pairs, where the first
    ⍝ item is 2 times a random uniform float (?0) times pi (○)
    ⍝ and the second is the arccosine (¯1○) of another random
    ⍝ uniform float.

    U ← ⍵ 2 ⍴ ∊{(○2×?0)(¯1○?0)}¨⍳⍵

    ⍝ Create a 2 x ⍵ array filled with U[;1]+U[;2] (+/U) and
    ⍝ U[;1]-U[;2] (-/U). Transpose it into an ⍵ x 2 array of
    ⍝ chord point pairs and return it.

    ⍉ 2 ⍵ ⍴ ∊(+/U)(-/U)
}

注意:我之前的19字节解决方案无效,因为它返回的是(x,y)而不是(x + y,xy)。悲伤比比皆是。


3

Java,114字节

n->{for(;n-->0;){double a=2*Math.PI*Math.random(),b=Math.acos(Math.random());System.out.println(a+b+" "+(a-b));}};

Java的基本实现。用作lambda表达式。

用法示例


您不能通过存放在Math某个地方来减小尺寸吗?或者其他的东西?(我不是Java程序员)
Ismael Miguel

@IsmaelMiguel这将花费额外的2个字符。
TheNumberOne

抱歉:/试图减少显示的次数很诱人Math。对于使用代码生成另一个代码来解决该问题,元数据怎么说?
Ismael Miguel

2
@IsmaelMiguel这是一场公平的比赛,尽管如果您实际上在metagolfing方面比在高尔夫方面更出色,我会感到惊讶。
马丁·恩德

3

Ruby,72个字节

我的第一个高尔夫!我使用了与每个人相同的代码,我希望那可以

gets.chomp.to_i.times{puts"#{x=2*Math::PI*rand},#{x+2*Math.acos(rand)}"}

2

爪哇,115123

这与大多数其他工具基本相同,但是我需要一个Java分数来解决这个问题,所以就这样:

void i(int n){for(double x;n-->0;System.out.println(x+2*Math.acos(Math.random())+" "+x))x=2*Math.PI*Math.random();}

pastebin上可以找到1000个和弦,这是一次运行的前五个和弦:

8.147304676211474 3.772704020731153
8.201346559916786 3.4066194978900106
4.655131524088468 2.887965593766409
4.710707820868578 3.8493686706403984
3.3839198612642423 1.1604092552846672

1

CJam,24 22字节

与其他算法类似,这是CJam中的一个版本。

{2P*mr_1dmrmC_++]p}ri*

输入1000会产生如下分布:

在此处输入图片说明

怎么运行的

该算法很简单 x = 2 * Pi * rand(); print [x, x + 2 * acos(rand())]

{                 }ri*        e# Run this loop int(input) times
 2P*mr                        e# x := 2 * Pi * rand()
      _                       e# copy x
       1dmr                   e# y := rand()
           mC                 e# z := acos(y)
             _++              e# o := x + z + z
                ]             e# Wrap x and o in an array
                 p            e# Print the array to STDOUT on a new line

更新:感谢Martin,节省了2个字节!

在这里尝试


1

的Python 3,144 117字节

(感谢Blckknght的 lambda指针)

使用与其他人相同的方法:

import math as m;from random import random as r;f=lambda n:[(x,x+2*m.acos(r()))for x in(2*m.pi*r()for _ in range(n))]

从Python文档中:

Python使用Mersenne Twister作为核心生成器。它产生53位精度的浮点数,周期为2 19937 -1。

输出量

>>> f(10)
[(4.8142617617843415, 0.3926824824852387), (3.713855302706769, 1.4014527571152318), (3.0705105305032188, 0.7693910749957577), (1.3583477245841715, 0.9120275474824304), (3.8977143863671646, 1.3309852045392736), (0.9047010644291349, 0.6884780437147916), (3.333698164797664, 1.116653229885653), (3.0027328050516493, 0.6695430795843016), (5.258167740541786, 1.1524381034989306), (4.86435124286598, 1.5676690324824722)]

等等。

可视化

可视化


如果对函数使用lambda并返回列表f=lambda n:[(x,x+2*m.acos(r()))for x in(2*m.pi*r()for _ in range(n))]
推导

啊,我原来有一个lambda。我想我没有想过将列表理解力提高一倍。谢谢!@Blckknght
扎克·盖茨

通过修改导入可以缩短为109字节:tio.run/#python2
Triggernometry '18

1

Perl,60岁

#!perl -l
use Math::Trig;print$x=2*pi*rand,$",$x+2*acos rand for 1..<>

1

R,60 56 53 49字节

由于@JayCe并将其更改为函数,因此额外增加了4个字节。

使用与其他相同的基本公式。R默认情况下使用Mersenne-Twister方法,但可以设置其他方法。输出以空格分隔的列表。

function(n,y=acos(runif(n)))runif(n)*2*pi+c(y,-y)

在线尝试!


嗨,Micky,您可以通过使其成为函数而不定义x 来保存4个字节
JayCe '18年

@JayCe更好的感谢
MickyT

0

SmileBASIC,62个字节

INPUT N
FOR I=1TO N
X=PI()*2*RNDF()Y=ACOS(RNDF())?X+Y,X-Y
NEXT
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.