Perl - 116 Bytes 87 Bytes (siehe Update unten)
#!perl -p
$.<<=1,$_>>=2until$_&3;
{$n=$_;@a=map{$n-=$a*($a-=$_%($b=1|($a=0|sqrt$n)>>1));$_/=$b;$a*$.}($j++)x4;$n&&redo}
$_="@a"
Zählt man den Shebang als ein Byte, werden Zeilenumbrüche hinzugefügt, um die horizontale Vernunft zu gewährleisten.
So etwas wie eine Kombination aus Code-Golf -Code- Einreichung mit dem schnellsten Code .
Die durchschnittliche (schlechteste?) Fallkomplexität scheint O (log n) O (n 0,07 ) zu sein . Nichts, was ich gefunden habe, läuft langsamer als 0,001s und ich habe den gesamten Bereich von 900000000 - 999999999 überprüft . Wenn Sie etwas finden, das wesentlich länger dauert (~ 0,1s oder länger), lassen Sie es mich bitte wissen.
Beispielnutzung
$ echo 123456789 | timeit perl four-squares.pl
11110 157 6 2
Elapsed Time: 0:00:00.000
$ echo 1879048192 | timeit perl four-squares.pl
32768 16384 16384 16384
Elapsed Time: 0:00:00.000
$ echo 999950883 | timeit perl four-squares.pl
31621 251 15 4
Elapsed Time: 0:00:00.000
Die letzten beiden scheinen Worst-Case-Szenarien für andere Einreichungen zu sein. In beiden Fällen wird die gezeigte Lösung buchstäblich als erstes überprüft. Für 123456789
ist es das zweite.
Wenn Sie einen Wertebereich testen möchten, können Sie das folgende Skript verwenden:
use Time::HiRes qw(time);
$t0 = time();
# enter a range, or comma separated list here
for (1..1000000) {
$t1 = time();
$initial = $_;
$j = 0; $i = 1;
$i<<=1,$_>>=2until$_&3;
{$n=$_;@a=map{$n-=$a*($a-=$_%($b=1|($a=0|sqrt$n)>>1));$_/=$b;$a*$i}($j++)x4;$n&&redo}
printf("%d: @a, %f\n", $initial, time()-$t1)
}
printf('total time: %f', time()-$t0);
Am besten in eine Datei umleiten. 1..1000000
Auf meinem Computer dauert der Bereich ungefähr 14 Sekunden (71000 Werte pro Sekunde), und der Bereich 999000000..1000000000
dauert ungefähr 20 Sekunden (50000 Werte pro Sekunde), was der durchschnittlichen Komplexität von O (log n) entspricht .
Aktualisieren
Bearbeiten : Es stellt sich heraus, dass dieser Algorithmus einem sehr ähnlich ist, der von mentalen Taschenrechnern seit mindestens einem Jahrhundert verwendet wird .
Seit dem ursprünglichen Posten habe ich jeden Wert im Bereich von 1..1000000000 überprüft . Das "Worst-Case" -Verhalten wurde durch den Wert 699731569 angezeigt , bei dem insgesamt 190 Kombinationen getestet wurden, bevor eine Lösung gefunden wurde. Wenn Sie 190 als kleine Konstante betrachten - und das tue ich sicherlich -, kann das Worst-Case-Verhalten im erforderlichen Bereich als O (1) betrachtet werden . Das ist so schnell wie das Nachschlagen der Lösung von einem riesigen Tisch und im Durchschnitt möglicherweise sogar schneller.
Eine andere Sache. Nach 190 Iterationen hat alles, was größer als 144400 ist, noch nicht einmal den ersten Durchgang geschafft. Die Logik für die erste Durchquerung ist wertlos - sie wird nicht einmal verwendet. Der obige Code kann einiges gekürzt werden:
#!perl -p
$.*=2,$_/=4until$_&3;
@a=map{$=-=$%*($%=$=**.5-$_);$%*$.}$j++,(0)x3while$=&&=$_;
$_="@a"
Welches führt nur den ersten Durchgang der Suche. Wir müssen jedoch bestätigen, dass es keine Werte unter 144400 gibt , für die der zweite Durchgang erforderlich ist:
for (1..144400) {
$initial = $_;
# reset defaults
$.=1;$j=undef;$==60;
$.*=2,$_/=4until$_&3;
@a=map{$=-=$%*($%=$=**.5-$_);$%*$.}$j++,(0)x3while$=&&=$_;
# make sure the answer is correct
$t=0; $t+=$_*$_ for @a;
$t == $initial or die("answer for $initial invalid: @a");
}
Kurz gesagt, für den Bereich 1..1000000000 gibt es eine nahezu konstante Zeitlösung , die Sie sich ansehen .
Aktualisiertes Update
@Dennis und ich haben diesen Algorithmus mehrfach verbessert. Sie können den Fortschritt in den Kommentaren und der anschließenden Diskussion verfolgen, wenn dies Sie interessiert. Die durchschnittliche Anzahl der Iterationen für den erforderlichen Bereich ist von etwas mehr als 4 auf 1,229 gesunken , und die Zeit, die zum Testen aller Werte für 1..1000000000 erforderlich ist, wurde von 18 auf 2 Millionen Sekunden auf 41 Sekunden gesenkt . Der schlimmste Fall erforderte zuvor 190 Iterationen. Der schlimmste Fall, 854382778 , benötigt nur noch 21 .
Der endgültige Python-Code lautet wie folgt:
from math import sqrt
# the following two tables can, and should be pre-computed
qqr_144 = set([ 0, 1, 2, 4, 5, 8, 9, 10, 13,
16, 17, 18, 20, 25, 26, 29, 32, 34,
36, 37, 40, 41, 45, 49, 50, 52, 53,
56, 58, 61, 64, 65, 68, 72, 73, 74,
77, 80, 81, 82, 85, 88, 89, 90, 97,
98, 100, 101, 104, 106, 109, 112, 113, 116,
117, 121, 122, 125, 128, 130, 133, 136, 137])
# 10kb, should fit entirely in L1 cache
Db = []
for r in range(72):
S = bytearray(144)
for n in range(144):
c = r
while True:
v = n - c * c
if v%144 in qqr_144: break
if r - c >= 12: c = r; break
c -= 1
S[n] = r - c
Db.append(S)
qr_720 = set([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121,
144, 145, 160, 169, 180, 196, 225, 241, 244, 256, 265, 289,
304, 324, 340, 361, 369, 385, 400, 409, 436, 441, 481, 484,
496, 505, 529, 544, 576, 580, 585, 601, 625, 640, 649, 676])
# 253kb, just barely fits in L2 of most modern processors
Dc = []
for r in range(360):
S = bytearray(720)
for n in range(720):
c = r
while True:
v = n - c * c
if v%720 in qr_720: break
if r - c >= 48: c = r; break
c -= 1
S[n] = r - c
Dc.append(S)
def four_squares(n):
k = 1
while not n&3:
n >>= 2; k <<= 1
odd = n&1
n <<= odd
a = int(sqrt(n))
n -= a * a
while True:
b = int(sqrt(n))
b -= Db[b%72][n%144]
v = n - b * b
c = int(sqrt(v))
c -= Dc[c%360][v%720]
if c >= 0:
v -= c * c
d = int(sqrt(v))
if v == d * d: break
n += (a<<1) - 1
a -= 1
if odd:
if (a^b)&1:
if (a^c)&1:
b, c, d = d, b, c
else:
b, c = c, b
a, b, c, d = (a+b)>>1, (a-b)>>1, (c+d)>>1, (c-d)>>1
a *= k; b *= k; c *= k; d *= k
return a, b, c, d
Dabei werden zwei vorberechnete Korrekturtabellen verwendet, eine mit einer Größe von 10 KB und eine mit einer Größe von 253 KB. Der obige Code enthält die Generatorfunktionen für diese Tabellen, obwohl diese wahrscheinlich zur Kompilierungszeit berechnet werden sollten.
Eine Version mit kleineren Korrekturtabellen finden Sie hier: http://codepad.org/1ebJC2OV Diese Version erfordert durchschnittlich 1.620 Iterationen pro Term, wobei der schlechteste Fall 38 ist. Die gesamte Reichweite beträgt ca. 3 m 21 s. Ein wenig Zeit wird wettgemacht, indem and
für die b- Korrektur bitweise anstelle von Modulo verwendet wird.
Verbesserungen
Gerade Werte ergeben eher eine Lösung als ungerade Werte.
Der Artikel zur mentalen Berechnung, der mit dem vorherigen Artikel verknüpft ist, stellt fest, dass, wenn nach dem Entfernen aller vier Faktoren der zu zerlegende Wert gerade ist, dieser Wert durch zwei geteilt und die Lösung rekonstruiert werden kann:
Obwohl dies für die mentale Berechnung sinnvoll sein kann (kleinere Werte sind in der Regel einfacher zu berechnen), ist es algorithmisch wenig sinnvoll. Wenn Sie 256 zufällige 4- Tupel nehmen und die Summe der Quadrate Modulo 8 untersuchen , werden Sie feststellen, dass die Werte 1 , 3 , 5 und 7 jeweils durchschnittlich 32- mal erreicht werden. Die Werte 2 und 6 werden jedoch jeweils 48 Mal erreicht. Wenn Sie ungerade Werte mit 2 multiplizieren , erhalten Sie im Durchschnitt eine Lösung mit 33% weniger Iterationen. Die Rekonstruktion ist die folgende:
Es muss darauf geachtet werden, dass a und b sowie c und d die gleiche Parität haben , aber wenn überhaupt eine Lösung gefunden wurde, ist eine ordnungsgemäße Reihenfolge garantiert.
Unmögliche Pfade müssen nicht überprüft werden.
Nach der Auswahl des zweiten Wertes, b , kann es bereits unmöglich sein, dass eine Lösung existiert, wenn die möglichen quadratischen Reste für ein gegebenes Modulo gegeben sind. Anstatt es trotzdem zu überprüfen oder mit der nächsten Iteration fortzufahren, kann der Wert von b "korrigiert" werden, indem er um den kleinsten Betrag verringert wird, der möglicherweise zu einer Lösung führen könnte. In den beiden Korrekturtabellen werden diese Werte gespeichert, einer für b und der andere für c . Die Verwendung eines höheren Modulos (genauer gesagt eines Moduls mit relativ weniger quadratischen Resten) führt zu einer besseren Verbesserung. Der Wert a muss nicht korrigiert werden. durch Ändern von n auf gerade werden alle Werte vona sind gültig.