Rev 1: Ruby, 354 Bytes
weiter golfen dank blutorange.
->a{t=s=Math::PI/18E4
d=r=c=0
a=a.map{|e|e-a[0]}
0.upto(36E4){|i|b=a.map{|e|(e/Complex.polar(1,i*s)).rect}.transpose
m,n=b
if n.min>=f=0
l=[m.max-x=m.min,n.max].max
a.each_index{|j|f+=((l-w=n[j])*(x+l-v=m[j])*(x-v)*w)**2}
(1E-9>q=f/l**8)&&(c>0&&(i-d)%9E4%89E3>1E3?c=9E9:0;c+=1;d=i)
q<t&&(r=i)&&t=q;end}
c<101&&a[1]?c<1?'impossible':r%9E4/1.0E3:'unknown'}
Ruby, 392 Bytes
->(a){
s=Math::PI/18E4
t=1
d=r=c=0
a=a.map{|e|e-a[0]}
(0..36E4).each{|i|
b=a.map{|e|(e/Complex.polar(1,i*s)).rect}.transpose
m=b[0]
n=b[1]
x=m.min
if n.min>=0
l=[m.max-x,n.max].max
f=0
a.each_index{|j|f+=((l-n[j])*(x+l-m[j])*(x-m[j])*n[j])**2}
q=f/l**8
if q<1E-9
c>0&&(i-d)%9E4%89E3>1E3?(c=9E9):0
c+=1
d=i
end
if q<t
r=i
t=q
end
end
}
c>100||a.size<2?'unknown':c<1? 'impossible':r%9E4/1.0E3
}
Der Algorithmus lautet wie folgt:
-Wählen Sie einen beliebigen Punkt (den ersten) und verschieben Sie diesen zum Ursprung (subtrahieren Sie die Koordinaten dieses Punktes von allen Punkten in der Liste.)
-Versuchen Sie alle Umdrehungen des Quadrats um den Ursprung in Schritten von 0,001 Grad um 360 Grad.
- Wenn bei einer bestimmten Drehung alle Punkte über der y-Achse liegen, zeichnen Sie das kleinstmögliche Quadrat um alle Punkte, einschließlich des niedrigsten und des am weitesten links liegenden Punkts.
-Überprüfen Sie, ob alle Punkte am Rand liegen. Dies geschieht mit einer weichen Berechnung, die jeden Punkt nimmt, die quadratischen Abstände von allen Kanten findet und sie miteinander multipliziert. Dies ergibt eher eine gute Übereinstimmung als eine Ja / Nein-Antwort. Es wird interpretiert, dass eine Lösung gefunden wird, wenn dieses Produkt dividiert durch die Seitenlänge ^ 8 kleiner als 1E-9 ist. In der Praxis ist dies weniger als ein Toleranzgrad.
-Die beste Anpassung wird um 90 Grad vorgenommen und als korrekter Winkel angegeben.
Derzeit gibt der Code den Wert mehrdeutig zurück, wenn mehr als 100 Lösungen gefunden werden (bei einer Auflösung von 0,001 Grad. Das sind 0,1 Grad Toleranz.)
erste voll funktionsfähige Funktion im Testprogramm
Ich habe die Auflösung auf 1/10 der erforderlichen Auflösung belassen, um die Geschwindigkeit angemessen zu machen. Im letzten Testfall ist ein Fehler von 0,01 Grad aufgetreten.
g=->(a){
s=Math::PI/18000
t=1
d=r=-1
c=0
a=a.map{|e| e-a[0]}
(0..36000).each{|i|
b=a.map{|e|(e/Complex.polar(1,i*s)).rect}.transpose
m=b[0]
n=b[1]
x=m.min
if n.min>=0
l=[m.max-x,n.max].max
f=0
a.each_index{|j|f+=((l-n[j])*(x+l-m[j])*(x-m[j])*n[j])**2}
q=f/l**8
if q<1E-9
j=(i-d)%9000
c>0&&j>100&&j<8900?(c=9E9):0
c+=1
d=i
end
if q<t
r=i
t=q
end
end
}
print "t=",t," r=",r," c=",c," d=",d,"\n"
p c>100||a.size<2?'unknown':c<1? 'impossible':r%9000/100.0
}
#ambiguous
#g.call([Complex(0,0)])
#g.call([Complex(0,0),Complex(1,0)])
#g.call([Complex(0,0),Complex(1,0),Complex(0,1)])
#g.call([Complex(0,0),Complex(1,0),Complex(0,1),Complex(1,1)])
#g.call([Complex(0,1),Complex(0,2),Complex(1,0),Complex(1,3),Complex(2,0),Complex(2,3),Complex(3,1),Complex(3,2)])
#impossible
#g.call([Complex(0,0),Complex(1,0),Complex(2,0),Complex(3,1),Complex(4,2)])
#g.call([Complex(0,0),Complex(1,0),Complex(2,0),Complex(1,1)])
#g.call([Complex(0,1),Complex(0,2),Complex(1,0),Complex(1,3),Complex(2,0),Complex(2,3),Complex(3,1),Complex(3,2),Complex(2,2)])
#g.call([Complex(2,0),Complex(0,1),Complex(2,2),Complex(0,3)])
#g.call([Complex(0,0),Complex(2,1),Complex(0,2),Complex(2,2),Complex(-1,1)])
#possible
g.call([Complex(0,0),Complex(1,0),Complex(2,0)])
g.call([Complex(0,0),Complex(0.3,0.3),Complex(0.6,0.6)]) #(should return 45)
g.call([Complex(0,0),Complex(0.1,0.2),Complex(0.2,0.4)]) #(should return appx 63.435 (the real value is arctan(2)))
g.call([Complex(0,0),Complex(0,1),Complex(2,1),Complex(2,2)])
g.call([Complex(0,1),Complex(0,2),Complex(1,0),Complex(1,4),Complex(2,0),Complex(2,4),Complex(4,1),Complex(4,3)])
Golf-Version, Auflösung gemäß Spezifikation, dauert etwa eine Minute pro Anruf im Testprogramm.
Es gibt immer noch einen lästigen Fehler von 0,001 Grad im letzten Testfall. Eine weitere Erhöhung der Auflösung würde diese wahrscheinlich beseitigen.
g=->(a){ #take an array of complex numbers as input
s=Math::PI/18E4 #step size PI/180000
t=1 #best fit found so far
d=r=c=0 #angles of (d) last valid result, (r) best fit; c= hit counter
a=a.map{|e|e-a[0]} #move shape so that first point coincides with origin
(0..36E4).each{|i| #0..360000
b=a.map{|e|(e/Complex.polar(1,i*s)).rect}.transpose #rotate each element by dividing by unit vector of angle i*s, convert to array...
m=b[0] #...transpose array [[x1,y1]..[xn,yn]] to [[x1..xn],[y1..yn]]...
n=b[1] #...and assign to variables m and n
x=m.min #find leftmost point
if n.min>=0 #if all points are above x axis
l=[m.max-x,n.max].max #find the sidelength of smallest square in which they will fit
f=0 #f= accumulator for errors. For each point
a.each_index{|j|f+=((l-n[j])*(x+l-m[j])*(x-m[j])*n[j])**2} #...add to f the product of the squared distances from each side of the smallest square containing all points
q=f/l**8 #q= f normalized with respect to the sidelength.
if q<1E-9 #consider a hit if <1E-9
c>0&&(i-d)%9E4%89E3>1E3?(c=9E9):0 #if at least one point is already found, and the difference between this hit and the last exceeds+/-1 deg (mod 90), set c to a high value
c+=1 #increment hit count by 1 (this catches infinitely varible cases)
d=i #store the current hit in d
end
if q<t #if current fit is better than previous one
r=i #store the new angle
t=q #and revise t to the new best fit.
end
end
}
c>100||a.size<2?'unknown':c<1? 'impossible':r%9E4/1.0E3 #calculate and return value, taking special care of case where single point given.
}
#ambiguous
puts g.call([Complex(0,0)])
puts g.call([Complex(0,0),Complex(1,0)])
puts g.call([Complex(0,0),Complex(1,0),Complex(0,1)])
puts g.call([Complex(0,0),Complex(1,0),Complex(0,1),Complex(1,1)])
puts g.call([Complex(0,1),Complex(0,2),Complex(1,0),Complex(1,3),Complex(2,0),Complex(2,3),Complex(3,1),Complex(3,2)])
#impossible
puts g.call([Complex(0,0),Complex(1,0),Complex(2,0),Complex(3,1),Complex(4,2)])
puts g.call([Complex(0,0),Complex(1,0),Complex(2,0),Complex(1,1)])
puts g.call([Complex(0,1),Complex(0,2),Complex(1,0),Complex(1,3),Complex(2,0),Complex(2,3),Complex(3,1),Complex(3,2),Complex(2,2)])
puts g.call([Complex(2,0),Complex(0,1),Complex(2,2),Complex(0,3)])
puts g.call([Complex(0,0),Complex(2,1),Complex(0,2),Complex(2,2),Complex(-1,1)])
#possible
puts g.call([Complex(0,0),Complex(1,0),Complex(2,0)])
puts g.call([Complex(0,0),Complex(0.3,0.3),Complex(0.6,0.6)]) #(should return 45)
puts g.call([Complex(0,0),Complex(0.1,0.2),Complex(0.2,0.4)]) #(should return appx 63.435 (the real value is arctan(2)))
puts g.call([Complex(0,0),Complex(0,1),Complex(2,1),Complex(2,2)])
puts g.call([Complex(0,1),Complex(0,2),Complex(1,0),Complex(1,4),Complex(2,0),Complex(2,4),Complex(4,1),Complex(4,3)])
Beachten Sie, dass dieser Algorithmus für etwa 30% mehr Code schnell angepasst werden könnte: Es ist offensichtlich, dass in Fällen mit einer begrenzten Anzahl von Lösungen eine der Kanten flach entlang eines Würfels liegt, sodass wir wirklich nur diese Winkel ausprobieren müssen das entspricht jedem Paar von Eckpunkten. Es wäre auch notwendig, ein bisschen zu wackeln, um zu überprüfen, ob es nicht unendlich viele Lösungen gibt.