Perl - 116 byte 87 byte (xem cập nhật bên dưới)
#!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"
Đếm shebang là một byte, các dòng mới được thêm vào cho độ ngang.
Một cái gì đó của một mã kết hợp mã golf nhanh nhất .
Độ phức tạp trường hợp trung bình (tệ nhất?) Dường như là O (log n) O (n 0,07 ) . Không có gì tôi tìm thấy chạy chậm hơn 0,001 giây và tôi đã kiểm tra toàn bộ phạm vi từ 900000000 - 999999999 . Nếu bạn tìm thấy bất cứ điều gì mất nhiều thời gian hơn thế, ~ 0,1s trở lên, xin vui lòng cho tôi biết.
Sử dụng mẫu
$ 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
Hai trong số cuối cùng có vẻ là trường hợp xấu nhất cho các bài nộp khác. Trong cả hai trường hợp, giải pháp hiển thị hoàn toàn theo nghĩa đen là điều đầu tiên được kiểm tra. Đối với 123456789
, đó là thứ hai.
Nếu bạn muốn kiểm tra một loạt các giá trị, bạn có thể sử dụng tập lệnh sau:
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);
Tốt nhất khi được dẫn đến một tập tin. Phạm vi 1..1000000
mất khoảng 14 giây trên máy tính của tôi (71000 giá trị mỗi giây) và phạm vi 999000000..1000000000
mất khoảng 20 giây (50000 giá trị mỗi giây), phù hợp với độ phức tạp trung bình O (log n) .
Cập nhật
Chỉnh sửa : Hóa ra thuật toán này rất giống với thuật toán đã được sử dụng bởi các máy tính tinh thần trong ít nhất một thế kỷ .
Kể từ khi đăng bài ban đầu, tôi đã kiểm tra mọi giá trị trong phạm vi từ 1..1000000000 . Hành vi 'trường hợp xấu nhất' được thể hiện bằng giá trị 699731569 , đã thử nghiệm tổng cộng 190 kết hợp trước khi đưa ra giải pháp. Nếu bạn coi 190 là một hằng số nhỏ - và tôi chắc chắn làm được - hành vi xấu nhất trong phạm vi yêu cầu có thể được coi là O (1) . Đó là, nhanh như tìm kiếm giải pháp từ một cái bàn khổng lồ, và trung bình, hoàn toàn có thể nhanh hơn.
Một điều khác mặc dù. Sau 190 lần lặp, bất cứ điều gì lớn hơn 144400 thậm chí còn không vượt qua được lần đầu tiên. Logic cho giao dịch theo chiều rộng đầu tiên là vô giá trị - nó thậm chí không được sử dụng. Đoạn mã trên có thể được rút ngắn khá nhiều:
#!perl -p
$.*=2,$_/=4until$_&3;
@a=map{$=-=$%*($%=$=**.5-$_);$%*$.}$j++,(0)x3while$=&&=$_;
$_="@a"
Mà chỉ thực hiện vượt qua đầu tiên của tìm kiếm. Tuy nhiên, chúng tôi cần xác nhận rằng không có bất kỳ giá trị nào dưới 144400 cần vượt qua lần thứ hai:
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");
}
Nói tóm lại, trong phạm vi 1..1000000000 , tồn tại một giải pháp thời gian gần như không đổi và bạn đang xem xét nó.
Cập nhật cập nhật
@Dennis và tôi đã thực hiện một số cải tiến thuật toán này. Bạn có thể theo dõi tiến trình trong các bình luận bên dưới và thảo luận tiếp theo, nếu điều đó làm bạn quan tâm. Số lần lặp trung bình cho phạm vi yêu cầu đã giảm từ chỉ hơn 4 xuống còn 1.229 và thời gian cần thiết để kiểm tra tất cả các giá trị cho 1..1000000000 đã được cải thiện từ 18m 54s, xuống còn 2m 41s. Trường hợp xấu nhất trước đây yêu cầu 190 lần lặp; trường hợp xấu nhất bây giờ, 854382778 , chỉ cần 21 .
Mã Python cuối cùng là như sau:
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
Điều này sử dụng hai bảng hiệu chỉnh được tính toán trước, một kích thước 10kb, còn lại là 253kb. Mã ở trên bao gồm các hàm tạo cho các bảng này, mặc dù chúng có thể được tính toán tại thời điểm biên dịch.
Có thể tìm thấy phiên bản với các bảng hiệu chỉnh có kích thước khiêm tốn hơn tại đây: http://codepad.org/1ebJC2OV Phiên bản này yêu cầu trung bình 1.620 lần lặp mỗi thuật ngữ, trong trường hợp xấu nhất là 38 và toàn bộ phạm vi chạy trong khoảng 3 m 21. Một chút thời gian được tạo ra, bằng cách sử dụng bitwise and
để hiệu chỉnh b , thay vì modulo.
Cải tiến
Ngay cả các giá trị có nhiều khả năng tạo ra một giải pháp hơn các giá trị lẻ.
Bài báo tính toán tinh thần được liên kết với các ghi chú trước đó rằng, sau khi loại bỏ tất cả các yếu tố của bốn, giá trị được phân tách là chẵn, giá trị này có thể được chia cho hai và giải pháp được xây dựng lại:

Mặc dù điều này có thể có ý nghĩa cho tính toán tinh thần (các giá trị nhỏ hơn có xu hướng dễ tính toán hơn), nhưng nó không có ý nghĩa nhiều về mặt thuật toán. Nếu bạn lấy 256 ngẫu nhiên 4 lần và kiểm tra tổng của bình phương modulo 8 , bạn sẽ thấy rằng các giá trị 1 , 3 , 5 và 7 đều đạt trung bình 32 lần. Tuy nhiên, mỗi giá trị 2 và 6 đều đạt tới 48 lần. Nhân các giá trị lẻ với 2 sẽ tìm ra một giải pháp, trung bình, trong các lần lặp ít hơn 33% . Việc xây dựng lại như sau:

Cần phải cẩn thận rằng a và b có cùng tính chẵn lẻ, cũng như c và d , nhưng nếu một giải pháp được tìm thấy ở tất cả, một trật tự thích hợp được đảm bảo tồn tại.
Đường dẫn không thể không cần phải được kiểm tra.
Sau khi chọn giá trị thứ hai, b , có thể đã không thể tồn tại một giải pháp, với các dư lượng bậc hai có thể có cho bất kỳ modulo nào. Thay vì kiểm tra bằng cách nào, hoặc chuyển sang lần lặp tiếp theo, giá trị của b có thể được 'sửa chữa' bằng cách giảm nó bằng số tiền nhỏ nhất có thể dẫn đến một giải pháp. Hai bảng hiệu chỉnh lưu trữ các giá trị này, một cho b và một cho c . Sử dụng một modulo cao hơn (chính xác hơn, sử dụng một modulo có dư lượng bậc hai tương đối ít hơn) sẽ dẫn đến một sự cải thiện tốt hơn. Giá trị a không cần bất kỳ sự điều chỉnh nào; bằng cách sửa đổi n thành chẵn, tất cả các giá trị củaa là hợp lệ.