Python 2 usando pypy e pp: n = 15 in 3 minuti
Anche solo una semplice forza bruta. Interessante vedere che ho quasi la stessa velocità di kuroi neko con C ++. Il mio codice può raggiungere n = 12
in circa 5 minuti. E lo eseguo solo su un core virtuale.
modifica: riduce lo spazio di ricerca di un fattore di n
Ho notato, che un vettore ciclato A*
di A
produce gli stessi numeri probabilità (stessi numeri) come vettore originale A
quando iterate sopra B
. Es Il vettore (1, 1, 0, 1, 0, 0)
ha le stesse probabilità quanto ciascuno dei vettori (1, 0, 1, 0, 0, 1)
, (0, 1, 0, 0, 1, 1)
, (1, 0, 0, 1, 1, 0)
, (0, 0, 1, 1, 0, 1)
e (0, 1, 1, 0, 1, 0)
quando si sceglie una casuale B
. Pertanto non devo ripetere ciascuno di questi 6 vettori, ma solo circa 1 e sostituirlo count[i] += 1
con count[i] += cycle_number
.
Questo riduce la complessità da Theta(n) = 6^n
a Theta(n) = 6^n / n
. Pertanto n = 13
, è circa 13 volte più veloce della mia versione precedente. Calcola n = 13
in circa 2 minuti e 20 secondi. Perché n = 14
è ancora un po 'troppo lento. Ci vogliono circa 13 minuti.
modifica 2: programmazione multi-core
Non molto contento del prossimo miglioramento. Ho deciso di provare anche a eseguire il mio programma su più core. Sui miei 2 + 2 core ora posso calcolare n = 14
in circa 7 minuti. Solo un fattore di miglioramento 2.
Il codice è disponibile in questo repository github: Link . La programmazione multi-core rende un po 'brutta.
modifica 3: riduzione dello spazio di ricerca per A
vettori e B
vettori
Ho notato la stessa simmetria speculare per i vettori A
di Kuroi Neko. Ancora non sono sicuro, perché questo funziona (e se funziona per ciascuno n
).
La riduzione dello spazio di ricerca per i B
vettori è un po 'più intelligente. Ho sostituito la generazione dei vettori ( itertools.product
), con una propria funzione. Fondamentalmente, inizio con un elenco vuoto e lo metto in pila. Fino a quando lo stack è vuoto, rimuovo un elenco, se non ha la stessa lunghezza di n
, creo altri 3 elenchi (aggiungendo -1, 0, 1) e inserendoli nello stack. Se un elenco ha la stessa lunghezza di n
, posso valutare le somme.
Ora che ho generato i vettori da solo, posso filtrarli a seconda che riesca a raggiungere la somma = 0 o meno. Ad esempio, se il mio vettore A
è (1, 1, 1, 0, 0)
, e il mio vettore B
sembra (1, 1, ?, ?, ?)
, lo so, che non posso riempire i ?
valori, quindi A*B = 0
. Quindi non devo ripetere tutti i 6 vettori B
del modulo (1, 1, ?, ?, ?)
.
Possiamo migliorare su questo, se ignoriamo i valori per 1. Come notato nella domanda, i valori per i = 1
sono la sequenza A081671 . Esistono molti modi per calcolarli. Ho scelto il semplice ripetersi: a(n) = (4*(2*n-1)*a(n-1) - 12*(n-1)*a(n-2)) / n
. Dal momento che possiamo calcolare i = 1
praticamente in pochissimo tempo, possiamo filtrare più vettori per B
. Ad esempio, A = (0, 1, 0, 1, 1)
e B = (1, -1, ?, ?, ?)
. Possiamo ignorare i vettori, in cui il primo ? = 1
, perché il A * cycled(B) > 0
, per tutti questi vettori. Spero che tu possa seguire. Probabilmente non è il miglior esempio.
Con questo posso calcolare n = 15
in 6 minuti.
modifica 4:
La grande idea di kuroi neko implementata rapidamente, che dice ciò B
e -B
produce gli stessi risultati. Speedup x2. L'implementazione è solo un trucco rapido, però. n = 15
in 3 minuti.
Codice:
Per il codice completo visita Github . Il seguente codice è solo una rappresentazione delle caratteristiche principali. Ho lasciato fuori le importazioni, la programmazione multicore, la stampa dei risultati, ...
count = [0] * n
count[0] = oeis_A081671(n)
#generating all important vector A
visited = set(); todo = dict()
for A in product((0, 1), repeat=n):
if A not in visited:
# generate all vectors, which have the same probability
# mirrored and cycled vectors
same_probability_set = set()
for i in range(n):
tmp = [A[(i+j) % n] for j in range(n)]
same_probability_set.add(tuple(tmp))
same_probability_set.add(tuple(tmp[::-1]))
visited.update(same_probability_set)
todo[A] = len(same_probability_set)
# for each vector A, create all possible vectors B
stack = []
for A, cycled_count in dict_A.iteritems():
ones = [sum(A[i:]) for i in range(n)] + [0]
# + [0], so that later ones[n] doesn't throw a exception
stack.append(([0] * n, 0, 0, 0, False))
while stack:
B, index, sum1, sum2, used_negative = stack.pop()
if index < n:
# fill vector B[index] in all possible ways,
# so that it's still possible to reach 0.
if used_negative:
for v in (-1, 0, 1):
sum1_new = sum1 + v * A[index]
sum2_new = sum2 + v * A[index - 1 if index else n - 1]
if abs(sum1_new) <= ones[index+1]:
if abs(sum2_new) <= ones[index] - A[n-1]:
C = B[:]
C[index] = v
stack.append((C, index + 1, sum1_new, sum2_new, True))
else:
for v in (0, 1):
sum1_new = sum1 + v * A[index]
sum2_new = sum2 + v * A[index - 1 if index else n - 1]
if abs(sum1_new) <= ones[index+1]:
if abs(sum2_new) <= ones[index] - A[n-1]:
C = B[:]
C[index] = v
stack.append((C, index + 1, sum1_new, sum2_new, v == 1))
else:
# B is complete, calculate the sums
count[1] += cycled_count # we know that the sum = 0 for i = 1
for i in range(2, n):
sum_prod = 0
for j in range(n-i):
sum_prod += A[j] * B[i+j]
for j in range(i):
sum_prod += A[n-i+j] * B[j]
if sum_prod:
break
else:
if used_negative:
count[i] += 2*cycled_count
else:
count[i] += cycled_count
Uso:
Devi installare pypy (per Python 2 !!!). Il modulo Python parallelo non è portato su Python 3. Quindi devi installare il modulo Python parallelo pp-1.6.4.zip . Estrai, cd
nella cartella e chiama pypy setup.py install
.
Quindi puoi chiamare il mio programma con
pypy you-do-the-math.py 15
Determinerà automaticamente il numero di CPU. Potrebbero esserci dei messaggi di errore dopo aver terminato il programma, ignorali. n = 16
dovrebbe essere possibile sulla tua macchina.
Produzione:
Calculation for n = 15 took 2:50 minutes
1 83940771168 / 470184984576 17.85%
2 17379109692 / 470184984576 3.70%
3 3805906050 / 470184984576 0.81%
4 887959110 / 470184984576 0.19%
5 223260870 / 470184984576 0.05%
6 67664580 / 470184984576 0.01%
7 30019950 / 470184984576 0.01%
8 20720730 / 470184984576 0.00%
9 18352740 / 470184984576 0.00%
10 17730480 / 470184984576 0.00%
11 17566920 / 470184984576 0.00%
12 17521470 / 470184984576 0.00%
13 17510280 / 470184984576 0.00%
14 17507100 / 470184984576 0.00%
15 17506680 / 470184984576 0.00%
Note e idee:
- Ho un processore i7-4600m con 2 core e 4 thread. Non importa se utilizzo 2 o 4 thread. L'utilizzo della cpu è del 50% con 2 thread e del 100% con 4 thread, ma richiede comunque lo stesso tempo. Non so perché. Ho controllato, che ogni thread ha solo la metà dei dati, quando ci sono 4 thread, ho controllato i risultati, ...
- Uso molte liste. Python non è abbastanza efficiente nella memorizzazione, devo copiare un sacco di elenchi, ... Quindi ho pensato di usare un numero intero. Potrei usare i bit 00 (per 0) e 11 (per 1) nel vettore A, e i bit 10 (per -1), 00 (per 0) e 01 (per 1) nel vettore B. Per il prodotto di A e B, dovrei solo calcolare
A & B
e contare i blocchi 01 e 10. Il ciclismo può essere fatto spostando il vettore e usando le maschere, ... In realtà ho implementato tutto questo, lo puoi trovare in alcuni dei miei vecchi impegni su Github. Ma si è scoperto, per essere più lento rispetto alle liste. Immagino che pypy ottimizzi davvero le operazioni dell'elenco.