高尔夫最小的一圈!


29

问题:

给定笛卡尔平面中的一组非空点,请找到将它们全部包围的最小圆(Wikipedia链接)。

如果点的数量为三个或更少(如果有一个点,则圆的半径为零;如果有两个点,则连接点的线段为圆的直径;如果有),则此问题微不足道。 3个(非共线的)点,如果它们形成一个非钝角三角形,则可以得到一个全部接触它们的圆的方程;如果该三角形是钝角,则可以得到仅接触两个点并包含第三个点的圆的方程)。因此,为了应对这一挑战,积分的数量应大于三。

挑战:

  • 输入: 4个或更多非共线点的列表。这些点应具有X和Y坐标;坐标可以是浮点数。为了缓解挑战,任何两个点都不应共享相同的X坐标。
    例如:[(0,0), (2,1), (5,3), (-1,-1)]
  • 输出:值的元组(h,k,r),使得(xh)2+(yk)2=r2是包围所有点的最小圆的方程。

规则:

  • 您可以选择适合您程序的任何输入法。
  • 输出应打印到STDOUT函数或由函数返回。
  • 首选“普通”,通用语言,但任何esolang都是可以接受的。
  • 您可以假定这些点不是共线的。
  • 这是代码高尔夫球,因此以字节为单位的最小程序为准。挑战赛发布一周后将选出优胜者。
    • 请在回答的第一行中包括您使用的语言和以字节为单位的长度作为标题: # Language: n bytes

测试用例:

  • 1:
    • 输入: [(-8,0), (3,1), (-6.2,-8), (3,9.5)]
    • 输出: [-1.6, 0.75, 9.89]
  • 2:
    • 输入: [(7.1,-6.9), (-7,-9), (5,10), (-9.5,-8)]
    • 输出: [-1.73, 0.58, 11.58]
  • 3:
    • 输入: [(0,0), (1,2), (3,-4), (4,-5), (10,-10)]
    • 输出: [5.5, -4, 7.5]
  • 4:
    • 输入: [(6,6), (-6,7), (-7,-6), (6,-8)]
    • 输出: [0, -0.5, 9.60]

打高尔夫球快乐!


相关挑战:



8
“如果存在三个(非共线的)点,则可以得到一个将它们全部接触的圆的方程” –但这可能不是最小的封闭圆。对于钝角三角形的三个顶点,最小的包围圆是直径最大的一侧。
Anders Kaseorg

2
@Arnauld和你一样。对于测试用例2,我得到中心(-1.73,0.58),对于测试用例3(5.5,-4)。
罗宾·赖德

@Arnauld感谢您的评论。我已经相应地编辑了该帖子
Barranka

@Arnauld哎呀,对不起。确实。奥尔多(Aldo),请根据您的观察意见进行更正
巴兰卡(Barranka)

Answers:


26

Wolfram语言(Mathematica)28 27字节

#~BoundingRegion~"MinDisk"&

在线尝试!

内置插件在这里很方便。输出是具有中心和半径的磁盘对象。像其他人一样,我发现第二和第三测试用例与问题不同。

感谢@lirtosiast节省了一个字节!

如果需要列表作为输出,则可以用35个字节来完成(以增加8个字节为代价)。感谢@Roman指出这一点。


3
我期待内置Mathematica。但是我没想到Mathematica会有“磁盘对象”。
罗宾·赖德

36个字节以正确获取输出格式:Append@@BoundingRegion[#,"MinDisk"]&
Roman

20

JavaScript(ES6), 298 ... 243242 字节

返回一个数组[x, y, r]

p=>p.map(m=([c,C])=>p.map(([b,B])=>p.map(([a,A])=>p.some(([x,y])=>H(x-X,y-Y)>r,F=s=>Y=(d?((a*a+A*A-q)*j+(b*b+B*B-q)*k)/d:s)/2,d=c*(A-B)+a*(j=B-C)+b*(k=C-A),r=H(a-F(a+b),A-F(A+B,X=Y,j=c-b,k=a-c)))|r>m?0:o=[X,Y,m=r]),q=c*c+C*C),H=Math.hypot)&&o

在线尝试!

查看格式化的版本

怎么样?

方法

对于每对点(A,B),我们生成直径为A B的圆(X,Y,r)AB

X=Ax+Bx2,Y=Ay+By2,r=(AxBx2)2+(AyBy2)2

对于每三个不同的点(A,B,C),我们生成一个外接三角形A B C的圆(X,Y,r)ABC

d=Ax(ByCy)+Bx(CyAy)+Cx(AyBy)
X=(Ax2+Ay2)(ByCy)+(Bx2+By2)(CyAy)+(Cx2+Cy2)(AyBy)2d
Y=(Ax2+Ay2)(CxBx)+(Bx2+By2)(AxCx)+(Cx2+Cy2)(BxAx)2d
r=(XAx)2+(YAy)2

对于每个生成的圆,我们测试每个点(x,y)是否包含在其中:

(xX)2+(yY)2r

我们最终返回最小的有效圆。

实作

(X,Y)d0q=Cx2+Cy2

X=(Ax2+Ay2q)(ByCy)+(Bx2+By2q)(CyAy)2d
Y=(Ax2+Ay2q)(CxBx)+(Bx2+By2q)(AxCx)2d

F(j,k)

  • (ByCy,CyAy)X
  • (CxBx,AxCx)Y

Fs(X,Y)d=0


也许编写牛顿型数值优化器的时间更短...
qwr

您有正确性证明吗?我可以看到这是一个可行的方法,但是似乎还需要做大量的工作。
彼得·泰勒

3
@PeterTaylor Wikipedia上提到了该算法:天真的算法通过测试由所有成对点和三重点确定的圆来解决时间O(n ^ 4)的问题。但不幸的是,没有证据的链接。
Arnauld

是否会遇到无法解决的精度问题?
l4m2

1
@Arnauld如果您需要一些奇怪的数字,我可以说很好。如果它甚至在轻松的情况下失败,也可能是一个问题
l4m2

14

R,59个字节

function(x)nlm(function(y)max(Mod(x-y%*%c(1,1i))),0:1)[1:2]

在线尝试!

将输入作为复杂坐标的向量。Mod是复平面上的距离(模数),nlm是一种优化函数:它找到中心(输出为estimate)的位置,该位置使到输入点的最大距离最小,并给出相应的距离(输出为minimum),即半径。精确到3-6位数字;TIO页脚将输出舍入为2位数。

nlm将数字向量作为输入:y%*%c(1,1i)业务将其转换为复数。


9

果冻100 98字节

_²§½
1ịṭƊZIṚṙ€1N1¦,@ṭ@²§$µḢZḢ×Ø1œị$SḤ÷@P§
ZṀ+ṂHƲ€_@Ç+ḷʋ⁸,_²§½ʋḢ¥¥
œc3Ç€;ŒcZÆm,Hñ/$Ʋ€$ñ_ƭƒⱮṀ¥Ðḟ⁸ṚÞḢ

在线尝试!

我的Wolfram语言答案相反,Jelly需要很多代码来实现此目的(除非我遗漏了一些东西!)。该完整程序将点列表作为参数,并返回最小封闭圆的中心和半径。它为所有三个点的集合生成外接圆,并为所有两个点的集合生成直径,检查包括所有点的半径,然后选择半径最小的一个。

make_circumcircle位的代码受此站点上的代码启发,进而受维基百科的启发。


1
我不理解此代码,但是您可以生成三个重复的点的所有集合,并过滤掉三个相同点的列表,而不是分别用直径和外接圆来表示吗?
lirtosiast

2

APL(NARS),348个字符,696字节

f←{h←{0=k←⍺-1:,¨⍵⋄(k<0)∨k≥i←≢w←⍵:⍬⋄↑,/{w[⍵],¨k h w[(⍳i)∼⍳⍵]}¨⍳i-k}⋄1≥≡⍵:⍺h⍵⋄⍺h⊂¨⍵}
c←{⍵≡⍬:1⋄(x r)←⍵⋄(-r*2)++/2*⍨⍺-x}
p←{(b k)←⍺ ⍵⋄∧/¨1e¯13≥{{⍵{⍵c⍺}¨b}k[⍵]}¨⍳≢k}
s2←{(+/k),√+/↑2*⍨-/k←2÷⍨⍵}
s3←{0=d←2×-.×m←⊃{⍵,1}¨⍵:⍬⋄m[;1]←{+/2*⍨⍵}¨⍵⋄x←d÷⍨-.×m⋄m[;2]←{1⊃⍵}¨⍵⋄y←d÷⍨--.×m⋄(⊂x y),√+/2*⍨(x y)-1⊃⍵}
s←{v/⍨⍵p v←(s2¨2 f⍵)∪s3¨3 f⍵}
s1←{↑v/⍨sn=⌊/sn←{2⊃⍵}¨v←s⍵}

这是Arnauld解决方案中公式的一种“实现” ...结果和注释:

  s1 (¯8 0)(3 1)(¯6.2 ¯8)(3 9.5)
¯1.6 0.75  9.885469134 
  s1 (7.1 ¯6.9)(¯7 ¯9)(5 10)(¯9.5 ¯8)
¯1.732305109 0.5829680042  11.57602798 
  s1 (0 0)(1 2)(3 ¯4)(4 ¯5)(10 ¯10)
5.5 ¯4  7.5 
  s1 (6 6)(¯6 7)(¯7 ¯6)(6 ¯8)
0 ¯0.5  9.604686356 
  s1 (6 6)(¯6 7)(¯7 ¯6)(6 ¯8)(0 0)(1 2)(3 ¯4)(4 ¯5)(10 ¯10)
2 ¯1.5  11.67261753 
  s1 (6 6)(¯6 7)(¯7 ¯6)(6 ¯8)(1 2)(3 ¯4)(4 ¯5)(10 ¯10)(7.1 ¯6.9)(¯7 ¯9)(5 10)(¯9.5 ¯8)
1.006578947 ¯1.623355263  12.29023186 
  s1 (1 1)(2 2)(3 3)(4 4)
2.5 2.5  2.121320344 
  ⎕fmt s3 (1 1)(2 2)(3 3)(4 4)
┌0─┐
│ 0│
└~─┘

f:在欧米茄套装中找到阿尔法喷墨的组合

f←{h←{0=k←⍺-1:,¨⍵⋄(k<0)∨k≥i←≢w←⍵:⍬⋄↑,/{w[⍵],¨k h w[(⍳i)∼⍳⍵]}¨⍳i-k}⋄1≥≡⍵:⍺h⍵⋄⍺h⊂¨⍵}

((X,Y),r)从现在开始代表半径r和以(XY)为中心的一个圆周。

c:查找in中的点是否在in中的圆周((XY)r)内(结果<= 0)或在外部(结果> 0)在⍵中输入圆周的情况下以⍬作为输入,将在each中的每个可能的输入处返回1(超出圆周)。

c←{⍵≡⍬:1⋄(x r)←⍵⋄(-r*2)++/2*⍨⍺-x}

p:如果⍵是((XY)r)的数组;对于⍵中的每个((XY)r),如果数组all中的所有点都在((XY)r的内部),则写入1;否则写入0 NB 13 换句话说,在飞机上有限的点情况下(可能是故意建造的),并非100%的解决方案都得到了保险

p←{(b k)←⍺ ⍵⋄∧/¨1e¯13≥{{⍵{⍵c⍺}¨b}k[⍵]}¨⍳≢k}

s2:从2-中的2点开始,以((XY)r)格式返回圆周,其直径在那2点

s2←{(+/k),√+/↑2*⍨-/k←2÷⍨⍵}

s3:从3个点开始,通过这三个点返回格式((XY)r)的圆周如果存在问题(例如,点已对齐),它将失败并返回⍬。

s3←{0=d←2×-.×m←⊃{⍵,1}¨⍵:⍬⋄m[;1]←{+/2*⍨⍵}¨⍵⋄x←d÷⍨-.×m⋄m[;2]←{1⊃⍵}¨⍵⋄y←d÷⍨--.×m⋄(⊂x y),√+/2*⍨(x y)-1⊃⍵}

注意-。×找到矩阵nxn的行列式

  ⎕fmt ⊃{⍵,1}¨(¯8 0)(3 1)(¯6.2 ¯8)
┌3─────────┐     
3 ¯8    0 1│     |ax  ay 1|
│  3    1 1│   d=|bx  by 1|=ax(by-cy)-bx(ay-cy)+cx(ay-by)=ax(by-cy)+bx(cy-ay)+cx(ay-by)
│ ¯6.2 ¯8 1│     |cx  cy 1|
└~─────────┘

s:从in中的n个点中,找到由s2找到的圆周或包含所有n个点的s3类型的圆周。

s←{v/⍨⍵p v←(s2¨2 f⍵)∪s3¨3 f⍵}

s1:从上述s中找到的集合中计算出半径最小的那些,并返回半径最小的第一个。三个数字作为数组(第一个和第二个坐标是中心的坐标,而第三个是找到的圆周的半径)。

s1←{↑v/⍨sn=⌊/sn←{2⊃⍵}¨v←s⍵}

2

Python 2(PyPy)244242字节

P={complex(*p)for p in input()}
Z=9e999,
for p in P:
 for q in{p}^P:
	for r in{p}^P:R,S=1j*(p-q),q-r;C=S.imag and S.real/S.imag-1jor 1;c=(p+q-(S and(C*(p-r)).real/(C*R).real*R))/2;Z=min(Z,(max(abs(c-t)for t in P),c.imag,c.real))
print Z[::-1]

在线尝试!

它使用蛮力O(n ^ 4)算法,遍历每对点和三角形的点,计算中心,并保留需要最小半径的中心来包围所有点。它通过找到两个垂直平分线的交点来计算3个点的周围中心(或者,如果两个点相等,则使用中点和第三个点)。


欢迎来到PPCG!由于您使用的是Python 2,因此可以通过将第5行的两个空格转换为制表符来节省1个字节。
Stephen

P={x+y*1j for x,y in input()}也节省2个字节。
Xcoder先生

1

Python 212190字节

该解决方案是不正确的,我现在必须工作,所以我没有时间修复它。

a=eval(input())
b=lambda a,b: ((a[0]-b[0])**2+(a[1]-b[1])**2)**.5
c=0
d=1
for e in a:
    for f in a:
        g=b(e,f)
        if g>c:
            c=g
            d=[e,f]
print(((d[0][0]+d[1][0])/2,(d[0][1]+d[1][1])/2,c/2))

在线尝试!

我找出了最远的两个点,然后根据这些点生成了一个圆方程!

我知道这不是python中最短的,但是这是我能做的最好的!这也是我第一次尝试执行这些操作,因此请谅解!


2
这可以打更多。例如,您可以缩短if g>c:\n c=g\n d=[e,f]if g>c:c=g;d=[e,f],为您节省大量空白。我认为您不需要预先初始化d,也可以使用两个变量,E,F=e,f第10行会print缩短您的代码。我认为使用max和列表理解的解决方案甚至比两个循环还要短,但是我可能错了。可悲的是,您的解决方案不正确。对于(-1,0),(0,1.41),(0.5、0),(1,0)点,您的解决方案计算出一个围绕0且半径为1的圆。但是点(1,1.41)不在其中圈。
黑猫头鹰Kai

欢迎!谢谢您的回答。正如上面评论中指出的那样,您的解决方案不正确。强力解决方案的提示:最小的圆圈可以触摸两点或三点。您可以开始计算接触每对点的圆的方程,并检查是否有一个包含所有点的圆。然后计算与每个三元组点接触的圆的方程,并检查是否有一个包含所有点的圆。一旦获得所有可能的圆,请选择半径最小的圆。
Barranka

1
好吧,我将尝试使其工作,然后我将更新答案。我需要弄清楚如何检查圆中是否包含点。
Ben Morrison

确定圆的中心和半径后,检查中心与每个点之间的距离是否小于或等于半径;如果所有点都符合该条件,则该圆是一个候选者
Barranka
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.