Perl - 116 octets 87 octets (voir la mise à jour ci-dessous)
#!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"
En comptant le shebang comme un octet, des nouvelles lignes ont été ajoutées pour une raison horizontale.
Quelque chose d'une combinaison de code-golf - soumission de code le plus rapide .
La complexité moyenne (pire?) Des cas semble être O (log n) O (n 0,07 ) . Rien de ce que j'ai trouvé ne tourne plus lentement que 0,001 s, et j'ai vérifié toute la plage de 900000000 à 999999999 . Si vous trouvez quelque chose qui prend beaucoup plus de temps que cela, ~ 0,1 s ou plus, faites-le moi savoir.
Exemple d'utilisation
$ 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
Les deux derniers semblent être les scénarios les plus défavorables pour les autres soumissions. Dans les deux cas, la solution présentée est littéralement la toute première chose vérifiée. Car 123456789
c'est le second.
Si vous souhaitez tester une plage de valeurs, vous pouvez utiliser le script suivant:
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);
Meilleur lorsqu'il est redirigé vers un fichier. La plage 1..1000000
prend environ 14 secondes sur mon ordinateur (71000 valeurs par seconde) et la plage 999000000..1000000000
prend environ 20 secondes (50000 valeurs par seconde), conformément à la complexité moyenne O (log n) .
Mise à jour
Edit : Il s'avère que cet algorithme est très similaire à celui qui a été utilisé par les calculatrices mentales pendant au moins un siècle .
Depuis la publication initiale, j'ai vérifié chaque valeur sur la plage de 1 à 1000000000 . Le comportement du «pire des cas» a été démontré par la valeur 699731569 , qui a testé un grand total de 190 combinaisons avant d'arriver à une solution. Si vous considérez 190 comme une petite constante - et je le fais certainement - le pire des cas sur la plage requise peut être considéré comme O (1) . Autrement dit, aussi rapide que la recherche de la solution à partir d'une table géante, et en moyenne, peut-être plus rapidement.
Une autre chose cependant. Après 190 itérations, rien de plus grand que 144400 n'a même dépassé le premier passage. La logique de la traversée en largeur est sans valeur - elle n'est même pas utilisée. Le code ci-dessus peut être raccourci un peu:
#!perl -p
$.*=2,$_/=4until$_&3;
@a=map{$=-=$%*($%=$=**.5-$_);$%*$.}$j++,(0)x3while$=&&=$_;
$_="@a"
Qui n'effectue que la première passe de la recherche. Nous devons cependant confirmer qu'aucune valeur inférieure à 144400 n'a nécessité la deuxième passe:
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");
}
En bref, pour la gamme 1..1000000000 , une solution à temps quasi constant existe, et vous la regardez.
Mise à jour mise à jour
@Dennis et moi avons apporté plusieurs améliorations à cet algorithme. Vous pouvez suivre les progrès dans les commentaires ci-dessous, et la discussion ultérieure, si cela vous intéresse. Le nombre moyen d'itérations pour la plage requise est passé d'un peu plus de 4 à 1,229 , et le temps nécessaire pour tester toutes les valeurs pour 1..1000000000 a été amélioré de 18 m 54 s à 2 m 41 s. Le pire des cas nécessitait auparavant 190 itérations; le pire des cas maintenant, 854382778 , n'a besoin que de 21 .
Le code Python final est le suivant:
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
Celui-ci utilise deux tables de correction pré-calculées, l'une de 10 Ko, l'autre de 253 Ko. Le code ci-dessus inclut les fonctions de générateur pour ces tables, bien qu'elles devraient probablement être calculées au moment de la compilation.
Une version avec des tables de correction de taille plus modeste peut être trouvée ici: http://codepad.org/1ebJC2OV Cette version nécessite une moyenne de 1.620 itérations par terme, avec le pire des cas de 38 , et toute la gamme fonctionne en environ 3m 21s. Un peu de temps est compensé, en utilisant au niveau du bit and
pour la correction b , plutôt que modulo.
Améliorations
Les valeurs paires sont plus susceptibles de produire une solution que les valeurs impaires.
L'article de calcul mental lié à précédemment note que si, après avoir supprimé tous les facteurs de quatre, la valeur à décomposer est paire, cette valeur peut être divisée par deux, et la solution reconstruite:
Bien que cela puisse être logique pour le calcul mental (les valeurs plus petites ont tendance à être plus faciles à calculer), cela n'a pas beaucoup de sens algorithmiquement. Si vous prenez 256 4 -tuples aléatoires et examinez la somme des carrés modulo 8 , vous constaterez que les valeurs 1 , 3 , 5 et 7 sont chacune atteintes en moyenne 32 fois. Cependant, les valeurs 2 et 6 sont chacune atteintes 48 fois. La multiplication des valeurs impaires par 2 trouvera une solution, en moyenne, dans 33% d' itérations en moins. La reconstruction est la suivante:
Il faut veiller à ce que a et b aient la même parité, ainsi que c et d , mais si une solution a été trouvée, un bon ordre est garanti d'exister.
Les chemins impossibles n'ont pas besoin d'être vérifiés.
Après avoir sélectionné la deuxième valeur, b , il peut déjà être impossible pour une solution d'exister, étant donné les résidus quadratiques possibles pour un modulo donné. Au lieu de vérifier de toute façon ou de passer à l'itération suivante, la valeur de b peut être «corrigée» en la décrémentant de la plus petite quantité qui pourrait éventuellement conduire à une solution. Les deux tables de correction stockent ces valeurs, l'une pour b et l'autre pour c . L'utilisation d'un modulo plus élevé (plus précisément, l'utilisation d'un modulo avec relativement moins de résidus quadratiques) se traduira par une meilleure amélioration. La valeur a n'a besoin d'aucune correction; en modifiant n pour qu'il soit pair, toutes les valeurs dea sont valides.