Perl - 116 bayt 87 bayt (aşağıdaki güncellemeye bakın)
#!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"
Mesele bir bayt olarak sayılır, yatay akıl için yeni satırlar eklenir.
Bir kombinasyonu şey kod golf hızlı kod gönderme.
Ortalama (en kötü?) Vaka karmaşıklığı O (log n) O (n 0.07 ) gibi görünmektedir . Bulduğum hiçbir şey 0.001'den daha yavaş çalışmadı ve 900000000 - 999999999 arasındaki tüm aralığı kontrol ettim . Bundan daha uzun süren bir şey bulursanız, ~ 0.1s veya daha fazla, lütfen bana bildirin.
Örnek Kullanımı
$ 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
Bunların son ikisi diğer başvurular için en kötü senaryo gibi görünmektedir. Her iki durumda da, gösterilen çözüm tam anlamıyla kontrol edilen ilk şeydir. Çünkü 123456789
bu ikinci.
Bir değer aralığını test etmek isterseniz, aşağıdaki komut dosyasını kullanabilirsiniz:
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);
Bir dosyaya aktarıldığında en iyisi. Aralık 1..1000000
, bilgisayarımda yaklaşık 14 saniye 999000000..1000000000
sürer ( saniyede 71000 değer) ve aralık , O (günlük n) ortalama karmaşıklıkla tutarlı olarak yaklaşık 20 saniye (saniyede 50000 değer) sürer .
Güncelleme
Düzenleme : Bu algoritma zihinsel hesap makineleri tarafından en az bir yüzyıl boyunca kullanılan bir algoritmaya çok benzer olduğu ortaya çıkıyor .
Başlangıçta yayınladığımdan beri, 1..1000000000 aralığındaki her değeri kontrol ettim . 'En kötü durum' davranışı, bir çözüme ulaşmadan önce toplam 190 kombinasyonu test eden 699731569 değeriyle sergilendi . 190'ın küçük bir sabit olduğunu düşünürseniz - ve kesinlikle yaparsam - gerekli aralıktaki en kötü durum davranışı O (1) olarak düşünülebilir . Yani, çözümü dev bir tablodan aramak kadar hızlı ve ortalama olarak oldukça hızlı.
Yine de başka bir şey. 190 iterasyondan sonra , 144400'den daha büyük bir şey ilk geçişin ötesine bile geçmedi. Genişlik ilk geçişinin mantığı değersizdir - hatta kullanılmaz. Yukarıdaki kod biraz kısaltılabilir:
#!perl -p
$.*=2,$_/=4until$_&3;
@a=map{$=-=$%*($%=$=**.5-$_);$%*$.}$j++,(0)x3while$=&&=$_;
$_="@a"
Hangi sadece aramanın ilk geçişini gerçekleştirir. Yine de , ikinci geçişe ihtiyaç duyan 144400'ün altında herhangi bir değer olmadığını doğrulamamız gerekiyor:
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");
}
Kısacası, 1..1000000000 aralığı için, neredeyse sabit bir zaman çözümü mevcuttur ve buna bakıyorsunuz.
Güncelleme Güncellemesi
@Dennis ve ben bu algoritmayı geliştirdik. Aşağıdaki yorumlardaki ilerlemeyi ve ilginizi çekerse sonraki tartışmayı takip edebilirsiniz. Gerekli aralık için yineleme ortalama sayısı biraz üzerinde düşmüştür 4 aşağı 1.229 ve tüm değerleri test etmek için gerekli süre 1..1000000000 2m 41s kadar, 18m 54'lerinden yükseltilmiştir. En kötü durum daha önce 190 iterasyon gerektiriyordu ; Şimdi en kötü durum, 854382778 , sadece 21'e ihtiyaç duyuyor .
Son Python kodu şudur:
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
Bu, biri 10kb, diğeri 253kb olmak üzere önceden hesaplanmış iki düzeltme tablosu kullanır. Yukarıdaki kod, bu tablolar için jeneratör işlevlerini içerir, ancak bunlar derleme zamanında hesaplanmalıdır.
Daha mütevazı boyutta düzeltme tablolarına sahip bir sürüm burada bulunabilir: http://codepad.org/1ebJC2OV Bu sürüm, en kötü 38 ile her dönem için ortalama 1.620 yineleme gerektirir ve tüm aralık yaklaşık 3m 21s'de çalışır. Modulo yerine b düzeltmesi için bitsel kullanılarak biraz zaman oluşur .and
İyileştirmeler
Değerlerin bile tek bir değerden daha fazla çözüm üretme olasılığı daha yüksektir.
Daha önce bahsedilen zihinsel hesaplama makalesi, dört faktörün tüm faktörlerini kaldırdıktan sonra, ayrıştırılacak değerin eşit olması durumunda, bu değerin ikiye bölünebileceğini ve çözümün yeniden yapılandırılabileceğini not eder:
Bu zihinsel hesaplama için anlamlı olsa da (daha küçük değerlerin hesaplanması daha kolay olma eğilimindedir), algoritmik olarak fazla bir anlam ifade etmez. Eğer alırsan 256 rastgele 4 -tuples ve karelerinin toplamını incelemek modulo 8 , hangi değerlerin olduğunu göreceksiniz 1 , 3 , 5 ve 7 her ortalama ulaşılır 32 kez. Ancak, 2 ve 6 değerlerinin her birine 48 kez ulaşılır . Tek değerlerin 2 ile çarpılması , ortalama % 33 daha az yinelemede bir çözüm bulacaktır . İmar aşağıdaki gibidir:
A ve b'nin c ve d ile aynı pariteye sahip olmasına dikkat edilmelidir , ancak bir çözüm bulunmuşsa, uygun bir siparişin var olduğu garanti edilir.
İmkansız yolların kontrol edilmesi gerekmez.
İkinci değer olan b'yi seçtikten sonra, herhangi bir modülo için olası ikinci dereceden kalıntılar göz önüne alındığında, bir çözümün mevcut olması zaten imkansız olabilir. Yine de kontrol etmek veya bir sonraki yinelemeye geçmek yerine , b'nin değeri, muhtemelen bir çözüme yol açabilecek en küçük miktarda azaltılarak 'düzeltilebilir'. İki düzeltme tablosu, biri b için diğeri c için bu değerleri saklar . Daha yüksek bir modulo kullanmak (daha kesin olarak, daha az kuadratik kalıntıya sahip bir modulo kullanmak) daha iyi bir iyileşme ile sonuçlanacaktır. Değer bir herhangi bir düzeltme ihtiyacı yoktur; n'yi eşit olacak şekilde değiştirerek ,a geçerlidir.