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


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ให้ปฏิเสธเวกเตอร์และสร้างชุดตัวเลขใหม่ มิฉะนั้นถ้าl1ให้เวกเตอร์เป็นปกติและกลับผลลัพธ์
    • สร้างตัวเลขสุ่มสองตัวที่iและjจากการแจกแจงเครื่องแบบในช่วง(0,1)และแปลงให้เป็นพิกัดทรงกลมดังนี้:
      θ=2×π×iϕ=cos1(2×j1)
      เพื่อให้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,YN(0,1)และfY(y)=Ke-1fX(x)=Ke12x2fY(y)=Ke12y2, so by independence fXY(x,y)=K2e12(x2+y2)=fZ(z)=K2e12z2 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 l1. The only criterion for lx is that 0<x1. 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 v1 as v==1, instead of v<1. But thanks for the clarification that only 0<x1 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(cos1(z))=1z2.

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=1k2
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=siniunif(0,4),  junif(0,2)

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

t=ia(0)=iπ2,  p=a(j1)

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.