Python 2, 110 byte
n=input()
x=p=7*n|1
while~-p:x=p/2*x/p+2*10**n;p-=2
l=m=0
for c in`x`:
l=l*(p==c)+1;p=c
if l>m:m=l;print p*l
Il numero massimo di cifre da controllare è preso dallo stdin. 10.000 cifre termina in circa 2 secondi con PyPy 5.3.
Esempio di utilizzo
$ echo 10000 | pypy pi-runs.py
3
33
111
9999
99999
999999
Qualcosa di utile
from sys import argv
from gmpy2 import mpz
def pibs(a, b):
if a == b:
if a == 0:
return (1, 1, 1123)
p = a*(a*(32*a-48)+22)-3
q = a*a*a*24893568
t = 21460*a+1123
return (p, -q, p*t)
m = (a+b) >> 1
p1, q1, t1 = pibs(a, m)
p2, q2, t2 = pibs(m+1, b)
return (p1*p2, q1*q2, q2*t1 + p1*t2)
if __name__ == '__main__':
from sys import argv
digits = int(argv[1])
pi_terms = mpz(digits*0.16975227728583067)
p, q, t = pibs(0, pi_terms)
z = mpz(10)**digits
pi = 3528*q*z/t
l=m=0
x=0
for c in str(pi):
l=l*(p==c)+1;p=c
if l>m:m=l;print x,p*l
x+=1
Per questo sono passato da Chudnovsky a Ramanujan 39. Chudnovsky ha esaurito la memoria sul mio sistema poco dopo 100 milioni di cifre, ma Ramanujan è arrivato a 400 milioni, in soli 38 minuti circa. Penso che questo sia un altro caso in cui il tasso di crescita più lenta dei termini vince alla fine, almeno su un sistema con risorse limitate.
Esempio di utilizzo
$ python pi-ramanujan39-runs.py 400000000
0 3
25 33
155 111
765 9999
766 99999
767 999999
710106 3333333
22931752 44444444
24658609 777777777
386980421 6666666666
Generatori più veloci e illimitati
L'implementazione di riferimento fornita nella descrizione del problema è interessante. Utilizza un generatore illimitato, prelevato direttamente dalla carta Unbounded Spigot Algorithms for the Digits of Pi . Secondo l'autore, le implementazioni fornite sono "volutamente oscure", quindi ho deciso di fare nuove implementazioni di tutti e tre gli algoritmi elencati dall'autore, senza offuscamento deliberato. Ho anche aggiunto un quarto, basato su Ramanujan # 39 .
try:
from gmpy2 import mpz
except:
mpz = long
def g1_ref():
# Leibniz/Euler, reference
q, r, t = mpz(1), mpz(0), mpz(1)
i, j = 1, 3
while True:
n = (q+r)/t
if n*t > 4*q+r-t:
yield n
q, r = 10*q, 10*(r-n*t)
q, r, t = q*i, (2*q+r)*j, t*j
i += 1; j += 2
def g1_md():
# Leibniz/Euler, multi-digit
q, r, t = mpz(1), mpz(0), mpz(1)
i, j = 1, 3
z = mpz(10)**10
while True:
n = (q+r)/t
if n*t > 4*q+r-t:
for d in digits(n, i>34 and 10 or 1): yield d
q, r = z*q, z*(r-n*t)
u, v, x = 1, 0, 1
for k in range(33):
u, v, x = u*i, (2*u+v)*j, x*j
i += 1; j += 2
q, r, t = q*u, q*v+r*x, t*x
def g2_md():
# Lambert, multi-digit
q, r, s, t = mpz(0), mpz(4), mpz(1), mpz(0)
i, j, k = 1, 1, 1
z = mpz(10)**49
while True:
n = (q+r)/(s+t)
if n == q/s:
for d in digits(n, i>65 and 49 or 1): yield d
q, r = z*(q-n*s), z*(r-n*t)
u, v, w, x = 1, 0, 0, 1
for l in range(64):
u, v, w, x = u*j+v, u*k, w*j+x, w*k
i += 1; j += 2; k += j
q, r, s, t = q*u+r*w, q*v+r*x, s*u+t*w, s*v+t*x
def g3_ref():
# Gosper, reference
q, r, t = mpz(1), mpz(180), mpz(60)
i = 2
while True:
u, y = i*(i*27+27)+6, (q+r)/t
yield y
q, r, t, i = 10*q*i*(2*i-1), 10*u*(q*(5*i-2)+r-y*t), t*u, i+1
def g3_md():
# Gosper, multi-digit
q, r, t = mpz(1), mpz(0), mpz(1)
i, j = 1, 60
z = mpz(10)**50
while True:
n = (q+r)/t
if n*t > 6*i*q+r-t:
for d in digits(n, i>38 and 50 or 1): yield d
q, r = z*q, z*(r-n*t)
u, v, x = 1, 0, 1
for k in range(37):
u, v, x = u*i*(2*i-1), j*(u*(5*i-2)+v), x*j
i += 1; j += 54*i
q, r, t = q*u, q*v+r*x, t*x
def g4_md():
# Ramanujan 39, multi-digit
q, r, s ,t = mpz(0), mpz(3528), mpz(1), mpz(0)
i = 1
z = mpz(10)**3511
while True:
n = (q+r)/(s+t)
if n == (22583*i*q+r)/(22583*i*s+t):
for d in digits(n, i>597 and 3511 or 1): yield d
q, r = z*(q-n*s), z*(r-n*t)
u, v, x = mpz(1), mpz(0), mpz(1)
for k in range(596):
c, d, f = i*(i*(i*32-48)+22)-3, 21460*i-20337, -i*i*i*24893568
u, v, x = u*c, (u*d+v)*f, x*f
i += 1
q, r, s, t = q*u, q*v+r*x, s*u, s*v+t*x
def digits(x, n):
o = []
for k in range(n):
x, r = divmod(x, 10)
o.append(r)
return reversed(o)
Appunti
Sopra sono 6 implementazioni: le due implementazioni di riferimento fornite dall'autore (denotate _ref
) e quattro che calcolano i termini in batch, generando più cifre contemporaneamente ( _md
). Tutte le implementazioni sono state confermate a 100.000 cifre. Quando ho scelto le dimensioni dei lotti, ho scelto valori che lentamente perdono precisione nel tempo. Ad esempio, g1_md
genera 10 cifre per batch, con 33 iterazioni. Tuttavia, questo produrrà solo ~ 9,93 cifre corrette. Quando la precisione si esaurisce, la condizione di controllo fallisce, innescando l'esecuzione di un batch aggiuntivo. Questo sembra essere più performante di una precisione extra lentamente non necessaria nel tempo.
- g1 (Leibniz / Euler)
Viene j
mantenuta una variabile aggiuntiva , che rappresenta 2*i+1
. L'autore fa lo stesso nell'implementazione di riferimento. Il calcolo n
separato è molto più semplice (e meno oscuro), perché utilizza i valori correnti di q
, r
e t
, piuttosto che il successivo.
- g2 (Lambert)
Il controllo n == q/s
è certamente piuttosto lassista. Quello dovrebbe leggere n == (q*(k+2*j+4)+r)/(s*(k+2*j+4)+t)
, dove j
è 2*i-1
ed k
è i*i
. A iterazioni più elevate, i termini r
e t
diventano sempre meno significativi. Come è, è buono per le prime 100.000 cifre, quindi probabilmente è buono per tutti. L'autore non fornisce alcuna implementazione di riferimento.
- g3 (Gosper)
L'autore ipotizza che non sia necessario verificare che n
non cambierà nelle successive iterazioni e che serva solo a rallentare l'algoritmo. Anche se probabilmente è vero, il generatore sta trattenendo circa il 13% in più di cifre corrette rispetto a quelle attualmente generate, il che sembra piuttosto dispendioso. Ho aggiunto il check-in e aspetto che 50 cifre siano corrette, generandole tutte in una volta, con un notevole guadagno in termini di prestazioni.
- g4 (Ramanujan 39)
Calcolato come
Sfortunatamente, s
non azzera, a causa della composizione iniziale (3528 ÷), ma è ancora significativamente più veloce di g3. La convergenza è di ~ 5,89 cifre per termine, 3511 cifre vengono generate alla volta. Se è un po 'troppo, anche generare 271 cifre per 46 iterazioni è una scelta decente.
Tempi
Preso sul mio sistema, solo a scopo di confronto. I tempi sono elencati in secondi. Se un tempo ha richiesto più di 10 minuti, non ho eseguito ulteriori test.
| g1_ref | g1_md | g2_md | g3_ref | g3_md | g4_md
------------+---------+---------+---------+---------+---------+--------
10,000 | 1.645 | 0.229 | 0.093 | 0.312 | 0.062 | 0.062
20,000 | 6.859 | 0.937 | 0.234 | 1.140 | 0.250 | 0.109
50,000 | 55.62 | 5.546 | 1.437 | 9.703 | 1.468 | 0.234
100,000 | 247.9 | 24.42 | 5.812 | 39.32 | 5.765 | 0.593
200,000 | 2,158 | 158.7 | 25.73 | 174.5 | 33.62 | 2.156
500,000 | - | 1,270 | 215.5 | 3,173 | 874.8 | 13.51
1,000,000 | - | - | 1,019 | - | - | 58.02
È interessante che g2
alla fine superi g3
, nonostante un tasso di convergenza più lento. Sospetto che ciò sia dovuto al fatto che gli operandi crescono a un ritmo significativamente più lento, vincendo nel lungo periodo. L' g4_md
impianto più veloce è circa 235 volte più veloce dell'impianto g3_ref
su 500.000 cifre. Detto questo, c'è ancora un notevole sovraccarico per lo streaming delle cifre in questo modo. Il calcolo diretto di tutte le cifre utilizzando Ramanujan 39 ( fonte Python ) è circa 10 volte più veloce.
Perché non Chudnovsky?
L'algoritmo di Chudnovsky richiede una radice quadrata di piena precisione, che onestamente non sono sicuro su come lavorare - supponendo che potrebbe essere affatto. Ramanujan 39 è alquanto speciale in questo senso. Tuttavia, il metodo sembra favorire le formule simili a quelle di Machin, come quelle usate da y-cruncher, quindi potrebbe essere una strada che vale la pena esplorare.