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 = 12in 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 Aproduce gli stessi numeri probabilità (stessi numeri) come vettore originale Aquando 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] += 1con count[i] += cycle_number.
Questo riduce la complessità da Theta(n) = 6^na Theta(n) = 6^n / n. Pertanto n = 13, è circa 13 volte più veloce della mia versione precedente. Calcola n = 13in 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 = 14in 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 Avettori e Bvettori
Ho notato la stessa simmetria speculare per i vettori Adi Kuroi Neko. Ancora non sono sicuro, perché questo funziona (e se funziona per ciascuno n).
La riduzione dello spazio di ricerca per i Bvettori è 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 Bsembra (1, 1, ?, ?, ?), lo so, che non posso riempire i ?valori, quindi A*B = 0. Quindi non devo ripetere tutti i 6 vettori Bdel modulo (1, 1, ?, ?, ?).
Possiamo migliorare su questo, se ignoriamo i valori per 1. Come notato nella domanda, i valori per i = 1sono 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 = 1praticamente 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 = 15in 6 minuti.
modifica 4:
La grande idea di kuroi neko implementata rapidamente, che dice ciò Be -Bproduce gli stessi risultati. Speedup x2. L'implementazione è solo un trucco rapido, però. n = 15in 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, cdnella 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 = 16dovrebbe 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 & Be 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.