Il punto di memoising, per la sequenza di Collatz, è di evitare di calcolare parti della lista che hai già fatto. Il resto di una sequenza è completamente determinato dal valore corrente. Quindi vogliamo controllare la tabella il più spesso possibile, e salvare il resto del calcolo il prima possibile.
def collatz_sequence(start, table={}): # cheeky trick: store the (mutable) table as a default argument
"""Returns the Collatz sequence for a given starting number"""
l = []
n = start
while n not in l: # break if we find ourself in a cycle
# (don't assume the Collatz conjecture!)
if n in table:
l += table[n]
break
elif n%2 == 0:
l.append(n)
n = n//2
else:
l.append(n)
n = (3*n) + 1
table.update({n: l[i:] for i, n in enumerate(l) if n not in table})
return l
Funziona? Diamo spiare su di esso per assicurarsi che gli elementi memoised vengono utilizzati:
class NoisyDict(dict):
def __getitem__(self, item):
print("getting", item)
return dict.__getitem__(self, item)
def collatz_sequence(start, table=NoisyDict()):
# etc
In [26]: collatz_sequence(5)
Out[26]: [5, 16, 8, 4, 2, 1]
In [27]: collatz_sequence(5)
getting 5
Out[27]: [5, 16, 8, 4, 2, 1]
In [28]: collatz_sequence(32)
getting 16
Out[28]: [32, 16, 8, 4, 2, 1]
In [29]: collatz_sequence.__defaults__[0]
Out[29]:
{1: [1],
2: [2, 1],
4: [4, 2, 1],
5: [5, 16, 8, 4, 2, 1],
8: [8, 4, 2, 1],
16: [16, 8, 4, 2, 1],
32: [32, 16, 8, 4, 2, 1]}
Edit: io sapevo potrebbe essere ottimizzato! Il segreto è che ci sono due punti nella funzione (i due punti di ritorno) che sappiamo l
e table
non condividere elementi. Mentre precedentemente evitavo di chiamare table.update
con elementi già in table
testandoli, questa versione della funzione sfrutta invece la nostra conoscenza del flusso di controllo, risparmiando un sacco di tempo.
[collatz_sequence(x) for x in range(500001, 1000000)]
ora circa 2 secondi sul mio computer, mentre un'espressione simile con la versione di @ welter ha un clock di 400 ms. Penso che questo sia dovuto al fatto che le funzioni in realtà non calcolano la stessa cosa: la mia versione genera l'intera sequenza, mentre @ welter trova solo la sua lunghezza. Quindi non penso di poter ridurre la mia implementazione alla stessa velocità.
def collatz_sequence(start, table={}): # cheeky trick: store the (mutable) table as a default argument
"""Returns the Collatz sequence for a given starting number"""
l = []
n = start
while n not in l: # break if we find ourself in a cycle
# (don't assume the Collatz conjecture!)
if n in table:
table.update({x: l[i:] for i, x in enumerate(l)})
return l + table[n]
elif n%2 == 0:
l.append(n)
n = n//2
else:
l.append(n)
n = (3*n) + 1
table.update({x: l[i:] for i, x in enumerate(l)})
return l
PS - individuare l'errore!
ho pensato che il punto di Memoizzazione è stato quello di vedere se c'era prima un valore calcolato e se non poi di calcolare e conservarla. Sembra che lo stiate memorizzando ma non verificando mai per vedere se non è necessario ricalcolarlo. La mia osservazione non spiega il 'keyerror' sebbene sia –
Python 2 o Python 3? – Bryce