จุดสุ่มบนทรงกลม


31

ความท้าทาย

เขียนโปรแกรมหรือฟังก์ชั่นที่ไม่มีอินพุตและเอาต์พุตเวกเตอร์ที่มีความยาว1ในทิศทางสุ่มแบบตามหลักทฤษฏี

นี่เทียบเท่ากับจุดสุ่มบนทรงกลมที่อธิบายโดย

x2+Y2+Z2=1

ส่งผลให้เกิดการกระจายเช่นนี้

การกระจายคะแนนแบบสุ่มบนทรงกลมที่มีรัศมี 1

เอาท์พุต

ทุ่นลอยสามอันจากการแจกแจงแบบสุ่มในทางทฤษฎีซึ่งสมการx2+y2+z2=1มีค่าเป็นจริงถึงขีด จำกัด ที่แม่นยำ

คำพูดที่ท้าทาย

  • การกระจายแบบสุ่มจะต้องมีเครื่องแบบในทางทฤษฎี นั่นคือถ้าตัวสร้างตัวเลขสุ่มหลอกถูกแทนที่ด้วย RNG จริงจากตัวเลขจริงมันจะส่งผลให้มีการกระจายจุดสุ่มบนทรงกลมอย่างสม่ำเสมอ
  • การสร้างตัวเลขสุ่มสามตัวจากการแจกแจงเครื่องแบบและการทำให้เป็นมาตรฐานนั้นไม่ถูกต้อง: จะมีอคติต่อมุมของพื้นที่สามมิติ
  • ในทำนองเดียวกันการสร้างตัวเลขสุ่มสองตัวจากการแจกแจงเครื่องแบบและใช้พวกมันเป็นพิกัดทรงกลมนั้นไม่ถูกต้อง: จะมีอคติต่อขั้วของทรงกลม
  • ความสม่ำเสมอที่เหมาะสมสามารถทำได้โดยอัลกอริทึมรวมถึง แต่ไม่ จำกัด เพียง:
    • สร้างตัวเลขสุ่มสามตัวx , yและzจากการแจกแจงแบบปกติ (เกาส์เซียน) ประมาณ0และทำให้เป็นมาตรฐาน
    • สร้างสามตัวเลขสุ่มx , yและzจากเครื่องแบบกระจายอยู่ในช่วง(−1,1) ) คำนวณความยาวของเวคเตอร์โดยl=x2+y2+z2 2 จากนั้นถ้าl>1ให้ปฏิเสธเวกเตอร์และสร้างชุดตัวเลขใหม่ มิฉะนั้นถ้าl≤1ให้เวกเตอร์เป็นปกติและกลับผลลัพธ์
    • สร้างตัวเลขสุ่มสองตัวที่iและjจากการแจกแจงเครื่องแบบในช่วง(0,1)และแปลงให้เป็นพิกัดทรงกลมดังนี้:
      θ=2×π×iϕ=cos−1⁡(2×j−1)
      เพื่อให้x,yและzสามารถคำนวณได้โดย
      x=cos⁡(θ)×sin⁡(ϕ)y=sin⁡(θ)×sin⁡(ϕ)z=cos⁡(ϕ)
  • ระบุคำอธิบายสั้น ๆ ของอัลกอริทึมที่คุณใช้
  • อ่านเพิ่มเติมเกี่ยวกับการเลือกจุดทรงกลมในแม ธ เวิลด์

ตัวอย่างผลลัพธ์

[ 0.72422852 -0.58643067  0.36275628]
[-0.79158628 -0.17595886  0.58517488]
[-0.16428481 -0.90804027  0.38532243]
[ 0.61238768  0.75123833 -0.24621596]
[-0.81111161 -0.46269121  0.35779156]

ข้อสังเกตทั่วไป


การเลือก 3 reals อย่างสม่ำเสมอใน [-1, 1] แล้วปฏิเสธพวกเขา (และทำซ้ำ) ถ้าผลรวมของกำลังสองของพวกเขาไม่ใช่ 1?
— Grimmy

6
@Grimy ฉันชอบช่องโหว่นั้น ไม่ไม่ได้รับอนุญาตเนื่องจากมีโอกาสเป็นศูนย์ในทางทฤษฎีของผลลัพธ์
— Jitse

คำแนะนำ @ Grimy ไม่เหมือนกับการใช้งานตัวอย่างที่สองที่คุณพูดถึงหรือไม่ การแก้ปัญหานั้นยังไม่มีโอกาสที่จะสร้างผลลัพธ์ใด ๆ ในทางทฤษฎี
— ศศวัฒน์ปาดี

2
@SaswatPadhi ไม่ว่ามีโอกาสpi/6 ≈ 0.5236สร้างผลลัพธ์ นั่นคือพื้นที่ของทรงกลมที่ถูกจารึกไว้ในลูกบาศก์หน่วยพื้นที่
— หลุยส์เมนโด

1
@ LuisMendo ฉันเข้าใจแล้วใช่ไหม ความน่าจะเป็นคือ ~ 0.5 ในกรณีนี้ดังที่คุณกล่าวถึง สำหรับข้อเสนอของ Grimy มันคือ ~ 0
— Saswat Padhi

คำตอบ:



24

R , 23 ไบต์

x=rnorm(3)
x/(x%*%x)^.5

ลองออนไลน์!

สร้าง 3 การรับรู้ของN(0,1)แจกแจงและทำให้เวกเตอร์ผลลัพธ์เป็นปกติ

พล็อตของการรับรู้ 1000 รายการ:

ป้อนคำอธิบายรูปภาพที่นี่


2
คุณสามารถแสดงให้เห็นถึงการกระจาย 3 แกนตามปกติส่งผลให้เกิดการกระจายสม่ำเสมอทั่วทรงกลม? (ฉันไม่เห็น)
— Jeffrey สนับสนุน Monica

4
@ Jeffrey เป็นที่รู้จักกันดีในเรื่องความน่าจะเป็น / สถิติ แต่การพิสูจน์สำหรับ 2D (ซึ่งขยายอย่างประณีตถึง 3 มิติ) นั้นประมาณ: ให้และเป็นอิสระ จากนั้นf X ( x ) = K e - 1X,Y∼N(0,1)และfY(y)=Ke-1fX(x)=Ke−12x2fY(y)=Ke−12y2, so by independence fXY(x,y)=K2e−12(x2+y2)=fZ(z)=K2e−12‖z‖2 where z=(x,y), so it's clear that the distribution of z depends solely on the magnitude of z, and thus the direction is uniformly distributed.
— Giuseppe

1
So, the normal distribution gives us uniformly distributed points around the circle, and dividing by the magnitude ensures the points lie on the circle
— Giuseppe

23

x86-64 Machine Code - 63 62 55 49 bytes

6A 4F                push        4Fh  
68 00 00 80 3F       push        3F800000h  
C4 E2 79 18 4C 24 05 vbroadcastss xmm1,dword ptr [rsp+5]  
rand:
0F C7 F0             rdrand      eax  
73 FB                jnc         rand  
66 0F 6E C0          movd        xmm0,eax  
greaterThanOne:
66 0F 38 DC C0       aesenc      xmm0,xmm0  
0F 5B C0             cvtdq2ps    xmm0,xmm0  
0F 5E C1             divps       xmm0,xmm1  
C4 E3 79 40 D0 7F    vdpps       xmm2,xmm0,xmm0,7Fh  
0F 2F 14 24          comiss      xmm2,dword ptr [rsp]  
75 E9                jne         greaterThanOne
58                   pop         rax  
58                   pop         rax  
C3                   ret  

ใช้อัลกอริทึมที่สองปรับเปลี่ยน ส่งคืนเวกเตอร์ของ[x, y, z, 0]ใน xmm0

คำอธิบาย:

push 4Fh
push 3f800000h

ส่งค่าเป็น 1 และ 2 ^ 31 เป็นทุ่นไปยังสแต็ก ข้อมูลทับซ้อนกันเนื่องจากส่วนขยายสัญญาณช่วยประหยัดสองสามไบต์

vbroadcastss xmm1,dword ptr [rsp+5] โหลดค่าสำหรับ 2 ^ 31 เป็น 4 ตำแหน่งของ xmm1

rdrand      eax  
jnc         rand  
movd        xmm0,eax

สร้างจำนวนเต็มแบบสุ่ม 32 บิตและโหลดไปด้านล่างของ xmm0

aesenc      xmm0,xmm0  
cvtdq2ps    xmm0,xmm0  
divps       xmm0,xmm1 

สร้างจำนวนเต็มแบบสุ่ม 32 บิตแปลงเป็นทศนิยม (ลงนาม) และหารด้วย 2 ^ 31 เพื่อรับตัวเลขระหว่าง -1 ถึง 1

vdpps xmm2,xmm0,xmm0,7Fhเพิ่มกำลังสองของ 3 อันต่ำกว่าโดยใช้ผลิตภัณฑ์ดอทเองโดยปกปิดส่วนบนสุด นี่ให้ความยาว

comiss      xmm2,dword ptr [rsp]  
jne          rand+9h (07FF7A1DE1C9Eh)

เปรียบเทียบความยาวยกกำลังสองกับ 1 และปฏิเสธค่าหากไม่เท่ากับ 1 หากความยาวยกกำลังสองเท่ากับความยาวก็เท่ากับหนึ่ง นั่นหมายความว่าเวกเตอร์ได้รับการทำให้เป็นมาตรฐานแล้วและบันทึกสแควร์รูทและหาร

pop         rax  
pop         rax 

เรียกคืนสแต็ก

ret ส่งคืนค่าใน xmm0

Try it Online.


7
+1 Using aesenc to produce 128 "random" bits is just beautiful.
— DocMax

13

Python 2, 86 bytes

from random import*;R=random
z=R()*2-1
a=(1-z*z)**.5*1j**(4*R())
print a.real,a.imag,z

Try it online!

Generates the z-coordinate uniformly from -1 to 1. Then the x and y coordinates are sampled uniformly on a circle of radius (1-z*z)**.5.

It might not be obvious that the spherical distribution is in factor uniform over the z coordinate (and so over every coordinate). This is something special for dimension 3. See this proof that the surface area of a horizontal slice of a sphere is proportional to its height. Although slices near the equator have a bigger radius, slices near the pole are titled inward more, and it turns out these two effects exactly cancel.

To generate a random angle on this circle, we raise the imaginary unit 1j to a uniformly random power between 0 and 4, which saves us from needing trig functions, pi, or e, any of which would need an import. We then extract the real imaginary part. If we can output a complex number for two of the coordinates, the last line could just be print a,z.


86 bytes

from random import*
a,b,c=map(gauss,[0]*3,[1]*3)
R=(a*a+b*b+c*c)**.5
print a/R,b/R,c/R

Try it online!

Generates three normals and scales the result.


Python 2 with numpy, 57 bytes

from numpy import*
a=random.randn(3)
print a/sum(a*a)**.5

Try it online!

sum(a*a)**.5 is shorter than linalg.norm(a). We could also do dot(a,a) for the same length as sum(a*a). In Python 3, this can be shortened to a@a using the new operator @.


1
I like your first approach. I'm having trouble understanding how a bias towards the equator is avoided if z, from a uniform distribution, is left unmodified.
— Jitse

2
@Jitse The spherical distribution is in factor uniform over each coordinate. This is something special for dimension 3. See for instance this proof that the surface area of a slice of a sphere is proportional to its height. Regarding the intuition that this is biased to the equator, note that while slices near the equator have a bigger radius, ones near the pole are titled inward more which gives more area, and it turns out these two effects exactly cancel.
— xnor

Very nice! Thanks for the clarification and the reference.
— Jitse

@Jitse Thanks, I added it to the body. I realized though that I was only sampling positive z though, and fixed that for a few bytes.
— xnor

1
@Jitse Indeed, the surface area of a sphere equals the lateral surface area of the enclosing cylinder!
— Neil


10

Unity C#, 34 bytes

f=>UnityEngine.Random.onUnitSphere

Unity has a builtin for unit sphere random values, so I thought I'd post it.


Good use of a built in +1, You could just submit a function to be a bit shorter f=>Random.onUnitSphere
— LiefdeWen

@LiefdeWen I knew about lambdas, I just wasn't sure if that was enough (in terms of validity on Code Golf) because it isn't declaring f's Type; using var only works inside a method and System.Func<Vector3> was longer.
— Draco18s

1
In codegolf returning a function is perfectly fine, and you don't have to count the declaration either meaning you can do sneaky things with dynamic parameters. You also don't count the last semi-colon. You do however count all using statements you add. so your byte count needs to include the using. But f=>Random.onUnitSphere is a perfectly valid submission
— LiefdeWen

@LiefdeWen Yeah, I just wasn't sure how the declaration was handled and wasn't really feeling up to "searching meta."
— Draco18s

f=>UnityEngine.Random.onUnitSphere saves you the using
— Orace

6

MATL, 10 bytes

1&3Xrt2&|/

Try it online!

Explanation

This uses the first approach described in the challenge.

1&3Xr  % Generate a 1×3 vector of i.i.d standard Gaussian variables
t      % Duplicate
2&|    % Compute the 2-norm
/      % Divide, element-wise. Implicitly display

6

Ruby, 34 50 49 bytes

->{[z=rand*2-1]+((1-z*z)**0.5*1i**(rand*4)).rect}

Try it online!

Returns an array of 3 numbers [z,y,x].

x and y are generated by raising i (square root of -1) to a random power between 0 and 4. This complex number needs to be scaled appropriately according to the z value in accordance with Pythagoras theorem: (x**2 + y**2) + z**2 = 1.

The z coordinate (which is generated first) is simply a uniformly distributed number between -1 and 1. Though not immediately obvious, dA/dz for a slice through a sphere is constant (and equal to the perimeter of a circle of the same radius as the whole sphere.) .

This was apparently discovered by Archimedes who described it in a very non-calculus-like way, and it is known as Archimedes Hat-Box theorem. See https://brilliant.org/wiki/surface-area-sphere/

Another reference from comments on xnor's answer. A surprisingly short URL, describing a surprisingly simple formula: http://mathworld.wolfram.com/Zone.html


@Jitse I forgot to scale back the x and y at high values of z. Effectively the points defined a cylinder. It's fixed now but it cost a lot of bytes! I could save a few if the the output can be expressed with a complex number [z, x+yi] I'll leave it as it is unless you say that's OK.
— Level River St

Looks good! I really like this approach. For consistency, the required output is three floats, so I suggest leaving it like this.
— Jitse

Why not use z*z instead of z**2?
— Value Ink

@ValueInk yeah thanks I realised I'd missed that z*z. I've edited it in now. The other thing I could do is replace rand*4 with something like z*99 or x*9E9 (effectively limiting the possible values to a very fine spiral on the sphere) but I think that reduces the quality of the random.
— Level River St

4

05AB1E, 23 22 bytes

[тε5°x<Ýs/<Ω}DnOtDî#}/

Implements the 2nd algorithm.

Try it online or get a few more random outputs.

Explanation:

NOTE: 05AB1E doesn't have a builtin to get a random decimal value in the range [0,1). Instead, I create a list in increments of 0.00001, and pick random values from that list. This increment could be changed to 0.000000001 by changing the 5 to 9 in the code (although it would become rather slow..).

[            # Start an infinite loop:
 тε          #  Push 100, and map (basically, create a list with 3 values):
   5°        #   Push 100,000 (10**5)
     x       #   Double it to 200,000 (without popping)
      <      #   Decrease it by 1 to 199,999
       Ý     #   Create a list in the range [0, 199,999]
        s/   #   Swap to get 100,000 again, and divide each value in the list by this
          <  #   And then decrease by 1 to change the range [0,2) to [-1,1)
           Ω #   And pop and push a random value from this list
  }          #  After the map, we have our three random values
   D         #   Duplicate this list
    n        #   Square each inner value
     O       #   Take the sum of these squares
      t      #   Take the square-root of that
       D     #   Duplicate that as well
        î    #   Ceil it, and if it's now exactly 1:
         #   #    Stop the infinite loop
}/           # After the infinite loop: normalize by dividing
             # (after which the result is output implicitly)

1
Using l<1 is equally as valid as l≤1. The only criterion for l≤x is that 0<x≤1. You may just as well accept vectors with l<0.5 if it would save any bytes. Any value smaller than or equal to 1 removes the bias.
— Jitse

@Jitse Ok, implemented the normalization in both my Java and 05AB1E answers. I hope everything is correct now.
— Kevin Cruijssen

@Jitse Actually saved a byte by checking v≤1 as ⌈v⌉==1, instead of v<1. But thanks for the clarification that only 0<x≤1 is a requirement, and there isn't a strict ≤ requirement on l, as long as it's ≤1.
— Kevin Cruijssen

4

TI-BASIC, 15 bytes *

:randNorm(0,1,3
:Ans/√(sum(Ans²

Using the algorithm "generate 3 normally distributed values and normalize that vector".

Ending a program with an expression automatically prints the result on the Homescreen after the program terminates, so the result is actually shown, not just generated and blackholed.

*: randNorm( is a two-byte token, the rest are one-byte tokens. I've counted the initial (unavoidable) :, without that it would be 14 bytes. Saved as a program with a one-letter name, it takes 24 bytes of memory, which includes the 9 bytes of file-system overhead.


3

JavaScript (ES7),  77 76  75 bytes

Implements the 3rd algorithm, using sin⁡(ϕ)=sin⁡(cos−1⁡(z))=1−z2.

with(Math)f=_=>[z=2*(r=random)()-1,cos(t=2*PI*r(q=(1-z*z)**.5))*q,sin(t)*q]

Try it online!

Commented

with(Math)                       // use Math
f = _ =>                         //
  [ z = 2 * (r = random)() - 1,  // z = 2 * j - 1
    cos(                         //
      t =                        // θ =
        2 * PI *                 //   2 * π * i
        r(q = (1 - z * z) ** .5) // q = sin(ɸ) = sin(arccos(z)) = √(1 - z²)
                                 // NB: it is safe to compute q here because
                                 //     Math.random ignores its parameter(s)
    ) * q,                       // x = cos(θ) * sin(ɸ)
    sin(t) * q                   // y = sin(θ) * sin(ɸ)
  ]                              //

JavaScript (ES6), 79 bytes

Implements the 2nd algorithm.

f=_=>(n=Math.hypot(...v=[0,0,0].map(_=>Math.random()*2-1)))>1?f():v.map(x=>x/n)

Try it online!

Commented

f = _ =>                         // f is a recursive function taking no parameter
  ( n = Math.hypot(...           // n is the Euclidean norm of
      v =                        // the vector v consisting of:
        [0, 0, 0].map(_ =>       //
          Math.random() * 2 - 1  //   3 uniform random values in [-1, 1]
        )                        //
  )) > 1 ?                       // if n is greater than 1:
    f()                          //   try again until it's not
  :                              // else:
    v.map(x => x / n)            //   return the normalized vector

3

Processing 26 bytes

Full program

print(PVector.random3D());

This is the implementation https://github.com/processing/processing/blob/master/core/src/processing/core/PVector.java

  static public PVector random3D(PVector target, PApplet parent) {
    float angle;
    float vz;
    if (parent == null) {
      angle = (float) (Math.random()*Math.PI*2);
      vz    = (float) (Math.random()*2-1);
    } else {
      angle = parent.random(PConstants.TWO_PI);
      vz    = parent.random(-1,1);
    }
    float vx = (float) (Math.sqrt(1-vz*vz)*Math.cos(angle));
    float vy = (float) (Math.sqrt(1-vz*vz)*Math.sin(angle));
    if (target == null) {
      target = new PVector(vx, vy, vz);
      //target.normalize(); // Should be unnecessary
    } else {
      target.set(vx,vy,vz);
    }
    return target;
  }

2
You might want to make it clearer that the implementation is not part of your byte count. I missed it on first reading, then did a double-take.
— Level River St

I like that the implementation uses essentially the same approach as me, though
— Level River St

2

Python 2, 86 bytes

from random import*
x,y,z=map(gauss,[0]*3,[1]*3);l=(x*x+y*y+z*z)**.5
print x/l,y/l,z/l

Try it online!

Implements the first algorithm.


Python 2, 107 103 bytes

from random import*
l=2
while l>1:x,y,z=map(uniform,[-1]*3,[1]*3);l=(x*x+y*y+z*z)**.5
print x/l,y/l,z/l

Try it online!

Implements the second algorithm.


2
@RobinRyder This implementation rejects vectors with an initial length >1, which is valid as specified in the challenge.
— Jitse

@Jitse Right, sorry. I misread the code.
— Robin Ryder

2

Haskell, 125 123 119 118 bytes

import System.Random
f=mapM(\_->randomRIO(-1,1))"lol">>= \a->last$f:[pure$(/n)<$>a|n<-[sqrt.sum$map(^2)a::Double],n<1]

Try it online!

Does three uniforms randoms and rejection sampling.


It looks like your randoms are from the distribution (0,1) instead of (-1,1), so that only 1/8 of the sphere is covered.
— Jitse

@Jitse gotcha, thanks for noticing.
— Angs

2

JavaScript, 95 bytes

f=(a=[x,y,z]=[0,0,0].map(e=>Math.random()*2-1))=>(s=Math.sqrt(x*x+y*y+z*z))>1?f():a.map(e=>e/s)

You don't need not to input a.


Wow, I completely missed that. Fixed.
— Naruyoko

2

Julia 1.0, 24 bytes

x=randn(3)
x/hypot(x...)

Try it online!

Draws a vector of 3 values, drawn from a normal distribution around 0 with standard deviation 1. Then just normalizes them.


randn(), from a couple of quick tests, doesn't seem to be bound to the required range. Also, this doesn't include a check for hypot() returning a value >1, which should be rejected.
— Shaggy

3
@Shaggy it would appear randn simulates from a standard normal distribution rather than a uniform(0,1) one, so this approach is identical to the R one.
— Giuseppe

@Giuseppe Yes, exactly!
— user3263164

@Giuseppe, I think that I may not have a proper grasp on the maths behind this challenge but, if I'm understanding you correctly, you're saying that if any of the floats are outside the bounds of [-1,1) then dividing by them by the hypotenuse, which will be >1, offsets that? That leads me to wonder if the ternary in my solution is necessary ...
— Shaggy

@Shaggy no, the normal/Gaussian distribution has some properties (specifically, rotational invariance) that the uniform doesn't have, see this comment, for example
— Giuseppe

2

MathGolf, 21 19 18 bytes

{╘3Ƀ∞(ß_²Σ√_1>}▲/

Implementation of the 2nd algorithm.

Try it online or see a few more outputs at the same time.

Explanation:

{              }▲   # Do-while true by popping the value:
 ╘                  #  Discard everything on the stack to clean up previous iterations
  3É                #  Loop 3 times, executing the following three operations:
    ƒ               #   Push a random value in the range [0,1]
     ∞              #   Double it to make the range [0,2]
      (             #   Decrease it by 1 to make the range [-1,1]
       ß            #  Wrap these three values into a list
        _           #  Duplicate the list of random values
         ²          #  Square each value in the list
          Σ         #  Sum them
           √        #  And take the square-root of that
            _       #  Duplicate it as well
             1>     #  And check if it's larger than 1
                 /  # After the do-while, divide to normalize
                    # (after which the entire stack joined together is output implicitly,
                    #  which is why we need the `╘` to cleanup after every iteration)

2

Java 8 (@Arnauld's modified 3rd algorithm), 131 126 119 111 109 bytes

v->{double k=2*M.random()-1,t=M.sqrt(1-k*k),r[]={k,M.cos(k=2*M.PI*M.random())*t,M.sin(k)*t};return r;}

Port of @Arnauld's JavaScript answer, so make sure to upvote him!
-2 bytes thanks to @OlivierGrégoire.

This is implemented as:

k=N∩[−1,1)
t=1−k2
u=2π×(N∩[0,1))
x,y,z={k,cos⁡(u)×t,sin⁡(u)×t}

Try it online.

Previous 3rd algorithm implementation (131 126 119 bytes):

Math M;v->{double k=2*M.random()-1,t=2*M.PI*M.random();return k+","+M.cos(t)*M.sin(k=M.acos(k))+","+M.sin(t)*M.sin(k);}

Implemented as:

k=N∩[−1,1)
t=2π×(N∩[0,1))
x,y,z={k,cos⁡(t)×sin⁡(arccos⁡(k)),sin⁡(t)×sin⁡(arccos⁡(k))}

Try it online.

Explanation:

Math M;                         // Math on class-level to use for static calls to save bytes
v->{                            // Method with empty unused parameter & double-array return
  double k=2*M.random()-1,      //  Get a random value in the range [-1,1)
         t=M.sqrt(1-k*k),       //  Calculate the square-root of 1-k^2
    r[]={                       //  Create the result-array, containing:
         k,                     //   X: the random value `k`
         M.cos(k=2*M.PI         //   Y: first change `k` to TAU (2*PI)
                     *M.random()//       multiplied by a random [0,1) value
                )               //      Take the cosine of that
                 *t,            //      and multiply it by `t`
         M.sin(k)               //   Z: Also take the sine of the new `k` (TAU * random)
                  *t};          //      And multiply it by `t` as well
  return r;}                    //  Return this array as result

Java 8 (2nd algorithm), 153 143 bytes

v->{double x=2,y=2,z=2,l;for(;(l=Math.sqrt(x*x+y*y+z*z))>1;y=m(),z=m())x=m();return x/l+","+y/l+","+z/l;};double m(){return Math.random()*2-1;}

Try it online.

2nd algorithm:

v->{                              // Method with empty unused parameter & String return-type
  double x=2,y=2,z=2,l;           //  Start results a,b,c all at 2
  for(;(l=Math.sqrt(x*x+y*y+z*z)) //  Loop as long as the hypotenuse of x,y,z
       >1;                        //  is larger than 1
    y=m(),z=m())x=m();            //   Calculate a new x, y, and z
  return x/l+","+y/l+","+z/l;}    //  And return the normalized x,y,z as result
double m(){                       // Separated method to reduce bytes, which will:
  return Math.random()*2-1;}      //  Return a random value in the range [-1,1)

Using sqrt(1-k*k) actually saves more bytes in Java than it does in JS. :)
— Arnauld

@Arnauld Yep. Instead of 3x M.sin, 1x M.cos and 1x M.acos, your approach uses 2x M.sin and 1x M.sqrt, which is where the additional saved bytes mostly come from. :)
— Kevin Cruijssen

108 bytes Uses a modified 2nd algorithm where I only allow values where s == 1 (instead of s<=1 and then normalizing). It sometimes gives an answer but mostly doesn't because of the timeout. Edit: Oops, I forgot to Math.sqrt the result
— Olivier Grégoire

Actually, no, no need to sqrt because sqrt(1)==1. So I stand with my golf suggestion.
— Olivier Grégoire

1
109 bytes (you can use your string output instead of double[] as that doesn't change the byte-count.)
— Olivier Grégoire

1

Japt, 20 bytes

Port of Arnauld's implementation of the 2nd algorithm.

MhV=3ÆMrJ1
>1?ß:V®/U

Test it

MhV=3ÆMrJ1
Mh             :Get the hypotenuse of
  V=           :  Assign to V
    3Æ         :  Map the range [0,3)
      Mr       :    Random float
        J1     :    In range [-1,1)
>1?ß:V®/U      :Assign result to U
>1?            :If U is greater than 1
   ß           :  Run the programme again
    :V®/U      :Else map V, dividing all elements by U

1

Pyth, 24 bytes

W<1Ks^R2JmtO2.0 3;cR@K2J

Try it online!

Uses algorithm #2

W                         # while 
 <1                       #   1 < 
   Ks                     #       K := sum(
     ^R2                  #               map(lambda x:x**2,
        Jm      3         #                    J := map(                            , range(3))
          tO2.0           #                             lambda x: random(0, 2.0) - 1           )):
                 ;        #   pass
                   R   J  # [return] map(lambda x:            , J)
                  c @K2   #                        x / sqrt(K)

1

OCaml, 110 99 95 bytes

(fun f a c s->let t,p=f 4.*.a 0.,a(f 2.-.1.)in[c t*.s p;s t*.s p;c p])Random.float acos cos sin

EDIT: Shaved off some bytes by inlining i and j, replacing the first let ... in with a fun, and taking advantage of operator associativity to avoid some parens ().

Try it online


Original solution:

Random.(let a,c,s,i,j=acos,cos,sin,float 4.,float 2. in let t,p=i*.(a 0.),a (j-.1.) in[c t*.s p;s t*.s p;c p])

First I define:

a=arccos,  c=cos,  s=sini∼unif(0,4),  j∼unif(0,2)

OCaml's Random.float function includes the bounds. Then,

t=i⋅a(0)=iπ2,  p=a(j−1)

This is very similar to the 3rd example implementation (with ϕ=p and θ=t) − except that I pick i and j within larger intervals to avoid multiplication (with 2) later on.


1
I'm not quite familiar with this language, but it looks like you use the random floats between 0 and 1 directly as spherical coordinates. This is incorrect, as shown in the challenge remarks 3 and 4, since you end up with a bias towards the poles of the sphere. You can correct this by applying the method shown in remark 4.
— Jitse

Thank you! Totally missed that. Fixed the bug and updated my answer
— Saswat Padhi

1
Looks good! Very nice first answer!
— Jitse

Thank you :) I was able to reduce it to under-100 bytes!
— Saswat Padhi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.