Випадковий гольф дня №4: парадокс Бертрана


19

Про серію

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

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

Отвір 4: Парадокс Бертрана

Бертрана парадокс цікава проблема, яка показує , як різні методи для вибору випадкових акордів в колі може дати різні розподілу акордів, їх центрів і їх довжину.

У цьому виклику ви повинні генерувати випадкові акорди одиничного кола, використовуючи метод "правильний", тобто такий, який виробляє розподіл акордів, інваріантних під час масштабування та перекладу. У пов'язаній статті Вікіпедії "Метод 2" є таким методом.

Ось точні правила:

  • Ви повинні взяти одне ціле число,N яке визначає, скільки акордів потрібно повернути. Вихід повинен бути списком Nакордів, кожен із яких вказаний як дві точки на одиничному колі, заданий їх полярним кутом у радіанах.
  • Ваш код повинен мати можливість повернути щонайменше 2 20 різних значень для кожного з двох кутів . Якщо наявний RNG має менший діапазон, вам слід спочатку створити RNG з достатньо великим діапазоном поверх вбудованого, або ви повинні реалізувати свій власний відповідний RNG . Ця сторінка може бути корисною для цього.
  • Розподіл акордів повинен бути невідмінним від того, який виробляється "Методом 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

(Мова наразі не відображається, але фрагмент вимагає і аналізує його, і я можу в майбутньому додати таблицю лідерів за мовою.)

Відповіді:


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

Він використовує (трохи модифікований) алгоритм, який описала Wikipedia. У псевдокоді:

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; однак негативна частина симетрична з позитивною - негативні числа дають акорди, розмір яких більший за пі радіанів, але для ілюстрації парадокса Бертрана це не має значення.

Оскільки набір інструкцій 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, а потім робить це знову для відновлення початкового значення. jsІнструкція повторює цикл , коли ecxнегативний (це відбувається рівно один раз).


Мені подобається ця відповідь за використання збірки 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

Виріжте байт, змінивши програму для використання складок + сум і встановлення J на ​​кортеж, видаливши K.

Оригінал:

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

Відріжте 2 байти завдяки @orlp.

Пояснення:

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

1
Я рахую 25 байт ...
Малтісен

Крім того, ви можете краще відформатувати результат,+JK-JK
Maltysen

@Maltysen Обидва виправлені. Спасибі!
kirbyfan64sos

@orlp Я не мав поняття yі забув Z. Виправлено; Спасибі!
kirbyfan64sos

6

Джулія, 48 байт

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

Для цього використовується алгоритм методу 2, як і більшість відповідей поки що. Він створює функцію лямбда, яка приймає цілий вхід і повертає 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).

Circle


5

Піт, 22 байти

Порт відповіді С ++. У мене було ще 23-байтне рішення (зараз 22!), Але це була майже копія піт-відповіді @ kirbyfan64sos з оптимізаціями, тому мені довелося трохи подумати і творчо (ab) використовувати оператор fold.

m,-Fdsdm,y*.nZOZ.tOZ4Q

Зауважте, що це не працює зараз через помилку в операторі складки після введення 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 дерп. Вибачте;)
Мальтісен

4

IDL, 65 байт

Очевидно, що це той самий алгоритм, що і @rcrmn, навіть якщо я вивів його самостійно, перш ніж прочитати їх відповідь.

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

Функція randomu IDL використовує Mersenne Twister, який має період 2 19937 -1.

EDIT: Я пробіг 1000 акордів через візуалізатор вище, ось скріншот результату:

IDL bertrand


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<<';';}}

Тож це пряма реалізація правильного алгоритму зі сторінки вікіпедії. Основна проблема тут у гольфі - це настільки вигадливі назви, які мають випадкові класи генераторів. Але, на відміну від хорошого ol 'rand, він принаймні належно рівномірний.

Пояснення:

#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

Так, цілком правильно, збираюся це виправити зараз! Дуже дякую!
rorlork

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. Використовуйте як лямбда-вираз.

Приклад використання


Ви не можете зменшити розмір, зберігаючи Mathдесь? Або щось? (Я не програміст Java)
Ісмаель Мігель

@IsmaelMiguel Це коштувало б додаткових 2 символів.
TheNumberOne

Вибачте: / Заманливо намагатися зменшити кількість Mathпоказів. Що мета говорить про використання коду для створення іншого коду для вирішення проблеми?
Ісмаїл Мігель

2
@IsmaelMiguel Це чесна гра, хоча я буду здивований, якщо ти насправді кращий у метагольфінгу, ніж у гольфі.
Мартін Ендер

3

Рубін, 72 байти

Мій перший гольф тут! Я використовував той самий код, що і всі, я сподіваюся, що це нормально

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

2

Ява, 115 123

Це в основному те саме, що і більшість інших, але мені потрібна оцінка 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();}

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

Оновлення : 2 байти збережено завдяки Мартіну!

Спробуйте тут


1

Пітон 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)]

І так далі.

Візуалізація

візуалізація


Ви можете зекономити близько 20 байтів, якщо використовувати функцію лямбда та повернути розуміння списку (із внутрішнім виразом генератора):f=lambda n:[(x,x+2*m.acos(r()))for x in(2*m.pi*r()for _ in range(n))]
Blckknght

Ах, спочатку я мав лямбду. Я думаю, я не думав про подвоєння в розумінні списку. Спасибі! @Blckknght
Zach Gates

Можна скоротити до 109 байт, поповнившись з імпортом: tio.run/#python2
Triggernometry


1

R, 60 56 53 49 байт

Ще 4 байти завдяки @JayCe і змінивши його на функцію.

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

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

Спробуйте в Інтернеті!


Привіт Міккі, ви можете зберегти 4 байти , зробивши його функцією, а не визначивши x.
JayCe

@JayCe, це набагато краще спасибі
MickyT

0

SmileBASIC, 62 байти

INPUT N
FOR I=1TO N
X=PI()*2*RNDF()Y=ACOS(RNDF())?X+Y,X-Y
NEXT
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.