Perl - 116 byte 87 byte (lihat pembaruan di bawah)
#!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"
Menghitung shebang sebagai satu byte, baris baru ditambahkan untuk kewarasan horisontal.
Sesuatu dari pengiriman kode-golf tercepat-kode kombinasi .
Kompleksitas kasus rata-rata (terburuk?) Tampaknya O (log n) O (n 0,07 ) . Tidak ada yang saya temukan berjalan lebih lambat dari 0,001, dan saya telah memeriksa seluruh rentang dari 900000000 - 999999999 . Jika Anda menemukan sesuatu yang secara signifikan lebih lama dari itu, ~ 0,1s atau lebih, beri tahu saya.
Contoh Penggunaan
$ 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
Dua terakhir ini tampaknya merupakan kasus terburuk untuk kiriman lainnya. Dalam kedua contoh, solusi yang ditampilkan secara harfiah adalah hal pertama yang diperiksa. Sebab 123456789
, ini yang kedua.
Jika Anda ingin menguji berbagai nilai, Anda dapat menggunakan skrip berikut:
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);
Terbaik saat disalurkan ke file. Rentang ini 1..1000000
membutuhkan waktu sekitar 14 detik di komputer saya (nilai 71000 per detik), dan rentang 999000000..1000000000
waktu sekitar 20 detik (nilai 50000 per detik), konsisten dengan kompleksitas rata-rata O (log n) .
Memperbarui
Sunting : Ternyata algoritma ini sangat mirip dengan yang telah digunakan oleh kalkulator mental selama setidaknya satu abad .
Sejak semula memposting, saya telah memeriksa setiap nilai pada kisaran dari 1..1000000000 . Perilaku 'kasus terburuk' ditunjukkan oleh nilai 699731569 , yang menguji total 190 kombinasi sebelum sampai pada solusi. Jika Anda menganggap 190 sebagai konstanta kecil - dan tentu saja saya lakukan - perilaku terburuk pada rentang yang diperlukan dapat dianggap O (1) . Artinya, secepat mencari solusi dari meja raksasa, dan rata-rata, sangat mungkin lebih cepat.
Satu hal lagi. Setelah 190 iterasi, apapun yang lebih besar dari 144400 bahkan belum berhasil melampaui pass pertama. Logika untuk lintas luas pertama tidak berharga - bahkan tidak digunakan. Kode di atas dapat disingkat sedikit:
#!perl -p
$.*=2,$_/=4until$_&3;
@a=map{$=-=$%*($%=$=**.5-$_);$%*$.}$j++,(0)x3while$=&&=$_;
$_="@a"
Yang hanya melakukan pass pertama pencarian. Kami perlu mengonfirmasi bahwa tidak ada nilai di bawah 144400 yang memerlukan pass kedua, meskipun:
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");
}
Singkatnya, untuk kisaran 1..1000000000 , solusi waktu hampir konstan ada, dan Anda sedang melihatnya.
Pembaruan yang Diperbarui
@Dennis dan saya telah membuat beberapa peningkatan pada algoritma ini. Anda dapat mengikuti kemajuan dalam komentar di bawah, dan diskusi selanjutnya, jika itu menarik minat Anda. Jumlah rata-rata iterasi untuk rentang yang diperlukan telah turun dari lebih dari 4 ke 1.229 , dan waktu yang dibutuhkan untuk menguji semua nilai untuk 1..1000000000 telah ditingkatkan dari 18m 54s, turun ke 2m 41s. Kasus terburuk yang sebelumnya membutuhkan 190 iterasi; kasus terburuk sekarang, 854382778 , hanya perlu 21 .
Kode Python terakhir adalah sebagai berikut:
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
Ini menggunakan dua tabel koreksi yang sudah dihitung sebelumnya, satu berukuran 10kb, yang lain 253kb. Kode di atas termasuk fungsi generator untuk tabel ini, meskipun ini mungkin harus dihitung pada waktu kompilasi.
Versi dengan tabel koreksi berukuran lebih sederhana dapat ditemukan di sini: http://codepad.org/1ebJC2OV Versi ini membutuhkan rata-rata 1,620 iterasi per istilah, dengan kasus terburuk 38 , dan seluruh rentang berjalan sekitar 3m 21d. Sedikit waktu dibuat, dengan menggunakan bitwise and
untuk koreksi b , daripada modulo.
Perbaikan
Bahkan nilai lebih cenderung menghasilkan solusi daripada nilai ganjil.
Artikel perhitungan mental yang ditautkan dengan catatan sebelumnya bahwa jika, setelah menghilangkan semua faktor empat, nilai yang akan didekomposisi adalah genap, nilai ini dapat dibagi dua, dan solusi direkonstruksi:
Meskipun ini mungkin masuk akal untuk perhitungan mental (nilai yang lebih kecil cenderung lebih mudah untuk dihitung), itu tidak masuk akal secara algoritmik. Jika Anda mengambil 256 acak 4- tupel, dan memeriksa jumlah modulo 8 kotak , Anda akan menemukan bahwa nilai 1 , 3 , 5 , dan 7 masing-masing mencapai rata-rata 32 kali. Namun, nilai 2 dan 6 masing-masing mencapai 48 kali. Mengalikan nilai aneh dengan 2 akan menemukan solusi, rata-rata, dalam iterasi 33% lebih sedikit. Rekonstruksi adalah sebagai berikut:
Harus diperhatikan bahwa a dan b memiliki paritas yang sama, serta c dan d , tetapi jika solusi ditemukan sama sekali, pemesanan yang tepat dijamin ada.
Jalur yang tidak mungkin tidak perlu diperiksa.
Setelah memilih nilai kedua, b , mungkin sudah tidak mungkin untuk solusi ada, mengingat kemungkinan residu kuadratik untuk setiap modulo yang diberikan. Alih-alih memeriksa, atau beralih ke iterasi berikutnya, nilai b dapat 'dikoreksi' dengan menurunkannya dengan jumlah terkecil yang mungkin mengarah pada solusi. Dua tabel koreksi menyimpan nilai-nilai ini, satu untuk b , dan yang lainnya untuk c . Menggunakan modulo yang lebih tinggi (lebih akurat, menggunakan modulo dengan residu kuadratik yang relatif lebih sedikit) akan menghasilkan peningkatan yang lebih baik. Nilai a tidak perlu koreksi apa pun; dengan memodifikasi n menjadi genap, semua nilaia valid.