Calcular o ponto de Fermat de um triângulo

13

Isso é um pouco semelhante aos centros de um triângulo , mas com um ponto diferente. O ponto de Fermat é o ponto P no triângulo ABC, de modo que o valor de AP + BP + CP seja minimizado. Existem dois casos:

Se houver um ângulo maior que 120 graus, esse vértice é o ponto de fermat. Caso contrário, desenhe triângulos equilaterais em cada um dos lados do ABC. Conecte o vértice distante de cada triângulo equilátero ao vértice oposto do triângulo ABC. Fazer isso para cada um dos três triângulos equilaterais resulta em um único ponto comum de interseção para todas as três linhas, que é o ponto Fermat.

Ele deve ser executado dentro de 5 segundos em uma máquina razoável.

Entrada : um conjunto de 3 pontos, não necessariamente números inteiros. Isso pode ser tomado como uma matriz aninhada, sequência de caracteres, lista de tuplas etc. (o que for mais adequado ao seu idioma).

Saída : As coordenadas do ponto Fermat, novamente, no entanto, seu idioma lida melhor com os pontos. Imprecisões em pontos flutuantes não serão contadas contra você.

Casos de teste :

[[1, 1], [2, 2], [1, 2]] --> [1.2113248654051871, 1.788675134594813]
[[-1, -1], [-2, -1], [0, 0]] --> [-1, -1]
[[-1, -1], [1, -1], [0, 1]] --> [0, -0.42264973081037427]
[[0, 0], [0.5, 0.8660254037844386], [-5, 0]] --> [0, 0]
[[0, 0], [0, -5], [-0.8660254037844386, 0.5]] --> [0, 0]

Este é o código de golfe, então o código mais curto vence!

soktinpk
fonte
1
Tudo bem tentar todos os pontos em incrementos de precisão de ponto flutuante e selecionar aquele que minimiza a distância total?
Xnor 10/05
1
@xnor Se você puder fazê-lo dentro de 5 segundos.
Soktinpk
Até quantos números significativos a saída deve ser precisa? Além disso, está tudo bem se -0.0é produzido no lugar de alguns 0.0s?
R. Kap 15/05
@R. Kap, eu diria cerca de 5 ou 6 números significativos. Não há muito que erros de arredondamento sejam um problema. Quanto à segunda pergunta, isso parece bom.
Soktinpk

Respostas:

3

Haskell, 346 291 285 bytes

infixl 5£
z=zipWith
(?)=z(-)
t[a,b]=[-b,a]
a¤b=sum$z(*)a b
a%b=t a¤b
r a b c=[c%b/a%b,c%a/a%b]
x£y=2*x¤y<= -sqrt(x¤x*y¤y)
f[a,b,c]|a?b£c?b=b|a?c£b?c=c|b?a£c?a=a|[n,m,p,o]<-c?k a b c++a?k b c a=r[m,o][n,p][c%[n,m],a%[p,o]]
k a b c=map(/2)$z(+)a b?map(signum((b?a)%(c?a))*sqrt 3*)(t$b?a)

O mesmo código com algumas explicações

infixl 5£
z=zipWith

-- operator ? : difference of two vectors
(?)=z(-)            

-- function t : rotate a vector by +90 degrees
t[a,b]=[-b,a]       

-- operator ¤ : scalar product of two vectors ( a¤b = a0 * b0 + a1 * b1 )
a¤b=sum$z(*)a b     

-- operator % : "cross product" of two vectors ( a%b = a0 * b1 - a1 * b0 )
--      this returns actually the z coordinate of the 3d cross vector
--      other coordinates are nul since a and b are in the xy plan
a%b=t a¤b

-- function r : solves the system of two linear equations with two variables x0,x1 :
--      a0*x0 - b0*x1 = c0
--      a1*x0 - b1*x1 = c1
r a b c=[c%b/a%b,c%a/a%b]

-- operator £ : returns true if the angle between two vectors is >= 120 degrees
--      x¤y = ||x|| * ||y|| * cos(xyAngle)
--      so xyAngle>=120° is equivalent to : x¤y / (||x|| * ||y||) <= -0.5
x£y=2*x¤y<= -sqrt(x¤x*y¤y)

-- function k : takes 3 points A B C of a triangle and constructs the point C' 
--              of the equilateral triangle ABC' which is opposite to C:
--              C' = (A+B)/2 - ((+/-) sqrt(3)/2 * t(AB))
--
--      the sign +/- is given by the sign of the cross vector of AB an AC ((b?a)%(c?a))
--      which is >0 if the angle between AB and AC is positive
--      and <0 otherwise.
k a b c=map(/2)$z(+)a b?map(signum((b?a)%(c?a))*sqrt 3*)(t$b?a)

-- function f : returns the fermat point of a triangle
f[a,b,c]
    |a?b£c?b=b  -- return B if angle ABC >= 120°
    |a?c£b?c=c  -- return C if angle BCA >= 120°
    |b?a£c?a=a  -- return A if angle CAB >= 120°
    |[n,m,p,o]<-c?k a b c++a?k b c a= -- calculate the two segments C'C and A'A
        r[m,o][n,p][c%[n,m],a%[p,o]]  -- return their intersection

Testes:

main = do 
    print $ f [[1, 1], [2, 2], [1, 2]]
    print $ f [[-1, -1], [-2, -1], [0, 0]]
    print $ f [[-1, -1], [1, -1], [0, 1]]
    print $ f [[0, 0], [0.5, 0.8660254037844386], [-5, 0]]
    print $ f [[0, 0], [0, -5], [-0.8660254037844386, 0.5]]

Resultado:

[1.2113248654051871,1.7886751345948126]
[-1.0,-1.0]
[0.0,-0.42264973081037427]
[0.0,0.0]
[0.0,0.0]
Damien
fonte
Como você está contando bytes? £ e ¤ são 2 bytes cada em UTF-8, e eu não conheço um compilador Haskell que aceite ISO-8859-1. (Você tem muitos operadores ASCII gratuitos de 1 byte, portanto, isso é fácil de consertar.)
Anders Kaseorg
Estou contando isso com meu editor, que na verdade conta caracteres. Eu não sabia que eram 2 bytes, mas, como você disse, eu poderia substituí-lo por outros operadores de 1 byte. Esse código é compilado com o GHC 7.8.3 #
Damien
O GHC compila seu código quando codificado como UTF-8 com £e ¤como operadores de 2 bytes, mas não quando codificado como ISO-8859-1 com £e ¤como operadores de 1 byte. Os operadores de um byte disponíveis em UTF-8 são !, #, %, &, ?. Você deve substituir os operadores de 2 bytes ou ajustar sua contagem de bytes.
Anders Kaseorg
2

Pitão, 475 448 440 bytes

Qualquer ajuda para o golfe ainda é apreciada.

from math import *
d=lambda x,y:((x[0]-y[0])**2+(x[1]-y[1])**2)**0.5
s=lambda A,B,C:(d(B,C), d(C,A), d(A,B))
j=lambda a,b,c:acos((b*b+c*c-a*a)/(2*b*c))
t=lambda a,b,c:1/cos(j(a,b,c)-pi/6)
b=lambda A,B,C,p,q,r:[(p*A[i]+q*B[i]+r*C[i])/(p+q+r) for i in [0,1]] 
f=lambda A,B,C:A if j(*s(A,B,C)) >= 2*pi/3 else B if j(*s(B,C,A)) >= 2*pi/3 else C if j(*s(C,A,B)) >= 2*pi/3 else b(A,B,C,d(B,C)*t(*s(A,B,C)),d(C,A)*t(*s(B,C,A)),d(A,B)*t(*s(C,A,B)))

Ungolfed:

from math import *
#distance between two points
d = lambda x,y: ((x[0]-y[0])**2+(x[1]-y[1])**2)**0.5

#given the points, returns the sides 
s = lambda A,B,C : (d(B,C), d(C,A), d(A,B))

#given the sides, returns the angle
j = lambda a,b,c : acos((b*b+c*c-a*a)/(2*b*c))

#given the sides, returns secant of that angle
t = lambda a,b,c: 1/cos(j(a,b,c)-pi/6)

#given the sides and the Trilinear co-ordinates, returns the Cartesian co-ordinates
b = lambda A,B,C,p,q,r: [(p*A[i]+q*B[i]+r*C[i])/(p+q+r) for i in [0,1]] 

#this one checks if any of the angle is >= 2π/3 returns that point else computes the point
f = lambda A,B,C: A if j(*s(A,B,C)) >= 2*pi/3 else B if j(*s(B,C,A)) >= 2*pi/3 else C if j(*s(C,A,B)) >= 2*pi/3 else b(A,B,C,d(B,C)*t(*s(A,B,C)),d(C,A)*t(*s(B,C,A)),d(A,B)*t(*s(C,A,B)))

Entrada:

print('{}'.format(f([1, 1], [2, 2], [1, 2])))
print('{}'.format(f([-1, -1], [-2, -1], [0, 0])))
print('{}'.format(f([-1, -1], [1, -1], [0, 1])))
print('{}'.format(f([0, 0], [0.5, 0.8660254037844386], [-5, 0])))
print('{}'.format(f([0, 0], [0, -5], [-0.8660254037844386, 0.5])))

Resultado:

[1.2113248652983113, 1.7886751347016887]
[-1, -1]
[0.0, -0.42264973086764884]
[0, 0]
[0, 0]
abybaddi009
fonte
2
from math import*é um golfe bastante comum. Isso também permitirá que você o use em pivez de codificá-lo (o mesmo tamanho para 2*pi/3). Você também pode soltar um monte de espaço em branco como: d=lambda x,y:(....
FryAmTheEggman
2

Python 3.5, 1019 1016 998 982 969 953 bytes:

from math import*
def H(z,a,b):c=complex;T=lambda A,B:abs(c(*A)-c(*B));d=T(z,a);e=T(z,b);f=T(a,b);g=[d,e,f];h=max(g);g.remove(h);i=acos((sum(i*i for i in g)-(h*h))/(2*g[0]*g[-1]));_=[[z,a],[z,b],[a,b]];j,s,t=cos,sin,atan;N=[[b,a]for a,b in zip([b,a,z],[max(i,key=i.get)if i!=''else''for i in[{(g[0]+(h*j(t(l))),g[1]+(h*s(t(l)))):T(k,(g[0]+(h*j(t(l))),g[1]+(h*s(t(l))))),(g[0]-(h*j(t(l))),g[1]-(h*s(t(l)))):T(k,(g[0]-(h*j(t(l))),g[1]-(h*s(t(l)))))}if l else{(g[0]+h,g[1]):T(k,(g[0]+h,g[1])),(g[0]-h,g[1]):T(k,(g[0]-h,g[1]))}if l==0else''for g,h,l,k in zip([((a[0]+b[0])/2,(a[1]+b[1])/2)for a,b in _],[(3**0.5)*(i/2)for i in[d,e,f]],[-1/p if p else''if p==0else 0for p in[((a[1]-b[1])/(a[0]-b[0]))if a[0]-b[0]else''for a,b in _]],[b,a,z])]])if b!=''];I=N[0][0][1];J=N[0][0][0];K=N[1][0][1];G=N[1][0][0];A=(N[0][1][1]-I)/(N[0][1][0]-J);B=I-(A*J);C=(K-N[1][1][1])/(G-N[1][1][0]);D=K-(C*G);X=(D-B)/(A-C);Y=(A*X)+B;return[[X,Y],[[a,b][h==d],z][h==f]][i>2.0943]

Incrivelmente longo comparado com outras respostas, mas ei, pelo menos funciona! Eu não poderia estar mais feliz com o resultado que obtive, pois esse deve ser um dos desafios mais difíceis que já fiz. Estou tão feliz que isso realmente funciona! : D Agora, para as notas mais técnicas:

  • Esta função recebe cada par ordenado como uma lista ou uma tupla. Por exemplo, H((1,1),(2,2),(1,2))funcionará, mas também o será H([1,1],[2,2],[1,2]).
  • Emite as coordenadas dos pontos em uma lista de números inteiros ou em pontos flutuantes, dependendo da existência ou não de um ângulo maior ou igual a 120º.
  • Isso pode aparecer -0.0no lugar de 0.0algumas entradas. Por exemplo, a saída para a entrada [-1, -1], [1, -1], [0, 1]é [-0.0, -0.4226497308103744]. Espero que esteja tudo bem, embora, se não estiver, eu o alterarei, embora isso me custe mais alguns bytes. Tudo bem, como confirmado pelo OP .
  • Deve ser preciso até pelo menos 13para 14números significativos.

Vou tentar jogar mais isso com o tempo. Uma explicação, possivelmente muito longa, em breve.

Experimente Online! (Ideona)

R. Kap
fonte
1

Mathematica, 39 bytes

Sum[Norm[p-{x,y}],{p,#}]~NArgMin~{x,y}&

Constrói uma equação baseada nas distâncias entre os vértices e um ponto {x,y}. Em seguida, usa a NArgMinfunção para encontrar um mínimo global para essa equação, que será o ponto de Fermat por definição.

Exemplo

milhas
fonte
1
39 bytes, quando a próxima resposta mais curta for 285 ...
Bálint 15/05